Applying FXAA in PHP? - php

When I'm using PHP's GD image library to draw shapes it always shows hard edges. I've tried to use GD's imageantialias() function but that is for straight lines only.
In order to solve the problem, I've searched some anti-aliasing algorithms and found FXAA works pretty well so I'm going to give it a try. I tried to port the FXAA anti-aliasing filter from the GLSL shader here.
Then, when I finished porting the FXAA shader to PHP, it doesn't give me the correct result. I test the FXAA filter using the imagecolorallocatealpha() example on PHP.net:
<?php
require('fxaa.php');
$size = 300;
$image=imagecreatetruecolor($size, $size);
// something to get a white background with black border
$back = imagecolorallocate($image, 255, 255, 255);
$border = imagecolorallocate($image, 0, 0, 0);
imagefilledrectangle($image, 0, 0, $size - 1, $size - 1, $back);
imagerectangle($image, 0, 0, $size - 1, $size - 1, $border);
$yellow_x = 100;
$yellow_y = 75;
$red_x = 120;
$red_y = 165;
$blue_x = 187;
$blue_y = 125;
$radius = 150;
// allocate colors with alpha values
$yellow = imagecolorallocatealpha($image, 255, 255, 0, 75);
$red = imagecolorallocatealpha($image, 255, 0, 0, 75);
$blue = imagecolorallocatealpha($image, 0, 0, 255, 75);
// drawing 3 overlapped circle
imagefilledellipse($image, $yellow_x, $yellow_y, $radius, $radius, $yellow);
imagefilledellipse($image, $red_x, $red_y, $radius, $radius, $red);
imagefilledellipse($image, $blue_x, $blue_y, $radius, $radius, $blue);
FXAA::process($image);
// don't forget to output a correct header!
header('Content-Type: image/png');
// and finally, output the result
imagepng($image);
imagedestroy($image);
?>
Here's the original image:
And this is the processed image:
Here's another test image. (The left is FXAA-processed and the right is not.)
The example images' colour are messed up with the background and the edges is smoothened too much. This is not the expected result as I think. I don't understand what's wrong with my code so I seek for your help.
Also, here's the FXAA class I wrote & the original GLSL shader:
<?php
class FXAA {
const FXAA_REDUCE_MIN = 0.0078125;
const FXAA_REDUCE_MUL = 0.125;
const FXAA_SPAN_MAX = 8;
static $w = 0;
static $h = 0;
private static function add($a, $b){
return array($a[0] + $b[0], $a[1] + $b[1], $a[2] + $b[2]);
}
private static function dot($a, $b){
return $a[0] * $b[0] + $a[1] * $b[1] + $a[2] * $b[2];
}
private static function texture2D($img, $pos){
if(($pos[0] >= self::$w || $pos[0] < 0) || ($pos[1] >= self::$h || $pos[1] < 0)){
return array(0, 0, 0, 0);
}
$color = imagecolorat($img, $pos[0], $pos[1]);
$a = ($color >> 24) & 0xFF;
$r = ($color >> 16) & 0xFF;
$g = ($color >> 8) & 0xFF;
$b = $color & 0xFF;
return array($r, $g, $b, $a);
}
public static function process($img){
self::$w = imagesx($img);
self::$h = imagesy($img);
for($x = 0; $x < imagesx($img); $x++){
for($y = 0; $y < imagesy($img); $y++){
$rgbNW = self::texture2D($img,array($x - 1, $y - 1));
$rgbNE = self::texture2D($img,array($x + 1, $y - 1));
$rgbSW = self::texture2D($img,array($x - 1, $y + 1));
$rgbSE = self::texture2D($img,array($x + 1, $y + 1));
$rgbaM = $rgbM = self::texture2D($img,array($x, $y));
$opacity = array_pop($rgbM);
$luma = array(76, 149, 29);
$lumaNW = self::dot($rgbNW, $luma);
$lumaNE = self::dot($rgbNE, $luma);
$lumaSW = self::dot($rgbSW, $luma);
$lumaSE = self::dot($rgbSE, $luma);
$lumaM = self::dot($rgbM, $luma);
$lumaMin = min($lumaM, min(min($lumaNW, $lumaNE ), min($lumaSW, $lumaSE)));
$lumaMax = max($lumaM, max(max($lumaNW, $lumaNE), max($lumaSW, $lumaSE)));
$dir = array(
-(($lumaNW + $lumaNE) - ($lumaSW + $lumaSE)),
(($lumaNW + $lumaSW) - ($lumaNE + $lumaSE))
);
$dirReduce = max(($lumaNW + $lumaNE + $lumaSW + $lumaSE ) * ( 0.25 * self::FXAA_REDUCE_MUL ), self::FXAA_REDUCE_MIN);
$rcpDirMin = 1 / (min(abs($dir[0]), abs($dir[1])) + $dirReduce);
$dir[0] = min(self::FXAA_SPAN_MAX, max(-self::FXAA_SPAN_MAX, $dir[0] * $rcpDirMin));
$dir[1] = min(self::FXAA_SPAN_MAX, max(-self::FXAA_SPAN_MAX, $dir[1] * $rcpDirMin));
$rgbA = self::add(
self::texture2D($img, array($x + $dir[0] * (1 / 3 - 0.5), $y + $dir[1] * (1 / 3 - 0.5))),
self::texture2D($img, array($x + $dir[0] * (2 / 3 - 0.5), $y + $dir[1] * (1 / 3 - 0.5)))
);
$rgbA[0] *= 0.5;
$rgbA[1] *= 0.5;
$rgbA[2] *= 0.5;
$rgbB = self::add(
self::texture2D($img, array($x + $dir[0] * -0.5, $y + $dir[1] * -0.5)),
self::texture2D($img, array($x + $dir[0] * 0.5, $y + $dir[1] * 0.5))
);
$rgbB[0] = $rgbB[0] * 0.25 + $rgbA[0] * 0.5;
$rgbB[1] = $rgbB[1] * 0.25 + $rgbA[1] * 0.5;
$rgbB[2] = $rgbB[2] * 0.25 + $rgbA[2] * 0.5;
$lumaB = self::dot($rgbB, $luma);
if(($lumaB < $lumaMin) || ($lumaB > $lumaMax)){
imagesetpixel($img, $x, $y, imagecolorallocatealpha($img, $rgbA[0], $rgbA[1], $rgbA[2], $opacity));
}
else {
imagesetpixel($img, $x, $y, imagecolorallocatealpha($img, $rgbB[0], $rgbB[1], $rgbB[2], $opacity));
}
}
}
}
}
Original GLSL Shader:
uniform sampler2D tDiffuse;
uniform vec2 resolution;
varying vec2 vUv;
#define FXAA_REDUCE_MIN (1.0/128.0)
#define FXAA_REDUCE_MUL (1.0/8.0)
#define FXAA_SPAN_MAX 8.0
void main() {
vec3 rgbNW = texture2D( tDiffuse, ( gl_FragCoord.xy + vec2( -1.0, -1.0 ) ) * resolution ).xyz;
vec3 rgbNE = texture2D( tDiffuse, ( gl_FragCoord.xy + vec2( 1.0, -1.0 ) ) * resolution ).xyz;
vec3 rgbSW = texture2D( tDiffuse, ( gl_FragCoord.xy + vec2( -1.0, 1.0 ) ) * resolution ).xyz;
vec3 rgbSE = texture2D( tDiffuse, ( gl_FragCoord.xy + vec2( 1.0, 1.0 ) ) * resolution ).xyz;
vec4 rgbaM = texture2D( tDiffuse, gl_FragCoord.xy * resolution );
vec3 rgbM = rgbaM.xyz;
float opacity = rgbaM.w;
vec3 luma = vec3( 0.299, 0.587, 0.114 );
float lumaNW = dot( rgbNW, luma );
float lumaNE = dot( rgbNE, luma );
float lumaSW = dot( rgbSW, luma );
float lumaSE = dot( rgbSE, luma );
float lumaM = dot( rgbM, luma );
float lumaMin = min( lumaM, min( min( lumaNW, lumaNE ), min( lumaSW, lumaSE ) ) );
float lumaMax = max( lumaM, max( max( lumaNW, lumaNE) , max( lumaSW, lumaSE ) ) );
vec2 dir;
dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));
float dirReduce = max( ( lumaNW + lumaNE + lumaSW + lumaSE ) * ( 0.25 * FXAA_REDUCE_MUL ), FXAA_REDUCE_MIN );
float rcpDirMin = 1.0 / ( min( abs( dir.x ), abs( dir.y ) ) + dirReduce );
dir = min( vec2( FXAA_SPAN_MAX, FXAA_SPAN_MAX),max( vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),dir * rcpDirMin)) * resolution;
vec3 rgbA = 0.5 * (
texture2D( tDiffuse, gl_FragCoord.xy * resolution + dir * ( 1.0 / 3.0 - 0.5 ) ).xyz +
texture2D( tDiffuse, gl_FragCoord.xy * resolution + dir * ( 2.0 / 3.0 - 0.5 ) ).xyz );
vec3 rgbB = rgbA * 0.5 + 0.25 * (
texture2D( tDiffuse, gl_FragCoord.xy * resolution + dir * -0.5 ).xyz +
texture2D( tDiffuse, gl_FragCoord.xy * resolution + dir * 0.5 ).xyz );
float lumaB = dot( rgbB, luma );
if ( ( lumaB < lumaMin ) || ( lumaB > lumaMax ) ) {
gl_FragColor = vec4( rgbA, opacity );
} else {
gl_FragColor = vec4( rgbB, opacity );
}
}

The problem with your code is that it operates on a single image, and thus the anti-aliasing operation reads its own output.
Drawing the output of the anti-aliasing operation into a a new image will work.

Related

Replace the specific RGB color with another image using PHP

I derived a perfect color replace script from https://stackoverflow.com/a/32710756/1620626. I want to replace the target color with the image background. The original image is backgrounded with light green (rgb:0b255b1). I can replace it with blue but I don't have an idea how to replace with the image. Here's the script.
This is the original image.
Once I process this photo with the script. I've got this.
The script is perfectly replace the target color with the new one. Now I want to change from blue into the background. So the final idea is. This girl on the background image I chosen.
Here is the code:
<?php
//https://stackoverflow.com/a/32710756/1620626
function RGBtoHSL( $r, $g, $b ) {
$r /= 255;
$g /= 255;
$b /= 255;
$max = max( $r, $g, $b );
$min = min( $r, $g, $b );
$l = ( $max + $min ) / 2;
$d = $max - $min;
if( $d == 0 ){
$h = $s = 0;
} else {
$s = $d / ( 1 - abs( 2 * $l - 1 ) );
switch( $max ){
case $r:
$h = 60 * fmod( ( ( $g - $b ) / $d ), 6 );
if ($b > $g) {
$h += 360;
}
break;
case $g:
$h = 60 * ( ( $b - $r ) / $d + 2 );
break;
case $b:
$h = 60 * ( ( $r - $g ) / $d + 4 );
break;
}
}
return array( round( $h, 2 ), round( $s, 2 ), round( $l, 2 ) );
}
function HSLtoRGB( $h, $s, $l ){
$c = ( 1 - abs( 2 * $l - 1 ) ) * $s;
$x = $c * ( 1 - abs( fmod( ( $h / 60 ), 2 ) - 1 ) );
$m = $l - ( $c / 2 );
if ( $h < 60 ) {
$r = $c;
$g = $x;
$b = 0;
} else if ( $h < 120 ) {
$r = $x;
$g = $c;
$b = 0;
} else if ( $h < 180 ) {
$r = 0;
$g = $c;
$b = $x;
} else if ( $h < 240 ) {
$r = 0;
$g = $x;
$b = $c;
} else if ( $h < 300 ) {
$r = $x;
$g = 0;
$b = $c;
} else {
$r = $c;
$g = 0;
$b = $x;
}
$r = ( $r + $m ) * 255;
$g = ( $g + $m ) * 255;
$b = ( $b + $m ) * 255;
return array( floor( $r ), floor( $g ), floor( $b ) );
}
/* ---------------CHANGE THESE------------------- */
$colorToReplace = RGBtoHSL(0,255,1);//target color
$hueAbsoluteError = 7;//the more the clearer
$replacementColor = RGBtoHSL(0, 192, 239);//new color
/* ---------------------------------------------- */
$filename = 'images/01.png';
$im = imagecreatefrompng($filename);
$out = imagecreatetruecolor(imagesx($im), imagesy($im));
$transColor = imagecolorallocatealpha($out, 254, 254, 254, 127);
imagefill($out, 0, 0, $transColor);
for ($x = 0; $x < imagesx($im); $x++) {
for ($y = 0; $y < imagesy($im); $y++) {
$pixel = imagecolorat($im, $x, $y);
$red = ($pixel >> 16) & 0xFF;
$green = ($pixel >> 8) & 0xFF;
$blue = $pixel & 0xFF;
$alpha = ($pixel & 0x7F000000) >> 24;
$colorHSL = RGBtoHSL($red, $green, $blue);
if ((($colorHSL[0] >= $colorToReplace[0] - $hueAbsoluteError) && ($colorToReplace[0] + $hueAbsoluteError) >= $colorHSL[0])){
$color = HSLtoRGB($replacementColor[0], $replacementColor[1], $colorHSL[2]);
$red = $color[0];
$green= $color[1];
$blue = $color[2];
}
if ($alpha == 127) {
imagesetpixel($out, $x, $y, $transColor);
}
else {
imagesetpixel($out, $x, $y, imagecolorallocatealpha($out, $red, $green, $blue, $alpha));
}
}
}
imagecolortransparent($out, $transColor);
imagesavealpha($out, TRUE);
header('Content-type: image/png');
imagepng($out);
?>
You can just read two images, source and background, and take pixels from background image and set to the source.
Below is the last part of your code above which shows this idea:
$filename = 'images/01.png';
$bgFilename = 'images/background.png';
$im = imagecreatefrompng($filename);
$bg = imagecreatefrompng($bgFilename);
$out = imagecreatetruecolor(imagesx($im), imagesy($im));
$transColor = imagecolorallocatealpha($out, 254, 254, 254, 127);
imagefill($out, 0, 0, $transColor);
for ($x = 0; $x < imagesx($im); $x++) {
for ($y = 0; $y < imagesy($im); $y++) {
$pixel = imagecolorat($im, $x, $y);
$bgPixel = imagecolorat($bg, $x, $y);
$red = ($pixel >> 16) & 0xFF;
$green = ($pixel >> 8) & 0xFF;
$blue = $pixel & 0xFF;
$alpha = ($pixel & 0x7F000000) >> 24;
$colorHSL = RGBtoHSL($red, $green, $blue);
if ((($colorHSL[0] >= $colorToReplace[0] - $hueAbsoluteError) && ($colorToReplace[0] + $hueAbsoluteError) >= $colorHSL[0])){
// Instead of taking the replacementColor
/* $color = HSLtoRGB($replacementColor[0], $replacementColor[1], $colorHSL[2]); */
/* $red = $color[0]; */
/* $green= $color[1]; */
/* $blue = $color[2]; */
// We just take colors from the backround image pixel
$red = ($bgPixel >> 16) & 0xFF;
$green = ($bgPixel >> 8) & 0xFF;
$blue = $bgPixel & 0xFF;
}
if ($alpha == 127) {
imagesetpixel($out, $x, $y, $transColor);
}
else {
imagesetpixel($out, $x, $y, imagecolorallocatealpha($out, $red, $green, $blue, $alpha));
}
}
}
imagecolortransparent($out, $transColor);
imagesavealpha($out, TRUE);
header('Content-type: image/png');
imagepng($out);
The result can look like this:

PHP GD convert coordinates from Polar to Cartesian

I want to show percentages near or over each parts of the chart.
Do I have to convert coordinates from Polar to Cartesian?
To convert polar coordinates (r, θ) to rectangular coordinates (x, y) I used the following conversion:
x = r * cos( θ )
y = r * sin( θ )
in this graph
$x = $radius * cos( $Arc ); $y = $radius * sin( $Arc );
But results doesn't match (the percentages values aren't any close to each single parts of the chart), where I go wrong?
How can I fix it?
<?php
$W = 500; $H = 500;
$Image = imagecreate($W,$H);
$colors = array();
imagecolorallocate($Image,150,150,150); // grey background
$colors[0] = imagecolorallocate($Image,255,0,0); // red
$colors[1] = imagecolorallocate($Image,100,255,10); // green
$colors[2] = imagecolorallocate($Image,0,0,255); // bleu
$colors[3] = imagecolorallocate($Image,255,255,0); // yellow
$Country = array('England', 'Northern Ireland', 'Scotland', 'Wales');
$Population = array(53012456,1810863,5295403,3063456);
$TotPopulation = array_sum($Population);
$radius = 300; $StarAngle = 0;
foreach($Population as $index => $V )
{
$Arc = (360 * $V) / $TotPopulation;
$Percentage = (100 * $V) / $TotPopulation;
imagefilledarc(
$Image,
$W / 2, $H / 2, // center x,y
$radius,
$radius,
$StarAngle,
($StarAngle + $Arc), // end arc
$colors[$index],
IMG_ARC_PIE
);
$x = $radius * cos($Arc);
$y = $radius * sin($Arc);
imagestring($Image, 15, 5, 30 + $index*15 , 'x= '.number_format($x, 2, '.', '').' y='.number_format($y, 2, '.', '').' Country='.$Country[$index].' %='. number_format($Percentage, 2, '.', '').' ' , $colors[$index]);
$StarAngle += $Arc;
imagestring($Image, 15, 5, 430 + $index*15 , 'Country='.$Country[$index].' Population= $V %='.number_format($Percentage, 2, '.', '').' ' , $colors[$index]);
}
imagestring($Image, 5, 35, 10 , 'The population of the United Kingdom year 2011' , $colors[3]);
imagepng($Image);
You can try the library geo-math-php
composer require rkondratuk/geo-math-php:^1
Convert coordinates polar to cartesian:
<?php
use PhpGeoMath\Model\Polar3dPoint;
$polarPoint1 = new Polar3dPoint(
40.758742779050706, -73.97855507715238, Polar3dPoint::EARTH_RADIUS_IN_METERS
);
$cartesianpoint1 = $polarPoint1->buildCartesian3DPoint();
// Result:
$cartesianpoint1->x;
$cartesianpoint1->y;
$cartesianpoint1->z;
Convert coordinates cartesian to polar:
<?php
use PhpGeoMath\Model\Cartesian3dPoint;
$x = 1001;
$y = 205;
$z = 512;
$cartesianPoint2 = new Cartesian3dPoint($x, $y, $z);
$polarPoint2 = $cartesianPoint2->buildPolar3dPoint();
// Result:
$polarPoint2->lat;
$polarPoint2->lng;
$polarPoint2->radius;

How to draw flat topped hex (title) image in PHP?

I am trying to draw a hex using PHP. I have been trying to follow the manual from http://www.redblobgames.com/grids/hexagons/ supporting myself with leland hex generation class (https://github.com/pushcx/leland/blob/master/class_hex_image.php).
Unluckily my "hex" looks like this:
Can you please advice what am I doing wrong or tell me how can I create a proper hex?
It seems to me that function which grabs hex corners does not work correctly:
function hex_corners($center, $size)
{
$points = array();
for($i=0; $i <= 5; $i++)
{
$deg = 60 * $i; // Oblicz kąt, w którym znajduje sie róg hexu
$rad = pi() / 180 * $deg; // Przelicz kąt na radiany
$points[$i] = array_push($points, $center['x'] + $this->size / 2 * cos($rad), $center['y'] + $this->size / 2 * sin($rad));
}
return($points);
}
but I have tried to mimic the one described in http://www.redblobgames.com/grids/hexagons/ manual:
function hex_corner(center, size, i):
var angle_deg = 60 * i
var angle_rad = PI / 180 * angle_deg
return Point(center.x + size * cos(angle_rad),
center.y + size * sin(angle_rad))
I am using the following draw function:
public function hex_draw($x, $y)
{
$this->hex = imagecreatetruecolor ($this->size , $this->size);
$center['x'] = $this->size / 2;
$center['y'] = $this->size / 2;
$blue = imagecolorallocate($this->hex, 0, 0, 255);
// Get points
$points = $this->hex_corners($center, $this->size);
//die($print_r($points);
imagefilledpolygon($this->hex, $points, count($points)/2, $blue);
// flush image
header('Content-type: image/png');
imagepng($this->hex);
imagedestroy($this->hex);
}
My error was trivial.
In line
$points[$i] = array_push($points, $center['x'] + $this->size / 2 * cos($rad), $center['y'] + $this->size / 2 * sin($rad));
there was unneeded [$i] which caused problems.
Hex may be properly generated using the following code:
<?php
class hexmapimage
{
private $size = 100;
private $hex;
public function hex_draw($x, $y)
{
$this->hex = imagecreatetruecolor ($this->size , $this->size);
$center['x'] = $this->size / 2;
$center['y'] = $this->size / 2;
$black = imagecolorallocate($this->hex, 0, 0, 0);
$blue = imagecolorallocate($this->hex, 0, 0, 255);
$transparent = imagecolortransparent ($this->hex, $black);
// Get points
$points = $this->hex_corners($center, $this->size);
imagefilledrectangle($this->hex, 0, 0, $this->size, $this->size, $transparent);
imagefilledpolygon($this->hex, $points, count($points)/2, $blue);
// flush image
header('Content-type: image/png');
imagepng($this->hex);
imagedestroy($this->hex);
}
/*
* $center = array(x coordinate of center of hex, y coordinate of center of hex)
* $size = int (size of hex)
*/
function hex_corners($center, $size)
{
$points = array();
for($i=0; $i <= 5; $i++)
{
$deg = 60 * $i; // Oblicz kąt, w którym znajduje sie róg hexu
$rad = deg2rad($deg); // Przelicz kąt na radiany
array_push($points, $center['x'] + $this->size / 2 * cos($rad), $center['y'] + $this->size / 2 * sin($rad));
}
return($points);
}
}
$hex = new hexmapimage();
$hex->hex_draw(0, 0);

Convert RGB value to HSV/HSL [duplicate]

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})");
}
}

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})");
}
}

Categories