Changeset 222102 in webkit


Ignore:
Timestamp:
Sep 15, 2017 12:50:40 PM (7 years ago)
Author:
Matt Baker
Message:

Web Inspector: Canvas: recording parameters that include colors should show an InlineSwatch (2D canvas)
https://bugs.webkit.org/show_bug.cgi?id=176822
<rdar://problem/34402170>

Reviewed by Devin Rousso.

Source/WebInspectorUI:

Show inline swatches in the canvas recording action and state sidebars.

  • UserInterface/Models/Color.js:

Added helpers for dealing with CMYK and normalized RGB. The latter is
for RGB components scaled to the range [0, 1]. Also improved handling
for 8-bit channel values.
(WI.Color.rgb2hsv):
(WI.Color.cmyk2rgb):
(WI.Color.normalized2rgb):
(WI.Color._eightBitChannel):
(WI.Color.prototype._toRGBString):
(WI.Color.prototype._toRGBAString):
(WI.Color.prototype._componentToHexValue):
(WI.Color.prototype._rgbToHSL):
(WI.Color.prototype._componentToNumber): Deleted.
Replaced by _eightBitChannel.

  • UserInterface/Models/RecordingAction.js:

(WI.RecordingAction):
(WI.RecordingAction.prototype.getColorParameters):
Get a subset of parameters that describe a color. This can be an array
containing one value (e.g. fillStyle), or multiple values, as is the
case with non-standard API functions that describe color using multiple
parameters (e.g. setFillColor).

  • UserInterface/Views/InlineSwatch.css:

(.inline-swatch:not(.read-only):hover > span):
(.inline-swatch:hover > span): Deleted.

  • UserInterface/Views/InlineSwatch.js:

(WI.InlineSwatch):
Read-only colors shouldn't show a context menu or hover effects.

  • UserInterface/Views/RecordingActionTreeElement.css:

(.tree-outline:matches(:focus, .force-focus) .item.action > .titles .parameters > .inline-swatch):

  • UserInterface/Views/RecordingActionTreeElement.js:

(WI.RecordingActionTreeElement._generateDOM.createParameterElement):
(WI.RecordingActionTreeElement._generateDOM):
(WI.RecordingActionTreeElement._createSwatchForColorParameters):

  • UserInterface/Views/RecordingStateDetailsSidebarPanel.css:

(.sidebar > .panel.details.recording-state > .content > .data-grid .inline-swatch):

  • UserInterface/Views/RecordingStateDetailsSidebarPanel.js:

(WI.RecordingStateDetailsSidebarPanel.prototype._generateDetailsCanvas2D.isColorProperty):
(WI.RecordingStateDetailsSidebarPanel.prototype._generateDetailsCanvas2D.createInlineSwatch):
(WI.RecordingStateDetailsSidebarPanel.prototype._generateDetailsCanvas2D):
(WI.RecordingStateDetailsSidebarPanel):

LayoutTests:

Add tests for color space conversions.

  • inspector/model/color-expected.txt:
  • inspector/model/color.html:
Location:
trunk
Files:
12 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r222101 r222102  
     12017-09-15  Matt Baker  <mattbaker@apple.com>
     2
     3        Web Inspector: Canvas: recording parameters that include colors should show an InlineSwatch (2D canvas)
     4        https://bugs.webkit.org/show_bug.cgi?id=176822
     5        <rdar://problem/34402170>
     6
     7        Reviewed by Devin Rousso.
     8
     9        Add tests for color space conversions.
     10
     11        * inspector/model/color-expected.txt:
     12        * inspector/model/color.html:
     13
    1142017-09-15  Brent Fulgham  <bfulgham@apple.com>
    215
  • trunk/LayoutTests/inspector/model/color-expected.txt

    r222055 r222102  
    154154PASS: Color as 'HSLA' should be 'hsla(201, 100%, 70%, 0.5)'
    155155
     156-- Running test case: WI.Color.rgb2hsv
     157PASS: Should convert [0,0,0] to [0,0,0].
     158PASS: Should convert [255,255,255] to [0,0,1].
     159PASS: Should convert [255,0,0] to [0,1,1].
     160PASS: Should convert [0,255,0] to [120,1,1].
     161PASS: Should convert [0,0,255] to [240,1,1].
     162PASS: Should convert [-1,-1,-1] to [0,0,0].
     163PASS: Should convert [256,256,256] to [0,0,1].
     164PASS: Should convert [254.9,0,0] to [0,1,1].
     165
     166-- Running test case: WI.Color.cmyk2rgb
     167PASS: Should convert [0,0,0,1] to [0,0,0].
     168PASS: Should convert [1,0,0,0] to [0,255,255].
     169PASS: Should convert [0,1,0,0] to [255,0,255].
     170PASS: Should convert [0,0,1,0] to [255,255,0].
     171PASS: Should convert [0,0,0,0] to [255,255,255].
     172PASS: Should convert [2,0,0,0] to [0,255,255].
     173PASS: Should convert [-1,0,0,0] to [255,255,255].
     174
     175-- Running test case: WI.Color.normalized2rgb
     176PASS: Should convert [0,0,0] to [0,0,0].
     177PASS: Should convert [1,1,1] to [255,255,255].
     178PASS: Should convert [0.24,0.25,0.26] to [61,64,66].
     179PASS: Should convert [2,0,0] to [255,0,0].
     180PASS: Should convert [-1,0,0] to [0,0,0].
     181
  • trunk/LayoutTests/inspector/model/color.html

    r222055 r222102  
    299299    });
    300300
     301    function testColorConversion(func, value, expected) {
     302        let actual = func(...value);
     303        InspectorTest.expectShallowEqual(actual, expected, `Should convert ${JSON.stringify(value)} to ${JSON.stringify(expected)}.`);
     304    }
     305
     306    suite.addTestCase({
     307        name: "WI.Color.rgb2hsv",
     308        description: "Test conversion from RGB to HSV.",
     309        test(resolve, reject) {
     310            testColorConversion(WI.Color.rgb2hsv, [0, 0, 0], [0, 0, 0]);
     311            testColorConversion(WI.Color.rgb2hsv, [255, 255, 255], [0, 0, 1]);
     312            testColorConversion(WI.Color.rgb2hsv, [255, 0, 0], [0, 1, 1]);
     313            testColorConversion(WI.Color.rgb2hsv, [0, 255, 0], [120, 1, 1]);
     314            testColorConversion(WI.Color.rgb2hsv, [0, 0, 255], [240, 1, 1]);
     315
     316            // Out-of-bounds and floating point inputs.
     317            testColorConversion(WI.Color.rgb2hsv, [-1, -1, -1], [0, 0, 0]);
     318            testColorConversion(WI.Color.rgb2hsv, [256, 256, 256], [0, 0, 1]);
     319            testColorConversion(WI.Color.rgb2hsv, [254.9, 0, 0], [0, 1, 1]);
     320
     321            resolve();
     322        }
     323    });
     324
     325    suite.addTestCase({
     326        name: "WI.Color.cmyk2rgb",
     327        description: "Test conversion from CMYK to RGB.",
     328        test(resolve, reject) {
     329            testColorConversion(WI.Color.cmyk2rgb, [0, 0, 0, 1], [0, 0, 0]);
     330            testColorConversion(WI.Color.cmyk2rgb, [1, 0, 0, 0], [0, 255, 255]);
     331            testColorConversion(WI.Color.cmyk2rgb, [0, 1, 0, 0], [255, 0, 255]);
     332            testColorConversion(WI.Color.cmyk2rgb, [0, 0, 1, 0], [255, 255, 0]);
     333            testColorConversion(WI.Color.cmyk2rgb, [0, 0, 0, 0], [255, 255, 255]);
     334
     335            // Out-of-bounds inputs.
     336            testColorConversion(WI.Color.cmyk2rgb, [2, 0, 0, 0], [0, 255, 255]);
     337            testColorConversion(WI.Color.cmyk2rgb, [-1, 0, 0, 0], [255, 255, 255]);
     338
     339            resolve();
     340        }
     341    });
     342
     343    suite.addTestCase({
     344        name: "WI.Color.normalized2rgb",
     345        description: "Test conversion from normalized RGB to RGB.",
     346        test(resolve, reject) {
     347            testColorConversion(WI.Color.normalized2rgb, [0, 0, 0], [0, 0, 0]);
     348            testColorConversion(WI.Color.normalized2rgb, [1, 1, 1], [255, 255, 255]);
     349            testColorConversion(WI.Color.normalized2rgb, [0.24, 0.25, 0.26], [61, 64, 66]); // Values chosen to test round up/down behavior.
     350
     351            // Out-of-bounds inputs.
     352            testColorConversion(WI.Color.normalized2rgb, [2, 0, 0], [255, 0, 0]);
     353            testColorConversion(WI.Color.normalized2rgb, [-1, 0, 0], [0, 0, 0]);
     354
     355            resolve();
     356        }
     357    });
     358
    301359    suite.runTestCasesAndFinish();
    302360}
  • trunk/Source/WebInspectorUI/ChangeLog

    r222064 r222102  
     12017-09-15  Matt Baker  <mattbaker@apple.com>
     2
     3        Web Inspector: Canvas: recording parameters that include colors should show an InlineSwatch (2D canvas)
     4        https://bugs.webkit.org/show_bug.cgi?id=176822
     5        <rdar://problem/34402170>
     6
     7        Reviewed by Devin Rousso.
     8
     9        Show inline swatches in the canvas recording action and state sidebars.
     10
     11        * UserInterface/Models/Color.js:
     12        Added helpers for dealing with CMYK and normalized RGB. The latter is
     13        for RGB components scaled to the range [0, 1]. Also improved handling
     14        for 8-bit channel values.
     15        (WI.Color.rgb2hsv):
     16        (WI.Color.cmyk2rgb):
     17        (WI.Color.normalized2rgb):
     18        (WI.Color._eightBitChannel):
     19        (WI.Color.prototype._toRGBString):
     20        (WI.Color.prototype._toRGBAString):
     21        (WI.Color.prototype._componentToHexValue):
     22        (WI.Color.prototype._rgbToHSL):
     23        (WI.Color.prototype._componentToNumber): Deleted.
     24        Replaced by _eightBitChannel.
     25
     26        * UserInterface/Models/RecordingAction.js:
     27        (WI.RecordingAction):
     28        (WI.RecordingAction.prototype.getColorParameters):
     29        Get a subset of parameters that describe a color. This can be an array
     30        containing one value (e.g. fillStyle), or multiple values, as is the
     31        case with non-standard API functions that describe color using multiple
     32        parameters (e.g. setFillColor).
     33
     34        * UserInterface/Views/InlineSwatch.css:
     35        (.inline-swatch:not(.read-only):hover > span):
     36        (.inline-swatch:hover > span): Deleted.
     37        * UserInterface/Views/InlineSwatch.js:
     38        (WI.InlineSwatch):
     39        Read-only colors shouldn't show a context menu or hover effects.
     40
     41        * UserInterface/Views/RecordingActionTreeElement.css:
     42        (.tree-outline:matches(:focus, .force-focus) .item.action > .titles .parameters > .inline-swatch):
     43
     44        * UserInterface/Views/RecordingActionTreeElement.js:
     45        (WI.RecordingActionTreeElement._generateDOM.createParameterElement):
     46        (WI.RecordingActionTreeElement._generateDOM):
     47        (WI.RecordingActionTreeElement._createSwatchForColorParameters):
     48
     49        * UserInterface/Views/RecordingStateDetailsSidebarPanel.css:
     50        (.sidebar > .panel.details.recording-state > .content > .data-grid .inline-swatch):
     51
     52        * UserInterface/Views/RecordingStateDetailsSidebarPanel.js:
     53        (WI.RecordingStateDetailsSidebarPanel.prototype._generateDetailsCanvas2D.isColorProperty):
     54        (WI.RecordingStateDetailsSidebarPanel.prototype._generateDetailsCanvas2D.createInlineSwatch):
     55        (WI.RecordingStateDetailsSidebarPanel.prototype._generateDetailsCanvas2D):
     56        (WI.RecordingStateDetailsSidebarPanel):
     57
    1582017-09-14  Joseph Pecoraro  <pecoraro@apple.com>
    259
  • trunk/Source/WebInspectorUI/UserInterface/Models/Color.js

    r222055 r222102  
    158158    static rgb2hsv(r, g, b)
    159159    {
    160         r /= 255;
    161         g /= 255;
    162         b /= 255;
     160        r = WI.Color._eightBitChannel(r) / 255;
     161        g = WI.Color._eightBitChannel(g) / 255;
     162        b = WI.Color._eightBitChannel(b) / 255;
    163163
    164164        let min = Math.min(Math.min(r, g), b);
     
    229229    }
    230230
     231    static cmyk2rgb(c, m, y, k)
     232    {
     233        c = Number.constrain(c, 0, 1);
     234        m = Number.constrain(m, 0, 1);
     235        y = Number.constrain(y, 0, 1);
     236        k = Number.constrain(k, 0, 1);
     237
     238        let r = 255 - ((Math.min(1, c * (1 - k) + k)) * 255);
     239        let g = 255 - ((Math.min(1, m * (1 - k) + k)) * 255);
     240        let b = 255 - ((Math.min(1, y * (1 - k) + k)) * 255);
     241        return [r, g, b];
     242    }
     243
     244    static normalized2rgb(r, g, b)
     245    {
     246        return [
     247            WI.Color._eightBitChannel(r * 255),
     248            WI.Color._eightBitChannel(g * 255),
     249            WI.Color._eightBitChannel(b * 255)
     250        ];
     251    }
     252
     253    static _eightBitChannel(value)
     254    {
     255        return Number.constrain(Math.round(value), 0, 255);
     256    }
    231257
    232258    // Public
     
    485511            return this._toRGBAString();
    486512
    487         let rgba = this.rgba.slice(0, -1);
    488         rgba = rgba.map((value) => value.maxDecimals(2));
    489         return "rgb(" + rgba.join(", ") + ")";
     513        let r = WI.Color._eightBitChannel(this.rgb[0]);
     514        let g = WI.Color._eightBitChannel(this.rgb[1]);
     515        let b = WI.Color._eightBitChannel(this.rgb[2]);
     516        return `rgb(${r}, ${g}, ${b})`;
    490517    }
    491518
    492519    _toRGBAString()
    493520    {
    494         let rgba = this.rgba;
    495         rgba = rgba.map((value) => value.maxDecimals(2));
    496         return "rgba(" + rgba.join(", ") + ")";
     521        let r = WI.Color._eightBitChannel(this.rgb[0]);
     522        let g = WI.Color._eightBitChannel(this.rgb[1]);
     523        let b = WI.Color._eightBitChannel(this.rgb[2]);
     524        return `rgba(${r}, ${g}, ${b}, ${this.alpha})`;
    497525    }
    498526
     
    514542    }
    515543
    516     _componentToNumber(value)
    517     {
    518         return Number.constrain(value, 0, 255);
    519     }
    520 
    521544    _componentToHexValue(value)
    522545    {
    523         let hex = this._componentToNumber(value).toString(16);
     546        let hex = WI.Color._eightBitChannel(value).toString(16);
    524547        if (hex.length === 1)
    525548            hex = "0" + hex;
     
    529552    _rgbToHSL(rgb)
    530553    {
    531         let r = this._componentToNumber(rgb[0]) / 255;
    532         let g = this._componentToNumber(rgb[1]) / 255;
    533         let b = this._componentToNumber(rgb[2]) / 255;
     554        let r = WI.Color._eightBitChannel(rgb[0]) / 255;
     555        let g = WI.Color._eightBitChannel(rgb[1]) / 255;
     556        let b = WI.Color._eightBitChannel(rgb[2]) / 255;
    534557        let max = Math.max(r, g, b);
    535558        let min = Math.min(r, g, b);
  • trunk/Source/WebInspectorUI/UserInterface/Models/RecordingAction.js

    r222057 r222102  
    167167                this._stateModifiers.add(item);
    168168        }
     169    }
     170
     171    getColorParameters()
     172    {
     173        switch (this._name) {
     174        // 2D
     175        case "fillStyle":
     176        case "strokeStyle":
     177        case "shadowColor":
     178        // 2D (non-standard, legacy)
     179        case "setFillColor":
     180        case "setStrokeColor":
     181        // WebGL
     182        case "blendColor":
     183        case "clearColor":
     184            return this._parameters;
     185
     186        // 2D (non-standard, legacy)
     187        case "setShadow":
     188            return this._parameters.slice(3);
     189        }
     190
     191        return [];
    169192    }
    170193};
  • trunk/Source/WebInspectorUI/UserInterface/Views/InlineSwatch.css

    r221748 r222102  
    8585}
    8686
    87 .inline-swatch:hover > span {
     87.inline-swatch:not(.read-only):hover > span {
    8888    border-color: hsla(0, 0%, 25%, 0.8);
    8989}
  • trunk/Source/WebInspectorUI/UserInterface/Views/InlineSwatch.js

    r221748 r222102  
    3636        this._swatchElement.classList.add("inline-swatch", this._type.split("-").lastValue);
    3737
    38         switch (this._type) {
    39         case WI.InlineSwatch.Type.Color:
    40             this._swatchElement.title = WI.UIString("Click to select a color. Shift-click to switch color formats.");
    41             break;
    42         case WI.InlineSwatch.Type.Gradient:
    43             this._swatchElement.title = WI.UIString("Edit custom gradient");
    44             break;
    45         case WI.InlineSwatch.Type.Bezier:
    46             this._swatchElement.title = WI.UIString("Edit “cubic-bezier“ function");
    47             break;
    48         case WI.InlineSwatch.Type.Spring:
    49             this._swatchElement.title = WI.UIString("Edit “spring“ function");
    50             break;
    51         case WI.InlineSwatch.Type.Variable:
    52             this._swatchElement.title = WI.UIString("View variable value");
    53             break;
    54         default:
    55             WI.reportInternalError(`Unknown InlineSwatch type "${type}"`);
    56             break;
    57         }
    58 
    5938        this._boundSwatchElementClicked = null;
    60         if (!readOnly) {
     39
     40        if (readOnly)
     41            this._swatchElement.classList.add("read-only");
     42        else {
     43            switch (this._type) {
     44            case WI.InlineSwatch.Type.Color:
     45                this._swatchElement.title = WI.UIString("Click to select a color. Shift-click to switch color formats.");
     46                break;
     47            case WI.InlineSwatch.Type.Gradient:
     48                this._swatchElement.title = WI.UIString("Edit custom gradient");
     49                break;
     50            case WI.InlineSwatch.Type.Bezier:
     51                this._swatchElement.title = WI.UIString("Edit “cubic-bezier“ function");
     52                break;
     53            case WI.InlineSwatch.Type.Spring:
     54                this._swatchElement.title = WI.UIString("Edit “spring“ function");
     55                break;
     56            case WI.InlineSwatch.Type.Variable:
     57                this._swatchElement.title = WI.UIString("View variable value");
     58                break;
     59            default:
     60                WI.reportInternalError(`Unknown InlineSwatch type "${type}"`);
     61                break;
     62            }
     63
    6164            this._boundSwatchElementClicked = this._swatchElementClicked.bind(this);
    6265            this._swatchElement.addEventListener("click", this._boundSwatchElementClicked);
  • trunk/Source/WebInspectorUI/UserInterface/Views/RecordingActionTreeElement.css

    r221844 r222102  
    6262}
    6363
     64body:not(.window-inactive, .window-docked-inactive) .item.action > .titles .parameter.swizzled {
     65    color: var(--text-color-gray-medium);
     66}
     67
    6468body:not(.window-inactive, .window-docked-inactive) :matches(:focus, .force-focus) .item.action.selected > .titles .parameter.swizzled,
    6569body:not(.window-inactive, .window-docked-inactive) :matches(:focus, .force-focus) .item.action.selected::before {
    66     color: var(--selected-secondary-text-color);
     70    color: var(--console-secondary-text-color);
     71}
     72
     73.tree-outline:matches(:focus, .force-focus) .item.action > .titles .parameters > .inline-swatch {
     74    vertical-align: -1px;
    6775}
    6876
  • trunk/Source/WebInspectorUI/UserInterface/Views/RecordingActionTreeElement.js

    r221844 r222102  
    126126            copyText += ")";
    127127
     128        let colorParameters = recordingAction.getColorParameters();
     129        if (colorParameters.length) {
     130            let swatch = WI.RecordingActionTreeElement._createSwatchForColorParameters(colorParameters);
     131            let insertionIndex = recordingAction.parameters.indexOf(colorParameters[0]);
     132            let parameterElement = parametersContainer.children[insertionIndex];
     133            parametersContainer.insertBefore(swatch.element, parameterElement);
     134
     135            if (recordingAction.swizzleTypes[insertionIndex] === WI.Recording.Swizzle.String) {
     136                parameterElement.textContent = swatch.value.toString();
     137                parameterElement.classList.add("color");
     138            }
     139        }
     140
    128141        return {titleFragment, copyText};
     142    }
     143
     144    static _createSwatchForColorParameters(parameters)
     145    {
     146        let rgb = [];
     147        let color = null;
     148
     149        switch (parameters.length) {
     150        case 1:
     151        case 2:
     152            if (typeof parameters[0] === "string")
     153                color = WI.Color.fromString(parameters[0]);
     154            else
     155                rgb = WI.Color.normalized2rgb(parameters[0], parameters[0], parameters[0]);
     156            break;
     157
     158        case 4:
     159            rgb = WI.Color.normalized2rgb(parameters[0], parameters[1], parameters[2]);
     160            break;
     161
     162        case 5:
     163            rgb = WI.Color.cmyk2rgb(...parameters);
     164            break;
     165
     166        default:
     167            console.error("Unexpected number of color parameters.");
     168            return null;
     169        }
     170
     171        if (!color) {
     172            let alpha = parameters.length === 1 ? 1 : parameters.lastValue;
     173            color = new WI.Color(WI.Color.Format.RGBA, [...rgb, alpha]);
     174            if (!color)
     175                return null;
     176        }
     177
     178        const readOnly = true;
     179        return new WI.InlineSwatch(WI.InlineSwatch.Type.Color, color, readOnly);
    129180    }
    130181
  • trunk/Source/WebInspectorUI/UserInterface/Views/RecordingStateDetailsSidebarPanel.css

    r220114 r222102  
    3939    color: grey;
    4040}
     41
     42.sidebar > .panel.details.recording-state > .content > .data-grid .inline-swatch {
     43    vertical-align: -1px;
     44}
  • trunk/Source/WebInspectorUI/UserInterface/Views/RecordingStateDetailsSidebarPanel.js

    r222057 r222102  
    136136        state.webkitLineDashOffset = context.webkitLineDashOffset;
    137137
     138        function isColorProperty(name) {
     139            return name === "fillStyle" || name === "strokeStyle" || name === "shadowColor";
     140        }
     141
     142        function createInlineSwatch(value) {
     143            let color = WI.Color.fromString(value);
     144            if (!color)
     145                return null;
     146
     147            const readOnly = true;
     148            return new WI.InlineSwatch(WI.InlineSwatch.Type.Color, color, readOnly);
     149        }
     150
    138151        let action = actions[this._index];
    139152        for (let name in state) {
     
    155168                    value = JSON.stringify(value);
    156169                }
     170            } else if (isColorProperty(name)) {
     171                let swatch = createInlineSwatch(value);
     172                let label = swatch.value.toString();
     173                value = document.createElement("span");
     174                value.append(swatch.element, label);
    157175            }
    158176
Note: See TracChangeset for help on using the changeset viewer.