diff --git a/AppKit/CPColor.j b/AppKit/CPColor.j index 6f302cefd..e3fdd083e 100644 --- a/AppKit/CPColor.j +++ b/AppKit/CPColor.j @@ -156,11 +156,15 @@ var cachedBlackColor, } /*! - Creates a new color in HSB space. + Creates a new color based on the given HSB components. - @param hue the hue value - @param saturation the saturation value - @param brightness the brightness value + Note: earlier versions of this method took a hue component as degrees between 0-360, + and saturation and brightness components as percent between 0-100. This method has + now been corrected to take all components in the 0-1 range as in Cocoa. + + @param hue the hue component (0.0-1.0) + @param saturation the saturation component (0.0-1.0) + @param brightness the brightness component (0.0-1.0) @return the initialized color */ @@ -169,25 +173,61 @@ var cachedBlackColor, return [self colorWithHue:hue saturation:saturation brightness:brightness alpha:1.0]; } +/*! + Calibrated colors are not supported in Cappuccino. + + This method has the same result as [CPColor colorWithHue:saturation:brightness:alpha:]. +*/ ++ (CPColor)colorWithCalibratedHue:(float)hue saturation:(float)saturation brightness:(float)brightness alpha:(float)alpha +{ + return [self colorWithHue:hue saturation:saturation brightness:brightness alpha:alpha]; +} + +/*! + Creates a new color based on the given HSB components. + + Note: earlier versions of this method took a hue component as degrees between 0-360, + and saturation and brightness components as percent between 0-100. This method has + now been corrected to take all components in the 0-1 range as in Cocoa. + + @param hue the hue component (0.0-1.0) + @param saturation the saturation component (0.0-1.0) + @param brightness the brightness component (0.0-1.0) + @param alpha the opacity component (0.0-1.0) + + @return the initialized color +*/ + (CPColor)colorWithHue:(float)hue saturation:(float)saturation brightness:(float)brightness alpha:(float)alpha { + // Clamp values. + hue = MAX(MIN(hue, 1.0), 0.0); + saturation = MAX(MIN(saturation, 1.0), 0.0); + brightness = MAX(MIN(brightness, 1.0), 0.0); + if (saturation === 0.0) - return [CPColor colorWithCalibratedWhite:brightness / 100.0 alpha:alpha]; + return [CPColor colorWithCalibratedWhite:brightness alpha:alpha]; - var f = hue % 60, - p = (brightness * (100 - saturation)) / 10000, - q = (brightness * (6000 - saturation * f)) / 600000, - t = (brightness * (6000 - saturation * (60 -f))) / 600000, - b = brightness / 100.0; + var f = (hue * 360) % 60, + p = (brightness * (1 - saturation)), + q = (brightness * (60 - saturation * f)) / 60, + t = (brightness * (60 - saturation * (60 - f))) / 60, + b = brightness; - switch (FLOOR(hue / 60)) + switch (FLOOR(hue * 6)) { - case 0: return [CPColor colorWithCalibratedRed:b green:t blue:p alpha:alpha]; - case 1: return [CPColor colorWithCalibratedRed:q green:b blue:p alpha:alpha]; - case 2: return [CPColor colorWithCalibratedRed:p green:b blue:t alpha:alpha]; - case 3: return [CPColor colorWithCalibratedRed:p green:q blue:b alpha:alpha]; - case 4: return [CPColor colorWithCalibratedRed:t green:p blue:b alpha:alpha]; - case 5: return [CPColor colorWithCalibratedRed:b green:p blue:q alpha:alpha]; + case 0: + case 6: + return [CPColor colorWithCalibratedRed:b green:t blue:p alpha:alpha]; + case 1: + return [CPColor colorWithCalibratedRed:q green:b blue:p alpha:alpha]; + case 2: + return [CPColor colorWithCalibratedRed:p green:b blue:t alpha:alpha]; + case 3: + return [CPColor colorWithCalibratedRed:p green:q blue:b alpha:alpha]; + case 4: + return [CPColor colorWithCalibratedRed:t green:p blue:b alpha:alpha]; + case 5: + return [CPColor colorWithCalibratedRed:b green:p blue:q alpha:alpha]; } } @@ -575,6 +615,9 @@ var cachedBlackColor, /*! Returns an array with the HSB values for this color. + + The values are expressed as fractions between 0.0-1.0. + The index values are ordered as:
 Index   Component
@@ -621,12 +664,36 @@ var cachedBlackColor,
     }
 
     return [
-        ROUND(hue * 360.0),
-        ROUND(saturation * 100.0),
-        ROUND(brightness * 100.0)
+        hue,
+        saturation,
+        brightness
     ];
 }
 
+/*!
+    Returns the hue component, the H in HSB, of the receiver.
+*/
+- (float)hueComponent
+{
+    return [self hsbComponents][0];
+}
+
+/*!
+    Returns the saturation component, the S in HSB, of the receiver.
+*/
+- (float)saturationComponent
+{
+    return [self hsbComponents][1];
+}
+
+/*!
+    Returns the brightness component, the B in HSB, of the receiver.
+*/
+- (float)brightnessComponent
+{
+    return [self hsbComponents][2];
+}
+
 /*!
     Returns the CSS representation of this color. The color will
     be in one of the following forms:
diff --git a/AppKit/CPColorPicker.j b/AppKit/CPColorPicker.j
index 7a1d43961..14aed7793 100644
--- a/AppKit/CPColorPicker.j
+++ b/AppKit/CPColorPicker.j
@@ -158,12 +158,12 @@
         brightness = [_brightnessSlider floatValue];
 
     [_hueSaturationView setWheelBrightness:brightness / 100.0];
-    [_brightnessSlider setBackgroundColor:[CPColor colorWithHue:hue saturation:saturation brightness:100]];
+    [_brightnessSlider setBackgroundColor:[CPColor colorWithHue:hue / 360.0 saturation:saturation / 100.0 brightness:1]];
 
     var colorPanel = [self colorPanel],
         opacity = [colorPanel opacity];
 
-    _cachedColor = [CPColor colorWithHue:hue saturation:saturation brightness:brightness alpha:opacity];
+    _cachedColor = [CPColor colorWithHue:hue / 360.0 saturation:saturation / 100.0 brightness:brightness / 100.0 alpha:opacity];
 
     [[self colorPanel] setColor:_cachedColor];
 }
@@ -194,10 +194,10 @@
     var hsb = [newColor hsbComponents];
 
     [_hueSaturationView setPositionToColor:newColor];
-    [_brightnessSlider setFloatValue:hsb[2]];
-    [_hueSaturationView setWheelBrightness:hsb[2] / 100.0];
+    [_brightnessSlider setFloatValue:hsb[2] * 100.0];
+    [_hueSaturationView setWheelBrightness:hsb[2]];
 
-    [_brightnessSlider setBackgroundColor:[CPColor colorWithHue:hsb[0] saturation:hsb[1] brightness:100]];
+    [_brightnessSlider setBackgroundColor:[CPColor colorWithHue:hsb[0] saturation:hsb[1] brightness:1]];
 }
 
 - (CPImage)provideNewButtonImage
@@ -366,8 +366,8 @@
 {
     var hsb    = [aColor hsbComponents],
         bounds = [self bounds],
-        angle    = [self degreesToRadians:hsb[0]],
-        distance = (hsb[1] / 100.0) * _radius;
+        angle    = [self degreesToRadians:hsb[0] * 360.0],
+        distance = hsb[1] * _radius;
 
     [self setAngle:angle distance:distance];
 }
diff --git a/AppKit/CPSliderColorPicker.j b/AppKit/CPSliderColorPicker.j
index 38aa8427c..7e7f997b4 100644
--- a/AppKit/CPSliderColorPicker.j
+++ b/AppKit/CPSliderColorPicker.j
@@ -146,7 +146,7 @@
     [_hueLabel setTextColor:[CPColor blackColor]];
 
     _hueSlider = [[CPSlider alloc] initWithFrame:CGRectMake(15, 143, aFrame.size.width - 70, 20)];
-    [_hueSlider setMaxValue:359.0];
+    [_hueSlider setMaxValue:0.999];
     [_hueSlider setMinValue:0.0];
     [_hueSlider setTarget:self];
     [_hueSlider setAction:@selector(sliderChanged:)];
@@ -165,7 +165,7 @@
     [_saturationLabel setTextColor:[CPColor blackColor]];
 
     _saturationSlider = [[CPSlider alloc] initWithFrame:CGRectMake(15, 168, aFrame.size.width - 70, 20)];
-    [_saturationSlider setMaxValue:100.0];
+    [_saturationSlider setMaxValue:1.0];
     [_saturationSlider setMinValue:0.0];
     [_saturationSlider setTarget:self];
     [_saturationSlider setAction:@selector(sliderChanged:)];
@@ -184,7 +184,7 @@
     [_brightnessLabel setTextColor:[CPColor blackColor]];
 
     _brightnessSlider = [[CPSlider alloc] initWithFrame:CGRectMake(15, 194, aFrame.size.width - 70, 20)];
-    [_brightnessSlider setMaxValue:100.0];
+    [_brightnessSlider setMaxValue:1.0];
     [_brightnessSlider setMinValue:0.0];
     [_brightnessSlider setTarget:self];
     [_brightnessSlider setAction:@selector(sliderChanged:)];
@@ -282,6 +282,9 @@
 
 - (void)setColor:(CPColor)aColor
 {
+    if (!aColor)
+        [CPException raise:CPInvalidArgumentException reason:"aColor can't be nil"];
+
     [self updateRGBSliders:aColor];
     [self updateHSBSliders:aColor];
     [self updateHex:aColor];
@@ -313,9 +316,9 @@
 
 - (void)updateLabels
 {
-    [_hueValue setStringValue:ROUND([_hueSlider floatValue])];
-    [_saturationValue setStringValue:ROUND([_saturationSlider floatValue])];
-    [_brightnessValue setStringValue:ROUND([_brightnessSlider floatValue])];
+    [_hueValue setStringValue:ROUND([_hueSlider floatValue] * 360.0)];
+    [_saturationValue setStringValue:ROUND([_saturationSlider floatValue] * 100.0)];
+    [_brightnessValue setStringValue:ROUND([_brightnessSlider floatValue] * 100.0)];
 
     [_redValue setStringValue:ROUND([_redSlider floatValue] * 255)];
     [_greenValue setStringValue:ROUND([_greenSlider floatValue] * 255)];
@@ -363,15 +366,15 @@
                                    [self sliderChanged:_blueSlider];
                                    break;
 
-            case _hueValue:        [_hueSlider setFloatValue:MAX(MIN(ROUND(value), 360), 0)];
+            case _hueValue:        [_hueSlider setFloatValue:MAX(MIN(ROUND(value), 360) / 360.0, 0)];
                                    [self sliderChanged:_hueSlider];
                                    break;
 
-            case _saturationValue: [_saturationSlider setFloatValue:MAX(MIN(ROUND(value), 100), 0)];
+            case _saturationValue: [_saturationSlider setFloatValue:MAX(MIN(ROUND(value), 100) / 100.0, 0)];
                                    [self sliderChanged:_saturationSlider];
                                    break;
 
-            case _brightnessValue: [_brightnessSlider setFloatValue:MAX(MIN(ROUND(value), 100), 0)];
+            case _brightnessValue: [_brightnessSlider setFloatValue:MAX(MIN(ROUND(value), 100) / 100.0, 0)];
                                    [self sliderChanged:_brightnessSlider];
                                    break;
         }
diff --git a/Tests/AppKit/CPColorTest.j b/Tests/AppKit/CPColorTest.j
index a0ee065f1..b357a6805 100644
--- a/Tests/AppKit/CPColorTest.j
+++ b/Tests/AppKit/CPColorTest.j
@@ -8,7 +8,7 @@
 {
     var colors = ['000000', '0099CC', '7E8EAB', 'FFFFFF'];
     for (var i = 0; i < colors.length; ++i)
-        [self assert: colors[i] equals: [[CPColor colorWithHexString: colors[i]] hexString]];
+        [self assert:colors[i] equals:[[CPColor colorWithHexString:colors[i]] hexString]];
 }
 
 - (void)testColorWithCSSString
@@ -21,6 +21,33 @@
     [self assert:128 equals:ROUND([rgbaColour alphaComponent] * 255) message:"alpha component"];
 }
 
+- (void)testColorWithHue_saturation_brightness_
+{
+    var tests = [
+            [[0, 0, 0], [0, 0, 0]],
+            [[0, 0, 1], [1, 1, 1]],
+            [[0, 1, 1], [1, 0, 0]],
+            [[0.75, 1, 1], [0.5, 0, 1]],
+            [[0.5, 0.5, 0.5], [0.25, 0.5, 0.5]],
+            [[0.9, 0.8, 0.7], [0.7, 0.14, 0.476]]
+        ];
+
+    for (var i = 0; i < tests.length; i++)
+    {
+        var test = tests[i],
+            input = test[0],
+            expected = test[1],
+            c = [CPColor colorWithCalibratedHue:input[0] saturation:input[1] brightness:input[2] alpha:0.5];
+        [self assert:expected equals:[Math.round([c redComponent] * 1000) / 1000, Math.round([c greenComponent] * 1000) / 1000, Math.round([c blueComponent] * 1000) / 1000] message:@"hue: " + input[0] + " saturation: " + input[1] + " brightness: " + input[2]];
+    }
+
+    var hsb = [[CPColor colorWithHue:0.9 saturation:0.8 brightness:0.7] hsbComponents];
+    [self assert:[0.9, 0.8, 0.7] equals:[Math.round(hsb[0] * 10) / 10, Math.round(hsb[1] * 10) / 10, Math.round(hsb[2] * 10) / 10]];
+
+    hsb = [[CPColor colorWithHue:0.999 saturation:0.8 brightness:0.7] hsbComponents];
+    [self assert:[0.999, 0.8, 0.7] equals:[Math.round(hsb[0] * 1000) / 1000, Math.round(hsb[1] * 10) / 10, Math.round(hsb[2] * 10) / 10]];
+}
+
 - (void)testIsEqual_
 {
     // Based on https://gist.github.com/e06f749362cb1166439f by spakanati.