pixeler.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905
  1. <?php
  2. /**
  3. * tools/pixeler.php file
  4. *
  5. * @package Nanodicom
  6. * @category Tools
  7. * @author Nano Documet <nanodocumet@gmail.com>
  8. * @version 1.3.1
  9. * @copyright (c) 2010-2011
  10. * @license http://www.opensource.org/licenses/mit-license.php MIT-license
  11. */
  12. /**
  13. * Dicom_Pixeler class.
  14. *
  15. * Extends Nanodicom. Pixel data reader.
  16. * Currently support:
  17. * - Only uncompressed pixel data.
  18. * - Photometric Representations of: Monochrome1, Monochrome2 and RGB (color-by-plane and color-by-pixel)
  19. * - Big endian and little endian, explicit and implicit
  20. * - Pixel Representation: Unsigned Integer and 2's complement
  21. *
  22. * @package Nanodicom
  23. * @category Tools
  24. * @author Nano Documet <nanodocumet@gmail.com>
  25. * @version 1.3.1
  26. * @copyright (c) 2010-2011
  27. * @license http://www.opensource.org/licenses/mit-license.php MIT-license
  28. */
  29. class Dicom_Pixeler extends Nanodicom {
  30. /**
  31. * @var string driver to be used: GD (gd), ImageMagick (imagick), GraphicsMagick (gmagick)
  32. */
  33. public static $driver = 'gd';
  34. protected $_rows;
  35. protected $_cols;
  36. protected $_endian;
  37. protected $_vr_mode;
  38. protected $_dose_scaling;
  39. protected $_rescale_slope;
  40. protected $_rescale_intercept;
  41. protected $_samples_per_pixel;
  42. protected $_pixel_representation;
  43. /**
  44. * Supported Image Transfer Syntaxes for reading image data
  45. * indexed by Value of the Transfer Syntax
  46. * each array contains: Name.
  47. * @var array
  48. */
  49. public static $reading_image_transfer_syntaxes = array(
  50. // Implicit VR Little Endian "1.2.840.10008.1.2"
  51. Nanodicom::IMPLICIT_VR_LITTLE_ENDIAN => array(
  52. 'name' => 'Implicit VR Little Endian'
  53. ),
  54. // Explicit VR Little Endian "1.2.840.10008.1.2.1"
  55. Nanodicom::EXPLICIT_VR_LITTLE_ENDIAN => array(
  56. 'name' => 'Explicit VR Little Endian'
  57. ),
  58. // Explicit VT Big Endian "1.2.840.10008.1.2.2"
  59. Nanodicom::EXPLICIT_VR_BIG_ENDIAN => array(
  60. 'name' => 'Explicit VR Big Endian'
  61. ),
  62. // DICOM Deflated Little Endian (Explicit VR)
  63. '1.2.840.10008.1.2.1.99' => array(
  64. 'name' => 'DICOM Deflated Little Endian (Explicit VR)'
  65. ),
  66. // Jpeg Lossy Baseline (1) - 8 bits
  67. '1.2.840.10008.1.2.4.50' => array(
  68. 'name' => 'Jpeg Lossy Baseline (1) - 8 bits'
  69. ),
  70. // RLE - Run Length Encoding, Lossless
  71. '1.2.840.10008.1.2.5' => array(
  72. 'name' => 'RLE - Run Length Encoding, Lossless'
  73. ),
  74. );
  75. /**
  76. * Public method to set the driver to be used
  77. *
  78. * @param string name of the driver
  79. * @return object the instance to allow chaining
  80. */
  81. public function set_driver($driver)
  82. {
  83. self::$driver = $driver;
  84. return $this;
  85. }
  86. /**
  87. * Public method to get the driver currently set
  88. *
  89. * @return string the current driver set
  90. */
  91. public function get_driver()
  92. {
  93. return self::$driver;
  94. }
  95. /**
  96. * Public method to add a jpeg lossy 8 bits image
  97. * Still under development.
  98. *
  99. * @return string the name of the jpeg file
  100. */
  101. public function add_lossy_jpeg8($filename)
  102. {
  103. $dataset = array();
  104. // Get the data blob
  105. $blob = file_get_contents($filename);
  106. // Create first item
  107. $value = array(
  108. 'len' => 0,
  109. 'val' => '',
  110. 'vr' => 'IT',
  111. '_vr' => 'IT',
  112. 'bin' => FALSE,
  113. 'off' => 0, // We don't know the offset
  114. 'ds' => array(),
  115. 'done' => TRUE,
  116. );
  117. $dataset[0xFFFE][0xE000][] = $value;
  118. // Attach jpeg
  119. $value = array(
  120. 'len' => sprintf('%u', strlen($blob)),
  121. 'val' => $blob,
  122. 'vr' => 'IT',
  123. '_vr' => 'IT',
  124. 'bin' => TRUE,
  125. 'off' => 0, // We don't know the offset
  126. 'ds' => array(),
  127. 'done' => TRUE,
  128. );
  129. $dataset[0xFFFE][0xE000][] = $value;
  130. // Pixel Data element
  131. $value = array(
  132. 'len' => -1,
  133. 'val' => '',
  134. 'vr' => 'OB',
  135. '_vr' => 'OB',
  136. 'bin' => FALSE,
  137. 'off' => 0, // We don't know the offset
  138. 'ds' => $dataset,
  139. 'done' => TRUE,
  140. );
  141. $this->_dataset[0x7FE0][0x0010][0] = $value;
  142. // Delimeter
  143. $value = array(
  144. 'len' => 0,
  145. 'val' => '',
  146. 'vr' => 'DI',
  147. '_vr' => 'DI',
  148. 'bin' => FALSE,
  149. 'off' => 0, // We don't know the offset
  150. 'ds' => array(),
  151. 'done' => TRUE,
  152. );
  153. $this->_dataset[0xFFFE][0xE0DD][0] = $value;
  154. $info = getimagesize($filename);
  155. // Set the rows (height of image)
  156. $this->value(0x0028, 0x0010, (int) $info[1]);
  157. // Set the columns (width of image)
  158. $this->value(0x0028, 0x0011, (int) $info[0]);
  159. // Set the bits allocated
  160. $this->value(0x0028, 0x0100, 8);
  161. // Set the bits stored
  162. $this->value(0x0028, 0x0101, 8);
  163. // Set the high bit
  164. $this->value(0x0028, 0x0102, 7);
  165. // Set the samples per pixel
  166. $this->value(0x0028, 0x0002, 1);
  167. // Set the Photometric Interpretation
  168. $this->value(0x0028, 0x0004, 'MONOCHROME2');
  169. // TransferSyntaxUID. JPEG Lossy Baseline (1) - 8 bits
  170. $this->value(0x0002, 0x0010, '1.2.840.10008.1.2.4.50');
  171. return $this;
  172. }
  173. /**
  174. * Public method to get the images from the dicom object
  175. *
  176. * @param integer a default window width
  177. * @param integer a default window center
  178. * @return mixed false if something is missing or not image data found, otherwise an
  179. * array of GD objects
  180. */
  181. public function get_images($width = NULL, $center = NULL)
  182. {
  183. // Parse the object if not parsed yet
  184. $this->parse();
  185. // Set the profiler
  186. $this->profiler['pixel']['start'] = microtime(TRUE);
  187. $return = $this->_check_driver();
  188. // If FALSE, driver not found
  189. if ( ! $return)
  190. return FALSE;
  191. // Supported transfer syntaxes
  192. if ( ! array_key_exists(trim($this->_transfer_syntax), self::$reading_image_transfer_syntaxes))
  193. {
  194. // Not supported transfer syntax found
  195. throw new Nanodicom_Exception('Transfer syntax ":syntax" not supported',
  196. array(':syntax' => $this->_transfer_syntax), 300);
  197. }
  198. // Let's read some values from DICOM file
  199. $rows = $this->get(0x0028, 0x0010);
  200. $cols = $this->get(0x0028, 0x0011);
  201. if ($rows == NULL OR $cols === NULL)
  202. {
  203. // There is no rows, no samples per pixel, no pixel data or malformed dicom file
  204. throw new Nanodicom_Exception('There is no rows, no samples per pixel, no pixel data or malformed dicom file', NULL, 301);
  205. }
  206. $samples_per_pixel = $this->get(0x0028, 0x0002, 1);
  207. $bits_allocated = $this->get(0x0028, 0x0100);
  208. $bits_stored = $this->get(0x0028, 0x0101);
  209. $high_bit = $this->get(0x0028, 0x0102);
  210. $dose_scaling = $this->get(0x3004, 0x000E, 1);
  211. $window_width = ($width == NULL) ? $this->get(0x0028,0x1051, 0) : $width;
  212. $window_center = ($center == NULL) ? $this->get(0x0028,0x1050, 0) : $center;
  213. $rescale_intercept = $this->get(0x0028,0x1052, 0);
  214. $rescale_slope = $this->get(0x0028,0x1053, 1);
  215. $number_of_frames = (int) $this->get(0x0028,0x0008, 1);
  216. $pixel_representation = $this->get(0x0028,0x0103);
  217. $photometric_interpretation = trim($this->get(0x0028,0x0004, 'NONE'));
  218. $planar_configuration = $this->get(0x0028,0x0006, 0);
  219. $transfer_syntax_uid = trim($this->get(0x0002,0x0010));
  220. $blob = $this->get(0x7FE0,0x0010);
  221. // Save some values for internal use
  222. // TODO: improve this, probably using $this->pixeler[]?
  223. $this->_rows = $rows;
  224. $this->_cols = $cols;
  225. $this->_dose_scaling = $dose_scaling;
  226. $this->_rescale_slope = $rescale_slope;
  227. $this->_rescale_intercept = $rescale_intercept;
  228. $this->_samples_per_pixel = $samples_per_pixel;
  229. $this->_pixel_representation = $pixel_representation;
  230. // Window Center and Width can have multiple values. By now, just reading the first one.
  231. // It assumes the delimiter is the "\"
  232. if ( ! (strpos($window_center, "\\") === FALSE))
  233. {
  234. $temp = explode("\\", $window_center);
  235. $window_center = (int) $temp[0];
  236. }
  237. if ( ! (strpos($window_width, "\\") === FALSE))
  238. {
  239. $temp = explode("\\", $window_width);
  240. $window_width = (int) $temp[0];
  241. }
  242. // Setting some values
  243. $images = array();
  244. $max = array();
  245. $min = array();
  246. $current_position = $starting_position = 0;
  247. $current_image = 0;
  248. $bytes_to_read = (int) $bits_allocated/8;
  249. $image_size = $cols*$rows*$samples_per_pixel*$bytes_to_read;
  250. list($vr_mode, $endian) = Nanodicom::decode_transfer_syntax($transfer_syntax_uid);
  251. $this->_vr_mode = $vr_mode;
  252. $this->_endian = $endian;
  253. if ($transfer_syntax_uid == '1.2.840.10008.1.2.4.50')
  254. {
  255. // This is jpeg lossy 8-bits. Just get the data.
  256. $images = array();
  257. $counter = 0;
  258. foreach ($blob as $group => $elements)
  259. {
  260. foreach ($elements as $element => $indexes)
  261. {
  262. if ($element == Nanodicom::SEQUENCE_DELIMITER)
  263. continue;
  264. foreach ($indexes as $values)
  265. {
  266. $data = $values;
  267. if ($counter == 0)
  268. {
  269. // Read Basic Offset Table
  270. }
  271. else
  272. {
  273. // It is a real Item
  274. if ( ! isset($data['done']))
  275. {
  276. // Read value if not read yet
  277. $this->_read_value_from_blob($data, $group, $element);
  278. }
  279. $images[] = $this->_read_image_blob($data['val']);
  280. }
  281. $counter++;
  282. }
  283. }
  284. }
  285. return $images;
  286. }
  287. if ($transfer_syntax_uid == '1.2.840.10008.1.2.5')
  288. {
  289. // It is "RLE - Run Length Encoding, Lossless"
  290. $counter = 0;
  291. $temp = array();
  292. foreach ($blob as $group => $elements)
  293. {
  294. foreach ($elements as $element => $indexes)
  295. {
  296. if ($element == Nanodicom::SEQUENCE_DELIMITER)
  297. continue;
  298. foreach ($indexes as $values)
  299. {
  300. $data = $values;
  301. $data['g'] = $group;
  302. $data['e'] = $element;
  303. if ($counter == 0)
  304. {
  305. // Read Basic Offset Table
  306. }
  307. else
  308. {
  309. // It is a real Item
  310. $dir = realpath(dirname($this->_location)).DIRECTORY_SEPARATOR;
  311. $file = basename($this->_location);
  312. if ( ! isset($data['done']))
  313. {
  314. // Read value if not read yet
  315. $this->_read_value_from_blob($data, $group, $element);
  316. }
  317. // This is the item data
  318. $item = $data['val'];
  319. // Save the header
  320. $header = array();
  321. // 16 Unsigned Long values
  322. for ($i = 0; $i <= 16; $i++)
  323. {
  324. $chunk = substr($item, 4*$i, 4);
  325. $header[] = $this->{Nanodicom::$_read_int}(4, Nanodicom::LITTLE_ENDIAN, 4, Nanodicom::UNSIGNED, $chunk);
  326. }
  327. $total_segments = array_shift($header);
  328. $segment_index = 0;
  329. foreach ($header as $starting_byte_of_segment)
  330. {
  331. // This is a segment, do the uncompression
  332. if ($starting_byte_of_segment == 0)
  333. // Only process if the segment has a positive starting value
  334. break;
  335. $size_of_segment = ($header[$segment_index + 1] == 0)
  336. ? strlen($item) - $starting_byte_of_segment
  337. : $header[$segment_index + 1] - $starting_byte_of_segment;
  338. $temp[$counter - 1][$segment_index] = '';
  339. $expected_segment_size = $cols*$rows*$bytes_to_read/$total_segments;
  340. $bytes_count = $current_segment_size = 0;
  341. while ($bytes_count < $size_of_segment)
  342. {
  343. $tmp = $starting_byte_of_segment + $bytes_count;
  344. // Read "n"
  345. $n = substr($item, $starting_byte_of_segment + $bytes_count, 1);
  346. $n = $this->{Nanodicom::$_read_int}(1, Nanodicom::LITTLE_ENDIAN, 1, Nanodicom::SIGNED, $n);
  347. // Add 1
  348. $bytes_count++;
  349. if ($n >= 0 AND $n <= 127)
  350. {
  351. $temp[$counter - 1][$segment_index] .= substr($item, $starting_byte_of_segment + $bytes_count, $n + 1);
  352. $bytes_count = $bytes_count + $n + 1;
  353. $current_segment_size = $current_segment_size + $n + 1;
  354. }
  355. elseif ($n <= -1 AND $n >= -127)
  356. {
  357. $byte = substr($item, $starting_byte_of_segment + $bytes_count, 1);
  358. $temp[$counter - 1][$segment_index] .= str_repeat($byte, -$n + 1);
  359. $bytes_count++;
  360. $current_segment_size = $current_segment_size - $n + 1;
  361. }
  362. else
  363. {
  364. // Do nothing
  365. }
  366. }
  367. $segment_index++;
  368. }
  369. }
  370. // Increment counter
  371. $counter++;
  372. }
  373. }
  374. }
  375. $counter--;
  376. $blob = '';
  377. for ($count = 0; $count < $counter; $count++)
  378. {
  379. if ($photometric_interpretation == 'RGB')
  380. {
  381. if ($planar_configuration == 1)
  382. {
  383. // Color-by-plane: RRR..., GGG..., BBB...
  384. $blob .= implode('', $temp[$count]);
  385. }
  386. else
  387. {
  388. // Color-by-pixel: RGB, RGB, RGB..
  389. }
  390. }
  391. else
  392. {
  393. $dimension = $cols*$rows;
  394. for ($i = 0; $i < $dimension; $i++)
  395. {
  396. $part = '';
  397. foreach ($temp[$count] as $segment)
  398. {
  399. $part = substr($segment, $i, 1) . $part;
  400. }
  401. $blob .= $part;
  402. }
  403. }
  404. }
  405. }
  406. // Blob here has "uncompressed" data (from raw or RLE)
  407. if ($photometric_interpretation == 'PALETTE COLOR')
  408. {
  409. $this->_samples_per_pixel = 3;
  410. $palettes = array();
  411. $palettes['R'] = array($this->get(0x0028,0x1101), $this->get(0x0028,0x1201));
  412. $palettes['G'] = array($this->get(0x0028,0x1102), $this->get(0x0028,0x1202));
  413. $palettes['B'] = array($this->get(0x0028,0x1103), $this->get(0x0028,0x1203));
  414. $palettes['A'] = array($this->get(0x0028,0x1104), $this->get(0x0028,0x1204));
  415. $entries = (int) $palettes['R'][0]['val1'];
  416. $entries = ($entries == 0) ? pow(2, 16) : $entries;
  417. $first = $palettes['R'][0]['val2'];
  418. $size = $palettes['R'][0]['val3'];
  419. $palette_byte_size = (int) $size/8;
  420. $offset = 8;
  421. $current_position = 0;
  422. $colors = array('R', 'G', 'B');
  423. // Now let's create the right values for the images
  424. for ($index = 0; $index < $number_of_frames; $index++)
  425. {
  426. // Create the image object
  427. $image = self::create_image($cols, $rows);
  428. for($y = 0; $y < $rows; $y++)
  429. {
  430. for ($x = 0; $x < $cols; $x++)
  431. {
  432. $rgb = array();
  433. $value = $this->_read_gray($blob, $current_position, $bytes_to_read);
  434. $palette_index = ($value <= $first)
  435. ? $first
  436. : (($value > $first + $entries) ? $first + $entries - 1 : $value);
  437. $current_position += $bytes_to_read;
  438. for ($sample = 0; $sample < 3; $sample++)
  439. {
  440. $tmp = $this->_read_gray($palettes[$colors[$sample]][1]
  441. , $palette_byte_size*$palette_index, $palette_byte_size);
  442. $rgb[$sample] = $tmp >> 8;
  443. }
  444. // Set the color
  445. $color = $this->_allocate_color_rgb($image, $rgb);
  446. $rgb = NULL;
  447. // Set the pixel value
  448. $this->_set_pixel($image, $x, $y, $color);
  449. $color = NULL;
  450. }
  451. }
  452. // Append the current image
  453. $images[] = $image;
  454. $image = NULL;
  455. }
  456. return $images;
  457. }
  458. else
  459. {
  460. // Do this if no window center and window width are set and when samples per pixel is 1 (gray images).
  461. if (($window_center == 0 OR $window_width == 0) AND $samples_per_pixel == 1)
  462. {
  463. $length = strlen($blob);
  464. // This is costly performance wise! :(
  465. while ($current_position < $starting_position + $length)
  466. {
  467. if ($current_position == $starting_position + $current_image*$image_size)
  468. {
  469. // A new image has been found
  470. $x = 0;
  471. $y = 0;
  472. $max[$current_image] = -200000; // Small enough so it will be properly calculated
  473. $min[$current_image] = 200000; // Large enough so it wil be properly calculated
  474. $current_image++;
  475. }
  476. $gray = $this->_read_gray($blob, $current_position, $bytes_to_read);
  477. $current_position += $bytes_to_read;
  478. // Getting the max
  479. if ($gray > $max[$current_image - 1])
  480. {
  481. // max
  482. $max[$current_image - 1] = $gray;
  483. }
  484. // Getting the min
  485. if ($gray < $min[$current_image - 1])
  486. {
  487. // min
  488. $min[$current_image - 1] = $gray;
  489. }
  490. $y++;
  491. if ($y == $cols)
  492. {
  493. // Next row
  494. $x++;
  495. $y = 0;
  496. }
  497. }
  498. }
  499. }
  500. $current_position = $starting_position = 0;
  501. // Now let's create the right values for the images
  502. for ($index = 0; $index < $number_of_frames; $index++)
  503. {
  504. if ($samples_per_pixel == 1)
  505. {
  506. // Real max and min according to window center & width (if set)
  507. $maximum = ($window_center != 0 AND $window_width != 0)
  508. ? round($window_center + $window_width/2)
  509. : $max[$index];
  510. $minimum = ($window_center != 0 AND $window_width != 0)
  511. ? round($window_center - $window_width/2)
  512. : $min[$index];
  513. // Check if window and level are sent
  514. $maximum = ( ! empty($window) AND ! empty($level))
  515. ? round($level + $window/2)
  516. : $maximum;
  517. $minimum = ( ! empty($window) AND ! empty($level))
  518. ? round($level - $window/2)
  519. : $minimum;
  520. if ($maximum == $minimum)
  521. {
  522. // Something wrong. Avoid having a zero division
  523. throw new Nanodicom_Exception('Division by zero', NULL, 302);
  524. }
  525. }
  526. // Create the image object
  527. $image = self::create_image($cols, $rows);
  528. $pixels = array();
  529. for($y = 0; $y < $rows; $y++)
  530. {
  531. for ($x = 0; $x < $cols; $x++)
  532. {
  533. switch ($samples_per_pixel)
  534. {
  535. case 1:
  536. $gray = $this->_read_gray($blob, $current_position, $bytes_to_read);
  537. $current_position += $bytes_to_read;
  538. // truncating pixel values over max and below min
  539. $gray = ($gray > $maximum) ? $maximum : $gray;
  540. $gray = ($gray < $minimum) ? $minimum : $gray;
  541. // Converting to gray value
  542. $gray = ($gray - $minimum)/($maximum - $minimum)*255;
  543. // For MONOCHROME1 we have to invert the pixel values.
  544. if ($photometric_interpretation == 'MONOCHROME1')
  545. {
  546. $gray = 255 - $gray;
  547. }
  548. // Set the (gray) color
  549. $color = $this->_allocate_color_gray($image, $gray);
  550. $gray = NULL;
  551. break;
  552. case 3:
  553. // It has 3 colors
  554. $rgb = array();
  555. for ($sample = 0; $sample < $samples_per_pixel; $sample++)
  556. {
  557. $current_position = ($planar_configuration == 0)
  558. ? $current_position
  559. : $sample * ($rows * $cols) + ($y*$cols + $x);
  560. $rgb[$sample] = $this->_read_gray($blob, $current_position, $bytes_to_read);
  561. $current_position += $bytes_to_read;
  562. }
  563. // Set the color
  564. $color = $this->_allocate_color_rgb($image, $rgb);
  565. $rgb = NULL;
  566. break;
  567. default:
  568. break;
  569. }
  570. // Set the pixel value
  571. $this->_set_pixel($image, $x, $y, $color);
  572. $color = NULL;
  573. }
  574. }
  575. // Append the current image
  576. $images[] = $image;
  577. $image = NULL;
  578. }
  579. // Collect the ending time for the profiler
  580. $this->profiler['pixel']['end'] = microtime(TRUE);
  581. return $images;
  582. }
  583. /**
  584. * Public method to write images based on library used and format
  585. *
  586. * @param resource the image object instance
  587. * @param string the format to be saved. defaults to jpeg
  588. * @return boolean true on success, false on failure
  589. */
  590. public function write_image($image, $location, $format = 'jpg')
  591. {
  592. switch (self::$driver)
  593. {
  594. case 'gd' :
  595. switch ($format)
  596. {
  597. case 'png': imagepng($image, $location.'.png');
  598. break;
  599. case 'gif': imagegif($image, $location.'.gif');
  600. break;
  601. case 'jpg':
  602. default:
  603. imagejpeg($image, $location.'.jpg');
  604. break;
  605. }
  606. break;
  607. case 'gmagick' :
  608. case 'imagick' :
  609. switch ($format)
  610. {
  611. case 'png': $format = 'png';
  612. break;
  613. case 'gif': $format = 'gif';
  614. break;
  615. default: $format = 'jpg';
  616. break;
  617. }
  618. $image->writeImage($location.'.'.$format);
  619. break;
  620. }
  621. return TRUE;
  622. }
  623. /**
  624. * Internal method to read image from blob (string)
  625. *
  626. * @param string the binary data to be read
  627. * @return mixed an instance of the image based on library (driver), false on failure
  628. */
  629. protected function _read_image_blob($string)
  630. {
  631. switch (self::$driver)
  632. {
  633. case 'gd' : return imagecreatefromstring($string);
  634. case 'gmagick' :
  635. case 'imagick' :
  636. $image = new imagick();
  637. $image->readImageBlob($string);
  638. return $image;
  639. }
  640. return FALSE;
  641. }
  642. /**
  643. * Internal method to allocate the color for image instance (color)
  644. *
  645. * @param resource the image object instance
  646. * @param integer the color value
  647. * @return mixed color value or class, false on failure
  648. */
  649. protected function _allocate_color_rgb($image, $rgb)
  650. {
  651. switch (self::$driver)
  652. {
  653. case 'gd': return imagecolorallocate($image, $rgb[0], $rgb[1], $rgb[2]);
  654. case 'gmagick': return new GmagickPixel('rgb('.$rgb[0].','.$rgb[1].','.$rgb[2].')');
  655. case 'imagick': return new ImagickPixel('rgb('.$rgb[0].','.$rgb[1].','.$rgb[2].')');
  656. }
  657. return FALSE;
  658. }
  659. /**
  660. * Internal method to allocate the color for image instance (gray)
  661. *
  662. * @param resource the image object instance
  663. * @param integer the gray value
  664. * @return mixed color value or class, false on failure
  665. */
  666. protected function _allocate_color_gray($image, $gray)
  667. {
  668. switch (self::$driver)
  669. {
  670. case 'gd': return imagecolorallocate($image, $gray, $gray, $gray);
  671. case 'gmagick': return new GmagickPixel('rgb('.$gray.','.$gray.','.$gray.')');
  672. case 'imagick': return new ImagickPixel('rgb('.$gray.','.$gray.','.$gray.')');
  673. }
  674. return FALSE;
  675. }
  676. /**
  677. * Internal method to set a pixel into the existing image object instance
  678. *
  679. * @param resource the image object instance
  680. * @param integer the x position
  681. * @param integer the y position
  682. * @param integer the color to be set
  683. * @return boolean true on success, false on failure
  684. */
  685. protected function _set_pixel( & $image, $x, $y, $color)
  686. {
  687. switch (self::$driver)
  688. {
  689. case 'gd': imagesetpixel($image, $x, $y, $color);
  690. return TRUE;
  691. case 'gmagick': $draw = new GmagickDraw();
  692. $draw->setFillColor($color);
  693. $draw->point($x, $y);
  694. $image->drawImage($draw);
  695. return TRUE;
  696. case 'imagick': $draw = new ImagickDraw();
  697. $draw->setFillColor($color);
  698. $draw->point($x, $y);
  699. $image->drawImage($draw);
  700. return TRUE;
  701. }
  702. return FALSE;
  703. }
  704. /**
  705. * Public static method to create an instance of the image
  706. *
  707. * @param integer the number of columns
  708. * @param integet the number of rows
  709. * @return mixed the corresponding object from the set driver or false on failure
  710. */
  711. public static function create_image($cols, $rows)
  712. {
  713. switch (self::$driver)
  714. {
  715. case 'gd': return imagecreatetruecolor($cols, $rows);
  716. case 'gmagick': $image = new Gmagick();
  717. $image->newImage($cols, $rows, 'none');
  718. return $image;
  719. case 'imagick': $image = new Imagick();
  720. $image->newImage($cols, $rows, 'none');
  721. return $image;
  722. }
  723. return FALSE;
  724. }
  725. /**
  726. * Internal method to check if drivers are installed
  727. *
  728. * @return bool true if driver is installed, false otherwise
  729. */
  730. protected function _check_driver()
  731. {
  732. switch (self::$driver)
  733. {
  734. case 'gd': return function_exists('imagecreatetruecolor');
  735. case 'gmagick': return class_exists('Gmagick');
  736. //case 'imagick': return (class_exists('Imagick') AND method_exists('Imagick', 'importImagePixels'));
  737. case 'imagick': return class_exists('Imagick');
  738. }
  739. return FALSE;
  740. }
  741. /**
  742. * Internal method to read a 'gray' value.
  743. *
  744. * @param string the blob that holds the pixel data
  745. * @param integer the current position in the string to read
  746. * @param integet the number of bytes to read
  747. * @return integer the gray or color value at the given location
  748. */
  749. protected function _read_gray($blob, $current_position, $bytes_to_read)
  750. {
  751. if ($this->_samples_per_pixel == 1)
  752. {
  753. $chunk = substr($blob, $current_position, $bytes_to_read);
  754. $gray = $this->{Nanodicom::$_read_int}($bytes_to_read, $this->_endian, $bytes_to_read, Nanodicom::UNSIGNED, $chunk);
  755. $chunk = NULL;
  756. // Checking if 2's complement
  757. $gray = ($this->_pixel_representation)
  758. ? self::complement2($gray, $this->_high_bit)
  759. : $gray;
  760. // Getting the right value according to slope and intercept
  761. $gray = $gray*$this->_rescale_slope + $this->_rescale_intercept;
  762. // Multiplying for dose_grid_scaling
  763. return $gray*$this->_dose_scaling;
  764. }
  765. else
  766. {
  767. // Read current position, it is 3 samples
  768. $chunk = substr($blob, $current_position, $bytes_to_read);
  769. return $this->{Nanodicom::$_read_int}($bytes_to_read, $this->_endian, $bytes_to_read, Nanodicom::UNSIGNED, $chunk);
  770. }
  771. }
  772. /**
  773. * Static method to find the complement of 2, returns an integer.
  774. *
  775. * @param integer the integer number to convert
  776. * @param integer the high bit for the value. By default 15 (assumes 2 bytes)
  777. * @return integer the number after complement's 2 applied
  778. */
  779. public static function complement2($number, $high_bit = 15)
  780. {
  781. $sign = $number >> $high_bit;
  782. if ($sign)
  783. {
  784. // Negative
  785. $number = -pow(2, $high_bit + 1) - $number;
  786. }
  787. return $number;
  788. }
  789. }