1<?php
2/**
3 * Rubify action plugin (post processing)
4 *
5 * @license	GPLv3 (http://www.gnu.org/licenses/gpl.html)
6 * @link	   http://www.dokuwiki.org/plugin:dpicorrect
7 * @author	 Mike "Pomax" Kamermans <pomax@nihongoresources.com>
8 */
9
10if(!defined('DOKU_INC')) die();
11if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
12require_once(DOKU_PLUGIN.'action.php');
13
14class action_plugin_dpicorrect extends DokuWiki_Action_Plugin {
15
16	var $NO_RESOLUTION = -1;
17
18	function getInfo() {
19	  return array(
20		'author' => 'Mike "Pomax" Kamermans',
21		'email'  => 'pomax@nihongoresources.com',
22		'date'   => '2010-09-16',
23		'name'   => 'DPI correct',
24		'desc'   => 'Corrects image size based on their resolution (dpi), giving them CSS width and height values in inches, instead of pixels',
25		'url'	=> 'http://www.dokuwiki.org/plugin:dpicorrect');
26	}
27
28	/**
29	 * Postprocesses the html that was built from that, to rubify kanji that have associated furigana.
30	 */
31	function register(&$controller)
32	{
33		$controller->register_hook('IMAGE_LINK_POSTPROCESS', 'AFTER', $this, '_dpicorrect');
34	}
35
36	/**
37	 * check if an image was a .jpg or .png image, because these can have custom dpi values.
38	 */
39	function _dpicorrect(&$event, $param)
40	{
41		// reference to data and associated data type
42		$img = $event->data;
43
44		// get image link. We only care about JPG and PNG, because GIF images are an on-screen format, and inherit the screen's dpi
45		preg_match("/media=([^\"]+\.(jpg|png))\"/",$img,$matches);
46		$link = str_replace(":","/",$matches[1]);
47
48		$file = 'data/media/'.$link;
49
50		// Not unimportant: only run if the link is valid.
51		if(file_exists($file))
52		{
53			if(strpos($link,".jpg")!==false) {
54				$jpg = ImageCreateFromJPEG($file);
55				$imgdpi = $this->get_dpi_for_jpeg($file);
56				// if there is no explicit dpi value, leave this image alone
57				if($imgdpi == $this->NO_RESOLUTION) { return; }
58				// if it's not, compute the corrected image dimensions
59				$width = ImageSX($jpg) / $imgdpi["x"];
60				$height = ImageSY($jpg) / $imgdpi["y"]; }
61
62			else if (strpos($link,".png")!==false) {
63				$png = imagecreatefrompng($file);
64				$imgdpi = $this->get_dpi_for_png($file);
65				// if there is no explicit dpi value, leave this image alone
66				if($imgdpi == $this->NO_RESOLUTION) { return; }
67				// if it's not, compute the corrected image dimensions
68				$width = ImageSX($png) / $imgdpi["x"];
69				$height = ImageSY($png) / $imgdpi["y"]; }
70
71			// retrofit the dimensions as explicit width/height attributes now
72			$event->data = str_replace('class="media"','class="media" style="width: '.$width.'in; height: '.$height.'in;"',$img);
73		}
74	}
75
76	/**
77	 * Mostly from http://www.w3.org/Graphics/JPEG/jfif3.pdf, with with a quick peek
78	 * for the JFIF mark, because it's not guaranteeds to be at byte position 2!
79	 */
80	function get_dpi_for_jpeg($filename)
81	{
82		// open the file and read first 20 bytes.
83		$a = fopen($filename,'r');
84		$string = fread($a,20);
85		fclose($a);
86		$pos = strpos($string, "JFIF");
87		// if the image claims it's jpg but has no JFIF header, we can't do anything with it.
88		if($pos===false) { return $this->NO_RESOLUTION; }
89		// JFIF is followed by a zero byte, then a 2 byte version number, then 1 byte 'unit', and 2x2 byte x/y dimensions
90		$pos += 5 + 2;
91		$unit = ord(substr($string, $pos, 1));
92		$x = (ord(substr($string, $pos+1, 1)) << 8) + ord(substr($string, $pos+2, 1));
93		$y = (ord(substr($string, $pos+3, 1)) << 8) + ord(substr($string, $pos+4, 1));
94		// if unit is 0, there is no unit. if unit is 1, the dimensions are in  pixels per inch, and if the unit is 2, dimensions are in pixels per cm.
95		// note that if the dimensions are 0 pixels per unit, no sensible resolution can be determined.
96		if($unit == 0 || $xdim==0 || $ydim==0) { return $this->NO_RESOLUTION; }
97		if($unit==2) { $x *= 2.54; $y *= 2.54; }
98		// finally, return
99		return array("x"=>$x, "y"=>$y);
100	}
101
102	/**
103	 * PNG file layout: cycle through chunks until we find "pHYs" chunk. This chunk consists of 4 + 4 + 1 bytes:
104	 *   - xdim in pixels per unit
105	 *   - ydim in pixels per unit
106	 *   - unit (0=unspecified==useless, 1=meters)
107	 * see http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html
108	 */
109	function get_dpi_for_png($filename)
110	{
111		// open the file and start reading chunks
112		$a = fopen($filename,'r');
113		fread($a,8);  // dummy data: 0x137 0x80 0x78 0x71 0x13 0x10 0x26 0x10
114		$skip = 0;
115		$chunk = "";
116		while($chunk != "pHYs" && $chunk != "IDAT") {
117			// skip ahead to next chunk, making sure to skip the 4 byte crc, too
118			if($skip>0) { fread($a, $skip+4);}
119			// get size of chunk (in terms of bytes that it takes up after the chunk name)
120			$b1 = ord(fread($a,1)) << 24; $b2 = ord(fread($a,1)) << 16; $b3 = ord(fread($a,1)) << 8; $b4 = ord(fread($a,1));
121			$skip = $b1  + $b2 + $b3 + $b4;
122			// get name of chunk
123			$chunk = fread($a,4); }
124		// no physical dimensions chunk exists: assume screen resolution (dpi=72)
125		if($chunk == "IDAT") { fclose($a); return $this->NO_RESOLUTION; }
126
127		// pHYs chunk found, get xdim/ydim/unit values
128		$b1 = ord(fread($a,1)) << 24; $b2 = ord(fread($a,1)) << 16; $b3 = ord(fread($a,1)) << 8; $b4 = ord(fread($a,1));
129		$xdim = $b1  + $b2 + $b3 + $b4;
130		$b1 = ord(fread($a,1)) << 24; $b2 = ord(fread($a,1)) << 16; $b3 = ord(fread($a,1)) << 8; $b4 = ord(fread($a,1));
131		$ydim = $b1  + $b2 + $b3 + $b4;
132		$unit = ord(fread($a,1));
133		fclose($a);
134
135		// if the unit is unspecified, it means we can't tell the physical dimensions. Same for if there are 0 pixels per unit.
136		if($unit != 1 || $xdim==0 || $ydim==0) { return $this->NO_RESOLUTION; }
137
138		// For PNG, dimensions are written as pixels per meter, so we need to conver to pixels per inch
139		return array("x"=>$xdim * 0.0254, "y"=>$ydim * 0.0254);
140	}
141}
142