PageRenderTime 66ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/site/sites/all/modules/click_heatmap/clickheat/classes/Heatmap.class.php

https://bitbucket.org/kaerast/ppl
PHP | 380 lines | 287 code | 19 blank | 74 comment | 45 complexity | 00e3446aa65027fbc4013ae9e4e5ec6e MD5 | raw file
Possible License(s): GPL-2.0, AGPL-1.0
  1. <?php
  2. /**
  3. * ClickHeat : Classe de génération des cartes / Maps generation class
  4. *
  5. * Cette classe est VOLONTAIREMENT écrite pour PHP 4
  6. * This class is VOLUNTARILY written for PHP 4
  7. *
  8. * @author Yvan Taviaud - LabsMedia - www.labsmedia.com
  9. * @since 08/05/2007
  10. **/
  11. class Heatmap
  12. {
  13. /** @var integer $memory Limite de mémoire / Memory limit */
  14. var $memory = 8388608;
  15. /** @var integer $step Groupement des pixels / Pixels grouping */
  16. var $step = 5;
  17. /** @var integer $startStep */
  18. var $startStep;
  19. /** @var integer $dot Taille des points de chaleur / Heat dots size */
  20. var $dot = 19;
  21. /** @var boolean $heatmap Affichage sous forme de carte de température / Show as heatmap */
  22. var $heatmap = true;
  23. /** @var boolean $palette Correctif pour la gestion de palette (cas des carrés rouges) / Correction for palette (in case of red squares) */
  24. var $palette = false;
  25. /** @var boolean $rainbow Affichage de l'arc-en-ciel / Show rainbow */
  26. var $rainbow = true;
  27. /** @var boolean $copyleft Affichage du copyleft / Show copyleft */
  28. var $copyleft = true;
  29. /** @var integer $width Largeur de l'image / Image width */
  30. var $width;
  31. /** @var integer $height Hauteur de l'image / Image height */
  32. var $height;
  33. /** @var integer $maxClicks Nombre de clics maximum (sur 1 pixel) / Maximum clicks (on 1 pixel) */
  34. var $maxClicks;
  35. /** @var integer $maxY Hauteur maximale (point le plus bas) / Maximum height (lowest point) */
  36. var $maxY;
  37. /** @var resource $image Ressource image / Image resource */
  38. var $image;
  39. /** @var string $file Nom du fichier image (incluant le %d) / Image filename (including %d) */
  40. var $file;
  41. /** @var string $path Chemin du fichier image / Image path */
  42. var $path;
  43. /** @var string $cache Chemin du cache / Cache path */
  44. var $cache;
  45. /** @var string $error Erreur / Error */
  46. var $error = '';
  47. /** @var array $__colors Niveaux de dégradé (de 0 à 127) / Gradient levels (from 0 to 127) */
  48. var $__colors = array(50, 70, 90, 110, 120);
  49. /** @var integer $__low Niveau minimal de couleur RVB / Lower RGB level of color */
  50. var $__low = 0;
  51. /** @var integer $__high Niveau maximal de couleur RVB / Higher RGB level of color */
  52. var $__high = 255;
  53. /** @var integer $__grey Niveau du gris (couleur du 0 clic) / Grey level (color of no-click) */
  54. var $__grey = 240;
  55. function Heatmap() {}
  56. function generate($width, $height = 0)
  57. {
  58. /** First check paths */
  59. $this->path = rtrim($this->path, '/').'/';
  60. $this->cache = rtrim($this->cache, '/').'/';
  61. $this->file = str_replace('/', '', $this->file);
  62. if (!is_dir($this->path) || $this->path === '/')
  63. {
  64. return $this->raiseError('path = "'.$this->path.'" is not a directory or is "/"');
  65. }
  66. if (!is_dir($this->cache) || $this->cache === '/')
  67. {
  68. return $this->raiseError('cache = "'.$this->cache.'" is not a directory or is "/"');
  69. }
  70. if (strpos($this->file, '%d') === false)
  71. {
  72. return $this->raiseError('file = "'.$this->file.'" doesn\'t include a \'%d\' for image number');
  73. }
  74. $files = array('filenames' => array(), 'absolutes' => array()); /** Generated files list */
  75. $this->startStep = (int) floor(($this->step - 1) / 2);
  76. $nbOfImages = 1; /** Will be modified after the first image is created */
  77. $this->maxClicks = 1; /** Must not be zero for divisions */
  78. $this->maxY = 0;
  79. /**
  80. * Memory consumption :
  81. * imagecreate : about 200,000 + 5 * $width * $height bytes
  82. * dots : about 6,000 + 360 * DOT_WIDTH bytes each (100 dots)
  83. * imagepng : about 4 * $width * $height bytes
  84. * So a rough idea of the memory is 10 * $width * $height + 500,000 (2 images) + 100 * (DOT_WIDTH * 360 + 6000)
  85. **/
  86. $this->width = (int) abs($width);
  87. if ($this->width === 0)
  88. {
  89. return $this->raiseError('Width can\'t be 0');
  90. }
  91. $height = (int) abs($height);
  92. if ($height === 0)
  93. {
  94. /** Calculating height from memory consumption, and add a 100% security margin : 10 => 20 */
  95. $this->height = floor(($this->memory - 500000 - 100 * ($this->dot * 360 + 6000)) / (20 * $width));
  96. /** Limit height to 1000px max, with a modulo of 10 */
  97. $this->height = (int) max(100, min(1000, $this->height - $this->height % 10));
  98. }
  99. else
  100. {
  101. /** Force height */
  102. $this->height = $height;
  103. }
  104. /** Startup tasks */
  105. if ($this->startDrawing() === false)
  106. {
  107. return false;
  108. }
  109. $files['width'] = $this->width;
  110. $files['height'] = $this->height;
  111. for ($image = 0; $image < $nbOfImages; $image++)
  112. {
  113. /** Image creation */
  114. $this->image = imagecreatetruecolor($this->width, $this->height);
  115. if ($this->heatmap === false)
  116. {
  117. $grey = imagecolorallocate($this->image, $this->__grey, $this->__grey, $this->__grey);
  118. imagefill($this->image, 0, 0, $grey);
  119. }
  120. else
  121. {
  122. /** Image is filled in the color "0", which means 0 click */
  123. imagefill($this->image, 0, 0, 0);
  124. }
  125. /** Draw next pixels for this image */
  126. if ($this->drawPixels($image) === false)
  127. {
  128. return false;
  129. }
  130. if ($image === 0)
  131. {
  132. if ($this->maxY === 0)
  133. {
  134. if (defined('LANG_ERROR_DATA') === true)
  135. {
  136. return $this->raiseError(LANG_ERROR_DATA);
  137. }
  138. else
  139. {
  140. $this->maxY = 1;
  141. }
  142. }
  143. $nbOfImages = (int) ceil($this->maxY / $this->height);
  144. $files['count'] = $nbOfImages;
  145. }
  146. if ($this->heatmap === true)
  147. {
  148. imagepng($this->image, sprintf($this->cache.$this->file.'_temp', $image));
  149. }
  150. else
  151. {
  152. /** "No clicks under this line" message */
  153. if ($image === $nbOfImages - 1 && defined('LANG_NO_CLICK_BELOW') === true)
  154. {
  155. $black = imagecolorallocate($this->image, 0, 0, 0);
  156. imageline($this->image, 0, $this->height - 1, $this->width, $this->height - 1, $black);
  157. imagestring($this->image, 1, 1, $this->height - 9, LANG_NO_CLICK_BELOW, $black);
  158. }
  159. imagepng($this->image, sprintf($this->path.$this->file, $image));
  160. }
  161. imagedestroy($this->image);
  162. /** Result files */
  163. $files['filenames'][] = sprintf($this->file, $image);
  164. $files['absolutes'][] = sprintf($this->path.$this->file, $image);
  165. }
  166. /** End tasks */
  167. if ($this->finishDrawing() === false)
  168. {
  169. return false;
  170. }
  171. if ($this->heatmap === false)
  172. {
  173. return $files;
  174. }
  175. /** Now, our image is a direct representation of the clicks on each pixel, so create some fuzzy dots to put a nice blur effect if user asked for a heatmap */
  176. for ($i = 0; $i < 128; $i++)
  177. {
  178. $dots[$i] = imagecreatetruecolor($this->dot, $this->dot);
  179. imagealphablending($dots[$i], false);
  180. }
  181. for ($x = 0; $x < $this->dot; $x++)
  182. {
  183. for ($y = 0; $y < $this->dot; $y++)
  184. {
  185. $sinX = sin($x * pi() / $this->dot);
  186. $sinY = sin($y * pi() / $this->dot);
  187. for ($i = 0; $i < 128; $i++)
  188. {
  189. $alpha = 127 - $i * $sinX * $sinY * $sinX * $sinY;
  190. imagesetpixel($dots[$i], $x, $y, ((int) $alpha) * 16777216);
  191. }
  192. }
  193. }
  194. /**
  195. * Colors creation :
  196. * grey => deep blue (rgB) => light blue (rGB) => green (rGb) => yellow (RGb) => red (Rgb)
  197. * 0 $this->__colors[0] $this->__colors[1] $this->__colors[2] $this->__colors[3] 128
  198. **/
  199. sort($this->__colors);
  200. $colors = array();
  201. for ($i = 0; $i < 128; $i++)
  202. {
  203. /** Red */
  204. if ($i < $this->__colors[0])
  205. {
  206. $colors[$i][0] = $this->__grey + ($this->__low - $this->__grey) * $i / $this->__colors[0];
  207. }
  208. elseif ($i < $this->__colors[2])
  209. {
  210. $colors[$i][0] = $this->__low;
  211. }
  212. elseif ($i < $this->__colors[3])
  213. {
  214. $colors[$i][0] = $this->__low + ($this->__high - $this->__low) * ($i - $this->__colors[2]) / ($this->__colors[3] - $this->__colors[2]);
  215. }
  216. else
  217. {
  218. $colors[$i][0] = $this->__high;
  219. }
  220. /** Green */
  221. if ($i < $this->__colors[0])
  222. {
  223. $colors[$i][1] = $this->__grey + ($this->__low - $this->__grey) * $i / $this->__colors[0];
  224. }
  225. elseif ($i < $this->__colors[1])
  226. {
  227. $colors[$i][1] = $this->__low + ($this->__high - $this->__low) * ($i - $this->__colors[0]) / ($this->__colors[1] - $this->__colors[0]);
  228. }
  229. elseif ($i < $this->__colors[3])
  230. {
  231. $colors[$i][1] = $this->__high;
  232. }
  233. else
  234. {
  235. $colors[$i][1] = $this->__high - ($this->__high - $this->__low) * ($i - $this->__colors[3]) / (127 - $this->__colors[3]);
  236. }
  237. /** Blue */
  238. if ($i < $this->__colors[0])
  239. {
  240. $colors[$i][2] = $this->__grey + ($this->__high - $this->__grey) * $i / $this->__colors[0];
  241. }
  242. elseif ($i < $this->__colors[1])
  243. {
  244. $colors[$i][2] = $this->__high;
  245. }
  246. elseif ($i < $this->__colors[2])
  247. {
  248. $colors[$i][2] = $this->__high - ($this->__high - $this->__low) * ($i - $this->__colors[1]) / ($this->__colors[2] - $this->__colors[1]);
  249. }
  250. else
  251. {
  252. $colors[$i][2] = $this->__low;
  253. }
  254. }
  255. for ($image = 0; $image < $nbOfImages; $image++)
  256. {
  257. $img = imagecreatetruecolor($this->width, $this->height);
  258. $white = imagecolorallocate($img, 255, 255, 255);
  259. /** «imagefill» doesn't work correctly on some hosts, ending on a red drawing */
  260. imagefilledrectangle($img, 0, 0, $this->width - 1, $this->height - 1, $white);
  261. imagealphablending($img, true);
  262. $imgSrc = @imagecreatefrompng(sprintf($this->cache.$this->file.'_temp', $image));
  263. @unlink(sprintf($this->cache.$this->file.'_temp', $image));
  264. if ($imgSrc === false)
  265. {
  266. return $this->raiseError('::MEMORY_OVERFLOW::');
  267. }
  268. for ($x = $this->startStep; $x < $this->width; $x += $this->step)
  269. {
  270. for ($y = $this->startStep; $y < $this->height; $y += $this->step)
  271. {
  272. $dot = (int) ceil(imagecolorat($imgSrc, $x, $y) / $this->maxClicks * 100);
  273. if ($dot !== 0)
  274. {
  275. imagecopy($img, $dots[$dot], ceil($x - $this->dot / 2), ceil($y - $this->dot / 2), 0, 0, $this->dot, $this->dot);
  276. }
  277. }
  278. }
  279. /** Destroy image source */
  280. imagedestroy($imgSrc);
  281. /** Rainbow */
  282. if ($image === 0 && $this->rainbow === true)
  283. {
  284. for ($i = 1; $i < 128; $i += 2)
  285. {
  286. /** Erase previous alpha channel so that clicks don't change the heatmap by combining their alpha */
  287. imageline($img, ceil($i/2), 0, ceil($i/2), 10, 16777215);
  288. /** Then put our alpha */
  289. imageline($img, ceil($i/2), 0, ceil($i/2), 10, (127 - $i) * 16777216);
  290. }
  291. }
  292. /** Some version of imagetruecolortopalette() don't transform alpha value to non alpha */
  293. if ($this->palette === true)
  294. {
  295. for ($x = 0; $x < $this->width; $x++)
  296. {
  297. for ($y = 0; $y < $this->height; $y++)
  298. {
  299. /** Get Alpha value (0->127) and transform it to red (divide color by 16777216 and multiply by 65536 * 2 (red is 0->255), so divide it by 128) */
  300. imagesetpixel($img, $x, $y, (imagecolorat($img, $x, $y) & 0x7F000000) / 128);
  301. }
  302. }
  303. }
  304. /** Change true color image to palette then change palette colors */
  305. imagetruecolortopalette($img, false, 127);
  306. for ($i = 0, $max = imagecolorstotal($img); $i < $max; $i++)
  307. {
  308. $color = imagecolorsforindex($img, $i);
  309. imagecolorset($img, $i, $colors[floor(127 - $color['red'] / 2)][0], $colors[floor(127 - $color['red'] / 2)][1], $colors[floor(127 - $color['red'] / 2)][2]);
  310. }
  311. $grey = imagecolorallocate($img, $this->__grey, $this->__grey, $this->__grey);
  312. $gray = imagecolorallocate($img, ceil($this->__grey / 2), ceil($this->__grey / 2), ceil($this->__grey / 2));
  313. $white = imagecolorallocate($img, 255, 255, 255);
  314. $black = imagecolorallocate($img, 0, 0, 0);
  315. /** maxClicks */
  316. if ($image === 0 && $this->rainbow === true)
  317. {
  318. imagerectangle($img, 0, 0, 65, 11, $white);
  319. imagefilledrectangle($img, 0, 11, 65, 18, $white);
  320. imagestring($img, 1, 0, 11, '0', $black);
  321. $right = 66 - strlen($this->maxClicks) * 5;
  322. imagestring($img, 1, $right, 11, $this->maxClicks, $black);
  323. imagestring($img, 1, floor($right / 2) - 12, 11, 'clicks', $black);
  324. }
  325. if ($image === $nbOfImages - 1)
  326. {
  327. /** "No clicks under this line" message */
  328. if (defined('LANG_NO_CLICK_BELOW') === true)
  329. {
  330. imageline($img, 0, $this->height - 1, $this->width, $this->height - 1, $gray);
  331. imagestring($img, 1, 1, $this->height - 9, LANG_NO_CLICK_BELOW, $gray);
  332. }
  333. /** Copyleft */
  334. if ($this->copyleft === true)
  335. {
  336. imagestring($img, 1, $this->width - 160, $this->height - 9, 'Open source heatmap by ClickHeat', $grey);
  337. imagestring($img, 1, $this->width - 161, $this->height - 9, 'Open source heatmap by ClickHeat', $gray);
  338. }
  339. }
  340. /** Save PNG file */
  341. imagepng($img, sprintf($this->path.$this->file, $image));
  342. imagedestroy($img);
  343. }
  344. for ($i = 0; $i < 100; $i++)
  345. {
  346. imagedestroy($dots[$i]);
  347. }
  348. return $files;
  349. }
  350. /**
  351. * Retourne une erreur / Returns an error
  352. *
  353. * @param string $error
  354. **/
  355. function raiseError($error)
  356. {
  357. $this->error = $error;
  358. return false;
  359. }
  360. }
  361. ?>