chrome-devtools-frontend
Version:
Chrome DevTools UI
116 lines (96 loc) • 3.57 kB
text/typescript
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/* eslint-disable rulesdir/no-lit-render-outside-of-view */
import * as Common from '../../../core/common/common.js';
import * as RenderCoordinator from '../../../ui/components/render_coordinator/render_coordinator.js';
import {html, render} from '../../../ui/lit/lit.js';
import srgbOverlayStyles from './srgbOverlay.css.js';
interface SrgbOverlayProps {
// [0 - 1] corresponding to HSV hue
hue: number;
width: number;
height: number;
}
const SRGB_LABEL_HEIGHT = 10;
const SRGB_LABEL_BOTTOM = 3;
const SRGB_TEXT_UPPER_POINT_FROM_BOTTOM = SRGB_LABEL_HEIGHT + SRGB_LABEL_BOTTOM;
const EPSILON = 0.001;
// TODO(crbug.com/1409892): Use `Color` class here for a better code (and not duplicate isInGamut logic here)
function isColorInSrgbGamut(hsv: Common.ColorUtils.Color3D): boolean {
const rgba = Common.Color.hsva2rgba([...hsv, 1]);
const xyzd50 = Common.ColorConverter.ColorConverter.displayP3ToXyzd50(rgba[0], rgba[1], rgba[2]);
const srgb = Common.ColorConverter.ColorConverter.xyzd50ToSrgb(xyzd50[0], xyzd50[1], xyzd50[2]);
return srgb.every(val => val + EPSILON >= 0 && val - EPSILON <= 1);
}
export class SrgbOverlay extends HTMLElement {
readonly #shadow = this.attachShadow({mode: 'open'});
#getLinePoints({hue, width, height}: SrgbOverlayProps): Array<{x: number, y: number}>|null {
if (width === 0 || height === 0) {
return null;
}
const step = 1 / window.devicePixelRatio;
const linePoints = [];
let x = 0;
for (let y = 0; y < height; y += step) {
const value = 1 - (y / height);
for (; x < width; x += step) {
const saturation = x / width;
if (!isColorInSrgbGamut([hue, saturation, value])) {
linePoints.push({x, y});
break;
}
}
}
if (linePoints.length === 0) {
return null;
}
const lastPoint = linePoints[linePoints.length - 1];
if (lastPoint.x < width) {
linePoints.push({
y: lastPoint.y,
x: width,
});
}
return linePoints;
}
#closestPointAtHeight(points: Array<{x: number, y: number}>, atHeight: number): {x: number, y: number}|null {
let min = Infinity;
let closestPoint = null;
for (const point of points) {
if (Math.abs(atHeight - point.y) <= min) {
min = Math.abs(atHeight - point.y);
closestPoint = point;
}
}
return closestPoint;
}
render({hue, width, height}: SrgbOverlayProps): Promise<void> {
return RenderCoordinator.write('Srgb Overlay render', () => {
const points = this.#getLinePoints({hue, width, height});
if (!points || points.length === 0) {
return;
}
const closestPoint = this.#closestPointAtHeight(points, height - SRGB_TEXT_UPPER_POINT_FROM_BOTTOM);
if (!closestPoint) {
return;
}
render(
html`
<style>${srgbOverlayStyles}</style>
<span class="label" style="right: ${width - closestPoint.x}px">sRGB</span>
<svg>
<polyline points=${
points.map(point => `${point.x.toFixed(2)},${point.y.toFixed(2)}`).join(' ')} class="gamut-line" />
</svg>
`,
this.#shadow, {host: this});
});
}
}
customElements.define('devtools-spectrum-srgb-overlay', SrgbOverlay);
declare global {
interface HTMLElementTagNameMap {
'devtools-spectrum-srgb-overlay': SrgbOverlay;
}
}