Extract internal EXE info - php

Windows EXE files have some metadata like CompanyName, FileVersion, InternalName, ProductName, OriginalFileName, ProductVersion, etc.
How can I extract such metadata from using PHP?

I got curious about this, so I decided to write this function:
function getFileVersionInfo($filename,$encoding='UTF-8'){
$dat = file_get_contents($filename);
if($pos=strpos($dat,mb_convert_encoding('VS_VERSION_INFO','UTF-16LE'))){
$pos-= 6;
$six = unpack('v*',substr($dat,$pos,6));
$dat = substr($dat,$pos,$six[1]);
if($pos=strpos($dat,mb_convert_encoding('StringFileInfo','UTF-16LE'))){
$pos+= 54;
$res = [];
$six = unpack('v*',substr($dat,$pos,6));
while($six[2]){
$nul = strpos($dat,"\0\0\0",$pos+6)+1;
$key = mb_convert_encoding(substr($dat,$pos+6,$nul-$pos-6),$encoding,'UTF-16LE');
$val = mb_convert_encoding(substr($dat,ceil(($nul+2)/4)*4,$six[2]*2-2),$encoding,'UTF-16LE');
$res[$key] = $val;
$pos+= ceil($six[1]/4)*4;
$six = unpack('v*',substr($dat,$pos,6));
}
return $res;
}
}
}
It works with 32-bit and 64-bit exe. Usage example:
echo "<pre>".print_r(getFileVersionInfo('notepad.exe'),1)."</pre>";
echo "<pre>".print_r(getFileVersionInfo('php.exe'),1)."</pre>";
echo "<pre>".print_r(getFileVersionInfo('jre-7u9-windows-x64.exe'),1)."</pre>";
notepad.exe (32-bit):
Array
(
[CompanyName] => Microsoft Corporation
[FileDescription] => Notepad
[FileVersion] => 6.1.7600.16385 (win7_rtm.090713-1255)
[InternalName] => Notepad
[LegalCopyright] => © Microsoft Corporation. All rights reserved.
[OriginalFilename] => NOTEPAD.EXE
[ProductName] => Microsoft® Windows® Operating System
[ProductVersion] => 6.1.7600.16385
)
php.exe (32-bit):
Array
(
[Comments] => Thanks to Edin Kadribasic, Marcus Boerger, Johannes Schlueter, Moriyoshi Koizumi, Xinchen Hui
[CompanyName] => The PHP Group
[FileDescription] => CLI
[FileVersion] => 7.0.12
[InternalName] => CLI SAPI
[LegalCopyright] => Copyright © 1997-2016 The PHP Group
[LegalTrademarks] => PHP
[OriginalFilename] => php.exe
[ProductName] => PHP
[ProductVersion] => 7.0.12
[URL] => http://www.php.net
)
jre-7u9-windows-x64.exe (64-bit):
Array
(
[CompanyName] => Oracle Corporation
[FileDescription] => Java(TM) Platform SE binary
[FileVersion] => 7.0.90.5
[Full Version] => 1.7.0_09-b05
[InternalName] => Setup Launcher
[LegalCopyright] => Copyright © 2012
[OriginalFilename] => jinstall.exe
[ProductName] => Java(TM) Platform SE 7 U9
[ProductVersion] => 7.0.90.5
)
Something interesting about php.exe: the Comments and URL don't show up in the Details tab.
At least in my computer.
Enjoy.
Update 1: I forgot error checking. Now it returns null if the version info doesn't exist.
Update 2: Many thanks to #Abela for bringing an encoding issue to my attention.
I added an optional 2nd parameter that defaults to UTF-8 which should work for most purposes.
If you need single-byte-character output, use ISO-8859-1 instead, like this:
getFileVersionInfo('php.exe','ISO-8859-1');

This is what works for me. Works with .dll-s too.
function fsubstr($file_handle,$start,$lenght)
{
fseek($file_handle,$start);
$result = fread($file_handle,$lenght);
return $result;
}
function fGetFileVersion($FileName)
{
$handle = fopen($FileName, 'rb');
if(!$handle)
{
return FALSE;
}
$Header = fread($handle, 64);
if(substr($Header, 0, 2) != 'MZ')
{
return FALSE;
}
$PEOffset = unpack("V", substr($Header, 60, 4));
if($PEOffset[1] < 64)
{
return FALSE;
}
fseek($handle, $PEOffset[1], SEEK_SET);
$Header = fread($handle, 24);
if(substr($Header, 0, 2) != 'PE')
{
return FALSE;
}
$Machine = unpack("v", substr($Header, 4, 2));
if($Machine[1] != 332)
{
return FALSE;
}
$NoSections = unpack("v", substr($Header, 6, 2));
$OptHdrSize = unpack("v", substr($Header, 20, 2));
fseek($handle, $OptHdrSize[1], SEEK_CUR);
$ResFound = FALSE;
for ($x = 0; $x < $NoSections[1]; $x++)
{
$SecHdr = fread($handle, 40);
if (substr($SecHdr, 0, 5) == '.rsrc')
{
$ResFound = TRUE;
break;
}
}
if(!$ResFound)
{
return FALSE;
}
$InfoVirt = unpack("V", substr($SecHdr, 12, 4));
$InfoSize = unpack("V", substr($SecHdr, 16, 4));
$InfoOff = unpack("V", substr($SecHdr, 20, 4));
$offset = $InfoOff[1];
$NumDirs = unpack("v", fsubstr($handle, $offset + 14, 2));
$InfoFound = FALSE;
for ($x = 0; $x <$NumDirs[1]; $x++)
{
$Type = unpack("V", fsubstr($handle, $offset + ($x * 8) + 16, 4));
if($Type[1] == 16)
{
//FILEINFO resource
$InfoFound = TRUE;
$SubOff = unpack("V", fsubstr($handle, $offset + ($x * 8) + 20, 4));
break;
}
}
if (!$InfoFound)
{
return FALSE;
}
$SubOff[1] &= 0x7fffffff;
$InfoOff = unpack("V", fsubstr($handle, $offset + $SubOff[1] + 20, 4)); //offset of first FILEINFO
$InfoOff[1] &= 0x7fffffff;
$InfoOff = unpack("V", fsubstr($handle, $offset + $InfoOff[1] + 20, 4)); //offset to data
$DataOff = unpack("V", fsubstr($handle, $offset + $InfoOff[1], 4));
$DataSize = unpack("V", fsubstr($handle, $offset + $InfoOff[1] + 4, 4));
$CodePage = unpack("V", fsubstr($handle, $offset + $InfoOff[1] + 8, 4));
$DataOff[1] -= $InfoVirt[1];
$Version = unpack("v4", fsubstr($handle, $offset + $DataOff[1] + 48, 8));
return sprintf("%u.%u.%u.%u",$Version[2],$Version[1],$Version[4],$Version[3]);
}

Related

Retrieving data from the session

How to retrieve data in .htm page using twig.
public function onRun()
{
$captchaImagePath = '/Applications/MAMP/htdocs/install-master/storage/app/uploads/captcha/';
Log::info($captchaImagePath);
$captchaImageUrl = '/Applications/MAMP/htdocs/install-master/storage/app/uploads/captcha/';
$captchaFontPath = '/Applications/MAMP/htdocs/install-master/storage/app/uploads/fonts/verdana.ttf';
$val = array(
'word_length' => 5,
'word' => '',
'img_path' => $captchaImagePath,
'img_url' => $captchaImageUrl,
'font_path' => $captchaFontPath,
'img_width' => '150',
'img_height' => 30,
'expiration' => 7200
);
$img_path=$captchaImagePath;
$img_url=$captchaImageUrl;
$font_path=$captchaFontPath;
$captcha = $this->create_captcha($val,$img_path,$img_url,$font_path);
$url = Request::url();
if (ends_with($url, ['.html', '.htm']))
{
$url = str_replace(['.html', '.htm'], '', $url);
return Redirect::to($url, 301)->with($captcha);
}
Log::info($url);
Log::info($captcha);
}
the function in same file public function create_captcha($data = '', $img_path = '', $img_url = '', $font_path = '')
{
// Log::info($data);
// Log::info($img_path);
// Log::info($img_url);
// Log::info($font_path);
if(!isset($data['word_length']))
{
$length=5;
}
else
{
$length=$data['word_length'];
}
//Log::info($length);
$defaults = array('word' => '', 'word_length' => $length,'img_path' => '', 'img_url' => '', 'img_width' => '150', 'img_height' => '30', 'font_path' => '', 'expiration' => 7200);
// Log::info($defaults);
foreach ($defaults as $key => $val)
{
if ( ! is_array($data))
{
if ( ! isset($$key) OR $$key == '')
{
$$key = $val;
// Log::info( $$key);
}
}
else
{
$$key = ( ! isset($data[$key])) ? $val : $data[$key];
}
}
// Log::info($img_path); Log::info($img_url);
if ($img_path == '' OR $img_url == '')
{
return FALSE;
}
if ( ! is_dir($img_path))
{
return FALSE;
}
if ( ! is_writable($img_path))
{
return FALSE;
}
if ( ! extension_loaded('gd'))
{
return FALSE;
}
// -----------------------------------
// Remove old images
// -----------------------------------
list($usec, $sec) = explode(" ", microtime());
$now = ((float)$usec + (float)$sec);
$current_dir = #opendir($img_path);
while($filename = #readdir($current_dir))
{
if ($filename != "." and $filename != ".." and $filename != "index.html")
{
$name = str_replace(".jpg", "", $filename);
if (($name + $expiration) < $now)
{
#unlink($img_path.$filename);
}
}
}
#closedir($current_dir);
// -----------------------------------
// Do we have a "word" yet?
// -----------------------------------
if ($word == '')
{
//$pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$str = '';
for ($i = 0; $i < $word_length; $i++)
{
$str .= substr($pool, mt_rand(0, strlen($pool) -1), 1);
}
$word = $str;
}
// -----------------------------------
// Determine angle and position
// -----------------------------------
$length = strlen($word);
$angle = ($length >= 6) ? rand(-($length-6), ($length-6)) : 0;
$x_axis = rand(6, (360/$length)-16);
$y_axis = ($angle >= 0 ) ? rand($img_height, $img_width) : rand(6, $img_height);
// -----------------------------------
// Create image
// -----------------------------------
// PHP.net recommends imagecreatetruecolor(), but it isn't always available
if (function_exists('imagecreatetruecolor'))
{
$im = imagecreatetruecolor($img_width, $img_height);
}
else
{
$im = imagecreate($img_width, $img_height);
}
// -----------------------------------
// Assign colors
// -----------------------------------
$bg_color = imagecolorallocate ($im, 255, 255, 255);
$border_color = imagecolorallocate ($im, 232, 244, 252);
$text_color = imagecolorallocate ($im, 57, 136, 190);
$grid_color = imagecolorallocate($im, 220, 239, 253);
$shadow_color = imagecolorallocate($im, 255, 240, 240);
// -----------------------------------
// Create the rectangle
// -----------------------------------
ImageFilledRectangle($im, 0, 0, $img_width, $img_height, $bg_color);
// -----------------------------------
// Create the spiral pattern
// -----------------------------------
$theta = 1;
$thetac = 7;
$radius = 16;
$circles = 20;
$points = 32;
for ($i = 0; $i < ($circles * $points) - 1; $i++)
{
$theta = $theta + $thetac;
$rad = $radius * ($i / $points );
$x = ($rad * cos($theta)) + $x_axis;
$y = ($rad * sin($theta)) + $y_axis;
$theta = $theta + $thetac;
$rad1 = $radius * (($i + 1) / $points);
$x1 = ($rad1 * cos($theta)) + $x_axis;
$y1 = ($rad1 * sin($theta )) + $y_axis;
imageline($im, $x, $y, $x1, $y1, $grid_color);
$theta = $theta - $thetac;
}
// -----------------------------------
// Write the text
// -----------------------------------
$use_font = ($font_path != '' AND file_exists($font_path) AND function_exists('imagettftext')) ? TRUE : FALSE;
if ($use_font == FALSE)
{
$font_size = 5;
$x = rand(0, $img_width/($length/3));
$y = 0;
}
else
{
$font_size = 16;
$x = rand(0, $img_width/($length/1.5));
$y = $font_size+2;
}
for ($i = 0; $i < strlen($word); $i++)
{
if ($use_font == FALSE)
{
$y = rand(0 , $img_height/2);
imagestring($im, $font_size, $x, $y, substr($word, $i, 1), $text_color);
$x += ($font_size*2);
}
else
{
$y = rand($img_height/2, $img_height-3);
imagettftext($im, $font_size, $angle, $x, $y, $text_color, $font_path, substr($word, $i, 1));
$x += $font_size;
}
}
// -----------------------------------
// Create the border
// -----------------------------------
imagerectangle($im, 0, 0, $img_width-1, $img_height-1, $border_color);
// -----------------------------------
// Generate the image
// -----------------------------------
$img_name = $now.'.jpg';
ImageJPEG($im, $img_path.$img_name);
$img = "<img src=\"$img_url$img_name\" width=\"$img_width\" height=\"$img_height\" style=\"border:0;\" alt=\" \" />";
ImageDestroy($im);
return array('word' => $word, 'time' => $now, 'image' => $img);
}
}
now how to use captcha which is image + word created through above function in
default.htm
<span id="captcha">
<img src="{{captcha.image}}" width="150" height="30" style="border:0;" alt=" " /> </span>
the file created by above function saves in given path bt how to show that image when form appears...................................................................................................................................................
First you need to correct your syntex, to flash session while redirecting you need to use
with('name', 'value')
so you need to use
if (ends_with($url, ['.html', '.htm']))
{
$url = str_replace(['.html', '.htm'], '', $url);
return Redirect::to($url, 301)->with('captcha', $captcha); // <- correct this
// $this->create_captcha() must return string value
// here $captch seems object/image so you should not pass objects in session
}
instead I guess you need to pass some random value
$someRandomValue = 'blabla';
$val = array(
'word_length' => 5,
'word' => '', // <----------------- something here [$someRandomValue]
'img_path' => $captchaImagePath,
'img_url' => $captchaImageUrl,
'font_path' => $captchaFontPath,
'img_width' => '150',
'img_height' => 30,
'expiration' => 7200
);
and then pass it like
with('captcha', $someRandomValue)
and now in other place you can get it by
$captchaValue = session::get('captcha')
so this $captchaValue will be same as $someRandomValue
in short in last you need user input as text/string and compare with $captchaValue (this will be from session) to validate it
if any doubt please comment.

Detect mp3 duration in PHP condition advice

I have this script and is working just fine, but i want some conditions for duration, if duration of the file is bigger than 2 minutes to stop uploading the file and if the file is smaller than 1minute, to do the same. Any help appreciated! PS: i don't have any logical upload script.
PHP Script:
<?php
$mp3file = new MP3File("test.mp3");
$duration1 = $mp3file->getDurationEstimate();//(faster) for CBR only
$duration2 = $mp3file->getDuration();//(slower) for VBR (or CBR)
echo MP3File::formatTime($duration2)."\n";
?>
<?php
class MP3File
{
protected $filename;
public function __construct($filename)
{
$this->filename = $filename;
}
public static function formatTime($duration) //as hh:mm:ss
{
//return sprintf("%d:%02d", $duration/60, $duration%60);
$hours = floor($duration / 3600);
$minutes = floor( ($duration - ($hours * 3600)) / 60);
$seconds = $duration - ($hours * 3600) - ($minutes * 60);
return sprintf("%02d:%02d:%02d", $hours, $minutes, $seconds);
}
//Read first mp3 frame only... use for CBR constant bit rate MP3s
public function getDurationEstimate()
{
return $this->getDuration($use_cbr_estimate=true);
}
//Read entire file, frame by frame... ie: Variable Bit Rate (VBR)
public function getDuration($use_cbr_estimate=false)
{
$fd = fopen($this->filename, "rb");
$duration=0;
$block = fread($fd, 100);
$offset = $this->skipID3v2Tag($block);
fseek($fd, $offset, SEEK_SET);
while (!feof($fd))
{
$block = fread($fd, 10);
if (strlen($block)<10) { break; }
//looking for 1111 1111 111 (frame synchronization bits)
else if ($block[0]=="\xff" && (ord($block[1])&0xe0) )
{
$info = self::parseFrameHeader(substr($block, 0, 4));
if (empty($info['Framesize'])) { return $duration; } //some corrupt mp3 files
fseek($fd, $info['Framesize']-10, SEEK_CUR);
$duration += ( $info['Samples'] / $info['Sampling Rate'] );
}
else if (substr($block, 0, 3)=='TAG')
{
fseek($fd, 128-10, SEEK_CUR);//skip over id3v1 tag size
}
else
{
fseek($fd, -9, SEEK_CUR);
}
if ($use_cbr_estimate && !empty($info))
{
return $this->estimateDuration($info['Bitrate'],$offset);
}
}
return round($duration);
}
private function estimateDuration($bitrate,$offset)
{
$kbps = ($bitrate*1000)/8;
$datasize = filesize($this->filename) - $offset;
return round($datasize / $kbps);
}
private function skipID3v2Tag(&$block)
{
if (substr($block, 0,3)=="ID3")
{
$id3v2_major_version = ord($block[3]);
$id3v2_minor_version = ord($block[4]);
$id3v2_flags = ord($block[5]);
$flag_unsynchronisation = $id3v2_flags & 0x80 ? 1 : 0;
$flag_extended_header = $id3v2_flags & 0x40 ? 1 : 0;
$flag_experimental_ind = $id3v2_flags & 0x20 ? 1 : 0;
$flag_footer_present = $id3v2_flags & 0x10 ? 1 : 0;
$z0 = ord($block[6]);
$z1 = ord($block[7]);
$z2 = ord($block[8]);
$z3 = ord($block[9]);
if ( (($z0&0x80)==0) && (($z1&0x80)==0) && (($z2&0x80)==0) && (($z3&0x80)==0) )
{
$header_size = 10;
$tag_size = (($z0&0x7f) * 2097152) + (($z1&0x7f) * 16384) + (($z2&0x7f) * 128) + ($z3&0x7f);
$footer_size = $flag_footer_present ? 10 : 0;
return $header_size + $tag_size + $footer_size;//bytes to skip
}
}
return 0;
}
public static function parseFrameHeader($fourbytes)
{
static $versions = array(
0x0=>'2.5',0x1=>'x',0x2=>'2',0x3=>'1', // x=>'reserved'
);
static $layers = array(
0x0=>'x',0x1=>'3',0x2=>'2',0x3=>'1', // x=>'reserved'
);
static $bitrates = array(
'V1L1'=>array(0,32,64,96,128,160,192,224,256,288,320,352,384,416,448),
'V1L2'=>array(0,32,48,56, 64, 80, 96,112,128,160,192,224,256,320,384),
'V1L3'=>array(0,32,40,48, 56, 64, 80, 96,112,128,160,192,224,256,320),
'V2L1'=>array(0,32,48,56, 64, 80, 96,112,128,144,160,176,192,224,256),
'V2L2'=>array(0, 8,16,24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160),
'V2L3'=>array(0, 8,16,24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160),
);
static $sample_rates = array(
'1' => array(44100,48000,32000),
'2' => array(22050,24000,16000),
'2.5' => array(11025,12000, 8000),
);
static $samples = array(
1 => array( 1 => 384, 2 =>1152, 3 =>1152, ), //MPEGv1, Layers 1,2,3
2 => array( 1 => 384, 2 =>1152, 3 => 576, ), //MPEGv2/2.5, Layers 1,2,3
);
//$b0=ord($fourbytes[0]);//will always be 0xff
$b1=ord($fourbytes[1]);
$b2=ord($fourbytes[2]);
$b3=ord($fourbytes[3]);
$version_bits = ($b1 & 0x18) >> 3;
$version = $versions[$version_bits];
$simple_version = ($version=='2.5' ? 2 : $version);
$layer_bits = ($b1 & 0x06) >> 1;
$layer = $layers[$layer_bits];
$protection_bit = ($b1 & 0x01);
$bitrate_key = sprintf('V%dL%d', $simple_version , $layer);
$bitrate_idx = ($b2 & 0xf0) >> 4;
$bitrate = isset($bitrates[$bitrate_key][$bitrate_idx]) ? $bitrates[$bitrate_key][$bitrate_idx] : 0;
$sample_rate_idx = ($b2 & 0x0c) >> 2;//0xc => b1100
$sample_rate = isset($sample_rates[$version][$sample_rate_idx]) ? $sample_rates[$version][$sample_rate_idx] : 0;
$padding_bit = ($b2 & 0x02) >> 1;
$private_bit = ($b2 & 0x01);
$channel_mode_bits = ($b3 & 0xc0) >> 6;
$mode_extension_bits = ($b3 & 0x30) >> 4;
$copyright_bit = ($b3 & 0x08) >> 3;
$original_bit = ($b3 & 0x04) >> 2;
$emphasis = ($b3 & 0x03);
$info = array();
$info['Version'] = $version;//MPEGVersion
$info['Layer'] = $layer;
//$info['Protection Bit'] = $protection_bit; //0=> protected by 2 byte CRC, 1=>not protected
$info['Bitrate'] = $bitrate;
$info['Sampling Rate'] = $sample_rate;
//$info['Padding Bit'] = $padding_bit;
//$info['Private Bit'] = $private_bit;
//$info['Channel Mode'] = $channel_mode_bits;
//$info['Mode Extension'] = $mode_extension_bits;
//$info['Copyright'] = $copyright_bit;
//$info['Original'] = $original_bit;
//$info['Emphasis'] = $emphasis;
$info['Framesize'] = self::framesize($layer, $bitrate, $sample_rate, $padding_bit);
$info['Samples'] = $samples[$simple_version][$layer];
return $info;
}
private static function framesize($layer, $bitrate,$sample_rate,$padding_bit)
{
if ($layer==1)
return intval(((12 * $bitrate*1000 /$sample_rate) + $padding_bit) * 4);
else //layer 2, 3
return intval(((144 * $bitrate*1000)/$sample_rate) + $padding_bit);
}
}
?>
I have managed a solution with this script. See the code below:
<?php
require 'mp3class.php';
$fileMp3 = new MP3File("test.mp3");
$duration = $fileMp3->getDuration();
$time_limit ="00:02:27";
$accept = "Your file has ".MP3File::formatTime($duration)." minute and can be uploaded ";
$decline = "Your file has ".MP3File::formatTime($duration)." minutes and cannot be uploaded";
if(MP3File::formatTime($duration) <= $time_limit) {
echo $accept;
} else {
echo $decline;
}
?>

How to compare image similarity using php regardless of scale, rotation?

I want to compare similarity between below images. Acording to my requirements I want to identify all of these images as similar, since it has use the same color, same clip art. The only difference in these images are rotation ,scale and the placement of the clip art. Since all 3 t-shirts has used the same color and clip art I want to identify all 3 images as similar. I tried out the method described in hackerfactor.com. But it doesn't give me correct result acording to my requirements. How to identify all these images as similar?DO you have any suggestions? Please help me.
The below images should be recognized as different from above images.(Even though the tshirts has the same color, clip arts are different. Last tshirt is different from above, because it has use the same clip art, but twice. )
Moved to GitHub
Because this question is quite interesting, I moved the whole thing to GitHub where you can find the current implementation:
ImageCompare
Original answer
I made a very simple approach, using img-resize and comparing the average color of the resized images.
$binEqual = [
file_get_contents('http://i.stack.imgur.com/D8ct1.png'),
file_get_contents('http://i.stack.imgur.com/xNZt1.png'),
file_get_contents('http://i.stack.imgur.com/kjGjm.png')
];
$binDiff = [
file_get_contents('http://i.stack.imgur.com/WIOHs.png'),
file_get_contents('http://i.stack.imgur.com/ljoBT.png'),
file_get_contents('http://i.stack.imgur.com/qEKSK.png')
];
function getAvgColor($bin, $size = 10) {
$target = imagecreatetruecolor($size, $size);
$source = imagecreatefromstring($bin);
imagecopyresized($target, $source, 0, 0, 0, 0, $size, $size, imagesx($source), imagesy($source));
$r = $g = $b = 0;
foreach(range(0, $size - 1) as $x) {
foreach(range(0, $size - 1) as $y) {
$rgb = imagecolorat($target, $x, $y);
$r += $rgb >> 16;
$g += $rgb >> 8 & 255;
$b += $rgb & 255;
}
}
unset($source, $target);
return (floor($r / $size ** 2) << 16) + (floor($g / $size ** 2) << 8) + floor($b / $size ** 2);
}
function compAvgColor($c1, $c2, $tolerance = 4) {
return abs(($c1 >> 16) - ($c2 >> 16)) <= $tolerance &&
abs(($c1 >> 8 & 255) - ($c2 >> 8 & 255)) <= $tolerance &&
abs(($c1 & 255) - ($c2 & 255)) <= $tolerance;
}
$perms = [[0,1],[0,2],[1,2]];
foreach($perms as $perm) {
var_dump(compAvgColor(getAvgColor($binEqual[$perm[0]]), getAvgColor($binEqual[$perm[1]])));
}
foreach($perms as $perm) {
var_dump(compAvgColor(getAvgColor($binDiff[$perm[0]]), getAvgColor($binDiff[$perm[1]])));
}
For the used size and color-tolerance I get the expected result:
bool(true)
bool(true)
bool(true)
bool(false)
bool(false)
bool(false)
More advanced implementation
Empty T-Shirt to compare:
$binEqual = [
file_get_contents('http://i.stack.imgur.com/D8ct1.png'),
file_get_contents('http://i.stack.imgur.com/xNZt1.png'),
file_get_contents('http://i.stack.imgur.com/kjGjm.png')
];
$binDiff = [
file_get_contents('http://i.stack.imgur.com/WIOHs.png'),
file_get_contents('http://i.stack.imgur.com/ljoBT.png'),
file_get_contents('http://i.stack.imgur.com/qEKSK.png')
];
class Color {
private $r = 0;
private $g = 0;
private $b = 0;
public function __construct($r = 0, $g = 0, $b = 0)
{
$this->r = $r;
$this->g = $g;
$this->b = $b;
}
public function r()
{
return $this->r;
}
public function g()
{
return $this->g;
}
public function b()
{
return $this->b;
}
public function toInt()
{
return $this->r << 16 + $this->g << 8 + $this->b;
}
public function toRgb()
{
return [$this->r, $this->g, $this->b];
}
public function mix(Color $color)
{
$this->r = round($this->r + $color->r() / 2);
$this->g = round($this->g + $color->g() / 2);
$this->b = round($this->b + $color->b() / 2);
}
public function compare(Color $color, $tolerance = 500)
{
list($r1, $g1, $b1) = $this->toRgb();
list($r2, $g2, $b2) = $color->toRgb();
$diff = round(sqrt(pow($r1 - $r2, 2) + pow($g1 - $g2, 2) + pow($b1 - $b2, 2)));
printf("Comp r(%s : %s), g(%s : %s), b(%s : %s) Diff %s \n", $r1, $r2, $g1, $g2, $b1, $b2, $diff);
return $diff <= $tolerance;
}
public static function fromInt($int) {
return new self($int >> 16, $int >> 8 & 255, $int & 255);
}
}
function getAvgColor($bin, $size = 5) {
$target = imagecreatetruecolor($size, $size);
$targetTmp = imagecreatetruecolor($size, $size);
$sourceTmp = imagecreatefrompng('http://i.stack.imgur.com/gfn5A.png');
$source = imagecreatefromstring($bin);
imagecopyresized($target, $source, 0, 0, 0, 0, $size, $size, imagesx($source), imagesy($source));
imagecopyresized($targetTmp, $sourceTmp, 0, 0, 0, 0, $size, $size, imagesx($source), imagesy($source));
$r = $g = $b = $relPx = 0;
$baseColor = new Color();
foreach(range(0, $size - 1) as $x) {
foreach(range(0, $size - 1) as $y) {
if (imagecolorat($target, $x, $y) != imagecolorat($targetTmp, $x, $y))
$baseColor->mix(Color::fromInt(imagecolorat($target, $x, $y)));
}
}
unset($source, $target, $sourceTmp, $targetTmp);
return $baseColor;
}
$perms = [[0,0], [1,0], [2,0], [1,0], [1,1], [1,2], [2,0], [2,1], [2,2]];
echo "Equal\n";
foreach($perms as $perm) {
var_dump(getAvgColor($binEqual[$perm[0]])->compare(getAvgColor($binEqual[$perm[1]])));
}
echo "Different\n";
foreach($perms as $perm) {
var_dump(getAvgColor($binEqual[$perm[0]])->compare(getAvgColor($binDiff[$perm[1]])));
}
Result:
Equal
Comp r(101 : 101), g(46 : 46), b(106 : 106) Diff 0
bool(true)
Comp r(121 : 101), g(173 : 46), b(249 : 106) Diff 192
bool(true)
Comp r(219 : 101), g(179 : 46), b(268 : 106) Diff 241
bool(true)
Comp r(121 : 101), g(173 : 46), b(249 : 106) Diff 192
bool(true)
Comp r(121 : 121), g(173 : 173), b(249 : 249) Diff 0
bool(true)
Comp r(121 : 219), g(173 : 179), b(249 : 268) Diff 100
bool(true)
Comp r(219 : 101), g(179 : 46), b(268 : 106) Diff 241
bool(true)
Comp r(219 : 121), g(179 : 173), b(268 : 249) Diff 100
bool(true)
Comp r(219 : 219), g(179 : 179), b(268 : 268) Diff 0
bool(true)
Different
Comp r(101 : 446), g(46 : 865), b(106 : 1242) Diff 1442
bool(false)
Comp r(121 : 446), g(173 : 865), b(249 : 1242) Diff 1253
bool(false)
Comp r(219 : 446), g(179 : 865), b(268 : 1242) Diff 1213
bool(false)
Comp r(121 : 446), g(173 : 865), b(249 : 1242) Diff 1253
bool(false)
Comp r(121 : 654), g(173 : 768), b(249 : 1180) Diff 1227
bool(false)
Comp r(121 : 708), g(173 : 748), b(249 : 1059) Diff 1154
bool(false)
Comp r(219 : 446), g(179 : 865), b(268 : 1242) Diff 1213
bool(false)
Comp r(219 : 654), g(179 : 768), b(268 : 1180) Diff 1170
bool(false)
Comp r(219 : 708), g(179 : 748), b(268 : 1059) Diff 1090
bool(false)
In this calculation the background is ignored what leads to bigger difference in the avg color.
Final implementation (OOP)
Quite interessting topic. So i tryed to tune it up a liddle bit.
This is now a complete OOP implementation. You can now create a new image and subtract some mask of it in order to eliminate a background. Then you can compare one image to another using the compare method. To keep the calculation limited it's better to resize your image first (masks are allways fittet to the current image)
The compare algorythme it self chunks the two images into serveral tiles, then eliminates tiles, that are almost equal to white average color and then compares the average color of all remaining tile-permutations.
Class Image {
const HASH_SIZE = 8;
const AVG_SIZE = 10;
private $img = null;
public function __construct($resource)
{
$this->img = $resource;;
}
private function permute(array $a1, array $a2) {
$perms = array();
for($i = 0; $i < sizeof($a1); $i++) {
for($j = $i; $j < sizeof($a2); $j++) {
if ($i != $j) {
$perms[] = [$a1[$i],
$a2[$j]];
}
}
}
return $perms;
}
public function compare(Image $comp) {
$avgComp = array();
foreach($comp->chunk(25) as $chunk) {
$avgComp[] = $chunk->avg();
}
$avgOrg = array();
foreach($this->chunk(25) as $chunk) {
$avgOrg[] = $chunk->avg();
}
$white = Color::fromInt(0xFFFFFF);
$avgComp = array_values(array_filter($avgComp, function(Color $color) use ($white){
return $white->compare($color, 1000);
}));
$avgOrg = array_values(array_filter($avgOrg, function(Color $color) use ($white){
return $white->compare($color, 1000);
}));
$equal = 0;
$pairs = $this->permute($avgOrg, $avgComp);
foreach($pairs as $pair) {
$equal += $pair[0]->compare($pair[1], 100) ? 1 : 0;
}
return ($equal / sizeof($pairs));
}
public function substract(Image $mask, $tolerance = 50)
{
$size = $this->size();
if ($mask->size() != $size) {
$mask = $mask->resize($size);
}
for ($x = 0; $x < $size[0]; $x++) {
for ($y = 0; $y < $size[1]; $y++) {
if ($this->colorat($x, $y)->compare($mask->colorat($x, $y), $tolerance))
imagesetpixel($this->img, $x, $y, 0xFFFFFF);
}
}
return $this;
}
public function avg($size = 10)
{
$target = $this->resize([self::AVG_SIZE, self::AVG_SIZE]);
$avg = Color::fromInt(0x000000);
$white = Color::fromInt(0xFFFFFF);
for ($x = 0; $x < self::AVG_SIZE; $x++) {
for ($y = 0; $y < self::AVG_SIZE; $y++) {
$color = $target->colorat($x, $y);
if (!$color->compare($white, 10))
$avg->mix($color);
}
}
return $avg;
}
public function colorat($x, $y)
{
return Color::fromInt(imagecolorat($this->img, $x, $y));
}
public function chunk($chunkSize = 10)
{
$collection = new ImageCollection();
$size = $this->size();
for($x = 0; $x < $size[0]; $x += $chunkSize) {
for($y = 0; $y < $size[1]; $y += $chunkSize) {
switch (true) {
case ($x + $chunkSize > $size[0] && $y + $chunkSize > $size[1]):
$collection->push($this->slice(['x' => $x, 'y' => $y, 'height' => $size[0] - $x, 'width' => $size[1] - $y]));
break;
case ($x + $chunkSize > $size[0]):
$collection->push($this->slice(['x' => $x, 'y' => $y, 'height' => $size[0] - $x, 'width' => $chunkSize]));
break;
case ($y + $chunkSize > $size[1]):
$collection->push($this->slice(['x' => $x, 'y' => $y, 'height' => $chunkSize, 'width' => $size[1] - $y]));
break;
default:
$collection->push($this->slice(['x' => $x, 'y' => $y, 'height' => $chunkSize, 'width' => $chunkSize]));
break;
}
}
}
return $collection;
}
public function slice(array $rect)
{
return Image::fromResource(imagecrop($this->img, $rect));
}
public function size()
{
return [imagesx($this->img), imagesy($this->img)];
}
public function resize(array $size = array(100, 100))
{
$target = imagecreatetruecolor($size[0], $size[1]);
imagecopyresized($target, $this->img, 0, 0, 0, 0, $size[0], $size[1], imagesx($this->img), imagesy($this->img));
return Image::fromResource($target);
}
public function show()
{
header("Content-type: image/png");
imagepng($this->img);
die();
}
public function save($name = null, $path = '') {
if ($name === null) {
$name = $this->hash();
}
imagepng($this->img, $path . $name . '.png');
return $this;
}
public function hash()
{
// Resize the image.
$resized = imagecreatetruecolor(self::HASH_SIZE, self::HASH_SIZE);
imagecopyresampled($resized, $this->img, 0, 0, 0, 0, self::HASH_SIZE, self::HASH_SIZE, imagesx($this->img), imagesy($this->img));
// Create an array of greyscale pixel values.
$pixels = [];
for ($y = 0; $y < self::HASH_SIZE; $y++)
{
for ($x = 0; $x < self::HASH_SIZE; $x++)
{
$rgb = imagecolorsforindex($resized, imagecolorat($resized, $x, $y));
$pixels[] = floor(($rgb['red'] + $rgb['green'] + $rgb['blue']) / 3);
}
}
// Free up memory.
imagedestroy($resized);
// Get the average pixel value.
$average = floor(array_sum($pixels) / count($pixels));
// Each hash bit is set based on whether the current pixels value is above or below the average.
$hash = 0; $one = 1;
foreach ($pixels as $pixel)
{
if ($pixel > $average) $hash |= $one;
$one = $one << 1;
}
return $hash;
}
public static function fromResource($resource)
{
return new self($resource);
}
public static function fromBin($binf)
{
return new self(imagecreatefromstring($bin));
}
public static function fromFile($path)
{
return new self(imagecreatefromstring(file_get_contents($path)));
}
}
class ImageCollection implements IteratorAggregate
{
private $images = array();
public function __construct(array $images = array())
{
$this->images = $images;
}
public function push(Image $image) {
$this->images[] = $image;
return $this;
}
public function pop()
{
return array_pop($this->images);
}
public function save()
{
foreach($this->images as $image)
{
$image->save();
}
return $this;
}
public function getIterator() {
return new ArrayIterator($this->images);
}
}
class Color {
private $r = 0;
private $g = 0;
private $b = 0;
public function __construct($r = 0, $g = 0, $b = 0)
{
$this->r = $r;
$this->g = $g;
$this->b = $b;
}
public function r()
{
return $this->r;
}
public function g()
{
return $this->g;
}
public function b()
{
return $this->b;
}
public function toInt()
{
return $this->r << 16 + $this->g << 8 + $this->b;
}
public function toRgb()
{
return [$this->r, $this->g, $this->b];
}
public function mix(Color $color)
{
$this->r = round($this->r + $color->r() / 2);
$this->g = round($this->g + $color->g() / 2);
$this->b = round($this->b + $color->b() / 2);
}
public function compare(Color $color, $tolerance = 500)
{
list($r1, $g1, $b1) = $this->toRgb();
list($r2, $g2, $b2) = $color->toRgb();
$diff = round(sqrt(pow($r1 - $r2, 2) + pow($g1 - $g2, 2) + pow($b1 - $b2, 2)));
//printf("Comp r(%s : %s), g(%s : %s), b(%s : %s) Diff %s \n", $r1, $r2, $g1, $g2, $b1, $b2, $diff);
return $diff <= $tolerance;
}
public static function fromInt($int) {
return new self($int >> 16, $int >> 8 & 255, $int & 255);
}
}
$mask = Image::fromFile('http://i.stack.imgur.com/gfn5A.png');
$image1 = Image::fromFile('http://i.stack.imgur.com/D8ct1.png')->resize([50, 100])->substract($mask, 100);
$image2 = Image::fromFile('http://i.stack.imgur.com/xNZt1.png')->resize([50, 100])->substract($mask, 100);
$image3 = Image::fromFile('http://i.stack.imgur.com/kjGjm.png')->resize([50, 100])->substract($mask, 100);
$other1 = Image::fromFile('http://i.stack.imgur.com/WIOHs.png')->resize([50, 100])->substract($mask, 100);
$other2 = Image::fromFile('http://i.stack.imgur.com/ljoBT.png')->resize([50, 100])->substract($mask, 100);
$other3 = Image::fromFile('http://i.stack.imgur.com/qEKSK.png')->resize([50, 100])->substract($mask, 100);
echo "Equal\n";
var_dump(
$image1->compare($image2),
$image1->compare($image3),
$image2->compare($image3)
);
echo "Image 1 to Other\n";
var_dump(
$image1->compare($other1),
$image1->compare($other2),
$image1->compare($other3)
);
echo "Image 2 to Other\n";
var_dump(
$image2->compare($other1),
$image2->compare($other2),
$image2->compare($other3)
);
echo "Image 3 to Other\n";
var_dump(
$image3->compare($other1),
$image3->compare($other2),
$image3->compare($other3)
);
Result:
Equal
float(0.47619047619048)
float(0.53333333333333)
float(0.4)
Image 1 to Other
int(0)
int(0)
int(0)
Image 2 to Other
int(0)
int(0)
int(0)
Image 3 to Other
int(0)
int(0)
int(0)
I'm not claiming to really know anything about this topic, which I think generally is termed 'vision'.
What I would do however, is something along these lines:
Flow:
Posterise, to minimal number of colors/shades (guess).
Remove two largest colors (white + shirt).
Compare remaining color-palette, and fail if schemes differ too much.
Calculate a coarse polygon around any remaining 'color-blobs' (see https://en.wikipedia.org/wiki/Convex_hull )
Compare number of polygons and largest polygon’s number of angles and angle-values (not size), from each image, and fail or pass.
Main problem in such a setup, will be rounding ... as in posterising a color, that is precisely at middelpoint between two colors ... sometimes it gets colorA, sometimes it gets colorB.
Same with the polygons, I guess.
SIMILAR computes the normalized cross correlation similarity metric between two equal dimensioned images. The normalized cross correlation metric measures how similar two images are, not how different they are.The range of ncc metric values is between 0 (dissimilar) and 1 (similar). If mode=g, then the two images will be converted to grayscale. If mode=rgb, then the two images first will be converted to colorspace=rgb. Next, the ncc similarity metric will be computed for each channel. Finally, they will be combined into an rms value. NOTE: this metric does not work for constant color channels as it produces an ncc metric = 0/0 for that channel. Thus it is not advised to run the script with either image having a totally opaque or totally transparent alpha channel that is enabled.
try this api,
http://www.phpclasses.org/package/8255-PHP-Compare-two-images-to-find-if-they-are-similar.html
As someone mentioned, anything else than calculating the histogram of the images and comparing them is not easily achievable. Here is an example that gives the correct result for the images provided in question. The key point here is how to get the right balance between the number of peak color levels and what is acceptable amount of them ( similarity( $histograms, $levels = 30, $enough = 28 ) ).
function histograms( $images ) {
foreach( $images as $img ) {
$image = imagecreatefrompng( $img );
$width = imagesx( $image );
$height = imagesy( $image );
$num_pixels = $width * $height;
$histogram = [];
for ( $x = 0; $x < $width; $x++ ) {
for ( $y = 0; $y < $height; $y++ ) {
$rgb = imagecolorat( $image, $y, $x );
$rgb = [ $rgb >> 16, ( $rgb >> 8 ) & 0xFF, $rgb & 0xFF ];
$histo_v = (int) round( ( $rgb[0] + $rgb[1] + $rgb[02] ) / 3 );
$histogram[ $histo_v ] = array_key_exists( $histo_v, $histogram ) ? $histogram[ $histo_v ] + $histo_v/$num_pixels : $histo_v/$num_pixels;
}
}
$histograms[$img] = $histogram;
arsort( $histograms[$img] );
}
return $histograms;
}
function similarity( $histograms, $levels = 30, $enough = 28 ) {
$keys = array_keys( $histograms );
$output = [];
for ( $x = 0; $x < count( $histograms ) - 1; $x++ ) {
for ( $y = $x + 1; $y < count( $histograms ); $y++ ) {
$similarity = count( array_intersect_key( array_slice( $histograms[ $keys[$x] ], 0, $levels, true ), array_slice( $histograms[ $keys[$y] ], 0, $levels, true ) ) );
if ( $similarity > $enough ) $output[] = [ $keys[$x], $keys[$y], $similarity ];
}
}
return $output;
}
$histograms = histograms( [ 'http://i.stack.imgur.com/D8ct1.png', 'http://i.stack.imgur.com/xNZt1.png', 'http://i.stack.imgur.com/kjGjm.png', 'http://i.stack.imgur.com/WIOHs.png', 'http://i.stack.imgur.com/ljoBT.png', 'http://i.stack.imgur.com/qEKSK.png' ] );
$similarity = similarity( $histograms );
print_r( $similarity );
/*
Array
(
[0] => Array
(
[0] => http://i.stack.imgur.com/D8ct1.png
[1] => http://i.stack.imgur.com/xNZt1.png
[2] => 30
)
[1] => Array
(
[0] => http://i.stack.imgur.com/D8ct1.png
[1] => http://i.stack.imgur.com/kjGjm.png
[2] => 30
)
[2] => Array
(
[0] => http://i.stack.imgur.com/D8ct1.png
[1] => http://i.stack.imgur.com/qEKSK.png
[2] => 29
)
[3] => Array
(
[0] => http://i.stack.imgur.com/xNZt1.png
[1] => http://i.stack.imgur.com/kjGjm.png
[2] => 30
)
[4] => Array
(
[0] => http://i.stack.imgur.com/xNZt1.png
[1] => http://i.stack.imgur.com/qEKSK.png
[2] => 29
)
[5] => Array
(
[0] => http://i.stack.imgur.com/kjGjm.png
[1] => http://i.stack.imgur.com/qEKSK.png
[2] => 29
)
)
*/
This article also helped me to create the histograms.

RGB to HSV in PHP

In PHP, what is the most straightforward way to convert a RGB triplet to HSV values?
Here is a simple, straightforward method that returns HSV values as degrees and percentages, which is what Photoshop's color picker uses.
Note that the return values are not rounded, you can do that yourself if required. Keep in mind that H(360) == H(0), so H values of 359.5 and greater should round to 0
Heavily documented for learning purposes.
/**
* Licensed under the terms of the BSD License.
* (Basically, this means you can do whatever you like with it,
* but if you just copy and paste my code into your app, you
* should give me a shout-out/credit :)
*/
<?php
function RGBtoHSV($R, $G, $B) // RGB values: 0-255, 0-255, 0-255
{ // HSV values: 0-360, 0-100, 0-100
// Convert the RGB byte-values to percentages
$R = ($R / 255);
$G = ($G / 255);
$B = ($B / 255);
// Calculate a few basic values, the maximum value of R,G,B, the
// minimum value, and the difference of the two (chroma).
$maxRGB = max($R, $G, $B);
$minRGB = min($R, $G, $B);
$chroma = $maxRGB - $minRGB;
// Value (also called Brightness) is the easiest component to calculate,
// and is simply the highest value among the R,G,B components.
// We multiply by 100 to turn the decimal into a readable percent value.
$computedV = 100 * $maxRGB;
// Special case if hueless (equal parts RGB make black, white, or grays)
// Note that Hue is technically undefined when chroma is zero, as
// attempting to calculate it would cause division by zero (see
// below), so most applications simply substitute a Hue of zero.
// Saturation will always be zero in this case, see below for details.
if ($chroma == 0)
return array(0, 0, $computedV);
// Saturation is also simple to compute, and is simply the chroma
// over the Value (or Brightness)
// Again, multiplied by 100 to get a percentage.
$computedS = 100 * ($chroma / $maxRGB);
// Calculate Hue component
// Hue is calculated on the "chromacity plane", which is represented
// as a 2D hexagon, divided into six 60-degree sectors. We calculate
// the bisecting angle as a value 0 <= x < 6, that represents which
// portion of which sector the line falls on.
if ($R == $minRGB)
$h = 3 - (($G - $B) / $chroma);
elseif ($B == $minRGB)
$h = 1 - (($R - $G) / $chroma);
else // $G == $minRGB
$h = 5 - (($B - $R) / $chroma);
// After we have the sector position, we multiply it by the size of
// each sector's arc (60 degrees) to obtain the angle in degrees.
$computedH = 60 * $h;
return array($computedH, $computedS, $computedV);
}
?>
<?php
function RGB_TO_HSV ($R, $G, $B) // RGB Values:Number 0-255
{ // HSV Results:Number 0-1
$HSL = array();
$var_R = ($R / 255);
$var_G = ($G / 255);
$var_B = ($B / 255);
$var_Min = min($var_R, $var_G, $var_B);
$var_Max = max($var_R, $var_G, $var_B);
$del_Max = $var_Max - $var_Min;
$V = $var_Max;
if ($del_Max == 0)
{
$H = 0;
$S = 0;
}
else
{
$S = $del_Max / $var_Max;
$del_R = ( ( ( $var_Max - $var_R ) / 6 ) + ( $del_Max / 2 ) ) / $del_Max;
$del_G = ( ( ( $var_Max - $var_G ) / 6 ) + ( $del_Max / 2 ) ) / $del_Max;
$del_B = ( ( ( $var_Max - $var_B ) / 6 ) + ( $del_Max / 2 ) ) / $del_Max;
if ($var_R == $var_Max) $H = $del_B - $del_G;
else if ($var_G == $var_Max) $H = ( 1 / 3 ) + $del_R - $del_B;
else if ($var_B == $var_Max) $H = ( 2 / 3 ) + $del_G - $del_R;
if ($H<0) $H++;
if ($H>1) $H--;
}
$HSL['H'] = $H;
$HSL['S'] = $S;
$HSL['V'] = $V;
return $HSL;
}
Thoroughly tested and compressed, this is the function I'm going to stick with for converting RGB to HSV:
function RGBtoHSV($r,$g,$b) {
$r=($r/255); $g=($g/255); $b=($b/255);
$maxRGB=max($r,$g,$b); $minRGB=min($r,$g,$b); $chroma=$maxRGB-$minRGB;
if($chroma==0) return array('h'=>0,'s'=>0,'v'=>$maxRGB);
if($r==$minRGB)$h=3-(($g-$b)/$chroma);
elseif($b==$minRGB)$h=1-(($r-$g)/$chroma); else $h=5-(($b-$r)/$chroma);
return array('h'=>60*$h,'s'=>$chroma/$maxRGB,'v'=>$maxRGB);
}
Example:
Example using color "DarkSalmon":
echo '<pre><code>'. print_r( RGBtoHSV(233,150,122), true ) .'</code></pre>';
...returns:
Array
(
[h] => 15.135135135135
[s] => 0.47639484978541
[v] => 0.91372549019608
)
I did it like this
function convertRgbToHsv($rgb)
{
$r = (int)substr($rgb, 0, 3) / 255;
$g = (int)substr($rgb, 3, 3) / 255;
$b = (int)substr($rgb, 6, 3) / 255;
$max = max($r, $g, $b);
$min = min($r, $g, $b);
$delta = $max - $min;
if (!$delta) {
$h = 0;
} else if ($r === $max) {
$h = 60 * ((($g - $b) / $delta) % 6);
} else if ($g === $max) {
$h = 60 * ((($b - $r) / $delta) + 2);
} else {
$h = 60 * ((($r - $g) / $delta) + 4);
}
$s = !!$max ? $delta / $max : 0;
$v = $max;
$hsv = array("h" => $h, "s" => $s, "v" => $v);
return $hsv;
}
Link to reference material here
Here's my spin on it, along with a unit test. Since the S and V values are percentages, this code returns them as integers (0, 100) as opposed to (0, 1) - Example, 75 instead of 0.75.
final class MathService
{
/**
* Converts an RGB point into HSV
*
* #param int $r
* #param int $g
* #param int $b
* #return array
*/
public function rgbToHsv(int $r, int $g, int $b): array
{
$rPrime = $r / 255;
$gPrime = $g / 255;
$bPrime = $b / 255;
$max = max([$rPrime, $gPrime, $bPrime]);
$min = min([$rPrime, $gPrime, $bPrime]);
$delta = $max - $min;
// Calculate H
if ($delta == 0) {
$h = 0;
} else {
if ($max === $rPrime) {
$h = 60 * ((($gPrime - $bPrime) / $delta) % 6);
}
if ($max === $gPrime) {
$h = 60 * ((($bPrime - $rPrime) / $delta) + 2);
}
if ($max === $bPrime) {
$h = 60 * ((($rPrime - $gPrime) / $delta) + 4);
}
}
// Calculate S
if ($max == 0) {
$s = 0;
} else {
$s = $delta / $max;
}
// Calculate V
$v = $max;
return [$h, (int)($s * 100), (int)($v * 100)];
}
}
PHPUnit test case with PHP 7.2
/**
* #test
*/
public function rgbToHsv_ComputesCorrectValues(): void
{
$service = new MathService();
$samples = [
// [R, G, B, H, S, V]
[0, 0, 0, 0, 0, 0],
[255, 255, 255, 0, 0, 100],
[255, 0, 0, 0, 100, 100],
[0, 255, 0, 120, 100, 100],
[0, 0, 255, 240, 100, 100],
[255, 255, 0, 60, 100, 100],
[0, 255, 255, 180, 100, 100],
[255, 0, 255, 300, 100, 100],
[192, 192, 192, 0, 0, 75],
[128, 128, 128, 0, 0, 50],
[128, 0, 0, 0, 100, 50],
[128, 128, 0, 60, 100, 50],
[0, 128, 0, 120, 100, 50],
[128, 0, 128, 300, 100, 50],
[0, 128, 128, 180, 100, 50],
[0, 0, 128, 240, 100, 50],
];
foreach ($samples as $sample) {
list($r, $g, $b) = array_slice($sample, 0, 3);
$expected = array_slice($sample, 3);
$hsv = $service->rgbToHsv($r, $g, $b);
list($h, $s, $v) = $hsv;
self::assertEquals($expected, $hsv, "Error converting ({$r}, ${g}, ${b}). Got ({$h}, {$s}, {$v})");
}
}

Get Version of exe via PHP

Is getting the version of a exe possible with php? I'd like to print the version of a file that can be downloaded...
Windows exe and php is running on linux server
I wanted the same thing, so I coded this:
It returns FALSE, if it can't get the version info, or an ARRAY of four elements with file version fields (numbers, which are separated by .) It works only for 32-bit PE files (as I had no need for other formats).
function GetFileVersion($FileName)
{
$handle = fopen($FileName, 'rb');
if(!$handle)
{
return FALSE;
}
$Header = fread($handle, 64);
if(substr($Header, 0, 2) != 'MZ')
{
return FALSE;
}
$PEOffset = unpack("V", substr($Header, 60, 4));
if($PEOffset[1] < 64)
{
return FALSE;
}
fseek($handle, $PEOffset[1], SEEK_SET);
$Header = fread($handle, 24);
if(substr($Header, 0, 2) != 'PE')
{
return FALSE;
}
$Machine = unpack("v", substr($Header, 4, 2));
if($Machine[1] != 332)
{
return FALSE;
}
$NoSections = unpack("v", substr($Header, 6, 2));
$OptHdrSize = unpack("v", substr($Header, 20, 2));
fseek($handle, $OptHdrSize[1], SEEK_CUR);
$ResFound = FALSE;
for ($x = 0; $x < $NoSections[1]; $x++)
{
$SecHdr = fread($handle, 40);
if (substr($SecHdr, 0, 5) == '.rsrc')
{
$ResFound = TRUE;
break;
}
}
if(!$ResFound)
{
return FALSE;
}
$InfoVirt = unpack("V", substr($SecHdr, 12, 4));
$InfoSize = unpack("V", substr($SecHdr, 16, 4));
$InfoOff = unpack("V", substr($SecHdr, 20, 4));
fseek($handle, $InfoOff[1], SEEK_SET);
$Info = fread($handle, $InfoSize[1]);
$NumDirs = unpack("v", substr($Info, 14, 2));
$InfoFound = FALSE;
for ($x = 0; $x <$NumDirs[1]; $x++)
{
$Type = unpack("V", substr($Info, ($x * 8) + 16, 4));
if($Type[1] == 16)
{
//FILEINFO resource
$InfoFound = TRUE;
$SubOff = unpack("V", substr($Info, ($x * 8) + 20, 4));
break;
}
}
if (!$InfoFound)
{
return FALSE;
}
$SubOff[1] &= 0x7fffffff;
$InfoOff = unpack("V", substr($Info, $SubOff[1] + 20, 4)); //offset of first FILEINFO
$InfoOff[1] &= 0x7fffffff;
$InfoOff = unpack("V", substr($Info, $InfoOff[1] + 20, 4)); //offset to data
$DataOff = unpack("V", substr($Info, $InfoOff[1], 4));
$DataSize = unpack("V", substr($Info, $InfoOff[1] + 4, 4));
$CodePage = unpack("V", substr($Info, $InfoOff[1] + 8, 4));
$DataOff[1] -= $InfoVirt[1];
$Version = unpack("v4", substr($Info, $DataOff[1] + 48, 8));
$x = $Version[2];
$Version[2] = $Version[1];
$Version[1] = $x;
$x = $Version[4];
$Version[4] = $Version[3];
$Version[3] = $x;
return $Version;
}
I recently moved our hosting from Windows to Linux. This was quite easy to do with VBScript given the Microsoft objects, but on Linux and PHP I couldn't find anything. We wrote this function in PHP to scan the .exe file and extract the "Product Version" that a VB.Net application had in it. You can change the $key to be whatever string you can find that has the version info and a null terminator.
Note that this scans the files in 64k chunks looking for the $key string. If you have a large .exe, it may take a few seconds. My .exe is 52k so it is nearly instant. If you have a larger exe, you can change the scan.
<?php
function get_product_version($file_name)
{
$key = "P\x00r\x00o\x00d\x00u\x00c\x00t\x00V\x00e\x00r\x00s\x00i\x00o\x00n\x00\x00\x00";
$fptr = fopen($file_name, "rb");
$data = "";
while (!feof($fptr))
{
$data .= fread($fptr, 65536);
if (strpos($data, $key)!==FALSE)
break;
$data = substr($data, strlen($data)-strlen($key));
}
fclose($fptr);
if (strpos($data, $key)===FALSE)
return "";
$pos = strpos($data, $key)+strlen($key);
$version = "";
for ($i=$pos; $data[$i]!="\x00"; $i+=2)
$version .= $data[$i];
return $version;
}
echo get_product_version("/path_to_file/foo.exe");
?>
On a win32 machine you can use the COM extension and FileSystemObject.GetFileVersion() method to retrieve the version info.
e.g.
$path = getenv('SystemRoot').'\\NOTEPAD.EXE';
$fso = new COM('Scripting.FileSystemObject');
echo $path, ' : ', $fso->GetFileVersion($path);
prints (on my machine) C:\WINDOWS\NOTEPAD.EXE : 5.1.2600.5512
I am assuming you're not on Windows, and you mean the Version information that can be stored in Windows executables, and pops up in the properties dialog for such a file in Windows Explorer.
This information seems to be stored in the VS_VERSION_INFO block of an executable (see for example this question). I don't know any tool that extracts this information in a simple way, not even on Windows itself.
There seem to be several ways to get hold of this information via the Windows API (See a Perl example here) but I can't see any approach that works "from scratch" by just parsing the executable.
If you dig around a bit, you might be able to find a file format description that explains how to read the VS_VERSION_INFO information from an EXE file. Be prepared for a lot of work to get this to work reliably, though.
Be prepared to invest a lot of time and effort if you want to do this.
I combined the answers together and added the NamedDirs fix. Also want to highlight not to use the code by NeuD, the offset should remain 16; 14 is definitely wrong. Hope it helps someone.
function GetFileVersion($FileName)
{
return GetValueOfSeeking($FileName, "FileVersion");
}
function GetValueOfSeeking($FileName, $seeking)
{
$handle = fopen($FileName, 'rb');
if (!$handle) return FALSE;
$Header = fread($handle, 64);
if (substr($Header, 0, 2) != 'MZ') return FALSE;
$PEOffset = unpack("V", substr($Header, 60, 4));
if ($PEOffset[1]<64) return FALSE;
fseek($handle, $PEOffset[1], SEEK_SET);
$Header = fread ($handle, 24);
if (substr($Header, 0, 2) != 'PE') return FALSE;
$Machine = unpack("v", substr($Header, 4, 2));
if ($Machine[1] != 332) return FALSE;
$NoSections = unpack("v", substr($Header, 6, 2));
$OptHdrSize = unpack("v", substr($Header, 20, 2));
fseek($handle, $OptHdrSize[1], SEEK_CUR);
$ResFound = FALSE;
for ($x = 0; $x < $NoSections[1]; $x++)
{
//$x fixed here
$SecHdr = fread($handle, 40);
if (substr($SecHdr, 0, 5) == '.rsrc')
{
//resource section
$ResFound = TRUE;
break;
}
}
if (!$ResFound) return FALSE;
$InfoVirt = unpack("V", substr($SecHdr, 12, 4));
$InfoSize = unpack("V", substr($SecHdr, 16, 4));
$InfoOff = unpack("V", substr($SecHdr, 20, 4));
fseek($handle, $InfoOff[1], SEEK_SET);
$Info = fread($handle, $InfoSize[1]);
$NumNamedDirs = unpack("v",substr($Info, 12, 2));
$NumDirs = unpack("v", substr($Info, 14, 2));
$InfoFound = FALSE;
for ($x = 0; $x < ($NumDirs[1] + $NumNamedDirs[1]); $x++)
{
$Type = unpack("V", substr($Info, ($x * 8) + 16, 4));
if($Type[1] == 16)
{
//FILEINFO resource
$InfoFound = TRUE;
$SubOff = unpack("V", substr($Info, ($x * 8) + 20, 4));
break;
}
}
if (!$InfoFound) return FALSE;
if (0)
{
$SubOff[1] &= 0x7fffffff;
$InfoOff = unpack("V", substr($Info, $SubOff[1] + 20, 4)); //offset of first FILEINFO
$InfoOff[1] &= 0x7fffffff;
$InfoOff = unpack("V", substr($Info, $InfoOff[1] + 20, 4)); //offset to data
$DataOff = unpack("V", substr($Info, $InfoOff[1], 4));
$DataSize = unpack("V", substr($Info, $InfoOff[1] + 4, 4));
$CodePage = unpack("V", substr($Info, $InfoOff[1] + 8, 4));
$DataOff[1] -= $InfoVirt[1];
$Version = unpack("v4", substr($Info, $DataOff[1] + 48, 8));
$x = $Version[2];
$Version[2] = $Version[1];
$Version[1] = $x;
$x = $Version[4];
$Version[4] = $Version[3];
$Version[3] = $x;
return $Version;
}
//view data...
//echo print_r(explode("\x00\x00\x00", $Info));
// could prolly substr on VS_VERSION_INFO
$encodedKey = implode("\x00",str_split($seeking));
$StartOfSeekingKey = strpos($Info, $encodedKey);
if ($StartOfSeekingKey !== false) {
$ulgyRemainderOfData = substr($Info, $StartOfSeekingKey);
$ArrayOfValues = explode("\x00\x00\x00", $ulgyRemainderOfData);
// the key your are seeking is 0, where the value is one
return trim($ArrayOfValues[1]);
}
return false;
}
Elaborating on the answer Toni provided
One of the projects at work had a custom attribute baked into that resource section. On a linux machine we didnt want to install perl to use the windows function to get the values.
Instead one can use this variant of the GetFileVersion to get any value you are seeking. (FileVersion, ProductVersion, ProductName, CompanyName, CompanyWebsite)
It should return false for values that dont exist, and it functions rather quick.
$handle=fopen($FileName,'rb');
if (!$handle) return FALSE;
$Header=fread ($handle,64);
if (substr($Header,0,2)!='MZ') return FALSE;
$PEOffset=unpack("V",substr($Header,60,4));
if ($PEOffset[1]<64) return FALSE;
fseek($handle,$PEOffset[1],SEEK_SET);
$Header=fread ($handle,24);
if (substr($Header,0,2)!='PE') return FALSE;
$Machine=unpack("v",substr($Header,4,2));
if ($Machine[1]!=332) return FALSE;
$NoSections=unpack("v",substr($Header,6,2));
$OptHdrSize=unpack("v",substr($Header,20,2));
fseek($handle,$OptHdrSize[1],SEEK_CUR);
$ResFound=FALSE;
for ($x=0;$x<$NoSections[1];$x++) {
$SecHdr=fread($handle,40);
if (substr($SecHdr,0,5)=='.rsrc') { //resource section
$ResFound=TRUE;
break;
}
}
if (!$ResFound) return FALSE;
$InfoVirt=unpack("V",substr($SecHdr,12,4));
$InfoSize=unpack("V",substr($SecHdr,16,4));
$InfoOff=unpack("V",substr($SecHdr,20,4));
fseek($handle,$InfoOff[1],SEEK_SET);
$Info=fread($handle,$InfoSize[1]);
$NumDirs=unpack("v",substr($Info,14,2));
$InfoFound=FALSE;
for ($x=0;$x<$NumDirs[1];$x++) {
$Type=unpack("V",substr($Info,($x*8)+16,4));
if($Type[1]==16) { //FILEINFO resource
$InfoFound=TRUE;
$SubOff=unpack("V",substr($Info,($x*8)+20,4));
//echo $Info;
break;
}
}
if (!$InfoFound) return FALSE;
// i bypassed this, but if you knew the layout you could prolly do a little better then $ulgyRemainderOfData
/*
$SubOff[1]&=0x7fffffff;
$InfoOff=unpack("V",substr($Info,$SubOff[1]+20,4)); //offset of first FILEINFO
$InfoOff[1]&=0x7fffffff;
$InfoOff=unpack("V",substr($Info,$InfoOff[1]+20,4)); //offset to data
$DataOff=unpack("V",substr($Info,$InfoOff[1],4));
$DataSize=unpack("V",substr($Info,$InfoOff[1]+4,4));
$CodePage=unpack("V",substr($Info,$InfoOff[1]+8,4));
$DataOff[1]-=$InfoVirt[1];
$Version=unpack("v4",substr($Info,$DataOff[1]+48,8));
// swap 1-2 3-4 / endian ecoding issue
$x=$Version[2];
$Version[2]=$Version[1];
$Version[1]=$x;
$x=$Version[4];
$Version[4]=$Version[3];
$Version[3]=$x;
return $Version;
*/
//view data...
//echo print_r(explode("\x00\x00\x00", $Info));
// could prolly substr on VS_VERSION_INFO
$encodedKey = implode("\x00",str_split($seeking));
$StartOfSeekingKey = strpos($Info, $encodedKey);
if ($StartOfSeekingKey !== false) {
$ulgyRemainderOfData = substr($Info, $StartOfSeekingKey);
$ArrayOfValues = explode("\x00\x00\x00", $ulgyRemainderOfData);
// the key your are seeking is 0, where the value is one
return trim($ArrayOfValues[1]);
}
return false;
}
$fileVersion = GetValueOfSeeking("./the/path/to/some.exe", 'FileVersion');
$myAttribute = GetValueOfSeeking("./the/path/to/some.exe", 'CustomAttribute');
For me to get the code from Toni to work I had to do a small change:
function GetFileVersion($FileName) {
$handle=fopen($FileName,'rb');
if (!$handle) return FALSE;
$Header=fread ($handle,64);
if (substr($Header,0,2)!='MZ') return FALSE;
$PEOffset=unpack("V",substr($Header,60,4));
if ($PEOffset[1]<64) return FALSE;
fseek($handle,$PEOffset[1],SEEK_SET);
$Header=fread ($handle,24);
if (substr($Header,0,2)!='PE') return FALSE;
$Machine=unpack("v",substr($Header,4,2));
if ($Machine[1]!=332) return FALSE;
$NoSections=unpack("v",substr($Header,6,2));
$OptHdrSize=unpack("v",substr($Header,20,2));
fseek($handle,$OptHdrSize[1],SEEK_CUR);
$ResFound=FALSE;
for ($x=0;$x<$NoSections[1];$x++) { //$x fixed here
$SecHdr=fread($handle,40);
if (substr($SecHdr,0,5)=='.rsrc') { //resource section
$ResFound=TRUE;
break;
}
}
if (!$ResFound) return FALSE;
$InfoVirt=unpack("V",substr($SecHdr,12,4));
$InfoSize=unpack("V",substr($SecHdr,16,4));
$InfoOff=unpack("V",substr($SecHdr,20,4));
fseek($handle,$InfoOff[1],SEEK_SET);
$Info=fread($handle,$InfoSize[1]);
$NumDirs=unpack("v",substr($Info,16,2));
$InfoFound=FALSE;
for ($x=0;$x<$NumDirs[1];$x++) {
$Type=unpack("V",substr($Info,($x*8)+16,4));
if($Type[1]==16) { //FILEINFO resource
$InfoFound=TRUE;
$SubOff=unpack("V",substr($Info,($x*8)+20,4));
break;
}
}
if (!$InfoFound) return FALSE;
$SubOff[1]&=0x7fffffff;
$InfoOff=unpack("V",substr($Info,$SubOff[1]+20,4)); //offset of first FILEINFO
$InfoOff[1]&=0x7fffffff;
$InfoOff=unpack("V",substr($Info,$InfoOff[1]+20,4)); //offset to data
$DataOff=unpack("V",substr($Info,$InfoOff[1],4));
$DataSize=unpack("V",substr($Info,$InfoOff[1]+4,4));
$CodePage=unpack("V",substr($Info,$InfoOff[1]+8,4));
$DataOff[1]-=$InfoVirt[1];
$Version=unpack("v4",substr($Info,$DataOff[1]+48,8));
$x=$Version[2];
$Version[2]=$Version[1];
$Version[1]=$x;
$x=$Version[4];
$Version[4]=$Version[3];
$Version[3]=$x;
return $Version;
}
Where I only changed 14 to 16 in the line
$NumDirs=unpack("v",substr($Info,16,2));
Maybe this has something to do with what j_schultz already added in the comments.
I wrote a small code that will pull the file version directly from the executable.
PHP:
<?php
function exeparser_fileversion($file) {
$parser_model = array('begin'=>"F\x00i\x00l\x00e\x00V\x00e\x00r\x00s\x00i\x00o\x00n",'end'=>"\x00\x00\x00");
if (file_exists($file) && is_readable($file)) {
$version = file_get_contents($file);
$version = explode($parser_model['begin'], $version);
$version = explode($parser_model['end'], $version[1]);
$version = str_replace("\x00", null, $version[1]);
return ((!empty($version) ? "\x1b[32m$file version: $version\x1b[0m" : "\x1b[31mNo version\x1b[0m"));
} else {
print "\x1b[31m".(is_dir($file) ? "Specified path points to a directory, not a file." : "The specified path to the file may not exist or is not a file at all.")."\x1b[0m";
return false;
}
}
#print exeparser_fileversion($argv[1]);
?>
Windows command (optional):
#php "%~dp0\exeparser.php" %*
Result:

Categories