1// Copyright (C) 2019 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import {hsl} from 'color-convert';
16
17export interface Color {
18  c: string;
19  h: number;
20  s: number;
21  l: number;
22  a?: number;
23}
24
25const MD_PALETTE: Color[] = [
26  {c: 'red', h: 4, s: 90, l: 58},
27  {c: 'pink', h: 340, s: 82, l: 52},
28  {c: 'purple', h: 291, s: 64, l: 42},
29  {c: 'deep purple', h: 262, s: 52, l: 47},
30  {c: 'indigo', h: 231, s: 48, l: 48},
31  {c: 'blue', h: 207, s: 90, l: 54},
32  {c: 'light blue', h: 199, s: 98, l: 48},
33  {c: 'cyan', h: 187, s: 100, l: 42},
34  {c: 'teal', h: 174, s: 100, l: 29},
35  {c: 'green', h: 122, s: 39, l: 49},
36  {c: 'light green', h: 88, s: 50, l: 53},
37  {c: 'lime', h: 66, s: 70, l: 54},
38  {c: 'amber', h: 45, s: 100, l: 51},
39  {c: 'orange', h: 36, s: 100, l: 50},
40  {c: 'deep orange', h: 14, s: 100, l: 57},
41  {c: 'brown', h: 16, s: 25, l: 38},
42  {c: 'blue gray', h: 200, s: 18, l: 46},
43  {c: 'yellow', h: 54, s: 100, l: 62},
44];
45
46const GREY_COLOR: Color = {
47  c: 'grey',
48  h: 0,
49  s: 0,
50  l: 62
51};
52
53function hash(s: string, max: number): number {
54  let hash = 0x811c9dc5 & 0xfffffff;
55  for (let i = 0; i < s.length; i++) {
56    hash ^= s.charCodeAt(i);
57    hash = (hash * 16777619) & 0xffffffff;
58  }
59  return Math.abs(hash) % max;
60}
61
62export function hueForCpu(cpu: number): number {
63  return (128 + (32 * cpu)) % 256;
64}
65
66const DESAT_RED: Color = {
67  c: 'desat red',
68  h: 3,
69  s: 30,
70  l: 49
71};
72const DARK_GREEN: Color = {
73  c: 'dark green',
74  h: 120,
75  s: 44,
76  l: 34
77};
78const LIME_GREEN: Color = {
79  c: 'lime green',
80  h: 75,
81  s: 55,
82  l: 47
83};
84const TRANSPARENT_WHITE: Color = {
85  c: 'white',
86  h: 0,
87  s: 1,
88  l: 97,
89  a: 0.55,
90};
91const ORANGE: Color = {
92  c: 'orange',
93  h: 36,
94  s: 100,
95  l: 50
96};
97const INDIGO: Color = {
98  c: 'indigo',
99  h: 231,
100  s: 48,
101  l: 48
102};
103
104export function colorForState(state: string): Readonly<Color> {
105  if (state === 'Running') {
106    return DARK_GREEN;
107  } else if (state.startsWith('Runnable')) {
108    return LIME_GREEN;
109  } else if (state.includes('Uninterruptible Sleep')) {
110    if (state.includes('non-IO')) {
111      return DESAT_RED;
112    }
113    return ORANGE;
114  } else if (state.includes('Sleeping')) {
115    return TRANSPARENT_WHITE;
116  }
117  return INDIGO;
118}
119
120export function textColorForState(stateCode: string): string {
121  const background = colorForState(stateCode);
122  return background.l > 80 ? '#404040' : '#fff';
123}
124
125export function colorForTid(tid: number): Color {
126  const colorIdx = hash(tid.toString(), MD_PALETTE.length);
127  return Object.assign({}, MD_PALETTE[colorIdx]);
128}
129
130export function colorForThread(thread?: {pid?: number, tid: number}): Color {
131  if (thread === undefined) {
132    return Object.assign({}, GREY_COLOR);
133  }
134  const tid = thread.pid ? thread.pid : thread.tid;
135  return colorForTid(tid);
136}
137
138// 40 different random hues 9 degrees apart.
139export function randomColor(): string {
140  const hue = Math.floor(Math.random() * 40) * 9;
141  return '#' + hsl.hex([hue, 90, 30]);
142}
143
144// Chooses a color uniform at random based on hash(sliceName).  Returns [hue,
145// saturation, lightness].
146//
147// Prefer converting this to an RGB color using hsluv, not the browser's
148// built-in vanilla HSL handling.  This is because this function chooses
149// hue/lightness uniform at random, but HSL is not perceptually uniform.  See
150// https://www.boronine.com/2012/03/26/Color-Spaces-for-Human-Beings/.
151//
152// If isSelected, the color will be particularly dark, making it stand out.
153export function hslForSlice(
154    sliceName: string, isSelected: boolean|null): [number, number, number] {
155  const hue = hash(sliceName, 360);
156  // Saturation 100 would give the most differentiation between colors, but it's
157  // garish.
158  const saturation = 80;
159  const lightness = isSelected ? 30 : hash(sliceName + 'x', 40) + 40;
160  return [hue, saturation, lightness];
161}
162