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 17import {hash} from '../common/hash'; 18import {cachedHsluvToHex} from '../frontend/hsluv_cache'; 19 20export interface Color { 21 c: string; 22 h: number; 23 s: number; 24 l: number; 25 a?: number; 26} 27 28const MD_PALETTE: Color[] = [ 29 {c: 'red', h: 4, s: 90, l: 58}, 30 {c: 'pink', h: 340, s: 82, l: 52}, 31 {c: 'purple', h: 291, s: 64, l: 42}, 32 {c: 'deep purple', h: 262, s: 52, l: 47}, 33 {c: 'indigo', h: 231, s: 48, l: 48}, 34 {c: 'blue', h: 207, s: 90, l: 54}, 35 {c: 'light blue', h: 199, s: 98, l: 48}, 36 {c: 'cyan', h: 187, s: 100, l: 42}, 37 {c: 'teal', h: 174, s: 100, l: 29}, 38 {c: 'green', h: 122, s: 39, l: 49}, 39 {c: 'light green', h: 88, s: 50, l: 53}, 40 {c: 'lime', h: 66, s: 70, l: 54}, 41 {c: 'amber', h: 45, s: 100, l: 51}, 42 {c: 'orange', h: 36, s: 100, l: 50}, 43 {c: 'deep orange', h: 14, s: 100, l: 57}, 44 {c: 'brown', h: 16, s: 25, l: 38}, 45 {c: 'blue gray', h: 200, s: 18, l: 46}, 46 {c: 'yellow', h: 54, s: 100, l: 62}, 47]; 48 49export const GRAY_COLOR: Color = { 50 c: 'grey', 51 h: 0, 52 s: 0, 53 l: 62, 54}; 55 56// A piece of wisdom from a long forgotten blog post: "Don't make 57// colors you want to change something normal like grey." 58export const UNEXPECTED_PINK_COLOR: Color = { 59 c: '#ff69b4', 60 h: 330, 61 s: 1.0, 62 l: 0.706, 63}; 64 65export function hueForCpu(cpu: number): number { 66 return (128 + (32 * cpu)) % 256; 67} 68 69const DESAT_RED: Color = { 70 c: 'desat red', 71 h: 3, 72 s: 30, 73 l: 49, 74}; 75const DARK_GREEN: Color = { 76 c: 'dark green', 77 h: 120, 78 s: 44, 79 l: 34, 80}; 81const LIME_GREEN: Color = { 82 c: 'lime green', 83 h: 75, 84 s: 55, 85 l: 47, 86}; 87const TRANSPARENT_WHITE: Color = { 88 c: 'white', 89 h: 0, 90 s: 1, 91 l: 97, 92 a: 0.55, 93}; 94const ORANGE: Color = { 95 c: 'orange', 96 h: 36, 97 s: 100, 98 l: 50, 99}; 100const INDIGO: Color = { 101 c: 'indigo', 102 h: 231, 103 s: 48, 104 l: 48, 105}; 106 107export function colorForState(state: string): Readonly<Color> { 108 if (state === 'Running') { 109 return DARK_GREEN; 110 } else if (state.startsWith('Runnable')) { 111 return LIME_GREEN; 112 } else if (state.includes('Uninterruptible Sleep')) { 113 if (state.includes('non-IO')) { 114 return DESAT_RED; 115 } 116 return ORANGE; 117 } else if (state.includes('Sleeping') || state.includes('Idle')) { 118 return TRANSPARENT_WHITE; 119 } 120 return INDIGO; 121} 122 123export function textColorForState(stateCode: string): string { 124 const background = colorForState(stateCode); 125 return background.l > 80 ? '#404040' : '#fff'; 126} 127 128export function colorForString(identifier: string): Color { 129 const colorIdx = hash(identifier, MD_PALETTE.length); 130 return Object.assign({}, MD_PALETTE[colorIdx]); 131} 132 133export function colorForTid(tid: number): Color { 134 return colorForString(tid.toString()); 135} 136 137export function colorForThread(thread?: {pid?: number, tid: number}): Color { 138 if (thread === undefined) { 139 return Object.assign({}, GRAY_COLOR); 140 } 141 const tid = thread.pid ? thread.pid : thread.tid; 142 return colorForTid(tid); 143} 144 145// 40 different random hues 9 degrees apart. 146export function randomColor(): string { 147 const hue = Math.floor(Math.random() * 40) * 9; 148 return '#' + hsl.hex([hue, 90, 30]); 149} 150 151// Chooses a color uniform at random based on hash(sliceName). Returns [hue, 152// saturation, lightness]. 153// 154// Prefer converting this to an RGB color using hsluv, not the browser's 155// built-in vanilla HSL handling. This is because this function chooses 156// hue/lightness uniform at random, but HSL is not perceptually uniform. See 157// https://www.boronine.com/2012/03/26/Color-Spaces-for-Human-Beings/. 158// 159// If isSelected, the color will be particularly dark, making it stand out. 160export function hslForSlice( 161 sliceName: string, isSelected: boolean|null): [number, number, number] { 162 const hue = hash(sliceName, 360); 163 // Saturation 100 would give the most differentiation between colors, but it's 164 // garish. 165 const saturation = 80; 166 const lightness = isSelected ? 30 : hash(sliceName + 'x', 40) + 40; 167 return [hue, saturation, lightness]; 168} 169 170// Lightens the color for thread slices to represent wall time. 171export function colorForThreadIdleSlice( 172 hue: number, 173 saturation: number, 174 lightness: number, 175 isSelected: boolean|null): string { 176 // Increase lightness by 80% when selected and 40% otherwise, 177 // without exceeding 88. 178 let newLightness = isSelected ? lightness * 1.8 : lightness * 1.4; 179 newLightness = Math.min(newLightness, 88); 180 return cachedHsluvToHex(hue, saturation, newLightness); 181} 182 183export function colorToStr(color: Color) { 184 if (color.a !== undefined) { 185 return `hsla(${color.h}, ${color.s}%, ${color.l}%, ${color.a})`; 186 } 187 return `hsl(${color.h}, ${color.s}%, ${color.l}%)`; 188} 189 190export function colorCompare(x: Color, y: Color) { 191 return (x.h - y.h) || (x.s - y.s) || (x.l - y.l); 192} 193