2 * Copyright 2005 Sabre Airline Solutions
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
5 * file except in compliance with the License. You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
11 * either express or implied. See the License for the specific language governing permissions
12 * and limitations under the License.
16 Rico.Color = Class.create(
17 /** @lends Rico.Color# */
20 * @class Methods to manipulate color values.
22 * @param red integer (0-255)
23 * @param green integer (0-255)
24 * @param blue integer (0-255)
26 initialize: function(red, green, blue) {
27 this.rgb = { r: red, g : green, b : blue };
34 setGreen: function(g) {
38 setBlue: function(b) {
44 // get an HSB model, and set the new hue...
45 var hsb = this.asHSB();
48 // convert back to RGB...
49 this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
52 setSaturation: function(s) {
53 // get an HSB model, and set the new hue...
54 var hsb = this.asHSB();
57 // convert back to RGB and set values...
58 this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
61 setBrightness: function(b) {
62 // get an HSB model, and set the new hue...
63 var hsb = this.asHSB();
66 // convert back to RGB and set values...
67 this.rgb = Rico.Color.HSBtoRGB( hsb.h, hsb.s, hsb.b );
70 darken: function(percent) {
71 var hsb = this.asHSB();
72 this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.max(hsb.b - percent,0));
75 brighten: function(percent) {
76 var hsb = this.asHSB();
77 this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.min(hsb.b + percent,1));
80 blend: function(other) {
81 this.rgb.r = Math.floor((this.rgb.r + other.rgb.r)/2);
82 this.rgb.g = Math.floor((this.rgb.g + other.rgb.g)/2);
83 this.rgb.b = Math.floor((this.rgb.b + other.rgb.b)/2);
86 isBright: function() {
87 var hsb = this.asHSB();
88 return this.asHSB().b > 0.5;
92 return ! this.isBright();
96 return "rgb(" + this.rgb.r + "," + this.rgb.g + "," + this.rgb.b + ")";
100 return "#" + this.rgb.r.toColorPart() + this.rgb.g.toColorPart() + this.rgb.b.toColorPart();
104 return Rico.Color.RGBtoHSB(this.rgb.r, this.rgb.g, this.rgb.b);
107 toString: function() {
114 * Factory method for creating a color from an RGB string
115 * @param hexCode a 3 or 6 digit hex string, optionally preceded by a # symbol
116 * @returns a Rico.Color object
118 Rico.Color.createFromHex = function(hexCode) {
119 if(hexCode.length==4) {
120 var shortHexCode = hexCode;
123 hexCode += (shortHexCode.charAt(i) + shortHexCode.charAt(i));
125 if ( hexCode.indexOf('#') == 0 )
126 hexCode = hexCode.substring(1);
127 if (!hexCode.match(/^[0-9A-Fa-f]{6}$/)) return null;
128 var red = hexCode.substring(0,2);
129 var green = hexCode.substring(2,4);
130 var blue = hexCode.substring(4,6);
131 return new Rico.Color( parseInt(red,16), parseInt(green,16), parseInt(blue,16) );
135 * Retrieves the background color of an HTML element
136 * @param elem the DOM element whose background color should be retreived
137 * @returns a Rico.Color object
139 Rico.Color.createColorFromBackground = function(elem) {
141 if (!elem.style) return new Rico.Color(255,255,255);
142 var actualColor = Element.getStyle(elem, "background-color");
144 // if color is tranparent, check parent
145 // Safari returns "rgba(0, 0, 0, 0)", which means transparent
146 if ( actualColor.match(/^(transparent|rgba\(0,\s*0,\s*0,\s*0\))$/i) && elem.parentNode )
147 return Rico.Color.createColorFromBackground(elem.parentNode);
149 if (actualColor == null) return new Rico.Color(255,255,255);
151 if ( actualColor.indexOf("rgb(") == 0 ) {
152 var colors = actualColor.substring(4, actualColor.length - 1 );
153 var colorArray = colors.split(",");
154 return new Rico.Color( parseInt( colorArray[0],10 ),
155 parseInt( colorArray[1],10 ),
156 parseInt( colorArray[2],10 ) );
159 else if ( actualColor.indexOf("#") == 0 ) {
160 return Rico.Color.createFromHex(actualColor);
163 return new Rico.Color(255,255,255);
167 * Converts hue/saturation/brightness to RGB
168 * @returns a 3-element object: r=red, g=green, b=blue.
170 Rico.Color.HSBtoRGB = function(hue, saturation, brightness) {
176 if (saturation == 0) {
177 red = parseInt(brightness * 255.0 + 0.5,10);
182 var h = (hue - Math.floor(hue)) * 6.0;
183 var f = h - Math.floor(h);
184 var p = brightness * (1.0 - saturation);
185 var q = brightness * (1.0 - saturation * f);
186 var t = brightness * (1.0 - (saturation * (1.0 - f)));
188 switch (parseInt(h,10)) {
190 red = (brightness * 255.0 + 0.5);
191 green = (t * 255.0 + 0.5);
192 blue = (p * 255.0 + 0.5);
195 red = (q * 255.0 + 0.5);
196 green = (brightness * 255.0 + 0.5);
197 blue = (p * 255.0 + 0.5);
200 red = (p * 255.0 + 0.5);
201 green = (brightness * 255.0 + 0.5);
202 blue = (t * 255.0 + 0.5);
205 red = (p * 255.0 + 0.5);
206 green = (q * 255.0 + 0.5);
207 blue = (brightness * 255.0 + 0.5);
210 red = (t * 255.0 + 0.5);
211 green = (p * 255.0 + 0.5);
212 blue = (brightness * 255.0 + 0.5);
215 red = (brightness * 255.0 + 0.5);
216 green = (p * 255.0 + 0.5);
217 blue = (q * 255.0 + 0.5);
222 return { r : parseInt(red,10), g : parseInt(green,10) , b : parseInt(blue,10) };
226 * Converts RGB value to hue/saturation/brightness
227 * @param r integer (0-255)
228 * @param g integer (0-255)
229 * @param b integer (0-255)
230 * @returns a 3-element object: h=hue, s=saturation, b=brightness.
231 * (unlike some HSB documentation which states hue should be a value 0-360, this routine returns hue values from 0 to 1.0)
233 Rico.Color.RGBtoHSB = function(r, g, b) {
239 var cmax = (r > g) ? r : g;
243 var cmin = (r < g) ? r : g;
247 brightness = cmax / 255.0;
249 saturation = (cmax - cmin)/cmax;
256 var redc = (cmax - r)/(cmax - cmin);
257 var greenc = (cmax - g)/(cmax - cmin);
258 var bluec = (cmax - b)/(cmax - cmin);
261 hue = bluec - greenc;
263 hue = 2.0 + redc - bluec;
265 hue = 4.0 + greenc - redc;
272 return { h : hue, s : saturation, b : brightness };
276 * Creates a vertical gradient inside an element
277 * @param e element where gradient will be created
278 * @param startColor starting color, either a Rico.Color object or 6-character RGB string
279 * @param endColor ending color, either a Rico.Color object or 6-character RGB string
281 Rico.Color.createGradientV = function(e,startColor,endColor) {
282 var c1=typeof(startColor)=='string' ? Rico.Color.createFromHex(startColor) : startColor;
283 var c2=typeof(endColor)=='string' ? Rico.Color.createFromHex(endColor) : endColor;
284 if (Prototype.Browser.IE) {
285 e.style.filter = "progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=\"" + c1.asHex() + "\",EndColorStr=\"" + c2.asHex() + "\")";
287 var colorArray = Rico.Color.createColorPath(c1,c2,Math.min(e.offsetHeight,50));
288 var remh=e.offsetHeight,l=colorArray.length;
289 var div=Rico.Color.createGradientContainer();
290 var tmpDOM = document.createDocumentFragment();
291 for(var g,h,p=0;p<colorArray.length;p++) {
292 h = Math.round(remh/l) || 1;
293 g = document.createElement("div");
294 g.setAttribute("style","height:" + h + "px;width:100%;background-color:" + colorArray[p].asRGB() + ";");
295 tmpDOM.appendChild(g);
299 div.appendChild(tmpDOM);
306 * Creates a horizontal gradient inside an element
307 * @param e element where gradient will be created
308 * @param startColor starting color, either a Rico.Color object or 6-character RGB string
309 * @param endColor ending color, either a Rico.Color object or 6-character RGB string
311 Rico.Color.createGradientH = function(e,startColor,endColor) {
312 var c1=typeof(startColor)=='string' ? Rico.Color.createFromHex(startColor) : startColor;
313 var c2=typeof(endColor)=='string' ? Rico.Color.createFromHex(endColor) : endColor;
314 if (Prototype.Browser.IE) {
315 e.style.filter = "progid:DXImageTransform.Microsoft.Gradient(GradientType=1,StartColorStr=\"" + c1.asHex() + "\",EndColorStr=\"" + c2.asHex() + "\")";
317 var colorArray = Rico.Color.createColorPath(c1,c2,Math.min(e.offsetWidth,50));
318 var x=0,remw=e.offsetWidth,l=colorArray.length;
319 var div=Rico.Color.createGradientContainer();
320 var tmpDOM = document.createDocumentFragment();
321 for(var p=0;p<colorArray.length;p++) {
322 var w=Math.round(remw/l) || 1;
323 var g = document.createElement("div");
324 g.setAttribute("style","position:absolute;top:0px;left:" + x + "px;height:100%;width:" + w + "px;background-color:" + colorArray[p].asRGB() + ";");
325 tmpDOM.appendChild(g);
330 div.appendChild(tmpDOM);
336 /** creates containing element for gradient methods */
337 Rico.Color.createGradientContainer = function() {
338 var div=document.createElement('div');
339 div.style.height='100%';
340 div.style.width='100%';
341 div.style.position='absolute';
343 div.style.left='0px';
348 /** calculates intermediate color values for gradient methods */
349 Rico.Color.createColorPath = function(color1,color2,slices) {
351 var colorPercent = 1.0;
352 var delta=1.0/slices;
354 colorPath[colorPath.length]=Rico.Color.setColorHue(color1,colorPercent,color2);
356 } while(colorPercent>0);
360 Rico.Color.setColorHue = function(originColor,opacityPercent,maskRGB) {
361 return new Rico.Color(
362 Math.round(originColor.rgb.r*opacityPercent + maskRGB.rgb.r*(1.0-opacityPercent)),
363 Math.round(originColor.rgb.g*opacityPercent + maskRGB.rgb.g*(1.0-opacityPercent)),
364 Math.round(originColor.rgb.b*opacityPercent + maskRGB.rgb.b*(1.0-opacityPercent))
374 round: function(e, options) {
376 this._setOptions(options);
377 var color = this.options.color == "fromElement" ? this._background(e) : this.options.color;
378 var bgColor = this.options.bgColor == "fromParent" ? this._background(e.parentNode) : this.options.bgColor;
379 if (Prototype.Browser.Gecko && this.options.useMoz && !this.options.border && Element.getStyle(e,'background-image')=='none')
380 this._roundCornersGecko(e, color);
381 else if (typeof(Element.getStyle(e,'-webkit-border-radius'))=='string' && !this.options.border)
382 this._roundCornersWebKit(e, color);
384 this._roundCornersImpl(e, color, bgColor);
387 _roundCornersImpl: function(e, color, bgColor) {
388 this.options.numSlices = this.options.compact ? 2 : 4;
389 this.borderColor = this._borderColor(color,bgColor);
390 if(this.options.border)
391 this._renderBorder(e,bgColor);
392 if(this._isTopRounded())
393 this._roundTopCorners(e,color,bgColor);
394 if(this._isBottomRounded())
395 this._roundBottomCorners(e,color,bgColor);
398 _roundCornersGecko: function(e, color) {
399 var radius=this.options.compact ? '4px' : '8px';
400 if (this._hasString(this.options.corners, "all"))
401 Element.setStyle(e, {MozBorderRadius:radius}, true);
403 if (this._hasString(this.options.corners, "top", "tl")) Element.setStyle(e, {MozBorderRadiusTopleft:radius}, true);
404 if (this._hasString(this.options.corners, "top", "tr")) Element.setStyle(e, {MozBorderRadiusTopright:radius}, true);
405 if (this._hasString(this.options.corners, "bottom", "bl")) Element.setStyle(e, {MozBorderRadiusBottomleft:radius}, true);
406 if (this._hasString(this.options.corners, "bottom", "br")) Element.setStyle(e, {MozBorderRadiusBottomright:radius}, true);
410 _roundCornersWebKit: function(e, color) {
411 var radius=this.options.compact ? '4px' : '8px';
412 if (this._hasString(this.options.corners, "all"))
413 Element.setStyle(e, {WebkitBorderRadius:radius}, true);
415 if (this._hasString(this.options.corners, "top", "tl")) Element.setStyle(e, {WebkitBorderTopLeftRadius:radius}, true);
416 if (this._hasString(this.options.corners, "top", "tr")) Element.setStyle(e, {WebkitBorderTopRightRadius:radius}, true);
417 if (this._hasString(this.options.corners, "bottom", "bl")) Element.setStyle(e, {WebkitBorderBottomLeftRadius:radius}, true);
418 if (this._hasString(this.options.corners, "bottom", "br")) Element.setStyle(e, {WebkitBorderBottomRightRadius:radius}, true);
422 _renderBorder: function(el,bgColor) {
423 var wrapper=RicoUtil.wrapChildren(el);
424 var borderValue = "1px solid " + this._borderColor(bgColor);
425 Element.setStyle(wrapper,{
426 borderLeft: borderValue,
427 borderRight: borderValue,
430 padding: '1px', // prevents margin collapse
435 _roundTopCorners: function(el, color, bgColor) {
436 var corner = this._createCorner(bgColor);
437 for(var i=0 ; i < this.options.numSlices ; i++ )
438 corner.appendChild(this._createCornerSlice(color,bgColor,i,"top"));
439 el.style.paddingTop = '0px';
440 el.insertBefore(corner,el.firstChild);
443 _roundBottomCorners: function(el, color, bgColor) {
444 var corner = this._createCorner(bgColor);
445 for(var i=(this.options.numSlices-1) ; i >= 0 ; i-- )
446 corner.appendChild(this._createCornerSlice(color,bgColor,i,"bottom"));
447 el.style.paddingBottom = 0;
448 el.appendChild(corner);
451 _createCorner: function(bgColor) {
452 var corner = document.createElement("div");
453 corner.style.backgroundColor = (this._isTransparent() ? "transparent" : bgColor);
457 _createCornerSlice: function(color,bgColor, n, position) {
458 var slice = document.createElement("span");
460 var inStyle = slice.style;
461 inStyle.backgroundColor = color;
462 inStyle.display = "block";
463 inStyle.height = "1px";
464 inStyle.overflow = "hidden";
465 inStyle.fontSize = "1px";
467 if ( this.options.border && n == 0 ) {
468 inStyle.borderTopStyle = "solid";
469 inStyle.borderTopWidth = "1px";
470 inStyle.borderLeftWidth = "0px";
471 inStyle.borderRightWidth = "0px";
472 inStyle.borderBottomWidth = "0px";
473 inStyle.height = "0px"; // assumes css compliant box model
474 inStyle.borderColor = this.borderColor;
476 else if(this.borderColor) {
477 inStyle.borderColor = this.borderColor;
478 inStyle.borderStyle = "solid";
479 inStyle.borderWidth = "0px 1px";
482 if ( !this.options.compact && (n == (this.options.numSlices-1)) )
483 inStyle.height = "2px";
485 this._setMargin(slice, n, position);
486 this._setBorder(slice, n, position);
490 _setOptions: function(options) {
493 color : "fromElement",
494 bgColor : "fromParent",
498 useMoz : true // use native Gecko corners
500 Object.extend(this.options, options || {});
501 if (this._isTransparent()) this.options.blend = false;
504 _whichSideTop: function() {
505 if ( this._hasString(this.options.corners, "all", "top") )
508 if ( this.options.corners.indexOf("tl") >= 0 && this.options.corners.indexOf("tr") >= 0 )
511 if (this.options.corners.indexOf("tl") >= 0)
513 else if (this.options.corners.indexOf("tr") >= 0)
518 _whichSideBottom: function() {
519 if ( this._hasString(this.options.corners, "all", "bottom") )
522 if ( this.options.corners.indexOf("bl")>=0 && this.options.corners.indexOf("br")>=0 )
525 if(this.options.corners.indexOf("bl") >=0)
527 else if(this.options.corners.indexOf("br")>=0)
532 _borderColor : function(color,bgColor) {
533 if (color == "transparent") return bgColor;
534 if (this.options.border) return this.options.border;
535 if (!this.options.blend) return '';
536 var cc1 = Rico.Color.createFromHex(bgColor);
537 var cc2 = Rico.Color.createFromHex(color);
538 if (cc1==null || cc2==null) {
539 this.options.blend=false;
547 _setMargin: function(el, n, corners) {
548 var marginSize = this._marginSize(n);
549 var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();
551 if ( whichSide == "left" ) {
552 el.style.marginLeft = marginSize + "px"; el.style.marginRight = "0px";
554 else if ( whichSide == "right" ) {
555 el.style.marginRight = marginSize + "px"; el.style.marginLeft = "0px";
558 el.style.marginLeft = marginSize + "px"; el.style.marginRight = marginSize + "px";
562 _setBorder: function(el,n,corners) {
563 var borderSize = this._borderSize(n);
564 var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();
565 if ( whichSide == "left" ) {
566 el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = "0px";
568 else if ( whichSide == "right" ) {
569 el.style.borderRightWidth = borderSize + "px"; el.style.borderLeftWidth = "0px";
572 el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = borderSize + "px";
574 if (this.options.border) {
575 el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = borderSize + "px";
579 _marginSize: function(n) {
580 if ( this._isTransparent() )
583 var marginSizes = [ 5, 3, 2, 1 ];
584 var blendedMarginSizes = [ 3, 2, 1, 0 ];
585 var compactMarginSizes = [ 2, 1 ];
586 var smBlendedMarginSizes = [ 1, 0 ];
588 if ( this.options.compact && this.options.blend )
589 return smBlendedMarginSizes[n];
590 else if ( this.options.compact )
591 return compactMarginSizes[n];
592 else if ( this.options.blend )
593 return blendedMarginSizes[n];
595 return marginSizes[n];
598 _borderSize: function(n) {
599 var transparentBorderSizes = [ 5, 3, 2, 1 ];
600 var blendedBorderSizes = [ 2, 1, 1, 1 ];
601 var compactBorderSizes = [ 1, 0 ];
602 var actualBorderSizes = [ 0, 2, 0, 0 ];
604 if ( this.options.compact && (this.options.blend || this._isTransparent()) )
606 else if ( this.options.compact )
607 return compactBorderSizes[n];
608 else if ( this.options.blend )
609 return blendedBorderSizes[n];
610 else if ( this.options.border )
611 return actualBorderSizes[n];
612 else if ( this._isTransparent() )
613 return transparentBorderSizes[n];
617 _background: function(elem) {
619 var actualColor = Element.getStyle(elem, "background-color");
621 // if color is tranparent, check parent
622 // Safari returns "rgba(0, 0, 0, 0)", which means transparent
623 if ( actualColor.match(/^(transparent|rgba\(0,\s*0,\s*0,\s*0\))$/i) && elem.parentNode )
624 return this._background(elem.parentNode);
626 return actualColor == null ? "#ffffff" : actualColor;
632 _hasString: function(str) {
633 for(var i=1 ; i<arguments.length ; i++) {
634 if (str.indexOf(arguments[i]) >= 0) return true;
639 _isTransparent: function() { return this.options.color == "transparent"; },
640 _isTopRounded: function() { return this._hasString(this.options.corners, "all", "top", "tl", "tr"); },
641 _isBottomRounded: function() { return this._hasString(this.options.corners, "all", "bottom", "bl", "br"); },
642 _hasSingleTextChild: function(el) { return el.childNodes.length == 1 && el.childNodes[0].nodeType == 3; }
645 Rico.includeLoaded('ricoStyles.js');