From 50b40f4fbf5ec0bbb490079b0254a8826fa50d26 Mon Sep 17 00:00:00 2001 From: Heiko Rothe Date: Sat, 28 May 2016 14:50:46 +0200 Subject: [PATCH 1/7] Added RGB conversion for picking colors --- library/Phue/Command/SetLightState.php | 46 +++++++++++ library/Phue/Helper/ColorConversion.php | 104 ++++++++++++++++++++++++ library/Phue/Light.php | 45 ++++++++++ 3 files changed, 195 insertions(+) create mode 100644 library/Phue/Helper/ColorConversion.php diff --git a/library/Phue/Command/SetLightState.php b/library/Phue/Command/SetLightState.php index 7f6e2f3..6c3b575 100644 --- a/library/Phue/Command/SetLightState.php +++ b/library/Phue/Command/SetLightState.php @@ -9,6 +9,7 @@ namespace Phue\Command; use Phue\Client; +use Phue\Helper\ColorConversion; use Phue\Transport\TransportInterface; /** @@ -57,6 +58,16 @@ class SetLightState implements CommandInterface, ActionableInterface */ const XY_MAX = 1.0; + /** + * RGB Min + */ + const RGB_MIN = 0; + + /** + * RGB Max + */ + const RGB_MAX = 255; + /** * Color temperature min */ @@ -268,6 +279,41 @@ public function xy($x, $y) return $this; } + /** + * Sets xy and brightness calculated from RGB + * + * @param int $red + * Red value + * @param int $green + * Green value + * @param int $blue + * Blue value + * + * @throws \InvalidArgumentException + * + * @return self This object + */ + public function rgb($red, $green, $blue) + { + // Don't continue if rgb values are invalid + foreach (array( + $red, + $green, + $blue + ) as $value) { + if (! (self::RGB_MIN <= $value && $value <= self::RGB_MAX)) { + throw new \InvalidArgumentException( + "RGB values must be between " . self::RGB_MIN . " and " . + self::RGB_MAX + ); + } + } + + $xy = ColorConversion::convertRGBToXY($red, $green, $blue); + + return $this->xy($xy['x'], $xy['y'])->brightness($xy['bri']); + } + /** * Set color temperature * diff --git a/library/Phue/Helper/ColorConversion.php b/library/Phue/Helper/ColorConversion.php new file mode 100644 index 0000000..6b3b791 --- /dev/null +++ b/library/Phue/Helper/ColorConversion.php @@ -0,0 +1,104 @@ + + * @copyright Copyright (c) 2012 Michael K. Squires + * @license http://github.com/sqmk/Phue/wiki/License + */ +namespace Phue\Helper; + + +class ColorConversion +{ + /** + * Converts RGB values to XY values + * Based on: http://stackoverflow.com/a/22649803 + * + * @param int $red + * Red value + * @param int $green + * Green value + * @param int $blue + * Blue value + * + * @return array x, y, bri key/value + */ + public static function convertRGBToXY($red, $green, $blue) { + // Normalize the values to 1 + $normalizedToOne['red'] = $red / 255; + $normalizedToOne['green'] = $green / 255; + $normalizedToOne['blue'] = $blue / 255; + + // Make colors more vivid + foreach ($normalizedToOne as $key => $normalized) { + if ($normalized > 0.04045) { + $color[$key] = pow(($normalized + 0.055) / (1.0 + 0.055), 2.4); + } + else { + $color[$key] = $normalized / 12.92; + } + } + + // Convert to XYZ using the Wide RGB D65 formula + $xyz['x'] = $color['red'] * 0.649926 + $color['green'] * 0.103455 + $color['blue'] * 0.197109; + $xyz['y'] = $color['red'] * 0.234327 + $color['green'] * 0.743075 + $color['blue'] * 0.022598; + $xyz['z'] = $color['red'] * 0.000000 + $color['green'] * 0.053077 + $color['blue'] * 1.035763; + + // Calculate the x/y values + if (array_sum($xyz) == 0) { + $x = 0; + $y = 0; + } + else { + $x = $xyz['x'] / array_sum($xyz); + $y = $xyz['y'] / array_sum($xyz); + } + + return array( + 'x' => $x, + 'y' => $y, + 'bri' => $xyz['y'] * 255 + ); + } + + /** + * Converts XY (and brightness) values to RGB + * + * @param float $x + * X value + * @param float $y + * Y value + * @param int $bri + * Brightness value + * + * @return array red, green, blue key/value + */ + public static function convertXYToRGB($x, $y, $bri = 255) { + // Calculate XYZ + $z = 1.0 - $x - $y; + $xyz['y'] = $bri / 255; + $xyz['x'] = ($xyz['y'] / $y) * $x; + $xyz['z'] = ($xyz['y'] / $y) * $z; + + // Convert to RGB using Wide RGB D65 conversion + $color['red'] = $xyz['x'] * 1.656492 - $xyz['y'] * 0.354851 - $xyz['z'] * 0.255038; + $color['green'] = -$xyz['x'] * 0.707196 + $xyz['y'] * 1.655397 + $xyz['z'] * 0.036152; + $color['blue'] = $xyz['x'] * 0.051713 - $xyz['y'] * 0.121364 + $xyz['z'] * 1.011530; + + foreach ($color as $key => $normalized) { + // Apply reverse gamma correction + if ($normalized <= 0.0031308) { + $color[$key] = 12.92 * $normalized; + } + else { + $color[$key] = (1.0 + 0.055) * pow($normalized, 1.0 / 2.4) - 0.055; + } + + // Scale back from a maximum of 1 to a maximum of 255 + $color[$key] = round($color[$key] * 255); + } + + return $color; + } +} \ No newline at end of file diff --git a/library/Phue/Light.php b/library/Phue/Light.php index 9dbdb1d..209bc3a 100644 --- a/library/Phue/Light.php +++ b/library/Phue/Light.php @@ -9,6 +9,7 @@ namespace Phue; use Phue\Command\SetLightState; +use Phue\Helper\ColorConversion; use Phue\LightModel\AbstractLightModel; use Phue\LightModel\LightModelFactory; @@ -359,6 +360,50 @@ public function setXY($x, $y) return $this; } + /** + * Get calculated RGB + * + * @return array red, green, blue key/value + */ + public function getRGB() + { + $xy = $this->getXY(); + $bri = $this->getBrightness(); + $rgb = ColorConversion::convertXYToRGB($xy['x'], $xy['y'], $bri); + + return $rgb; + } + + /** + * Set XY and brightness calculated from RGB + * + * @param int $red + * Red value + * @param int $green + * Green value + * @param int $blue + * Blue value + * + * @return self This object + */ + public function setRGB($red, $green, $blue) + { + $x = new SetLightState($this); + $y = $x->rgb((int) $red, (int) $green, (int) $blue); + $this->client->sendCommand($y); + + // Change internal xy, brightness and colormode state + $xy = ColorConversion::convertRGBToXY($red, $green, $blue); + $this->attributes->state->xy = array( + $xy['x'], + $xy['y'] + ); + $this->attributes->state->bri = $xy['bri']; + $this->attributes->state->colormode = 'xy'; + + return $this; + } + /** * Get Color temperature * From 494d4eac7599f9b0fa28c7947f9d683c1950f0e0 Mon Sep 17 00:00:00 2001 From: Heiko Rothe Date: Sat, 28 May 2016 15:27:16 +0200 Subject: [PATCH 2/7] Added color conversion helper tests --- .../Phue/Test/Helper/ColorConversionTest.php | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 tests/Phue/Test/Helper/ColorConversionTest.php diff --git a/tests/Phue/Test/Helper/ColorConversionTest.php b/tests/Phue/Test/Helper/ColorConversionTest.php new file mode 100644 index 0000000..31c1be2 --- /dev/null +++ b/tests/Phue/Test/Helper/ColorConversionTest.php @@ -0,0 +1,72 @@ + + * @copyright Copyright (c) 2012 Michael K. Squires + * @license http://github.com/sqmk/Phue/wiki/License + */ +namespace Phue\Test\Helper; +use Phue\Helper\ColorConversion; + +/** + * Tests for Phue\Helper\ColorConversion + */ +class ColorConversionTest extends \PHPUnit_Framework_TestCase +{ + /** + * Test: convert RGB to XY and brightness + * + * @covers \Phue\Helper\ColorConversion::convertRGBToXY + */ + public function testConvertRGBToXY() + { + // Values from: http://www.developers.meethue.com/documentation/hue-xy-values + + // Alice Blue + $xy = ColorConversion::convertRGBToXY(239, 247, 255); + $this->assertEquals(0.3088, $xy['x'], '', 0.0001); + $this->assertEquals(0.3212, $xy['y'], '', 0.0001); + $this->assertEquals(233, $xy['bri']); + + // Firebrick + $xy = ColorConversion::convertRGBToXY(178, 33, 33); + $this->assertEquals(0.6622, $xy['x'], '', 0.0001); + $this->assertEquals(0.3024, $xy['y'], '', 0.0001); + $this->assertEquals(35, $xy['bri']); + + // Medium Sea Green + $xy = ColorConversion::convertRGBToXY(61, 178, 112); + $this->assertEquals(0.1979, $xy['x'], '', 0.0001); + $this->assertEquals(0.5005, $xy['y'], '', 0.0001); + $this->assertEquals(81, $xy['bri']); + } + + /** + * Test: convert XY and brightness to RGB + * + * @covers \Phue\Helper\ColorConversion::convertXYToRGB + */ + public function testConvertXYToRGB() + { + // Conversion back from the test above + + // Alice Blue + $rgb = ColorConversion::convertXYToRGB(0.3088, 0.3212, 233); + $this->assertEquals($rgb['red'], 239); + $this->assertEquals($rgb['green'], 247); + $this->assertEquals($rgb['blue'], 255); + + // Firebrick + $rgb = ColorConversion::convertXYToRGB(0.6622, 0.3024, 35); + $this->assertEquals($rgb['red'], 178); + $this->assertEquals($rgb['green'], 33); + $this->assertEquals($rgb['blue'], 33); + + // Medium Sea Green + $rgb = ColorConversion::convertXYToRGB(0.1979, 0.5005, 81); + $this->assertEquals($rgb['red'], 61); + $this->assertEquals($rgb['green'], 178); + $this->assertEquals($rgb['blue'], 112); + } +} From 816d1e1e850e3dc3ce2ea47b9e7602592269c185 Mon Sep 17 00:00:00 2001 From: Heiko Rothe Date: Sat, 28 May 2016 15:28:57 +0200 Subject: [PATCH 3/7] Fixed formula errors using the official docs at http://www.developers.meethue.com/documentation/color-conversions-rgb-xy --- library/Phue/Helper/ColorConversion.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/library/Phue/Helper/ColorConversion.php b/library/Phue/Helper/ColorConversion.php index 6b3b791..84be44a 100644 --- a/library/Phue/Helper/ColorConversion.php +++ b/library/Phue/Helper/ColorConversion.php @@ -24,7 +24,8 @@ class ColorConversion * * @return array x, y, bri key/value */ - public static function convertRGBToXY($red, $green, $blue) { + public static function convertRGBToXY($red, $green, $blue) + { // Normalize the values to 1 $normalizedToOne['red'] = $red / 255; $normalizedToOne['green'] = $green / 255; @@ -41,9 +42,9 @@ public static function convertRGBToXY($red, $green, $blue) { } // Convert to XYZ using the Wide RGB D65 formula - $xyz['x'] = $color['red'] * 0.649926 + $color['green'] * 0.103455 + $color['blue'] * 0.197109; - $xyz['y'] = $color['red'] * 0.234327 + $color['green'] * 0.743075 + $color['blue'] * 0.022598; - $xyz['z'] = $color['red'] * 0.000000 + $color['green'] * 0.053077 + $color['blue'] * 1.035763; + $xyz['x'] = $color['red'] * 0.664511 + $color['green'] * 0.154324 + $color['blue'] * 0.162028; + $xyz['y'] = $color['red'] * 0.283881 + $color['green'] * 0.668433 + $color['blue'] * 0.047685; + $xyz['z'] = $color['red'] * 0.000000 + $color['green'] * 0.072310 + $color['blue'] * 0.986039; // Calculate the x/y values if (array_sum($xyz) == 0) { @@ -58,7 +59,7 @@ public static function convertRGBToXY($red, $green, $blue) { return array( 'x' => $x, 'y' => $y, - 'bri' => $xyz['y'] * 255 + 'bri' => round($xyz['y'] * 255) ); } @@ -74,7 +75,8 @@ public static function convertRGBToXY($red, $green, $blue) { * * @return array red, green, blue key/value */ - public static function convertXYToRGB($x, $y, $bri = 255) { + public static function convertXYToRGB($x, $y, $bri = 255) + { // Calculate XYZ $z = 1.0 - $x - $y; $xyz['y'] = $bri / 255; From d7d562ed55c282d16bdc65857c25d03343d3b66f Mon Sep 17 00:00:00 2001 From: Heiko Rothe Date: Sat, 28 May 2016 15:59:01 +0200 Subject: [PATCH 4/7] Added rgb command tests --- tests/Phue/Test/Command/SetLightStateTest.php | 109 ++++++++++++++++++ tests/Phue/Test/LightTest.php | 36 ++++++ 2 files changed, 145 insertions(+) diff --git a/tests/Phue/Test/Command/SetLightStateTest.php b/tests/Phue/Test/Command/SetLightStateTest.php index 9755bc5..3d00ccd 100644 --- a/tests/Phue/Test/Command/SetLightStateTest.php +++ b/tests/Phue/Test/Command/SetLightStateTest.php @@ -10,6 +10,7 @@ use Phue\Client; use Phue\Command\SetLightState; +use Phue\Helper\ColorConversion; use Phue\Transport\TransportInterface; /** @@ -245,6 +246,52 @@ public function testXYSend($x, $y) $command->send($this->mockClient); } + /** + * Test: invalid RGB value + * + * @dataProvider providerInvalidRGB + * + * @covers \Phue\Command\SetLightState::rgb + * + * @expectedException \InvalidArgumentException + */ + public function testInvalidRGBValue($red, $green, $blue) + { + $_x = new SetLightState($this->mockLight); + $_x->rgb($red, $green, $blue); + } + + /** + * Test: set XY and brightness via RGB + * + * @dataProvider providerRGB + * + * @covers \Phue\Command\SetLightState::rgb + * @covers \Phue\Command\SetLightState::send + */ + public function testRGBSend($red, $green, $blue) + { + // Build command + $command = new SetLightState($this->mockLight); + + // Set expected payload + $xy = ColorConversion::convertRGBToXY($red, $green, $blue); + $this->stubTransportSendRequestWithPayload( + (object) array( + 'xy' => array( + $xy['x'], + $xy['y'] + ), + 'bri' => $xy['bri'] + )); + + // Ensure instance is returned + $this->assertEquals($command, $command->rgb($red, $green, $blue)); + + // Send + $command->send($this->mockClient); + } + /** * Test: Invalid color temp value * @@ -635,6 +682,68 @@ public function providerXY() ); } + /** + * Provider: Invalid RGB + * + * @return array + */ + public function providerInvalidRGB() + { + return array( + array( + - 1, + - 1, + - 1 + ), + array( + 50, + - 50, + 50 + ), + array( + 256, + 50, + 50 + ), + array( + 50, + 256, + 50 + ), + array( + 50, + 50, + 256 + ) + ); + } + + /** + * Provider: RGB + * + * @return array + */ + public function providerRGB() + { + return array( + array( + 0, + 150, + 255 + ), + array( + 10, + 135, + 245 + ), + array( + 150, + 150, + 150 + ) + ); + } + /** * Provider: Invalid Color temp * diff --git a/tests/Phue/Test/LightTest.php b/tests/Phue/Test/LightTest.php index 23791d7..b00315e 100644 --- a/tests/Phue/Test/LightTest.php +++ b/tests/Phue/Test/LightTest.php @@ -9,6 +9,7 @@ namespace Phue\Test; use Phue\Client; +use Phue\Helper\ColorConversion; use Phue\Light; /** @@ -265,6 +266,41 @@ public function testGetSetXY() ), $this->light->getXY()); } + /** + * Test: Get/Set RGB + * + * @covers \Phue\Light::getRGB + * @covers \Phue\Light::setRGB + */ + public function testGetSetRGB() + { + $this->stubMockClientSendSetLightStateCommand(); + + // Make sure original rgb is retrievable + $rgb = ColorConversion::convertXYToRGB( + $this->attributes->state->xy[0], + $this->attributes->state->xy[1], + $this->attributes->state->bri + ); + $this->assertEquals( + array( + 'red' => $rgb['red'], + 'green' => $rgb['green'], + 'blue' => $rgb['blue'] + ), $this->light->getRGB()); + + // Ensure setRGB returns self + $this->assertEquals($this->light, $this->light->setRGB(50, 50, 50)); + + // Make sure light attributes are updated + $this->assertEquals( + array( + 'red' => 50, + 'green' => 50, + 'blue' => 50 + ), $this->light->getRGB()); + } + /** * Test: Get/Set Color temp * From f7547208eef6c9f436838c84e6f038ee717cd119 Mon Sep 17 00:00:00 2001 From: Heiko Rothe Date: Sat, 28 May 2016 16:05:15 +0200 Subject: [PATCH 5/7] Updated readme --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index e0f57b7..4531d91 100644 --- a/README.md +++ b/README.md @@ -177,6 +177,9 @@ echo $light->getId(), "\n", $light->getSaturation(), "\n", $light->getXY()['x'], "\n", $light->getXY()['y'], "\n", + $light->getRGB()['red'], "\n", + $light->getRGB()['green'], "\n", + $light->getRGB()['blue'], "\n", $light->getEffect(), "\n", $light->getColorTemp(), "\n", $light->getColorMode(), "\n"; @@ -203,6 +206,9 @@ $light->setSaturation(255); // Changes color mode to 'xy' $light->setXY(0.25, 0.5); +// Set rgb (0 to 255 each), is converted to XY and brightness +$light->setRGB(30, 100, 50); + // Set color temp (153 min, 500 max), changes color mode to 'ct' $light->setColorTemp(300); @@ -317,6 +323,9 @@ echo $group->getId(), "\n", $group->getSaturation(), "\n", $group->getXY()['x'], "\n", $group->getXY()['y'], "\n", + $group->getRGB()['red'], "\n", + $group->getRGB()['green'], "\n", + $group->getRGB()['blue'], "\n", $group->getColorTemp(), "\n", $group->getColorMode(), "\n", $group->getEffect(), "\n"; @@ -347,6 +356,9 @@ $group->setSaturation(255); // Changes color mode to 'xy' $group->setXY(0.25, 0.5); +// Set rgb (0 to 255 each), is converted to XY and brightness +$group->setRGB(30, 100, 50); + // Set color temp (153 min, 500 max), changes color mode to 'ct' $group->setColorTemp(300); From 5b1391ca86428376b84900a8b295189776f41a7d Mon Sep 17 00:00:00 2001 From: Heiko Rothe Date: Sat, 28 May 2016 16:19:50 +0200 Subject: [PATCH 6/7] Minor formatting fixes --- library/Phue/Helper/ColorConversion.php | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/library/Phue/Helper/ColorConversion.php b/library/Phue/Helper/ColorConversion.php index 84be44a..6d02a20 100644 --- a/library/Phue/Helper/ColorConversion.php +++ b/library/Phue/Helper/ColorConversion.php @@ -24,7 +24,7 @@ class ColorConversion * * @return array x, y, bri key/value */ - public static function convertRGBToXY($red, $green, $blue) + public static function convertRGBToXY($red, $green, $blue) { // Normalize the values to 1 $normalizedToOne['red'] = $red / 255; @@ -35,8 +35,7 @@ public static function convertRGBToXY($red, $green, $blue) foreach ($normalizedToOne as $key => $normalized) { if ($normalized > 0.04045) { $color[$key] = pow(($normalized + 0.055) / (1.0 + 0.055), 2.4); - } - else { + } else { $color[$key] = $normalized / 12.92; } } @@ -50,8 +49,7 @@ public static function convertRGBToXY($red, $green, $blue) if (array_sum($xyz) == 0) { $x = 0; $y = 0; - } - else { + } else { $x = $xyz['x'] / array_sum($xyz); $y = $xyz['y'] / array_sum($xyz); } @@ -75,7 +73,7 @@ public static function convertRGBToXY($red, $green, $blue) * * @return array red, green, blue key/value */ - public static function convertXYToRGB($x, $y, $bri = 255) + public static function convertXYToRGB($x, $y, $bri = 255) { // Calculate XYZ $z = 1.0 - $x - $y; @@ -92,8 +90,7 @@ public static function convertXYToRGB($x, $y, $bri = 255) // Apply reverse gamma correction if ($normalized <= 0.0031308) { $color[$key] = 12.92 * $normalized; - } - else { + } else { $color[$key] = (1.0 + 0.055) * pow($normalized, 1.0 / 2.4) - 0.055; } @@ -103,4 +100,4 @@ public static function convertXYToRGB($x, $y, $bri = 255) return $color; } -} \ No newline at end of file +} From 2126222aabb74ef3002487fcf518dacd6df8c70f Mon Sep 17 00:00:00 2001 From: Heiko Rothe Date: Sat, 28 May 2016 16:49:14 +0200 Subject: [PATCH 7/7] Fixed memory freeing after connection close in PHP 7.0.7 --- library/Phue/Transport/Adapter/Curl.php | 2 +- library/Phue/Transport/Adapter/Streaming.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/Phue/Transport/Adapter/Curl.php b/library/Phue/Transport/Adapter/Curl.php index e28f3ae..a209f83 100644 --- a/library/Phue/Transport/Adapter/Curl.php +++ b/library/Phue/Transport/Adapter/Curl.php @@ -93,6 +93,6 @@ public function getContentType() public function close() { curl_close($this->curl); - unset($this->curl); + $this->curl = null; } } diff --git a/library/Phue/Transport/Adapter/Streaming.php b/library/Phue/Transport/Adapter/Streaming.php index 3c8bc34..25f6cb6 100644 --- a/library/Phue/Transport/Adapter/Streaming.php +++ b/library/Phue/Transport/Adapter/Streaming.php @@ -125,6 +125,6 @@ public function close() fclose($this->fileStream); } - unset($this->streamContext); + $this->streamContext = null; } }