1: <?php
2:
3: /**
4: * Alfred Bundler Icon and Color API file
5: *
6: * Interface for downloading, loading, and manipulating image resources to
7: * be used with the PHP implementation of the Alfred Bundler.
8: *
9: * This file is part of the Alfred Bundler, released under the MIT licence.
10: * Copyright (c) 2014 The Alfred Bundler Team
11: * See https://github.com/shawnrice/alfred-bundler for more information
12: *
13: * @copyright The Alfred Bundler Team 2014
14: * @license http://opensource.org/licenses/MIT MIT
15: * @version Taurus 1
16: * @link http://shawnrice.github.io/alfred-bundler
17: * @package AlfredBundler
18: * @since File available since Taurus 1
19: */
20:
21: if ( ! class_exists( 'AlfredBundlerIcon' ) ) :
22: /**
23: *
24: * A class used to get / manipulate icons...
25: *
26: * @package AlfredBundler
27: * @since Class available since Taurus 1
28: * @TODO document further
29: */
30: class AlfredBundlerIcon {
31:
32: public $background;
33: public $data;
34: public $cache;
35:
36:
37: /**
38: * Sets the variables to deal with icons
39: *
40: * @todo decide exactly where the cache is going to live
41: *
42: * @since Taurus 1
43: *
44: * @param object $bundler the bundler object that instantiates this
45: */
46: public function __construct( $bundler ) {
47:
48: $this->mime = finfo_open( FILEINFO_MIME_TYPE );
49: $this->bundler = $bundler; // in case we need any of the variables...
50: $this->data = $this->bundler->data;
51: // $this->cache = $cache;
52: $this->colors = array();
53: $this->fallback = $this->data . '/bundler/meta/icons/default.png';
54:
55: $this->setBackground();
56:
57: $this->cache = $bundler->cache . '/color';
58:
59: }
60:
61:
62:
63: /*****************************************************************************
64: * BEGIN SETUP FUNCTIONS
65: ****************************************************************************/
66:
67: /**
68: * Creates necessary folders for icons
69: *
70: * @since Taurus 1
71: *
72: */
73: private function setup() {
74:
75: if ( ! file_exists( $this->data . '/data' ) )
76: mkdir( $this->data . '/data' );
77:
78: if ( ! file_exists( $this->cache ) )
79: mkdir( $this->cache, 0775, TRUE );
80:
81: }
82:
83:
84: /**
85: * Set the 'background' variable to either 'light' or 'dark'
86: *
87: * As of Alfred v2.4 Build 277, environmental variables are available that
88: * expose the color of the theme background as sRGBa. If those variables
89: * are accessible, then we set the background with them.
90: *
91: * Otherwise, we fallback to a utility that comes with the bundler called
92: * 'LightOrDark' that reads the NSColor Object in the plist that Alfred
93: * uses to store the themes, and then it returns the value 'light' or 'dark'.
94: *
95: * @link [http://www.alfredforum.com/topic/4716-some-new-alfred-script-environment-variables-coming-in-alfred-24/] [Alfred 2.4 Environmental Variables]
96: *
97: * @since Taurus 1
98: *
99: */
100: private function setBackground() {
101:
102: if ( isset( $_ENV[ 'alfred_version' ] ) ) {
103: // Current Version >= v2.4:277
104: $this->setBackgroundFromEnv();
105: } else {
106: // Current Version < v2.4:277 -- or -- no env vars are set. Maybe
107: // you're running this outside of Alfred, eh?
108: $this->setBackgroundFromUtil();
109: }
110: // Make sure we've set the background
111: $this->validateBackground();
112:
113: }
114:
115: /**
116: * Sets the background to 'light' or 'dark' based on environmental variables
117: * based on a luminance calculation
118: *
119: * @see setBackground()
120: * @see setBackgroundFromUtil()
121: * @see getLuminance()
122: *
123: * @since Taurus 1
124: *
125: */
126: private function setBackgroundFromEnv() {
127: $pattern = "/rgba\(([0-9]{1,3}),([0-9]{1,3}),([0-9]{1,3}),([0-9.]{4,})\)/";
128: preg_match_all( $pattern, $_ENV[ 'alfred_theme_background' ], $matches );
129:
130: $this->background = $this->getLuminance( array( 'r' => $matches[1][0],
131: 'g' => $matches[2][0],
132: 'b' => $matches[3][0] ) );
133:
134: }
135:
136:
137: /**
138: * Sets the theme background color to either 'light' or 'dark'
139: *
140: * This function checks for the existence of a file and considers the contents
141: * versus the modified time of the Alfredpreferences.plist where theme info
142: * is stored. If necessary, it uses a utility to determine the 'light/dark'
143: * status of the current Alfred theme. This method exists for versions of
144: * Alfred pre 2.4 build 277.
145: *
146: * @see setBackground()
147: * @see setBackgroundFromEnv()
148: *
149: * @since Taurus 1
150: *
151: */
152: private function setBackgroundFromUtil() {
153:
154: // The Alfred preferences plist where the theme information is stored
155: $plist = "{$_SERVER[ 'HOME' ]}/Library/Preferences/com.runningwithcrayons.Alfred-Preferences.plist";
156: $cache = "{$this->data}/cache/misc/theme_background";
157: $util = "{$this->data}/bundler/includes/LightOrDark";
158:
159: if ( ! file_exists( "{$this->data}/data" ) ) {
160: mkdir( "{$this->data}/data", 0775, TRUE );
161: }
162:
163: if ( file_exists( $cache ) ) {
164: if ( filemtime( $cache ) > filemtime( $plist ) ) {
165: $this->background = file_get_contents( $cache );
166: return TRUE;
167: }
168: }
169:
170: if ( file_exists( $util ) ) {
171: $this->background = exec( "'$util'" );
172: file_put_contents( $cache, $this->background );
173: } else {
174: $this->background = 'dark';
175: }
176:
177: }
178:
179:
180: /**
181: * Sets the theme background to dark if it isn't set
182: *
183: * @see setBackground()
184: *
185: * @since Taurus 1
186: */
187: public function validateBackground() {
188:
189: if ( ! isset( $this->background ) )
190: $this->background = 'dark';
191:
192: if ( $this->background != 'light' && $this->background != 'dark' )
193: $this->background = 'dark';
194:
195: }
196:
197:
198: /*****************************************************************************
199: * END SETUP FUNCTIONS
200: ****************************************************************************/
201:
202: /*****************************************************************************
203: * BEGIN ICON FUNCTIONS
204: ****************************************************************************/
205:
206: /**
207: * Returns a path to an icon
208: *
209: * @link [http://icons.deanishe.net] [Icon server documentation]
210: *
211: * @see parseIconArguments()
212: * @see getSystemIcon()
213: * @see getIcon()
214: * @see downloadIcon()
215: * @see tryServers()
216: * @see validateImage()
217: * @see color()
218: * @see prepareIcon()
219: *
220: * @since Taurus 1
221: *
222: * @param array $args Associative array that evaluates to
223: * [ 'font' => font,
224: * 'name' => icon-name,
225: * 'color' => hex-color,
226: * 'alter' => hex-color|bool ].
227: * The first two are mandatory; color defaults to
228: * '000000', and alter defaults to FALSE.
229: *
230: * @return mixed FALSE on user error, and file path on success
231: */
232: public function icon( $args ) {
233:
234: if ( count( $args ) < 2 )
235: return FALSE;
236:
237: if ( ! $args = $this->parseIconArguments( $args ) )
238: return FALSE;
239:
240: // Return system icon or fallback
241: if ( $args[ 'font' ] == 'system' )
242: return $this->getSystemIcon( $args[ 'name' ] );
243:
244: $args = $this->prepareIcon( $args );
245:
246: $dir = "{$this->data}/data/assets/icons";
247: $icon = implode( '/', [ $dir, $args[ 'font' ], $args[ 'color' ], $args[ 'name' ] ] );
248:
249: if ( file_exists( $icon . '.png' ) ) {
250: return $icon . '.png';
251: }
252:
253: if ( $this->getIcon( $args ) ) {
254: return $icon . '.png';
255: }
256:
257: return $this->fallback;
258:
259: }
260:
261:
262: /**
263: * Validates and normalizes the arguments for the icon method
264: *
265: * @see icon()
266: *
267: * @since Taurus 1
268: *
269: * @param array $args the arguments passed to icon()
270: *
271: * @return array a normalized version of the input
272: */
273: private function parseIconArguments( $args ) {
274:
275: $args[ 'font' ] = strtolower( trim( $args[ 'font' ] ) );
276: if ( ! isset( $args[ 'alter' ] ) ) {
277: $args[ 'alter' ] = FALSE;
278: }
279: if ( ! isset( $args[ 'color' ] ) ) {
280: $args[ 'color' ] = '000000';
281: $args[ 'alter' ] = TRUE;
282: }
283: return $args;
284:
285: }
286:
287:
288: /**
289: * Fetches a system icon path
290: *
291: * @since Taurus 1
292: *
293: * @param string $name name of a system icon (with no extension)
294: *
295: * @return string path to system icon or fallback
296: */
297: private function getSystemIcon( $name ) {
298:
299: $icon = "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/{$name}.icns";
300:
301: if ( file_exists( $icon ) )
302: return $icon;
303:
304: return "{$this->data}/bundler/meta/icons/default.icns";
305: }
306:
307:
308: /**
309: * Queries server to download non-local icon
310: *
311: * @see icon()
312: *
313: * @since Taurus 1
314: *
315: * @param array $args Same args as icon()
316: *
317: * @return string Path to icon or fallback
318: */
319: public function getIcon( $args ) {
320:
321: $dir = implode( '/' , [ $this->data, 'data', 'assets', 'icons', $args[ 'font' ], $args[ 'color' ] ] );
322:
323: if ( ! file_exists( $dir ) )
324: mkdir( $dir, 0775, TRUE );
325:
326: $icon = $dir . '/' . $args[ 'name' ];
327:
328: $suburl = implode( '/', [ 'icon', $args[ 'font' ], $args[ 'color' ], $args[ 'name' ] ] );
329: $success = $this->tryServers( $suburl, $icon );
330:
331: // If success is true, then we downloaded the icon
332: if ( $success !== TRUE ) {
333: unlink( $icon . '.png' );
334: return FALSE;
335: }
336:
337: if ( $this->validateImage( $icon ) ) {
338: return $icon . '.png';
339: }
340:
341: // log error here
342: unlink( $icon . '.png' );
343: return $this->fallback;
344:
345: }
346:
347:
348: /**
349: * Cycles through a list of servers to download an icon
350: *
351: * The method tries to get the icon from the first server,
352: * if the server is unreachable, then it will go down the
353: * list until it succeeds. If none are available, then it
354: * reports its failure.
355: *
356: * @see icon()
357: * @see getIcon()
358: * @see downloadIcon()
359: *
360: * @since Taurus 1
361: *
362: * @param array $args associative array containing suburl
363: * arguments: font, name, color
364: * @param string $icon filepath to icon destination
365: *
366: * @return bool TRUE on success, FALSE on failure
367: */
368: public function tryServers( $args, $icon ) {
369:
370: $servers = explode( PHP_EOL, file_get_contents( $this->data . "/bundler/meta/icon_servers" ) );
371:
372: // Cycle through the servers until we find one that is up.
373: foreach ( $servers as $server ) :
374: $url = $server . '/' . $args;
375: $status = $this->downloadIcon( $url, $icon );
376: if ( $status === 0 )
377: return TRUE;
378: endforeach;
379:
380: //logerrorhere
381: return FALSE;
382:
383: }
384:
385:
386: /**
387: * Downloads an icon from a server
388: *
389: * @see icon()
390: * @see getIcon()
391: * @see tryServers()
392: *
393: * @since Taurus 1
394: *
395: * @param string $url url to file
396: * @param string $icon path to file destination
397: *
398: * @return int cURL exit status
399: */
400: public function downloadIcon( $url, $icon ) {
401:
402: $c = curl_init( $url );
403: $f = fopen( $icon . '.png', "w" );
404: curl_setopt_array( $c, array(
405: CURLOPT_FILE => $f,
406: CURLOPT_HEADER => FALSE,
407: CURLOPT_FOLLOWLOCATION => TRUE,
408: CURLOPT_CONNECTTIMEOUT => 2 ) );
409:
410: curl_exec( $c );
411: fclose( $f );
412:
413: $status = curl_errno( $c );
414: $info = curl_getinfo($c);
415:
416: if ( $info[ 'http_code' ] !== 200 ) {
417: // Log the error somehow here to show that it's not a good deal, yo.
418: $status = 404;
419: }
420:
421: curl_close( $c );
422:
423: return $status;
424:
425: }
426:
427:
428: /**
429: * Checks a file to see if it is a png.
430: *
431: * @since Taurus 1
432: *
433: * @param string $image file path to alleged image
434: *
435: * @return bool TRUE is a png, FALSE if not
436: */
437: public function validateImage( $image ) {
438: if ( finfo_file( $this->mime, $image . '.png' ) == 'image/png' )
439: return TRUE;
440: return FALSE;
441:
442: }
443:
444: /*****************************************************************************
445: * END ICON FUNCTIONS
446: ****************************************************************************/
447:
448:
449:
450: /*****************************************************************************
451: * BEGIN COLOR FUNCTIONS
452: ****************************************************************************/
453:
454: /**
455: * Normalizes and validates a color and adds it to the color array
456: *
457: * @see prepareIcon()
458: * @see checkHex()
459: * @see validateHex()
460: * @see normalizeHex()
461: * @see rgb()
462: * @see hsv()
463: * @see luminance()
464: * @see brightness()
465: * @see altered()
466: * @see hexToRgb()
467: * @see rgbToHex()
468: * @see rgbToHsv()
469: * @see hsvToRgb()
470: * @see alter()
471: * @see getLuminance()
472: * @see getBrightness()
473: *
474: * @since Taurus 1
475: *
476: * @param string $color a hex color
477: *
478: * @return string a hex normalized and validated hex color
479: */
480: public function color( $color ) {
481:
482: if ( ! in_array( $color, $this->colors ) ) {
483: if ( ! $color = $this->checkHex( $color ) )
484: return FALSE;
485: $this->colors[ $color ][ 'hex' ] = $color;
486: }
487: return $color;
488:
489: }
490:
491: /**
492: * Prepares the icon arguments for a proper query
493: *
494: * The color is first normalized. Then, if the `alter` variable
495: * has not been set, then it just send the arguments back. Otherwise
496: * a check is run to see if the theme background color is
497: * the same as the proposed icon color. If not, then it sends back
498: * the arguments. If so, then, if the `alter` variable is another
499: * hex color, it returns that. If, instead, it is TRUE, then alters
500: * the color accordingly so that the icon will best appear on
501: * the background.
502: *
503: * @see icon()
504: * @see color()
505: * @see checkHex()
506: * @see validateHex()
507: * @see normalizeHex()
508: * @see rgb()
509: * @see hsv()
510: * @see luminance()
511: * @see brightness()
512: * @see altered()
513: * @see hexToRgb()
514: * @see rgbToHex()
515: * @see rgbToHsv()
516: * @see hsvToRgb()
517: * @see alter()
518: * @see getLuminance()
519: * @see getBrightness()
520: *
521: * @since Taurus 1
522: *
523: * @param array $args an associative array of args passed to icon()
524: *
525: * @return array possible altered array of args to load an icon
526: */
527: public function prepareIcon( $args ) {
528:
529: $args[ 'color' ] = $this->color( $args[ 'color' ] );
530:
531: if ( $args[ 'alter' ] === FALSE )
532: return $args;
533:
534: if ( $this->brightness( $args[ 'color' ] ) != $this->background )
535: return $args;
536:
537:
538: if ( ! is_bool( $args[ 'alter' ] ) ) {
539: if ( ! $args[ 'color' ] = $this->color( $args[ 'alter' ] ) )
540: $args[ 'color' ] = '000000';
541:
542: return $args;
543: }
544: $args[ 'color' ] = $this->altered( $args[ 'color' ] );
545: return $args;
546:
547: }
548:
549:
550: /*****************************************************************************
551: * BEGIN GETTER FUNCTIONS (well, we set when not already set)
552: ****************************************************************************/
553:
554: /**
555: * Returns the RGB of a color, and it sets it if necessary
556: *
557: * @see color()
558: * @see prepareIcon()
559: * @see checkHex()
560: * @see validateHex()
561: * @see normalizeHex()
562: * @see hsv()
563: * @see luminance()
564: * @see brightness()
565: * @see altered()
566: * @see hexToRgb()
567: * @see rgbToHex()
568: * @see rgbToHsv()
569: * @see hsvToRgb()
570: * @see alter()
571: * @see getLuminance()
572: * @see getBrightness()
573: *
574: * @since Taurus 1
575: *
576: * @param string $color hex color
577: *
578: * @return array associative array of RGB values
579: */
580: public function rgb( $color ) {
581:
582: if ( ! isset( $this->colors[ $color ][ 'rgb' ] ) ) {
583: $this->colors[ $color ][ 'rgb' ] = $this->hexToRgb( $color );
584: }
585: return $this->colors[ $color ][ 'rgb' ];
586:
587: }
588:
589:
590:
591: /**
592: * Returns the HSV of a color, and it sets it if necessary
593: *
594: * @link [https://en.wikipedia.org/wiki/HSL_and_HSV] [color forumulas]
595: *
596: * @see color()
597: * @see prepareIcon()
598: * @see checkHex()
599: * @see validateHex()
600: * @see normalizeHex()
601: * @see rgb()
602: * @see luminance()
603: * @see brightness()
604: * @see altered()
605: * @see hexToRgb()
606: * @see rgbToHex()
607: * @see rgbToHsv()
608: * @see hsvToRgb()
609: * @see alter()
610: * @see getLuminance()
611: * @see getBrightness()
612: *
613: * @since Taurus 1
614: *
615: * @param string $color hex color
616: *
617: * @return array associative array of HSV values
618: */
619: public function hsv( $color ) {
620:
621: if ( ! isset( $this->colors[ $color ][ 'hsv' ] ) ) {
622: if ( ! isset( $this->colors[ $color ][ 'rgb' ] ) ) {
623: $this->rgb( $color );
624: }
625: $this->colors[ $color ][ 'hsv' ] = $this->rgbToHsv( $this->rgb( $color ) );
626: }
627: return $this->colors[ $color ][ 'hsv' ];
628:
629: }
630:
631:
632: /**
633: * Retrieves the altered color of the original
634: *
635: * @see color()
636: * @see prepareIcon()
637: * @see checkHex()
638: * @see validateHex()
639: * @see normalizeHex()
640: * @see rgb()
641: * @see hsv()
642: * @see luminance()
643: * @see brightness()
644: * @see hexToRgb()
645: * @see rgbToHex()
646: * @see rgbToHsv()
647: * @see hsvToRgb()
648: * @see alter()
649: * @see getLuminance()
650: * @see getBrightness()
651: *
652: * @since Taurus 1
653: *
654: * @param string $color a hex color
655: *
656: * @return string a hex color (lighter or darker than the original)
657: */
658: public function altered( $color ) {
659:
660: if ( ! isset( $this->colors[ $color ][ 'altered' ] ) ) {
661: if ( ! $this->colors[ $color ][ 'altered' ] = $this->cached( $color ) )
662: $this->colors[ $color ][ 'altered' ] = $this->alter( $color );
663: }
664: return $this->colors[ $color ][ 'altered' ];
665:
666: }
667:
668:
669:
670: /**
671: * Retrieves the luminance of a hex color
672: *
673: * @see color()
674: * @see prepareIcon()
675: * @see checkHex()
676: * @see validateHex()
677: * @see normalizeHex()
678: * @see rgb()
679: * @see hsv()
680: * @see brightness()
681: * @see altered()
682: * @see hexToRgb()
683: * @see rgbToHex()
684: * @see rgbToHsv()
685: * @see hsvToRgb()
686: * @see alter()
687: * @see getLuminance()
688: * @see getBrightness()
689: *
690: * @since Taurus 1
691: *
692: * @param string $color a hex color
693: *
694: * @return float the luminance between 0 and 1
695: */
696: public function luminance( $color ) {
697:
698: if ( ! isset( $this->colors[ $color ][ 'luminance' ] ) ) {
699: $this->colors[ $color ][ 'luminance' ] = $this->getLuminance( $color );
700: }
701: return $this->colors[ $color ][ 'luminance' ];
702:
703: }
704:
705:
706: /**
707: * Queries whether and image is 'light' or 'dark'
708: *
709: * @see color()
710: * @see prepareIcon()
711: * @see checkHex()
712: * @see validateHex()
713: * @see normalizeHex()
714: * @see rgb()
715: * @see hsv()
716: * @see luminance()
717: * @see altered()
718: * @see hexToRgb()
719: * @see rgbToHex()
720: * @see rgbToHsv()
721: * @see hsvToRgb()
722: * @see alter()
723: * @see getLuminance()
724: * @see getBrightness()
725: *
726: * @since Taurus 1
727: *
728: * @param string $color a hex color
729: *
730: * @return string 'light' or 'dark'
731: */
732: private function brightness( $color ) {
733:
734: if ( ! isset( $this->colors[ $color ][ 'brightness' ] ) ) {
735: $this->colors[ $color ][ 'brightness' ] = $this->getBrightness( $color );
736: }
737: return $this->colors[ $color ][ 'brightness' ];
738:
739: }
740:
741:
742: /**
743: * Retrieves the cached result of an altered color
744: *
745: * @since Taurus 1
746: *
747: * @param string $color a hex color
748: *
749: * @return mixed the altered hex color if found, FALSE if not
750: */
751: private function cached( $color ) {
752: $cache = $this->cache . '/' . md5( $color );
753: $key = md5( $color );
754:
755: if ( file_exists( $cache ) ) {
756: return file_get_contents( $cache );
757: }
758: return FALSE;
759:
760: }
761:
762:
763: /**
764: * Caches an altered color in the color cache
765: *
766: * @since Taurus 1
767: *
768: * @param string $color a hex color
769: *
770: */
771: private function cache( $color ) {
772:
773: file_put_contents( $this->cache . '/' . md5( $color ), $this->colors[$color]['altered'] );
774:
775: }
776:
777: /*****************************************************************************
778: * END GETTER FUNCTIONS
779: ****************************************************************************/
780:
781: /*****************************************************************************
782: * BEGIN CONVERSION FUNCTIONS
783: ****************************************************************************/
784:
785: /**
786: * Converts a Hex color to an RGB Color
787: *
788: * @see color()
789: * @see prepareicon()
790: * @see checkhex()
791: * @see validatehex()
792: * @see normalizehex()
793: * @see rgb()
794: * @see hsv()
795: * @see luminance()
796: * @see brightness()
797: * @see altered()
798: * @see rgbtohex()
799: * @see rgbtohsv()
800: * @see hsvtorgb()
801: * @see alter()
802: * @see getluminance()
803: * @see getBrightness()
804: *
805: * @since Taurus 1
806: *
807: * @param string $color A hex color
808: * @return array An array of RGB values
809: */
810: public function hexToRgb( $hex ) {
811:
812: $r = hexdec( substr( $hex, 0, 2 ) );
813: $g = hexdec( substr( $hex, 2, 2 ) );
814: $b = hexdec( substr( $hex, 4, 2 ) );
815: return [ 'r' => $r, 'g' => $g, 'b' => $b ];
816:
817: }
818:
819:
820: /**
821: * Converts an RGB color to a Hex color
822: *
823: * @see color()
824: * @see prepareIcon()
825: * @see checkHex()
826: * @see validateHex()
827: * @see normalizeHex()
828: * @see rgb()
829: * @see hsv()
830: * @see luminance()
831: * @see brightness()
832: * @see altered()
833: * @see hexToRgb()
834: * @see rgbToHsv()
835: * @see hsvToRgb()
836: * @see alter()
837: * @see getLuminance()
838: * @see getBrightness()
839: *
840: * @since Taurus 1
841: *
842: * @param array $rgb an associative array of RGB values
843: *
844: * @return string a hex color
845: */
846: public function rgbToHex( $rgb ) {
847:
848: $hex .= str_pad( dechex( $rgb[ 'r' ] ), 2, '0', STR_PAD_LEFT );
849: $hex .= str_pad( dechex( $rgb[ 'g' ] ), 2, '0', STR_PAD_LEFT );
850: $hex .= str_pad( dechex( $rgb[ 'b' ] ), 2, '0', STR_PAD_LEFT );
851: return $hex;
852:
853: }
854:
855:
856: /**
857: * Converts RGB color to HSV color
858: *
859: * @link [https://en.wikipedia.org/wiki/HSL_and_HSV] [color forumulas]
860: *
861: * @see color()
862: * @see prepareIcon()
863: * @see checkHex()
864: * @see validateHex()
865: * @see normalizeHex()
866: * @see rgb()
867: * @see hsv()
868: * @see luminance()
869: * @see brightness()
870: * @see altered()
871: * @see hexToRgb()
872: * @see rgbToHex()
873: * @see hsvToRgb()
874: * @see alter()
875: * @see getLuminance()
876: * @see getBrightness()
877: *
878: * @since Taurus 1
879: *
880: * @param array $rgb associative array of rgb values
881: *
882: * @return array an associate array of hsv values
883: */
884: public function rgbToHsv( $rgb ) {
885:
886: $r = $rgb[ 'r' ];
887: $g = $rgb[ 'g' ];
888: $b = $rgb[ 'b' ];
889:
890:
891: $min = min( $r, $g, $b );
892: $max = max( $r, $g, $b );
893: $chroma = $max - $min;
894:
895: //if $chroma is 0, then s is 0 by definition, and h is undefined but 0 by convention.
896: if ( $chroma == 0 ) {
897: return [ 'h' => 0, 's' => 0, 'v' => $max / 255 ];
898: }
899:
900: if ( $r == $max ) {
901: $h = ( $g - $b ) / $chroma;
902:
903: if ( $h < 0.0 )
904: $h += 6.0;
905:
906: } else if ( $g == $max ) {
907: $h = ( ( $b - $r ) / $chroma ) + 2.0;
908: } else { //$b == $max
909: $h = ( ( $r - $g ) / $chroma ) + 4.0;
910: }
911:
912: $h *= 60.0;
913: $s = $chroma / $max;
914: $v = $max / 255;
915:
916: return [ 'h' => $h, 's' => $s, 'v' => $v ];
917:
918: }
919:
920: /**
921: * Convert HSV color to RGB
922: *
923: * @link [https://en.wikipedia.org/wiki/HSL_and_HSV] [color forumulas]
924: *
925: * @see color()
926: * @see prepareIcon()
927: * @see checkHex()
928: * @see validateHex()
929: * @see normalizeHex()
930: * @see rgb()
931: * @see hsv()
932: * @see luminance()
933: * @see brightness()
934: * @see altered()
935: * @see hexToRgb()
936: * @see rgbToHex()
937: * @see rgbToHsv()
938: * @see alter()
939: * @see getLuminance()
940: * @see getBrightness()
941: *
942: * @since Taurus 1
943: *
944: * @param array $hsv associative array of hsv values ( 0 <= h < 360, 0 <= s <= 1, 0 <= v <= 1)
945: *
946: * @return array An array of RGB values
947: */
948: public function hsvToRgb( $hsv ) {
949:
950: $h = $hsv[ 'h' ];
951: $s = $hsv[ 's' ];
952: $v = $hsv[ 'v' ];
953:
954: $chroma = $s * $v;
955: $h /= 60.0;
956: $x = $chroma * ( 1.0 - abs( ( fmod( $h, 2.0 ) ) - 1.0 ) );
957: $min = $v - $chroma;
958:
959: if ( $h < 1.0 ) {
960: $r = $chroma;
961: $g = $x;
962: } else if ( $h < 2.0 ) {
963: $r = $x;
964: $g = $chroma;
965: } else if ( $h < 3.0 ) {
966: $g = $chroma;
967: $b = $x;
968: } else if ( $h < 4.0 ) {
969: $g = $x;
970: $b = $chroma;
971: } else if ( $h < 5.0 ) {
972: $r = $x;
973: $b = $chroma;
974: } else if ( $h <= 6.0 ) {
975: $r = $chroma;
976: $b = $x;
977: }
978:
979: $r = round( ( $r + $min ) * 255 );
980: $g = round( ( $g + $min ) * 255 );
981: $b = round( ( $b + $min ) * 255 );
982:
983: return [ 'r' => $r, 'g' => $g, 'b' => $b ];
984:
985: }
986:
987:
988: /**
989: * Gets the luminance of a color between 0 and 1
990: *
991: * @link https://en.wikipedia.org/wiki/Luminance_(relative)
992: * @link https://en.wikipedia.org/wiki/Luma_(video)
993: * @link https://en.wikipedia.org/wiki/CCIR_601
994: *
995: * @see color()
996: * @see prepareIcon()
997: * @see checkHex()
998: * @see validateHex()
999: * @see normalizeHex()
1000: * @see rgb()
1001: * @see hsv()
1002: * @see luminance()
1003: * @see brightness()
1004: * @see altered()
1005: * @see hexToRgb()
1006: * @see rgbToHex()
1007: * @see rgbToHsv()
1008: * @see hsvToRgb()
1009: * @see alter()
1010: * @see getBrightness()
1011: *
1012: * @since Taurus 1
1013: *
1014: * @param mixed $color a hex color (string) or an associative array of RGB values
1015: *
1016: * @return float Luminance on a scale of 0 to 1
1017: */
1018: public function getLuminance( $color ) {
1019:
1020: if ( ! is_array( $color ) )
1021: $rgb = $this->rgb( $color );
1022: else
1023: $rgb = $color;
1024:
1025: return ( 0.299 * $rgb[ 'r' ] + 0.587 * $rgb[ 'g' ] + 0.114 * $rgb[ 'b' ] ) / 255;
1026:
1027: }
1028:
1029:
1030: /**
1031: * Determines whether a color is 'light' or 'dark'
1032: *
1033: * @see color()
1034: * @see prepareIcon()
1035: * @see checkHex()
1036: * @see validateHex()
1037: * @see normalizeHex()
1038: * @see rgb()
1039: * @see hsv()
1040: * @see luminance()
1041: * @see brightness()
1042: * @see altered()
1043: * @see hexToRgb()
1044: * @see rgbToHex()
1045: * @see rgbToHsv()
1046: * @see hsvToRgb()
1047: * @see alter()
1048: * @see getLuminance()
1049: *
1050: * @since Taurus 1
1051: *
1052: * @param string $color a hex color
1053: *
1054: * @return string either 'light' or 'dark'
1055: */
1056: public function getBrightness( $color ) {
1057:
1058: if ( isset( $this->colors[ $color ][ 'brightness' ] ) )
1059: return $this->colors[ $color ][ 'brightness' ];
1060:
1061: if ( $this->luminance( $color ) > .5 )
1062: $this->colors[ $color ][ 'brightness' ] = 'light';
1063: else
1064: $this->colors[ $color ][ 'brightness' ] = 'dark';
1065:
1066: return $this->colors[ $color ][ 'brightness' ];
1067:
1068: }
1069:
1070:
1071: /**
1072: * Either lightens or darkens an image
1073: *
1074: * The function starts with a hex color and converts it into
1075: * an RGB color space and then to an HSV color space. The V(alue)
1076: * in HSV is set between 0 (black) and 1 (white), which is a
1077: * measure of 'brightness' where 0.5 is neutral. Thus, we retain
1078: * the hue and saturation and keep the relative brightness of the
1079: * color by pushing it on the other side of neutral but at the
1080: * same distance from neutral. E.g.: 0.7 becomes 0.3; 0.12 becomes
1081: * 0.88; 0.0 becomes 1.0; and 0.5 becomes 0.5.
1082: *
1083: * @see color()
1084: * @see prepareIcon()
1085: * @see checkHex()
1086: * @see validateHex()
1087: * @see normalizeHex()
1088: * @see rgb()
1089: * @see hsv()
1090: * @see luminance()
1091: * @see brightness()
1092: * @see altered()
1093: * @see hexToRgb()
1094: * @see rgbToHex()
1095: * @see rgbToHsv()
1096: * @see hsvToRgb()
1097: * @see getLuminance()
1098: * @see getBrightness()
1099: *
1100: * @since Taurus 1
1101: *
1102: * @param string $color a hex color
1103: *
1104: * @return string a hex color
1105: */
1106: public function alter( $color ) {
1107:
1108: $hsv = $this->hsv( $color );
1109: $hsv[ 'v' ] = 1 - $hsv[ 'v' ];
1110: $rgb = $this->hsvToRgb( $hsv );
1111: $this->colors[ $color ][ 'altered' ] = $this->rgbToHex( $rgb );
1112: $altered = $this->color( $this->colors[ $color ][ 'altered' ] );
1113:
1114: $this->cache( $color ); // Cache the conversion
1115:
1116: return $this->colors[ $color ][ 'altered' ];
1117:
1118: }
1119:
1120: /*****************************************************************************
1121: * END CONVERSION FUNCTIONS
1122: ****************************************************************************/
1123:
1124: /*****************************************************************************
1125: * BEGIN VALIDATION / NORMALIZATION FUNCTIONS
1126: ****************************************************************************/
1127:
1128: /**
1129: * Checks to see if a color is a valid hex and normalizes the hex color
1130: *
1131: * @see color()
1132: * @see prepareIcon()
1133: * @see validateHex()
1134: * @see normalizeHex()
1135: * @see rgb()
1136: * @see hsv()
1137: * @see luminance()
1138: * @see brightness()
1139: * @see altered()
1140: * @see hexToRgb()
1141: * @see rgbToHex()
1142: * @see rgbToHsv()
1143: * @see hsvToRgb()
1144: * @see alter()
1145: * @see getLuminance()
1146: * @see getBrightness()
1147: *
1148: * @since Taurus 1
1149: *
1150: * @param string $color A hex color
1151: *
1152: * @return mixed FALSE on non-hex or hex color (normalized) to six characters and lowercased
1153: */
1154: public function checkHex( $hex ) {
1155:
1156: return $this->validateHex( $this->normalizeHex( $hex ) );
1157:
1158: }
1159:
1160:
1161: /**
1162: * Normalizes all hex colors to six, lowercase characters
1163: *
1164: * @see color()
1165: * @see prepareIcon()
1166: * @see checkHex()
1167: * @see validateHex()
1168: * @see rgb()
1169: * @see hsv()
1170: * @see luminance()
1171: * @see brightness()
1172: * @see altered()
1173: * @see hexToRgb()
1174: * @see rgbToHex()
1175: * @see rgbToHsv()
1176: * @see hsvToRgb()
1177: * @see alter()
1178: * @see getLuminance()
1179: * @see getBrightness()
1180: *
1181: * @since Taurus 1
1182: *
1183: * @param string $hex a hex color
1184: *
1185: * @return string a normalized hex color
1186: */
1187: public function normalizeHex( $hex ) {
1188:
1189: $hex = strtolower( str_replace( '#', '', $hex ) );
1190: if ( strlen( $hex ) == 3 )
1191: $hex = preg_replace( "/(.)(.)(.)/", "\\1\\1\\2\\2\\3\\3", $hex );
1192: return $hex;
1193:
1194: }
1195:
1196: /**
1197: * Validates a hex color
1198: *
1199: * @see color()
1200: * @see prepareIcon()
1201: * @see checkHex()
1202: * @see normalizeHex()
1203: * @see rgb()
1204: * @see hsv()
1205: * @see luminance()
1206: * @see brightness()
1207: * @see altered()
1208: * @see hexToRgb()
1209: * @see rgbToHex()
1210: * @see rgbToHsv()
1211: * @see hsvToRgb()
1212: * @see alter()
1213: * @see getLuminance()
1214: * @see getBrightness()
1215: *
1216: * @since Taurus 1
1217: *
1218: * @param string $hex a hex color
1219: *
1220: * @return mixed FALSE on failure, the hex value on success
1221: */
1222: public function validateHex( $hex ) {
1223:
1224: if ( strlen( $hex ) != 3 && strlen( $hex ) != 6 )
1225: return FALSE; // Not a valid hex value
1226: if ( ! preg_match( "/([0-9a-f]{3}|[0-9a-f]{6})/", $hex ) )
1227: return FALSE; // Not a valid hex value
1228: return $hex;
1229:
1230: }
1231:
1232:
1233: /*****************************************************************************
1234: * END VALIDATION / NORMALIZATION FUNCTIONS
1235: ****************************************************************************/
1236: /*****************************************************************************
1237: * END COLOR FUNCTIONS
1238: ****************************************************************************/
1239:
1240: }
1241:
1242: endif;
1243:
1244: