Files
Vectrace/src/components/svg-tracer-options.tsx
T
2026-06-29 09:41:02 +02:00

177 lines
7.0 KiB
TypeScript

import { useEffect, useState } from "react";
import type { ImageEditor } from "../handler/image-editor";
import type { TracerOptions } from "../interface";
import { NumericInputWithLabelTable } from "./numeric-labeld-input";
import styled from "styled-components";
import { Button } from "../styles";
import { FaBezierCurve } from "react-icons/fa6";
import { VerticalSlider } from "./vertical-slider";
import { TRANSPARENCY_THRESHOLD_DEFAULT } from "../svg-tracer";
import type { VectraceHandler } from "../handler/vectrace-handler";
import { clamp } from "lodash";
import { Hint } from "./tooltip";
const Table = styled.table`
padding-right: 4px;
`;
const WrapperColumn = styled.div`
display: flex;
flex-direction: column;
`;
const WrapperRow = styled.div`
display: flex;
flex-direction: row;
height: 100px;
`;
const LayerPanel = styled.div`
display: flex;
flex-direction: column;
width: 100px;
overflow: auto;
height: 97px;
`;
const ToggleButton = styled.button<{ $enabled: boolean }>`
background-color: ${({ $enabled }) => ($enabled ? "var(--secondary-color)" : "var(--primary-color)")};
color: white;
border: 1px solid white;
&:hover {
color: var(--secondary-color);
background-color: white;
}
`;
export function SvgTracerOptions({ selected, handler }: { selected: ImageEditor[]; handler: VectraceHandler }) {
const [options, setOptions] = useState(selected[0]?.tracerOptions ?? {});
const [updated, setUpdated] = useState(false);
useEffect(() => {
setOptions(selected[0]?.tracerOptions ?? {});
setUpdated(true);
}, [selected.map((e) => `${e.id}${JSON.stringify(e.svgData.options)}`).join("")]);
const redraw = () => {
selected.forEach((e) => {
e.setTracerSvg({ ...options });
});
setUpdated(true);
};
const setValue = (key: keyof TracerOptions, value: any) => {
setOptions({ ...options, [key]: value });
setUpdated(false);
};
return (
<WrapperRow>
<Hint
hint={
"Transparency cutoff — pixels more transparent than this value are ignored when tracing. " +
"Lower = only opaque pixels traced, Higher = transparent pixels included in the path."
}
>
<VerticalSlider
max={255}
min={0}
value={options.transparencyThreshold ?? TRANSPARENCY_THRESHOLD_DEFAULT}
onLabelClick={async () => {
const value = await handler.popup.prompt(
"Transparency threshold",
"Enter transparency threshold between 0-255",
(options.transparencyThreshold ?? TRANSPARENCY_THRESHOLD_DEFAULT).toString(),
);
if (value) {
const int = parseInt(value, 10);
if (!isNaN(int)) {
setValue("transparencyThreshold", clamp(int, 0, 255));
}
}
}}
onChange={(value) => {
setValue("transparencyThreshold", value);
}}
/>
</Hint>
<WrapperColumn>
<Button
$active={!updated}
onClick={() => {
redraw();
}}
>
<span>
<span>Redraw</span>
<FaBezierCurve style={{ paddingLeft: 10 }} />
</span>
</Button>
<WrapperRow>
<Table>
<tbody>
<Hint
hint={
"Line threshold — how closely straight lines must follow the original shape. Lower = more accurate, more points."
}
>
<NumericInputWithLabelTable
name="Ltres"
value={options.ltres}
onChange={(value) => setValue("ltres", value)}
/>
</Hint>
<Hint
hint={
"Curve threshold — how closely curves must follow the original shape. Lower = smoother curves, more points."
}
>
<NumericInputWithLabelTable
name="Qtres"
value={options.qtres}
onChange={(value) => setValue("qtres", value)}
/>
</Hint>
<Hint
hint={
"Ignore tiny paths smaller than this many pixels. Higher = cleaner output, but small details get dropped."
}
>
<NumericInputWithLabelTable
name="Path omit"
value={options.pathomit}
onChange={(value) => setValue("pathomit", value)}
/>
</Hint>
<Hint hint={"Decimal precision of path coordinates. Lower = smaller file, slightly less accurate."}>
<NumericInputWithLabelTable
name="Round corners"
value={options.roundcoords}
onChange={(value) => setValue("roundcoords", value)}
/>
</Hint>
</tbody>
</Table>
<LayerPanel>
{(options.allowedPaths || []).map((e, i) => {
return (
<ToggleButton
key={i}
$enabled={e}
onClick={() => {
const array = options?.allowedPaths ? [...options.allowedPaths] : [];
array[i] = !array[i];
setValue("allowedPaths", array);
}}
>
{`${i + 1} Layer`}
</ToggleButton>
);
})}
</LayerPanel>
</WrapperRow>
</WrapperColumn>
</WrapperRow>
);
}