Skip to content

Commit

Permalink
Color Ramp working
Browse files Browse the repository at this point in the history
  • Loading branch information
newcat committed Feb 25, 2024
1 parent f550ec6 commit 4e33376
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 10 deletions.
46 changes: 43 additions & 3 deletions src/graph/nodes/colors/ColorRampNode.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { markRaw } from "vue";
import { NodeInterface, defineNode } from "baklavajs";
import { ColorArrayInterface, ColorSingleInterface, SliderInterface } from "../../interfaces";
import { Color } from "../../colors";
import { Color, mix } from "../../colors";
import { LmsCalculationContext } from "../../types";
import ColorRampOption from "../../options/ColorRampOption.vue";

export interface ColorRampStop {
Expand All @@ -11,15 +12,15 @@ export interface ColorRampStop {
}

export interface ColorRampValue {
mode: "RGB" | "HSV" | "HSL";
mode: "rgb" | "hsl";
stops: ColorRampStop[];
}

export const ColorRampNode = defineNode({
type: "Color Ramp",
inputs: {
colorRamp: () =>
new NodeInterface<ColorRampValue>("Color Ramp", { mode: "RGB", stops: [] })
new NodeInterface<ColorRampValue>("Color Ramp", { mode: "rgb", stops: [] })
.setPort(false)
.setComponent(markRaw(ColorRampOption)),
factor: () => new SliderInterface("Factor", 0.5, 0, 1),
Expand All @@ -28,4 +29,43 @@ export const ColorRampNode = defineNode({
colorRamp: () => new ColorArrayInterface("Color Ramp"),
value: () => new ColorSingleInterface("Value").setComponent(null),
},
calculate({ colorRamp, factor }, ctx: LmsCalculationContext) {
if (colorRamp.stops.length === 0) {
return {
colorRamp: [[0, 0, 0]] as Color[],
value: [0, 0, 0] as Color,
};
} else if (colorRamp.stops.length === 1) {
return {
colorRamp: [colorRamp.stops[0].color],
value: colorRamp.stops[0].color,
};
}

const stops = colorRamp.stops.sort((a, b) => a.position - b.position);

function getColorAtPosition(position: number) {
const nextStopIndex = stops.findIndex((stop) => stop.position > position);
if (nextStopIndex === -1) {
return stops[stops.length - 1].color;
} else if (nextStopIndex === 0) {
return stops[0].color;
}
const prevStop = stops[nextStopIndex - 1];
const nextStop = stops[nextStopIndex];
const f = (position - prevStop.position) / (nextStop.position - prevStop.position);
return mix(prevStop.color, nextStop.color, f, colorRamp.mode);
}

const ramp: Color[] = [];
for (let i = 0; i < ctx.globalValues.resolution; i++) {
const position = i / (ctx.globalValues.resolution - 1);
ramp.push(getColorAtPosition(position));
}

return {
colorRamp: ramp,
value: getColorAtPosition(Math.max(0, Math.min(1, factor))),
};
},
});
45 changes: 38 additions & 7 deletions src/graph/options/ColorRampOption.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@
<div class="flex gap-2">
<Button icon="mdi mdi-plus" text rounded @click="addStop" />
<Button icon="mdi mdi-minus" text rounded @click="removeStop" />
<Button icon="mdi mdi-dots-vertical" text rounded />
<Dropdown v-model="modelValue.mode" :options="modes" placeholder="Mode" />
<Button icon="mdi mdi-dots-vertical" text rounded @click="ctxMenu?.toggle($event)" />
<Menu ref="ctxMenu" :model="ctxMenuItems" :popup="true" />
<Dropdown v-model="modelValue.mode" :options="modes" option-label="label" option-value="value" placeholder="Mode" />
</div>
<div class="color-ramp" ref="colorRampEl">
<div class="color-ramp" ref="colorRampEl" @mousedown="selectedStopId = ''">
<template v-for="stop in modelValue.stops" :key="stop.id">
<div class="color-stop-indicator" :style="{ left: `${Math.round(100 * stop.position)}%` }"></div>
<div
class="color-stop-handle"
:class="{ '--selected': selectedStopId === stop.id }"
:style="{ left: `${Math.round(100 * stop.position)}%` }"
@mousedown="onHandleMousedown(stop)"
@mousedown.stop="onHandleMousedown(stop)"
></div>
</template>
</div>
Expand All @@ -24,18 +25,30 @@
</template>
<script setup lang="ts">
import { computed, ref } from "vue";
import { ComponentInstance, computed, ref } from "vue";
import { v4 as uuidv4 } from "uuid";
import type { InterpolationMode } from "chroma-js";
import Button from "primevue/button";
import Dropdown from "primevue/dropdown";
import Menu from "primevue/menu";
import ColorPicker from "./ColorPicker.vue";
import type { ColorRampStop, ColorRampValue } from "../nodes/colors/ColorRampNode";
const modes = ["RGB", "HSV", "HSL"];
const modes: Array<{ label: string; value: InterpolationMode }> = [
{ label: "RGB", value: "rgb" },
{ label: "HSL", value: "hsl" },
];
const ctxMenuItems = [
{ label: "Flip Color Ramp", command: flip },
{ label: "Distribute Stops from Left", command: distributeLeft },
{ label: "Distribute Evenly", command: distributeEvenly },
];
const modelValue = defineModel<ColorRampValue>({ required: true });
const colorRampEl = ref<HTMLElement>();
const ctxMenu = ref<ComponentInstance<typeof Menu>>();
const selectedStopId = ref("");
const selectedStop = computed(() => modelValue.value.stops.find((s) => s.id === selectedStopId.value));
Expand All @@ -47,7 +60,11 @@ const cssLinearGradient = computed(() => {
return `rgb(${modelValue.value.stops[0].color[0]}, ${modelValue.value.stops[0].color[1]}, ${modelValue.value.stops[0].color[2]})`;
}
const args: string[] = ["to right"];
const cssColorSpace = {
rgb: "srgb",
hsl: "hsl",
};
const args: string[] = [`to right in ${cssColorSpace[modelValue.value.mode]}`];
for (const stop of modelValue.value.stops.toSorted((a, b) => a.position - b.position)) {
args.push(`rgb(${stop.color[0]}, ${stop.color[1]}, ${stop.color[2]}) ${Math.round(100 * stop.position)}%`);
}
Expand Down Expand Up @@ -88,6 +105,20 @@ function onMouseMove(event: MouseEvent) {
stop.position = Math.max(0, Math.min(1, (event.clientX - rect.left) / rect.width));
}
}
function flip() {
modelValue.value.stops = modelValue.value.stops.map((s) => ({ ...s, position: 1 - s.position }));
}
function distributeLeft() {
modelValue.value.stops.sort((a, b) => a.position - b.position);
modelValue.value.stops = modelValue.value.stops.map((s, i) => ({ ...s, position: i / modelValue.value.stops.length }));
}
function distributeEvenly() {
modelValue.value.stops.sort((a, b) => a.position - b.position);
modelValue.value.stops = modelValue.value.stops.map((s, i) => ({ ...s, position: i / (modelValue.value.stops.length - 1) }));
}
</script>
<style scoped>
Expand Down

0 comments on commit 4e33376

Please sign in to comment.