| Current Path : /home/megadansyp/www/components/com_eventgallery/vendor/svenbluege/jpeg-icc/ |
| Current File : /home/megadansyp/www/components/com_eventgallery/vendor/svenbluege/jpeg-icc/class.jpeg_icc.php |
<?php
/**
* PHP JPEG ICC profile manipulator class
*
* @author Richard Toth aka risko (risko@risko.org)
* @version 0.2
* License: BSD License
*
* http://sourceforge.net/projects/jpeg-icc/
* http://jpeg-icc.sourceforge.net/
* https://github.com/slavicv/jpeg-icc
*/
class JPEG_ICC
{
/**
* ICC header size in APP2 segment
*
* 'ICC_PROFILE' 0x00 chunk_no chunk_cnt
*/
const ICC_HEADER_LEN = 14;
/**
* maximum data len of a JPEG marker
*/
const MAX_BYTES_IN_MARKER = 65533;
/**
* ICC header marker
*/
const ICC_MARKER = "ICC_PROFILE\x00";
/**
* Rendering intent field (Bytes 64 to 67 in ICC profile data)
*/
const ICC_RI_PERCEPTUAL = 0x00000000;
const ICC_RI_RELATIVE_COLORIMETRIC = 0x00000001;
const ICC_RI_SATURATION = 0x00000002;
const ICC_RI_ABSOLUTE_COLORIMETRIC = 0x00000003;
/**
* ICC profile data
* @var string
*/
private $icc_profile = '';
/**
* ICC profile data size
* @var int
*/
private $icc_size = 0;
/**
* ICC profile data chunks count
* @var int
*/
private $icc_chunks = 0;
/**
* Class contructor
*/
public function __construct()
{
}
/**
* Load ICC profile from JPEG file.
*
* Returns true if profile successfully loaded, false otherwise.
*
* @param string file name
* @return bool
*/
public function LoadFromJPEG($fname)
{
$f = file_get_contents($fname);
$len = strlen($f);
$pos = 0;
$counter = 0;
$profile_chunks = array(); // tu su ulozene jednotlive casti profilu
while ($pos < $len && $counter < 1000)
{
$pos = strpos($f, "\xff", $pos);
if ($pos === false) break; // dalsie 0xFF sa uz nenaslo - koniec vyhladavania
$type = $this->getJPEGSegmentType($f, $pos);
switch ($type)
{
case 0xe2: // APP2
//echo "APP2 ";
$size = $this->getJPEGSegmentSize($f, $pos);
//echo "Size: $size\n";
if ($this->getJPEGSegmentContainsICC($f, $pos, $size))
{
//echo "+ ICC Profile: YES\n";
list($chunk_no, $chunk_cnt) = $this->getJPEGSegmentICCChunkInfo($f, $pos);
//echo "+ ICC Profile chunk number: $chunk_no\n";
//echo "+ ICC Profile chunks count: $chunk_cnt\n";
if ($chunk_no <= $chunk_cnt)
{
$profile_chunks[$chunk_no] = $this->getJPEGSegmentICCChunk($f, $pos);
if ($chunk_no == $chunk_cnt) // posledny kusok
{
ksort($profile_chunks);
$this->SetProfile(implode('', $profile_chunks));
return true;
}
}
}
$pos += $size + 2; // size of segment data + 2B size of segment marker
break;
case 0xe0: // APP0
case 0xe1: // APP1
case 0xe3: // APP3
case 0xe4: // APP4
case 0xe5: // APP5
case 0xe6: // APP6
case 0xe7: // APP7
case 0xe8: // APP8
case 0xe9: // APP9
case 0xea: // APP10
case 0xeb: // APP11
case 0xec: // APP12
case 0xed: // APP13
case 0xee: // APP14
case 0xef: // APP15
case 0xc0: // SOF0
case 0xc2: // SOF2
case 0xc4: // DHT
case 0xdb: // DQT
case 0xda: // SOS
case 0xfe: // COM
$size = $this->getJPEGSegmentSize($f, $pos);
$pos += $size + 2; // size of segment data + 2B size of segment marker
break;
case 0xd8: // SOI
case 0xdd: // DRI
case 0xd9: // EOI
case 0xd0: // RST0
case 0xd1: // RST1
case 0xd2: // RST2
case 0xd3: // RST3
case 0xd4: // RST4
case 0xd5: // RST5
case 0xd6: // RST6
case 0xd7: // RST7
default:
$pos += 2;
break;
}
$counter++;
}
return false;
}
/**
* Save previously loaded ICC profile into JPEG file.
*
* @param string JPEG file name
*/
public function SaveToJPEG($fname)
{
if ($this->icc_profile == '') throw new Exception("No profile loaded.\n");
if (!file_exists($fname)) throw new Exception("File $fname doesn't exist.\n");
if (!is_readable($fname)) throw new Exception("File $fname isn't readable.\n");
$dir = realpath($fname);
if (!is_writable($dir)) throw new Exception("Directory $fname isn't writeable.\n");
$f = file_get_contents($fname);
if ($this->insertProfile($f))
{
$fsize = strlen($f);
$ret = file_put_contents($fname, $f);
if ($ret === false || $ret < $fsize) throw new Exception ("Write failed.\n");
}
}
/**
* Load profile from ICC file.
*
* @param string file name
*/
public function LoadFromICC($fname)
{
if (!file_exists($fname)) throw new Exception("File $fname doesn't exist.\n");
if (!is_readable($fname)) throw new Exception("File $fname isn't readable.\n");
$this->SetProfile(file_get_contents($fname));
}
/**
* Save profile to ICC file.
*
* @param string file name
* @param bool [force overwrite]
*/
public function SaveToICC($fname, $force_overwrite = false)
{
if ($this->icc_profile == '') throw new Exception("No profile loaded.\n");
$dir = realpath($fname);
if (!is_writable($dir)) throw new Exception("Directory $fname isn't writeable.\n");
if (!$force_overwrite && file_exists($fname)) throw new Exception("File $fname exists.\n");
$ret = file_put_contents($fname, $this->icc_profile);
if ($ret === false || $ret < $this->icc_size) throw new Exception ("Write failed.\n");
}
/**
* Remove profile from JPEG file and save it as a new file.
* Overwriting destination file can be forced
*
* @param string source file
* @param string destination file
* @param bool [force overwrite]
* @return bool
*/
public function RemoveFromJPEG($input, $output, $force_overwrite = false)
{
if (!file_exists($input)) throw new Exception("File $input doesn't exist.\n");
if (!is_readable($input)) throw new Exception("File $input isn't readable.\n");
$dir = realpath($output);
if (!is_writable($dir)) throw new Exception("Directory $output isn't writeable.\n");
if (!$force_overwrite && file_exists($output)) throw new Exception("File $output exists.\n");
$f = file_get_contents($input);
$this->removeProfile($f);
$fsize = strlen($f);
$ret = file_put_contents($output, $f);
if ($ret === false || $ret < $fsize) throw new Exception ("Write failed.\n");
return true; // any other error throws exception
}
/**
* Set profile directly
*
* @param string profile data
*/
public function SetProfile($data)
{
$this->icc_profile = $data;
$this->icc_size = strlen($data);
$this->countChunks();
}
/**
* Get profile directly
*
* @return string
*/
public function GetProfile()
{
return $this->icc_profile;
}
/**
* Count in how many chunks we need to divide the profile to store it in JPEG APP2 segments
*/
private function countChunks()
{
$this->icc_chunks = ceil($this->icc_size / ((float) (self::MAX_BYTES_IN_MARKER - self::ICC_HEADER_LEN)));
}
/**
* Set Rendering Intent of the profile.
*
* Possilbe values are ICC_RI_PERCEPTUAL, ICC_RI_RELATIVE_COLORIMETRIC, ICC_RI_SATURATION or ICC_RI_ABSOLUTE_COLORIMETRIC.
*
* @param int rendering intent
*/
private function setRenderingIntent($newRI)
{
if ($this->icc_size >= 68)
{
substr_replace($this->icc_profile, pack('N', $newRI), 64, 4);
}
}
/**
* Get value of Rendering Intent field in ICC profile
*
* @return int
*/
private function getRenderingIntent()
{
if ($this->icc_size >= 68)
{
$arr = unpack('Nint', substr($this->icc_profile, 64, 4));
return $arr['int'];
}
return null;
}
/**
* Size of JPEG segment
*
* @param string file data
* @param int start of segment
* @return int
*/
private function getJPEGSegmentSize(&$f, $pos)
{
$arr = unpack('nint', substr($f, $pos + 2, 2)); // segment size has offset 2 and length 2B
return $arr['int'];
}
/**
* Type of JPEG segment
*
* @param string file data
* @param int start of segment
* @return int
*/
private function getJPEGSegmentType(&$f, $pos)
{
$arr = unpack('Cchar', substr($f, $pos + 1, 1)); // segment type has offset 1 and length 1B
return $arr['char'];
}
/**
* Check if segment contains ICC profile marker
*
* @param string file data
* @param int position of segment data
* @param int size of segment data (without 2 bytes of size field)
* @return bool
*/
private function getJPEGSegmentContainsICC(&$f, $pos, $size)
{
if ($size < self::ICC_HEADER_LEN) return false; // ICC_PROFILE 0x00 Marker_no Marker_cnt
return (bool) (substr($f, $pos + 4, self::ICC_HEADER_LEN - 2) == self::ICC_MARKER); // 4B offset in segment data = 2B segment marker + 2B segment size data
}
/**
* Get ICC segment chunk info
*
* @param string file data
* @param int position of segment data
* @return array {chunk_no, chunk_cnt}
*/
private function getJPEGSegmentICCChunkInfo(&$f, $pos)
{
$a = unpack('Cchunk_no/Cchunk_count', substr($f, $pos + 16, 2)); // 16B offset to data = 2B segment marker + 2B segment size + 'ICC_PROFILE' + 0x00, 1. byte chunk number, 2. byte chunks count
return array_values($a);
}
/**
* Returns chunk of ICC profile data from segment.
*
* @param string &data
* @param int current position
* @return string
*/
private function getJPEGSegmentICCChunk(&$f, $pos)
{
$data_offset = $pos + 4 + self::ICC_HEADER_LEN; // 4B JPEG APP offset + 14B ICC header offset
$size = $this->getJPEGSegmentSize($f, $pos);
$data_size = $size - self::ICC_HEADER_LEN - 2; // 14B ICC header - 2B of size data
return substr($f, $data_offset, $data_size);
}
/**
* Get data of given chunk
*
* @param int chunk number
* @return string
*/
private function getChunk($chunk_no)
{
if ($chunk_no > $this->icc_chunks) return '';
$max_chunk_size = self::MAX_BYTES_IN_MARKER - self::ICC_HEADER_LEN;
$from = ($chunk_no - 1) * $max_chunk_size;
$bytes = ($chunk_no < $this->icc_chunks) ? $max_chunk_size : $this->icc_size % $max_chunk_size;
return substr($this->icc_profile, $from, $bytes);
}
/**
* Prepare all data needed to be inserted into JPEG file to add ICC profile.
*
* @return string
*/
private function prepareJPEGProfileData()
{
$data = '';
for ($i = 1; $i <= $this->icc_chunks; $i++)
{
$chunk = $this->getChunk($i);
$chunk_size = strlen($chunk);
$data .= "\xff\xe2" . pack('n', $chunk_size + 2 + self::ICC_HEADER_LEN); // APP2 segment marker + size field
$data .= self::ICC_MARKER . pack('CC', $i, $this->icc_chunks); // profile marker inside segment
$data .= $chunk;
}
return $data;
}
/**
* Removes profile from JPEG data
*
* @param string &data
* @return bool
*/
private function removeProfile(&$jpeg_data)
{
$len = strlen($jpeg_data);
$pos = 0;
$counter = 0; // ehm...
$chunks_to_go = -1;
while ($pos < $len && $counter < 100)
{
$pos = strpos($jpeg_data, "\xff", $pos);
if ($pos === false) break; // no more 0xFF - we can end up with search
// analyze next segment
$type = $this->getJPEGSegmentType($jpeg_data, $pos);
switch ($type)
{
case 0xe2: // APP2
$size = $this->getJPEGSegmentSize($jpeg_data, $pos);
if ($this->getJPEGSegmentContainsICC($jpeg_data, $pos, $size))
{
list($chunk_no, $chunk_cnt) = $this->getJPEGSegmentICCChunkInfo($jpeg_data, $pos);
if ($chunks_to_go == -1) $chunks_to_go = $chunk_cnt; // first time save chunks count
$jpeg_data = substr_replace($jpeg_data, '', $pos, $size + 2); // remove this APP segment from dataset (segment size + 2B app marker)
$len -= $size + 2; // shorten the size
if (--$chunks_to_go == 0) return true; // no more icc profile chunks, store file
break; // go out without changing the position
}
$pos += $size + 2; // size of segment data + 2B size of segment marker
break;
case 0xe0: // APP0
case 0xe1: // APP1
case 0xe3: // APP3
case 0xe4: // APP4
case 0xe5: // APP5
case 0xe6: // APP6
case 0xe7: // APP7
case 0xe8: // APP8
case 0xe9: // APP9
case 0xea: // APP10
case 0xeb: // APP11
case 0xec: // APP12
case 0xed: // APP13
case 0xee: // APP14
case 0xef: // APP15
case 0xc0: // SOF0
case 0xc2: // SOF2
case 0xc4: // DHT
case 0xdb: // DQT
case 0xda: // SOS
case 0xfe: // COM
$size = $this->getJPEGSegmentSize($jpeg_data, $pos);
$pos += $size + 2; // size of segment data + 2B size of segment marker
break;
case 0xd8: // SOI
case 0xdd: // DRI
case 0xd9: // EOI
case 0xd0: // RST0
case 0xd1: // RST1
case 0xd2: // RST2
case 0xd3: // RST3
case 0xd4: // RST4
case 0xd5: // RST5
case 0xd6: // RST6
case 0xd7: // RST7
default:
$pos += 2;
break;
}
$counter++;
}
return false;
}
/**
* Inserts profile to JPEG data.
*
* Inserts profile immediately after SOI section
*
* @param string &data
* @return bool
*/
private function insertProfile(&$jpeg_data)
{
$len = strlen($jpeg_data);
$pos = 0;
$counter = 0; // ehm...
$chunks_to_go = -1;
while ($pos < $len && $counter < 100)
{
$pos = strpos($jpeg_data, "\xff", $pos);
if ($pos === false) break; // no more 0xFF - we can end up with search
// analyze next segment
$type = $this->getJPEGSegmentType($jpeg_data, $pos);
switch ($type)
{
case 0xd8: // SOI
$pos += 2;
$p_data = $this->prepareJPEGProfileData();
if ($p_data != '')
{
$before = substr($jpeg_data, 0, $pos);
$after = substr($jpeg_data, $pos);
$jpeg_data = $before . $p_data . $after;
return true;
}
return false;
//break;
case 0xe0: // APP0
case 0xe1: // APP1
case 0xe2: // APP2
case 0xe3: // APP3
case 0xe4: // APP4
case 0xe5: // APP5
case 0xe6: // APP6
case 0xe7: // APP7
case 0xe8: // APP8
case 0xe9: // APP9
case 0xea: // APP10
case 0xeb: // APP11
case 0xec: // APP12
case 0xed: // APP13
case 0xee: // APP14
case 0xef: // APP15
case 0xc0: // SOF0
case 0xc2: // SOF2
case 0xc4: // DHT
case 0xdb: // DQT
case 0xda: // SOS
case 0xfe: // COM
$size = $this->getJPEGSegmentSize($jpeg_data, $pos);
$pos += $size + 2; // size of segment data + 2B size of segment marker
break;
case 0xdd: // DRI
case 0xd9: // EOI
case 0xd0: // RST0
case 0xd1: // RST1
case 0xd2: // RST2
case 0xd3: // RST3
case 0xd4: // RST4
case 0xd5: // RST5
case 0xd6: // RST6
case 0xd7: // RST7
default:
$pos += 2;
break;
}
$counter++;
}
return false;
}
}
?>