Converting Kinemage parser to using Color rather than number[] and moving HSV conversion into standard location

This commit is contained in:
Russ Taylor
2026-04-20 14:34:05 -04:00
parent bcb18a8faf
commit 5e16c340dc
4 changed files with 161 additions and 124 deletions

View File

@@ -130,9 +130,7 @@ async function getPoints(ctx: RuntimeContext, kin: Kinemage) {
let group = index++;
builderState.add(positionArray[3 * j + 0], positionArray[3 * j + 1], positionArray[3 * j + 2], group);
// colorArray may be undefined; push a default color when not provided
colors.push(colorArray && colorArray.length > j * 3 ?
Color.fromRgb(255 * (colorArray[3 * j + 0]), 255 * (colorArray[3 * j + 1]), 255 * (colorArray[3 * j + 2]))
: Color.fromRgb(255, 255, 255));
colors.push(colorArray && colorArray.length > j ? colorArray[j] : Color.fromRgb(255, 255, 255))
// labelArray may be undefined; push an empty string when not provided
labels.push(labelArray && labelArray.length > j ? labelArray[j] : '');
}
@@ -182,15 +180,11 @@ async function getLines(ctx: RuntimeContext, kin: Kinemage) {
midX, midY, midZ,
group);
// widthArray may be undefined; push NaN when width not provided
widths.push(widthArray && widthArray.length > j ? widthArray[j] : NaN);
widths.push(widthArray && widthArray.length > j ? widthArray[j] : NaN)
// colorArray may be undefined; push a default color when not provided
colors.push(color1Array && color1Array.length > j * 3 ?
Color.fromRgb(255 * color1Array[3 * j + 0],
255 * color1Array[3 * j + 1],
255 * color1Array[3 * j + 2])
: Color.fromRgb(255, 255, 255));
colors.push(color1Array && color1Array.length > j ? color1Array[j] : Color.fromRgb(255, 255, 255))
// labelArray may be undefined; push an empty string when not provided
labels.push(label1Array && label1Array.length > j ? label1Array[j] : '');
labels.push(label1Array && label1Array.length > j ? label1Array[j] : '')
// Make the second half of the line from the midpoint to position2, labeled and colored based on position2.
group = index++;
@@ -198,15 +192,11 @@ async function getLines(ctx: RuntimeContext, kin: Kinemage) {
position2Array[3 * j + 0], position2Array[3 * j + 1], position2Array[3 * j + 2],
group);
// widthArray may be undefined; push NaN when width not provided
widths.push(widthArray && widthArray.length > j ? widthArray[j] : NaN);
widths.push(widthArray && widthArray.length > j ? widthArray[j] : NaN)
// colorArray may be undefined; push a default color when not provided
colors.push(color2Array && color2Array.length > j * 3 ?
Color.fromRgb(255 * color2Array[3 * j + 0],
255 * color2Array[3 * j + 1],
255 * color2Array[3 * j + 2])
: Color.fromRgb(255, 255, 255));
colors.push(color2Array && color2Array.length > j ? color2Array[j] : Color.fromRgb(255, 255, 255))
// labelArray may be undefined; push an empty string when not provided
labels.push(label2Array && label2Array.length > j ? label2Array[j] : '');
labels.push(label2Array && label2Array.length > j ? label2Array[j] : '')
}
}
@@ -267,12 +257,9 @@ async function getMesh(ctx: RuntimeContext, kin: Kinemage) {
// colorArray may be undefined; push a default color when not provided.
// There is one color per group, even if we have two triangles in this group.
const color = colorArray && colorArray.length > i * 9 ?
Color.fromRgb(255 * colorArray[9 * i + 0],
255 * colorArray[9 * i + 1],
255 * colorArray[9 * i + 2])
: Color.fromRgb(255, 255, 255);
const color = colorArray && colorArray.length > i * 3 ? colorArray[3 * i] : Color.fromRgb(255, 255, 255);
colors.push(color);
console.log('XXX ribbon %o color[%o] = %o', ri, group, color)
// labelArray may be undefined; push an empty string when not provided
const label = labelArray && labelArray.length > i ? labelArray[i] : '';
@@ -337,9 +324,7 @@ async function getSpheres(ctx: RuntimeContext, kin: Kinemage) {
// radiusArray may be undefined; push NaN when radius not provided
radii.push(radiusArray && radiusArray.length > j ? radiusArray[j] : NaN);
// colorArray may be undefined; push a default color when not provided
colors.push(colorArray && colorArray.length > j * 3 ?
Color.fromRgb(255 * (colorArray[3 * j + 0]), 255 * (colorArray[3 * j + 1]), 255 * (colorArray[3 * j + 2]))
: Color.fromRgb(255, 255, 255));
colors.push(colorArray && colorArray.length > j ? colorArray[j] : Color.fromRgb(255, 255, 255));
// labelArray may be undefined; push an empty string when not provided
labels.push(balls[i].labelArray && balls[i].labelArray.length > j ? balls[i].labelArray[j] : '');
}
@@ -374,7 +359,7 @@ function makePointsShapeGetter() {
_points,
colorFn, // color function reads per-point colors
() => 1, // size function
labelFn // label function reads per-point labels
labelFn // label function reads per-point labels
);
return _shape;
};

View File

@@ -8,7 +8,7 @@
/**
* file Kin Parser
* author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
// import { Debug, Log, ParserRegistry } from '../globals'
@@ -18,57 +18,38 @@
/// @todo Fill in commments
import { Kinemage, RibbonObject } from './schema';
import { Hsv } from '../../../mol-util/color/spaces/hsv';
import { Color } from '../../../mol-util/color';
function hsvToRgb (h: number, s: number, v: number) {
h /= 360
s /= 100
v /= 100
let r, g, b
const i = Math.floor(h * 6)
const f = h * 6 - i
const p = v * (1 - s)
const q = v * (1 - f * s)
const t = v * (1 - (1 - f) * s)
switch (i % 6) {
case 0: r = v; g = t; b = p; break
case 1: r = q; g = v; b = p; break
case 2: r = p; g = v; b = t; break
case 3: r = p; g = q; b = v; break
case 4: r = t; g = p; b = v; break
case 5: r = v; g = p; b = q; break
}
return [ r, g, b ] as number []
}
const ColorDict: {[k: string]: number[]} = {
red: hsvToRgb(0, 100, 100),
orange: hsvToRgb(20, 100, 100),
gold: hsvToRgb(40, 100, 100),
yellow: hsvToRgb(60, 100, 100),
lime: hsvToRgb(80, 100, 100),
green: hsvToRgb(120, 80, 100),
sea: hsvToRgb(150, 100, 100),
cyan: hsvToRgb(180, 100, 85),
sky: hsvToRgb(210, 75, 95),
blue: hsvToRgb(240, 70, 100),
purple: hsvToRgb(275, 75, 100),
magenta: hsvToRgb(300, 95, 100),
hotpink: hsvToRgb(335, 100, 100),
pink: hsvToRgb(350, 55, 100),
peach: hsvToRgb(25, 75, 100),
lilac: hsvToRgb(275, 55, 100),
pinktint: hsvToRgb(340, 30, 100),
peachtint: hsvToRgb(25, 50, 100),
yellowtint: hsvToRgb(60, 50, 100),
greentint: hsvToRgb(135, 40, 100),
bluetint: hsvToRgb(220, 40, 100),
lilactint: hsvToRgb(275, 35, 100),
white: hsvToRgb(0, 0, 100),
gray: hsvToRgb(0, 0, 50),
brown: hsvToRgb(20, 45, 75),
deadwhite: [ 1, 1, 1 ],
deadblack: [ 0, 0, 0 ],
invisible: [ 0, 0, 0 ]
const ColorDict: {[k: string]: Color } = {
red: Hsv.toColor(Hsv.fromArray([0, 100, 100])),
orange: Hsv.toColor(Hsv.fromArray([20, 100, 100])),
gold: Hsv.toColor(Hsv.fromArray([40, 100, 100])),
yellow: Hsv.toColor(Hsv.fromArray([60, 100, 100])),
lime: Hsv.toColor(Hsv.fromArray([80, 100, 100])),
green: Hsv.toColor(Hsv.fromArray([120, 80, 100])),
sea: Hsv.toColor(Hsv.fromArray([150, 100, 100])),
cyan: Hsv.toColor(Hsv.fromArray([180, 100, 85])),
sky: Hsv.toColor(Hsv.fromArray([210, 75, 95])),
blue: Hsv.toColor(Hsv.fromArray([240, 70, 100])),
purple: Hsv.toColor(Hsv.fromArray([275, 75, 100])),
magenta: Hsv.toColor(Hsv.fromArray([300, 95, 100])),
hotpink: Hsv.toColor(Hsv.fromArray([335, 100, 100])),
pink: Hsv.toColor(Hsv.fromArray([350, 55, 100])),
peach: Hsv.toColor(Hsv.fromArray([25, 75, 100])),
lilac: Hsv.toColor(Hsv.fromArray([275, 55, 100])),
pinktint: Hsv.toColor(Hsv.fromArray([340, 30, 100])),
peachtint: Hsv.toColor(Hsv.fromArray([25, 50, 100])),
yellowtint: Hsv.toColor(Hsv.fromArray([60, 50, 100])),
greentint: Hsv.toColor(Hsv.fromArray([135, 40, 100])),
bluetint: Hsv.toColor(Hsv.fromArray([220, 40, 100])),
lilactint: Hsv.toColor(Hsv.fromArray([275, 35, 100])),
white: Hsv.toColor(Hsv.fromArray([0, 0, 100])),
gray: Hsv.toColor(Hsv.fromArray([0, 0, 50])),
brown: Hsv.toColor(Hsv.fromArray([20, 45, 75])),
deadwhite: Hsv.toColor(Hsv.fromArray([0, 0, 100])),
deadblack: Hsv.toColor(Hsv.fromArray([0, 0, 0])),
invisible: Hsv.toColor(Hsv.fromArray([0, 0, 0]))
}
const reWhitespaceComma = /[\s,]+/
@@ -77,9 +58,9 @@ const reTrimCurly = /^{+|}+$/g
const reTrimQuotes = /^['"]+|['"]+$/g
const reCollapseEqual = /\s*=\s*/g
function parseListDef (line: string, localColorDict: {[k: string]: number[]}) {
function parseListDef (line: string, localColorDict: {[k: string]: Color}) {
let name
let defaultColor: number[] = localColorDict['white'] // Default color is white, but it can be overridden by the list definition
let defaultColor: Color = localColorDict['white'] // Default color is white, but it can be overridden by the list definition
let radius
let nobutton = false
let master = []
@@ -125,7 +106,7 @@ function parseListDef (line: string, localColorDict: {[k: string]: number[]}) {
}
}
function parseListElm (line: string, localColorDict: {[k: string]: number[]}) {
function parseListElm (line: string, localColorDict: {[k: string]: Color}) {
line = line.trim()
const idx1 = line.indexOf('{')
@@ -219,16 +200,20 @@ function parseGroup (line: string) {
}
function convertKinTriangleArrays (ribbonObject: RibbonObject) {
// have to convert ribbons/triangle lists from stripdrawmode to normal drawmode
// index [ 0 1 2 3 4 5 6 7 8 91011 ]
// label [ 0 1 2 3 4 5 ] to [ 0 1 2 1 2 3 2 3 4 3 4 5 ]
// index [ 0 1 2 3 4 5 6 7 8 91011 ]
// label/color [ 0 1 2 3 4 5 ] to [ 0 1 2 1 2 3 2 3 4 3 4 5 ]
// convertedindex [ 0 1 2 3 4 5 6 7 8 91011121314151617181920212223242526 ]
// index [ 0 1 2 3 4 5 6 7 8 91011121314 ] [ 0 1 2 3 4 5 6 7 8 3 4 5 6 7 8 91011 6 7 8 91011121314 ]
// position/color [ 0 0 0 1 1 1 2 2 2 3 3 3 4 4 4 ] to [ 0 0 0 1 1 1 2 2 2 1 1 1 2 2 2 3 3 3 2 2 2 3 3 3 4 4 4 ]
// position [ 0 0 0 1 1 1 2 2 2 3 3 3 4 4 4 ] to [ 0 0 0 1 1 1 2 2 2 1 1 1 2 2 2 3 3 3 2 2 2 3 3 3 4 4 4 ]
let { labelArray, positionArray, colorArray, breakArray } = ribbonObject
let convertedLabels = []
for (let i = 0; i < (labelArray.length - 2) * 3; ++i) {
convertedLabels[i] = labelArray[i - Math.floor(i / 3) * 2]
}
let convertedColors = []
for (let i = 0; i < (colorArray.length - 2) * 3; ++i) {
convertedColors[i] = colorArray[i - Math.floor(i / 3) * 2]
}
let convertedBreaks = []
for (let i = 0; i < (breakArray.length - 2) * 3; ++i) {
convertedBreaks[i] = breakArray[i - Math.floor(i / 3) * 2]
@@ -237,10 +222,6 @@ function convertKinTriangleArrays (ribbonObject: RibbonObject) {
for (let i = 0; i < (positionArray.length / 3 - 2) * 9; ++i) {
convertedPositions[i] = positionArray[i - Math.floor(i / 9) * 6]
}
let convertedColors = []
for (let i = 0; i < (colorArray.length / 3 - 2) * 9; ++i) {
convertedColors[i] = colorArray[i - Math.floor(i / 9) * 6]
}
let vector3Positions = []
for (let i = 0; i < (convertedPositions.length) / 3; ++i) {
vector3Positions.push([convertedPositions[i * 3], convertedPositions[i * 3] + 1, convertedPositions[i * 3] + 2])
@@ -268,8 +249,8 @@ function convertKinTriangleArrays (ribbonObject: RibbonObject) {
function removePointBreaksTriangleArrays (convertedRibbonObject: RibbonObject) {
// after converting ribbon/triangle arrys to drawmode, removed point break triangles
// label [ 0 1 2 3 4 5 ] to [ 0 1 2 1 2 3 2 3 4 3 4 5 ]
// position/color [ 0 0 0 1 1 1 2 2 2 3 3 3 4 4 4 ] to [ 0 0 0 1 1 1 2 2 2 1 1 1 2 2 2 3 3 3 2 2 2 3 3 3 4 4 4 ]
// label/color [ 0 1 2 3 4 5 ] to [ 0 1 2 1 2 3 2 3 4 3 4 5 ]
// position [ 0 0 0 1 1 1 2 2 2 3 3 3 4 4 4 ] to [ 0 0 0 1 1 1 2 2 2 1 1 1 2 2 2 3 3 3 2 2 2 3 3 3 4 4 4 ]
let { labelArray, positionArray, colorArray, breakArray } = convertedRibbonObject
let editedLabels = []
let editedPositions = []
@@ -294,15 +275,9 @@ function removePointBreaksTriangleArrays (convertedRibbonObject: RibbonObject) {
editedPositions.push(positionArray[positionPointer+6])
editedPositions.push(positionArray[positionPointer+7])
editedPositions.push(positionArray[positionPointer+8])
editedColors.push(colorArray[positionPointer])
editedColors.push(colorArray[positionPointer+1])
editedColors.push(colorArray[positionPointer+2])
editedColors.push(colorArray[positionPointer+3])
editedColors.push(colorArray[positionPointer+4])
editedColors.push(colorArray[positionPointer+5])
editedColors.push(colorArray[positionPointer+6])
editedColors.push(colorArray[positionPointer+7])
editedColors.push(colorArray[positionPointer+8])
editedColors.push(colorArray[breakPointer])
editedColors.push(colorArray[breakPointer+1])
editedColors.push(colorArray[breakPointer+2])
} else {
//console.log('X triangle break found')
//console.log('skipping: '+positionArray[positionPointer]+','+positionArray[positionPointer+1]+','+positionArray[positionPointer+2]+','
@@ -363,7 +338,7 @@ class KinParser {
this.kinemage = kinemage
// Keep a local copy of the ColorDict that we can update with new colors defined in the file.
let localColorDict: { [k: string]: number[] } = Object.assign({}, ColorDict)
let localColorDict: { [k: string]: Color } = Object.assign({}, ColorDict)
let currentGroup: string = ''
let currentGroupMasters: string[]
@@ -372,28 +347,28 @@ class KinParser {
let isDotList = false
let prevDotLabel = ''
let dotDefaultColor: number[]
let dotLabel: string[], dotPosition: number[], dotColor: number[]
let dotDefaultColor: Color
let dotLabel: string[], dotPosition: number[], dotColor: Color[]
let isVectorList = false
let prevVecLabel = ''
let prevVecPosition: number[]|null = null
let prevVecColor: number[]|null = null
let vecDefaultColor: number[], vecDefaultWidth: number
let vecLabel1: string[], vecLabel2: string[], vecPosition1: number[], vecPosition2: number[], vecColor1: number[], vecColor2: number[]
let prevVecColor: Color|null = null
let vecDefaultColor: Color, vecDefaultWidth: number
let vecLabel1: string[], vecLabel2: string[], vecPosition1: number[], vecPosition2: number[], vecColor1: Color[], vecColor2: Color[]
let vecWidth: number[]
let isBallList = false
let prevBallLabel = ''
let ballRadius: number[], ballDefaultColor: number[], ballDefaultRadius: number
let ballLabel: string[], ballPosition: number[], ballColor: number[]
let ballRadius: number[], ballDefaultColor: Color, ballDefaultRadius: number
let ballLabel: string[], ballPosition: number[], ballColor: Color[]
let isRibbonList = false
let ribbonIsTriangles = false
let prevRibbonPointLabel = ''
let ribbonListDefaultColor: number[] = localColorDict['white']
let ribbonPointLabelArray: string[], ribbonPointPositionArray: number[], ribbonPointBreakArray: boolean[], ribbonPointColorArray: number[]
let ribbonListDefaultColor: Color = localColorDict['white']
let ribbonPointLabelArray: string[], ribbonPointPositionArray: number[], ribbonPointBreakArray: boolean[], ribbonPointColorArray: Color[]
let isText = false
let isCaption = false
@@ -435,7 +410,7 @@ class KinParser {
dotLabel = []
dotPosition = []
dotColor = []
dotDefaultColor = listColor as number[]
dotDefaultColor = listColor
if (currentGroupMasters) {
listMasters = listMasters.concat(currentGroupMasters)
@@ -481,7 +456,7 @@ class KinParser {
vecColor1 = []
vecColor2 = []
vecWidth = []
vecDefaultColor = listColor as number[]
vecDefaultColor = listColor
vecDefaultWidth = 2
if (listWidth) {
vecDefaultWidth = listWidth
@@ -529,7 +504,7 @@ class KinParser {
ballRadius = []
ballPosition = []
ballColor = []
ballDefaultColor = listColor as number[]
ballDefaultColor = listColor
ballDefaultRadius = listRadius !== undefined ? listRadius : 1
if (currentGroupMasters) {
@@ -570,7 +545,7 @@ class KinParser {
ribbonPointPositionArray = []
ribbonPointBreakArray = []
ribbonPointColorArray = []
ribbonListDefaultColor = listColor as number[]
ribbonListDefaultColor = listColor
if (currentGroupMasters) {
listMasters = listMasters.concat(currentGroupMasters)
@@ -614,7 +589,7 @@ class KinParser {
dotLabel.push(label)
dotPosition.push(...position)
dotColor.push(...color)
dotColor.push(color)
} else if (isVectorList) {
// { n thr A 1 B13.79 1crnFH} P 17.047, 14.099, 3.625 { n thr A 1 B13.79 1crnFH} L 17.047, 14.099, 3.625
@@ -641,11 +616,11 @@ class KinParser {
vecLabel1.push(prevVecLabel)
vecPosition1.push(...prevVecPosition)
vecColor1.push(...prevVecColor as number[])
vecColor1.push(prevVecColor ? prevVecColor : vecDefaultColor)
vecLabel2.push(label)
vecPosition2.push(...position)
vecColor2.push(...color as number[])
vecColor2.push(color)
vecWidth.push(width)
}
}
@@ -676,7 +651,7 @@ class KinParser {
ballLabel.push(label)
ballRadius.push(radius)
ballPosition.push(...position)
ballColor.push(...color)
ballColor.push(color)
} else if (isRibbonList) {
let { label, color, position, isTriangleBreak } = parseListElm(line, localColorDict)
@@ -693,7 +668,8 @@ class KinParser {
ribbonPointLabelArray.push(label)
ribbonPointPositionArray.push(...position)
ribbonPointBreakArray.push(isTriangleBreak)
ribbonPointColorArray.push(...color)
ribbonPointColorArray.push(color)
console.log('XXX Pushing ribbon color ' + color)
} else if (isText) {
kinemage.texts.push(line)
} else if (isCaption) {

View File

@@ -4,6 +4,8 @@
* @author ReliaSolve <russ@reliasolve.com>
*/
import { Color } from '../../../mol-util/color';
export interface Kinemage {
readonly comments: ReadonlyArray<string>
kinemage?: number,
@@ -41,31 +43,31 @@ export interface KinListBase {
export interface DotList extends KinListBase {
labelArray: string[], ///< Array of labels per element
positionArray: number[], ///< Catenation of x, y, z for each element, 3x as many as elements
colorArray: number[] ///< Catenation of r, g, b for each element, 3x as many as elements
colorArray: Color[] ///< Color for each element, as many as elements
}
export interface BallList extends KinListBase {
labelArray: string[], ///< Array of labels per element
positionArray: number[], ///< Catenation of x, y, z for each element, 3x as many as elements
colorArray: number[], ///< Catenation of r, g, b for each element, 3x as many as elements
colorArray: Color[], ///< Color for each element, as many as elements
radiusArray: number[] ///< A single radius per element
}
export interface RibbonObject extends KinListBase {
labelArray: string[], ///< Array of labels per element
positionArray: number[], ///< Catenation of x, y, z for each element, 9x as many as triangles (3 vertices per triangle)
colorArray: number[], ///< Catenation of r, g, b for each element, 9x as many as triangles (3 colors per triangle)
colorArray: Color[], ///< Color for each element, as many as elements
breakArray: boolean[], ///< A single boolean per element indicating if there is a break there
pairTriangleNormals: boolean ///< Whether to pair every other triangle normal for lighting (true for ribbons, false for triangles)
}
export interface VectorList extends KinListBase {
label1Array: string[], ///< Array of labels per element
label2Array: string[], ///< Array of labels per element
label1Array: string[], ///< Array of labels for the first half of each element
label2Array: string[], ///< Array of labels for the second half of each element
position1Array: number[], ///< Catenation of x, y, z for each element, 3x as many as elements
position2Array: number[], ///< Catenation of x, y, z for each element, 3x as many as elements
color1Array: number[], ///< Catenation of r, g, b for each element, 3x as many as elements
color2Array: number[], ///< Catenation of r, g, b for each element, 3x as many as elements
color1Array: Color[], ///< Color for first half of each element, as many as elements
color2Array: Color[], ///< Color for second half of each element, as many as elements
width: number[] ///< A single width per element
}

View File

@@ -0,0 +1,74 @@
/**
* Copyright (c) 2026 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author ReliaSolve <russ@reliasolve.com>
*
* Adapted from kin-parser.ts file from the NGL project:
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* Adapted from hsl.ts in this same directory:
* @author David Sehnal <david.sehnal@gmail.com>
*/
import type { Color } from '../color';
import { Rgb } from './rgb';
export { Hsv };
/** Hsv tuple: [h, s, v]
* - h in [0,360] degrees
* - s in [0,100] percent
* - v in [0,100] percent
*/
interface Hsv extends Array<number> { [d: number]: number, '@type': 'hsv', length: 3 }
function Hsv() {
return Hsv.zero();
}
namespace Hsv {
export function zero(): Hsv {
const out = [0.0, 0.0, 0.0]
out[0] = 0
return out as Hsv;
}
/** Copy values from an array-like 3-tuple into `out`. */
export function fromArray(arr: ArrayLike<number>): Hsv {
const out = Hsv.zero();
out[0] = arr[0] ?? 0
out[1] = arr[1] ?? 0
out[2] = arr[2] ?? 0
return out
}
const _rgb = Rgb();
export function toColor(hsv: Hsv): Color {
toRgb(_rgb, hsv);
return Rgb.toColor(_rgb)
}
export function toRgb(out: Rgb, hsv: Hsv) {
let [h, s, v] = hsv;
h /= 360
s /= 100
v /= 100
let r = 0, g = 0, b = 0
const i = Math.floor(h * 6)
const f = h * 6 - i
const p = v * (1 - s)
const q = v * (1 - f * s)
const t = v * (1 - (1 - f) * s)
switch (i % 6) {
case 0: r = v; g = t; b = p; break
case 1: r = q; g = v; b = p; break
case 2: r = p; g = v; b = t; break
case 3: r = p; g = q; b = v; break
case 4: r = t; g = p; b = v; break
case 5: r = v; g = p; b = q; break
}
out[0] = r
out[1] = g
out[2] = b
return out
}
}