Skip to content
Snippets Groups Projects
Commit 4249064d authored by dsehnal's avatar dsehnal
Browse files

Add ColorTheme.palette support

- add example to basic-wrapper that uses it
parent 028c02f5
No related branches found
No related tags found
No related merge requests found
import { isPositionLocation } from '../../mol-geo/util/location-iterator';
import { Vec3 } from '../../mol-math/linear-algebra';
import { ColorTheme } from '../../mol-theme/color';
import { ThemeDataContext } from '../../mol-theme/theme';
import { Color } from '../../mol-util/color';
import { ColorNames } from '../../mol-util/color/names';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
export function CustomColorTheme(
ctx: ThemeDataContext,
props: PD.Values<{}>
): ColorTheme<{}> {
const { radius, center } = ctx.structure?.boundary.sphere!;
const radiusSq = Math.max(radius * radius, 0.001);
const scale = ColorTheme.PaletteScale;
return {
factory: CustomColorTheme,
granularity: 'vertex',
color: location => {
if (!isPositionLocation(location)) return ColorNames.black;
const dist = Vec3.squaredDistance(location.position, center);
const t = Math.min(dist / radiusSq, 1);
return ((t * scale) | 0) as Color;
},
palette: {
colors: [
ColorNames.red,
ColorNames.pink,
ColorNames.violet,
ColorNames.orange,
ColorNames.yellow,
ColorNames.green,
ColorNames.blue
]
},
props: props,
description: '',
};
}
export const CustomColorThemeProvider: ColorTheme.Provider<{}, 'basic-wrapper-custom-color-theme'> = {
name: 'basic-wrapper-custom-color-theme',
label: 'Custom Color Theme',
category: ColorTheme.Category.Misc,
factory: CustomColorTheme,
getParams: () => ({}),
defaultValues: { },
isApplicable: (ctx: ThemeDataContext) => true,
};
...@@ -97,6 +97,7 @@ ...@@ -97,6 +97,7 @@
addHeader('Misc'); addHeader('Misc');
addControl('Apply Stripes', () => BasicMolStarWrapper.coloring.applyStripes()); addControl('Apply Stripes', () => BasicMolStarWrapper.coloring.applyStripes());
addControl('Apply Custom Theme', () => BasicMolStarWrapper.coloring.applyCustomTheme());
addControl('Default Coloring', () => BasicMolStarWrapper.coloring.applyDefault()); addControl('Default Coloring', () => BasicMolStarWrapper.coloring.applyDefault());
addHeader('Interactivity'); addHeader('Interactivity');
......
...@@ -18,6 +18,7 @@ import { Asset } from '../../mol-util/assets'; ...@@ -18,6 +18,7 @@ import { Asset } from '../../mol-util/assets';
import { Color } from '../../mol-util/color'; import { Color } from '../../mol-util/color';
import { StripedResidues } from './coloring'; import { StripedResidues } from './coloring';
import { CustomToastMessage } from './controls'; import { CustomToastMessage } from './controls';
import { CustomColorThemeProvider } from './custom-theme';
import './index.html'; import './index.html';
import { buildStaticSuperposition, dynamicSuperpositionTest, StaticSuperpositionTestData } from './superposition'; import { buildStaticSuperposition, dynamicSuperpositionTest, StaticSuperpositionTestData } from './superposition';
require('mol-plugin-ui/skin/light.scss'); require('mol-plugin-ui/skin/light.scss');
...@@ -42,6 +43,7 @@ class BasicWrapper { ...@@ -42,6 +43,7 @@ class BasicWrapper {
}); });
this.plugin.representation.structure.themes.colorThemeRegistry.add(StripedResidues.colorThemeProvider!); this.plugin.representation.structure.themes.colorThemeRegistry.add(StripedResidues.colorThemeProvider!);
this.plugin.representation.structure.themes.colorThemeRegistry.add(CustomColorThemeProvider);
this.plugin.managers.lociLabels.addProvider(StripedResidues.labelProvider!); this.plugin.managers.lociLabels.addProvider(StripedResidues.labelProvider!);
this.plugin.customModelProperties.register(StripedResidues.propertyProvider, true); this.plugin.customModelProperties.register(StripedResidues.propertyProvider, true);
} }
...@@ -103,6 +105,13 @@ class BasicWrapper { ...@@ -103,6 +105,13 @@ class BasicWrapper {
} }
}); });
}, },
applyCustomTheme: async () => {
this.plugin.dataTransaction(async () => {
for (const s of this.plugin.managers.structure.hierarchy.current.structures) {
await this.plugin.managers.structure.component.updateRepresentationsTheme(s.components, { color: CustomColorThemeProvider.name as any });
}
});
},
applyDefault: async () => { applyDefault: async () => {
this.plugin.dataTransaction(async () => { this.plugin.dataTransaction(async () => {
for (const s of this.plugin.managers.structure.hierarchy.current.structures) { for (const s of this.plugin.managers.structure.hierarchy.current.structures) {
......
...@@ -18,11 +18,24 @@ export type ColorType = 'uniform' | 'instance' | 'group' | 'groupInstance' | 've ...@@ -18,11 +18,24 @@ export type ColorType = 'uniform' | 'instance' | 'group' | 'groupInstance' | 've
export type ColorData = { export type ColorData = {
uColor: ValueCell<Vec3>, uColor: ValueCell<Vec3>,
tColor: ValueCell<TextureImage<Uint8Array>>, tColor: ValueCell<TextureImage<Uint8Array>>,
tPalette: ValueCell<TextureImage<Uint8Array>>,
uColorTexDim: ValueCell<Vec2>, uColorTexDim: ValueCell<Vec2>,
dColorType: ValueCell<string>, dColorType: ValueCell<string>,
dUsePalette: ValueCell<boolean>,
} }
export function createColors(locationIt: LocationIterator, positionIt: LocationIterator, colorTheme: ColorTheme<any>, colorData?: ColorData): ColorData { export function createColors(locationIt: LocationIterator, positionIt: LocationIterator, colorTheme: ColorTheme<any>, colorData?: ColorData): ColorData {
const data = _createColors(locationIt, positionIt, colorTheme, colorData);
if (colorTheme.palette) {
ValueCell.updateIfChanged(data.dUsePalette, true);
updatePaletteTexture(colorTheme.palette, data.tPalette);
} else {
ValueCell.updateIfChanged(data.dUsePalette, false);
}
return data;
}
function _createColors(locationIt: LocationIterator, positionIt: LocationIterator, colorTheme: ColorTheme<any>, colorData?: ColorData): ColorData {
switch (Geometry.getGranularity(locationIt, colorTheme.granularity)) { switch (Geometry.getGranularity(locationIt, colorTheme.granularity)) {
case 'uniform': return createUniformColor(locationIt, colorTheme.color, colorData); case 'uniform': return createUniformColor(locationIt, colorTheme.color, colorData);
case 'instance': return createInstanceColor(locationIt, colorTheme.color, colorData); case 'instance': return createInstanceColor(locationIt, colorTheme.color, colorData);
...@@ -42,18 +55,20 @@ export function createValueColor(value: Color, colorData?: ColorData): ColorData ...@@ -42,18 +55,20 @@ export function createValueColor(value: Color, colorData?: ColorData): ColorData
return { return {
uColor: ValueCell.create(Color.toVec3Normalized(Vec3(), value)), uColor: ValueCell.create(Color.toVec3Normalized(Vec3(), value)),
tColor: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }), tColor: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
tPalette: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
uColorTexDim: ValueCell.create(Vec2.create(1, 1)), uColorTexDim: ValueCell.create(Vec2.create(1, 1)),
dColorType: ValueCell.create('uniform'), dColorType: ValueCell.create('uniform'),
dUsePalette: ValueCell.create(false),
}; };
} }
} }
/** Creates color uniform */ /** Creates color uniform */
export function createUniformColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData { function createUniformColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
return createValueColor(color(NullLocation, false), colorData); return createValueColor(color(NullLocation, false), colorData);
} }
export function createTextureColor(colors: TextureImage<Uint8Array>, type: ColorType, colorData?: ColorData): ColorData { function createTextureColor(colors: TextureImage<Uint8Array>, type: ColorType, colorData?: ColorData): ColorData {
if (colorData) { if (colorData) {
ValueCell.update(colorData.tColor, colors); ValueCell.update(colorData.tColor, colors);
ValueCell.update(colorData.uColorTexDim, Vec2.create(colors.width, colors.height)); ValueCell.update(colorData.uColorTexDim, Vec2.create(colors.width, colors.height));
...@@ -63,14 +78,16 @@ export function createTextureColor(colors: TextureImage<Uint8Array>, type: Color ...@@ -63,14 +78,16 @@ export function createTextureColor(colors: TextureImage<Uint8Array>, type: Color
return { return {
uColor: ValueCell.create(Vec3()), uColor: ValueCell.create(Vec3()),
tColor: ValueCell.create(colors), tColor: ValueCell.create(colors),
tPalette: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
uColorTexDim: ValueCell.create(Vec2.create(colors.width, colors.height)), uColorTexDim: ValueCell.create(Vec2.create(colors.width, colors.height)),
dColorType: ValueCell.create(type), dColorType: ValueCell.create(type),
dUsePalette: ValueCell.create(false),
}; };
} }
} }
/** Creates color texture with color for each instance */ /** Creates color texture with color for each instance */
export function createInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData { function createInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
const { instanceCount } = locationIt; const { instanceCount } = locationIt;
const colors = createTextureImage(Math.max(1, instanceCount), 3, Uint8Array, colorData && colorData.tColor.ref.value.array); const colors = createTextureImage(Math.max(1, instanceCount), 3, Uint8Array, colorData && colorData.tColor.ref.value.array);
locationIt.reset(); locationIt.reset();
...@@ -83,7 +100,7 @@ export function createInstanceColor(locationIt: LocationIterator, color: Locatio ...@@ -83,7 +100,7 @@ export function createInstanceColor(locationIt: LocationIterator, color: Locatio
} }
/** Creates color texture with color for each group (i.e. shared across instances) */ /** Creates color texture with color for each group (i.e. shared across instances) */
export function createGroupColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData { function createGroupColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
const { groupCount } = locationIt; const { groupCount } = locationIt;
const colors = createTextureImage(Math.max(1, groupCount), 3, Uint8Array, colorData && colorData.tColor.ref.value.array); const colors = createTextureImage(Math.max(1, groupCount), 3, Uint8Array, colorData && colorData.tColor.ref.value.array);
locationIt.reset(); locationIt.reset();
...@@ -95,7 +112,7 @@ export function createGroupColor(locationIt: LocationIterator, color: LocationCo ...@@ -95,7 +112,7 @@ export function createGroupColor(locationIt: LocationIterator, color: LocationCo
} }
/** Creates color texture with color for each group in each instance */ /** Creates color texture with color for each group in each instance */
export function createGroupInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData { function createGroupInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
const { groupCount, instanceCount } = locationIt; const { groupCount, instanceCount } = locationIt;
const count = instanceCount * groupCount; const count = instanceCount * groupCount;
const colors = createTextureImage(Math.max(1, count), 3, Uint8Array, colorData && colorData.tColor.ref.value.array); const colors = createTextureImage(Math.max(1, count), 3, Uint8Array, colorData && colorData.tColor.ref.value.array);
...@@ -108,7 +125,7 @@ export function createGroupInstanceColor(locationIt: LocationIterator, color: Lo ...@@ -108,7 +125,7 @@ export function createGroupInstanceColor(locationIt: LocationIterator, color: Lo
} }
/** Creates color texture with color for each vertex (i.e. shared across instances) */ /** Creates color texture with color for each vertex (i.e. shared across instances) */
export function createVertexColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData { function createVertexColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
const { groupCount, stride } = locationIt; const { groupCount, stride } = locationIt;
const colors = createTextureImage(Math.max(1, groupCount), 3, Uint8Array, colorData && colorData.tColor.ref.value.array); const colors = createTextureImage(Math.max(1, groupCount), 3, Uint8Array, colorData && colorData.tColor.ref.value.array);
locationIt.reset(); locationIt.reset();
...@@ -124,7 +141,7 @@ export function createVertexColor(locationIt: LocationIterator, color: LocationC ...@@ -124,7 +141,7 @@ export function createVertexColor(locationIt: LocationIterator, color: LocationC
} }
/** Creates color texture with color for each vertex in each instance */ /** Creates color texture with color for each vertex in each instance */
export function createVertexInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData { function createVertexInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
const { groupCount, instanceCount, stride } = locationIt; const { groupCount, instanceCount, stride } = locationIt;
const count = instanceCount * groupCount; const count = instanceCount * groupCount;
const colors = createTextureImage(Math.max(1, count), 3, Uint8Array, colorData && colorData.tColor.ref.value.array); const colors = createTextureImage(Math.max(1, count), 3, Uint8Array, colorData && colorData.tColor.ref.value.array);
...@@ -138,3 +155,34 @@ export function createVertexInstanceColor(locationIt: LocationIterator, color: L ...@@ -138,3 +155,34 @@ export function createVertexInstanceColor(locationIt: LocationIterator, color: L
} }
return createTextureColor(colors, 'vertexInstance', colorData); return createTextureColor(colors, 'vertexInstance', colorData);
} }
function updatePaletteTexture(palette: ColorTheme.Palette, cell: ValueCell<TextureImage<Uint8Array>>) {
let isSynced = true;
const texture = cell.ref.value;
if (palette.colors.length !== texture.width) {
isSynced = false;
} else {
const data = texture.array;
let o = 0;
for (const c of palette.colors) {
const [r, g, b] = Color.toRgb(c);
if (data[o++] !== r || data[o++] !== g || data[o++] !== b) {
isSynced = false;
break;
}
}
}
if (isSynced) return;
const array = new Uint8Array(palette.colors.length * 3);
let o = 0;
for (const c of palette.colors) {
const [r, g, b] = Color.toRgb(c);
array[o++] = r;
array[o++] = g;
array[o++] = b;
}
ValueCell.update(cell, { array, height: 1, width: palette.colors.length });
}
\ No newline at end of file
...@@ -185,7 +185,9 @@ export const ColorSchema = { ...@@ -185,7 +185,9 @@ export const ColorSchema = {
uColor: UniformSpec('v3', 'material'), uColor: UniformSpec('v3', 'material'),
uColorTexDim: UniformSpec('v2'), uColorTexDim: UniformSpec('v2'),
tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'), tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
tPalette: TextureSpec('image-uint8', 'rgb', 'ubyte', 'linear'),
dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance', 'vertex', 'vertexInstance']), dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance', 'vertex', 'vertexInstance']),
dUsePalette: DefineSpec('boolean'),
} as const; } as const;
export type ColorSchema = typeof ColorSchema export type ColorSchema = typeof ColorSchema
export type ColorValues = Values<ColorSchema> export type ColorValues = Values<ColorSchema>
......
...@@ -14,6 +14,10 @@ export const assign_color_varying = ` ...@@ -14,6 +14,10 @@ export const assign_color_varying = `
vColor.rgb = readFromTexture(tColor, int(aInstance) * uVertexCount + VertexID, uColorTexDim).rgb; vColor.rgb = readFromTexture(tColor, int(aInstance) * uVertexCount + VertexID, uColorTexDim).rgb;
#endif #endif
#ifdef dUsePalette
vPaletteV = ((vColor.r * 256.0 * 256.0 * 255.0 + vColor.g * 256.0 * 255.0 + vColor.b * 255.0) - 1.0) / 16777215.0;
#endif
#ifdef dOverpaint #ifdef dOverpaint
vOverpaint = readFromTexture(tOverpaint, aInstance * float(uGroupCount) + group, uOverpaintTexDim); vOverpaint = readFromTexture(tOverpaint, aInstance * float(uGroupCount) + group, uOverpaintTexDim);
#endif #endif
......
export const assign_material_color = ` export const assign_material_color = `
#if defined(dRenderVariant_color) #if defined(dRenderVariant_color)
#if defined(dColorType_uniform) #if defined(dUsePalette)
vec4 material = vec4(texture2D(tPalette, vec2(vPaletteV, 0.5)).rgb, uAlpha);
#elif defined(dColorType_uniform)
vec4 material = vec4(uColor, uAlpha); vec4 material = vec4(uColor, uAlpha);
#elif defined(dColorType_varying) #elif defined(dColorType_varying)
vec4 material = vec4(vColor.rgb, uAlpha); vec4 material = vec4(vColor.rgb, uAlpha);
......
...@@ -21,4 +21,9 @@ export const color_frag_params = ` ...@@ -21,4 +21,9 @@ export const color_frag_params = `
varying float vGroup; varying float vGroup;
varying float vTransparency; varying float vTransparency;
#endif #endif
#ifdef dUsePalette
uniform sampler2D tPalette;
varying float vPaletteV;
#endif
`; `;
\ No newline at end of file
...@@ -30,4 +30,8 @@ export const color_vert_params = ` ...@@ -30,4 +30,8 @@ export const color_vert_params = `
uniform vec2 uTransparencyTexDim; uniform vec2 uTransparencyTexDim;
uniform sampler2D tTransparency; uniform sampler2D tTransparency;
#endif #endif
#ifdef dUsePalette
varying float vPaletteV;
#endif
`; `;
\ No newline at end of file
...@@ -44,6 +44,9 @@ interface ColorTheme<P extends PD.Params> { ...@@ -44,6 +44,9 @@ interface ColorTheme<P extends PD.Params> {
readonly granularity: ColorType readonly granularity: ColorType
readonly color: LocationColor readonly color: LocationColor
readonly props: Readonly<PD.Values<P>> readonly props: Readonly<PD.Values<P>>
// if palette is defined, 24bit RGB color value normalized to interval [0, 1]
// is used as index to the colors
readonly palette?: Readonly<ColorTheme.Palette>
readonly contextHash?: number readonly contextHash?: number
readonly description?: string readonly description?: string
readonly legend?: Readonly<ScaleLegend | TableLegend> readonly legend?: Readonly<ScaleLegend | TableLegend>
...@@ -58,6 +61,12 @@ namespace ColorTheme { ...@@ -58,6 +61,12 @@ namespace ColorTheme {
Misc = 'Miscellaneous', Misc = 'Miscellaneous',
} }
export interface Palette {
colors: Color[]
}
export const PaletteScale = (1 << 24) - 1;
export type Props = { [k: string]: any } export type Props = { [k: string]: any }
export type Factory<P extends PD.Params> = (ctx: ThemeDataContext, props: PD.Values<P>) => ColorTheme<P> export type Factory<P extends PD.Params> = (ctx: ThemeDataContext, props: PD.Values<P>) => ColorTheme<P>
export const EmptyFactory = () => Empty; export const EmptyFactory = () => Empty;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment