Skip to content
Open
Show file tree
Hide file tree
Changes from 87 commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
33b0c5f
saving draft
uuuulala Nov 4, 2025
0cf9762
saving
uuuulala Nov 4, 2025
2917012
draft
uuuulala Nov 9, 2025
777ade9
draft
uuuulala Nov 9, 2025
7ef44c0
Merge remote-tracking branch 'origin/main' into logo-folds
uuuulala Nov 9, 2025
41df7b3
test logos rotation
uuuulala Nov 10, 2025
2272dbb
new params, advanced logic, basic coloring
uuuulala Nov 10, 2025
852955b
fixing build
uuuulala Nov 10, 2025
4db5009
u_angle origin fix
uuuulala Nov 11, 2025
8e9af67
sample logos update
uuuulala Nov 11, 2025
00fbb82
halftone lines template
uuuulala Nov 11, 2025
81c38af
u_colors support
uuuulala Nov 11, 2025
abc8d05
tweaks
uuuulala Nov 11, 2025
7429f5e
alpha-mask original idea
uuuulala Nov 11, 2025
935ac64
back to 512 workingSize
uuuulala Nov 11, 2025
42cea26
saving progress
uuuulala Nov 12, 2025
b48e0ef
saving progress on halftone lines
uuuulala Nov 12, 2025
d046bdc
saving progress on both
uuuulala Nov 13, 2025
5a78358
halftoneLines clean
uuuulala Nov 13, 2025
8f8b7bd
halftoneLines blur limit
uuuulala Nov 15, 2025
e031f8d
antialiased shift
uuuulala Nov 15, 2025
a58c3c8
saving progress on folds
uuuulala Nov 15, 2025
e19fa8d
new logo-spots sketch
uuuulala Nov 15, 2025
059e3e2
halftone lines cleanup + origColor support
uuuulala Nov 16, 2025
6081d42
folds fix and clean up
uuuulala Nov 16, 2025
1299796
logo list update
uuuulala Nov 17, 2025
6faf74a
folds adjusted
uuuulala Nov 17, 2025
e7b4fc3
blobs dynamic colors + cleanup
uuuulala Nov 17, 2025
ceb3e5f
folds polishing
uuuulala Nov 17, 2025
93a95b7
halftone presets
uuuulala Nov 17, 2025
de4f70f
blobsLogo renaming
uuuulala Nov 17, 2025
69b9b0b
halftoneLines classic preset
uuuulala Nov 17, 2025
4b58c9d
2 more folds presets
uuuulala Nov 18, 2025
3f458c9
warp sketch to save
uuuulala Nov 19, 2025
42ee0e1
progress
uuuulala Nov 24, 2025
99a2555
progress
uuuulala Nov 24, 2025
1101618
progress
uuuulala Nov 24, 2025
75ccfbb
clean-up
uuuulala Nov 24, 2025
543d1be
polishing
uuuulala Nov 24, 2025
c8c1b87
logos update
uuuulala Nov 24, 2025
78e776d
new preset
uuuulala Nov 24, 2025
4ba32cd
text replacements
uuuulala Nov 24, 2025
d013141
allowing more distortion
uuuulala Nov 24, 2025
6d26168
experimenting with 3d
uuuulala Nov 25, 2025
be3c3c0
blobs to voronoi
uuuulala Nov 25, 2025
1db44a9
blobs to fbm
uuuulala Nov 25, 2025
949db84
more experiments on 3d look
uuuulala Nov 25, 2025
0dfac04
GemSmoke component renamed + defs + controls reordering
uuuulala Nov 25, 2025
9c8e9d1
Merge branch 'main' into logo-folds
uuuulala Nov 25, 2025
6d1e6a7
gemSmoke defs update
uuuulala Nov 27, 2025
ebc396d
progress on 3d
uuuulala Nov 28, 2025
75e8b82
3d progress
uuuulala Nov 28, 2025
ca62a0a
3d progress
uuuulala Nov 28, 2025
1f79c24
Merge branch 'main' into logo-folds
uuuulala Nov 28, 2025
73a1a7b
grains on HTD
uuuulala Nov 28, 2025
14a18bc
adj
uuuulala Nov 28, 2025
57b6228
start on grid types
uuuulala Nov 29, 2025
606ddf3
new line params, polishing
uuuulala Nov 30, 2025
1190fff
consistent noise/angle distortion
uuuulala Nov 30, 2025
d49d0e9
progress
uuuulala Nov 30, 2025
978c99d
splitting grainSize
uuuulala Dec 1, 2025
3a0c33a
clean-up
uuuulala Dec 1, 2025
91ccdcb
presets
uuuulala Dec 1, 2025
494525c
leaving only the halftone lines
uuuulala Dec 2, 2025
cc938ef
added u_gridAngleDistortion back to radial grid
uuuulala Dec 2, 2025
ecba100
shaders defs
uuuulala Dec 2, 2025
0941a18
shader description added
uuuulala Dec 2, 2025
35e92c2
smoothness blur fixed ranges
uuuulala Dec 9, 2025
6ee6f74
transparency support + GLSL clean-up
uuuulala Dec 9, 2025
7f6834d
Merge remote-tracking branch 'origin/main' into halftone-lines
uuuulala Dec 9, 2025
c604564
sync test logo set with main
uuuulala Dec 9, 2025
336c72f
registry example added, formatting
uuuulala Dec 9, 2025
819786e
arty preset
uuuulala Dec 11, 2025
67e3db4
Merge remote-tracking branch 'origin/main' into halftone-lines
uuuulala Dec 17, 2025
52866db
replacing getImageUV with vertex uv
uuuulala Dec 17, 2025
d53ec64
Merge branch 'main' into halftone-lines
uuuulala Aug 19, 2026
da9e17e
non-glsl llm pass
uuuulala Aug 19, 2026
02c4ce2
dead code removed, gridRotation fixed for the radial mode
uuuulala Aug 19, 2026
f1ea14c
sampling blur to mipmap smoothing + new colorSmoothing prop
uuuulala Aug 20, 2026
1b489e6
size => gridSize
uuuulala Aug 20, 2026
18d7ea1
AA fixed
uuuulala Aug 20, 2026
15db35c
strokeOverflow back
uuuulala Aug 20, 2026
84ee819
keepGaps / keepStrokes
uuuulala Aug 20, 2026
bc53282
AA
uuuulala Aug 20, 2026
a55d87a
softness x0.5
uuuulala Aug 20, 2026
bbd4715
single gridOffset
uuuulala Aug 20, 2026
d9803b7
smaller gridOffset for noise
uuuulala Aug 21, 2026
cf057f8
grids
uuuulala Aug 21, 2026
2fd8981
p
uuuulala Aug 22, 2026
1212925
grids types & offsets
uuuulala Aug 22, 2026
0860ff8
3-color scheme
uuuulala Aug 23, 2026
b4ab0cf
-
uuuulala Aug 23, 2026
c2cf3ee
-
uuuulala Aug 23, 2026
903487f
presets
uuuulala Aug 23, 2026
d281990
preset
uuuulala Aug 23, 2026
20adf8c
presets
uuuulala Aug 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,19 @@
"type": "registry:component"
}
]
},
{
"name": "halftone-lines",
"type": "registry:component",
"title": "Halftone Lines Example",
"description": "Halftone Lines shader example.",
"dependencies": ["@paper-design/shaders-react"],
"files": [
{
"path": "registry/halftone-lines-example.tsx",
"type": "registry:component"
}
]
}
]
}
7 changes: 7 additions & 0 deletions docs/registry/halftone-lines-example.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use client';

import { HalftoneLines, HalftoneLinesProps } from '@paper-design/shaders-react';

export function HalftoneLinesExample(props: HalftoneLinesProps) {
return <HalftoneLines style={{ position: 'fixed', width: '100%', height: '100%' }} {...props} />;
}
9 changes: 9 additions & 0 deletions docs/src/app/(shaders)/halftone-lines/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Metadata } from 'next';

export const metadata: Metadata = {
title: 'Halftone Lines Filter • Paper',
};

export default function Layout({ children }: { children: React.ReactNode }) {
return <>{children}</>;
}
131 changes: 131 additions & 0 deletions docs/src/app/(shaders)/halftone-lines/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
'use client';

import { HalftoneLines, halftoneLinesPresets } from '@paper-design/shaders-react';
import { useControls, button, folder } from 'leva';
import { setParamsSafe, useResetLevaParams } from '@/helpers/use-reset-leva-params';
import { usePresetHighlight } from '@/helpers/use-preset-highlight';
import { cleanUpLevaParams } from '@/helpers/clean-up-leva-params';
import { HalftoneLinesGrid, HalftoneLinesGrids, ShaderFit } from '@paper-design/shaders';
import { levaImageButton } from '@/helpers/leva-image-button';
import { useState, useEffect, useCallback } from 'react';
import { toHsla } from '@/helpers/color-utils';
import { ShaderDetails } from '@/components/shader-details';
import { halftoneLinesDef } from '@/shader-defs/halftone-lines-def';
import { ShaderContainer } from '@/components/shader-container';
import { useUrlParams } from '@/helpers/use-url-params';

const { worldWidth, worldHeight, ...defaults } = halftoneLinesPresets[0].params;

const imageFiles = [
'001.webp',
'002.webp',
'003.webp',
'004.webp',
'005.webp',
'006.webp',
'007.webp',
'008.webp',
'009.webp',
'0010.webp',
'0011.webp',
'0012.webp',
'0013.webp',
'0014.webp',
'0015.webp',
'0016.webp',
'0017.webp',
'0018.webp',
] as const;

const HalftoneLinesWithControls = () => {
const [imageIdx, setImageIdx] = useState(-1);
const [image, setImage] = useState<HTMLImageElement | string>('/images/image-filters/0018.webp');

useEffect(() => {
if (imageIdx >= 0) {
const name = imageFiles[imageIdx];
const img = new Image();
img.src = `/images/image-filters/${name}`;
img.onload = () => setImage(img);
}
}, [imageIdx]);

const handleClick = useCallback(() => {
setImageIdx((prev) => (prev + 1) % imageFiles.length);
}, []);

const setImageWithoutStatus = useCallback((img?: HTMLImageElement) => {
setImage(img ?? '');
setImageIdx(-1);
}, []);

const [params, setParams] = useControls(() => {
const presets = Object.fromEntries(
halftoneLinesPresets.map(({ name, params: { worldWidth, worldHeight, ...preset } }) => [
name,
button(() => setParamsSafe(params, setParams, preset)),
])
);
return {
colorBack: { value: toHsla(defaults.colorBack), order: 100 },
colorFront: { value: toHsla(defaults.colorFront), order: 101 },
originalColors: { value: defaults.originalColors, order: 102 },

contrast: { value: defaults.contrast, min: 0, max: 1, order: 200 },
inverted: { value: defaults.inverted, order: 201 },
smoothness: { value: defaults.smoothness, min: 0, max: 1, order: 202 },
colorSmoothness: { value: defaults.colorSmoothness, min: 0, max: 1, order: 203 },
strokeWidth: { value: defaults.strokeWidth, min: 0.05, max: 1, order: 204 },
softness: { value: defaults.softness, min: 0, max: 1, order: 205 },
keepGaps: { value: defaults.keepGaps, order: 206 },
keepStrokes: { value: defaults.keepStrokes, order: 207 },

grid: {
value: defaults.grid,
options: Object.keys(HalftoneLinesGrids) as HalftoneLinesGrid[],
order: 250,
},
gridSize: { value: defaults.gridSize, min: 0, max: 1, order: 251 },
gridOffset: { value: defaults.gridOffset, min: -1, max: 1, order: 252 },
gridRotation: { value: defaults.gridRotation, min: 0, max: 360, order: 254 },
gridAngleDistortion: { value: defaults.gridAngleDistortion, min: 0, max: 1, order: 255 },
gridNoiseDistortion: { value: defaults.gridNoiseDistortion, min: 0, max: 1, order: 256 },

grainMixer: { value: defaults.grainMixer, min: 0, max: 1, order: 350 },
grainMixerSize: { value: defaults.grainMixerSize, min: 0, max: 1, order: 351 },
grainOverlay: { value: defaults.grainOverlay, min: 0, max: 1, order: 352 },
grainOverlaySize: { value: defaults.grainOverlaySize, min: 0, max: 1, order: 353 },

scale: { value: defaults.scale, min: 0.1, max: 4, order: 400 },
fit: { value: defaults.fit, options: ['contain', 'cover'] as ShaderFit[], order: 450 },
Image: folder(
{
'Upload image': levaImageButton(setImageWithoutStatus),
},
{ order: 0 }
),
Presets: folder(presets, { order: -1 }),
};
});

// Reset to defaults on mount, so that Leva doesn't show values from other
// shaders when navigating (if two shaders have a color1 param for example)
useResetLevaParams(params, setParams, defaults);
useUrlParams(params, setParams, halftoneLinesDef);
usePresetHighlight(halftoneLinesPresets, params);
cleanUpLevaParams(params);

return (
<>
<ShaderContainer shaderDef={halftoneLinesDef} currentParams={params}>
<HalftoneLines onClick={handleClick} {...params} image={image} />
</ShaderContainer>
<div onClick={handleClick} className="mx-auto mt-16 mb-48 w-fit text-base text-current/70 select-none">
Click to change the sample image
</div>
<ShaderDetails shaderDef={halftoneLinesDef} currentParams={params} />
</>
);
};

export default HalftoneLinesWithControls;
184 changes: 184 additions & 0 deletions docs/src/shader-defs/halftone-lines-def.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import { halftoneLinesPresets } from '@paper-design/shaders-react';
import type { ShaderDef } from './shader-def-types';
import { staticImageCommonParams } from './common-param-def';

const defaultParams = halftoneLinesPresets[0].params;

export const halftoneLinesDef: ShaderDef = {
name: 'Halftone lines',
description:
'Image filter pattern made of a line grid with optional stroke thickness and grid distortion; offering multiple grid types, grid distortions, and color palettes',
params: [
{
name: 'image',
type: 'HTMLImageElement | string',
description: 'The image to use for the effect',
},
{
name: 'colorBack',
type: 'string',
defaultValue: defaultParams.colorBack,
isColor: true,
description: 'Background color',
},
{
name: 'colorFront',
type: 'string',
defaultValue: defaultParams.colorFront,
isColor: true,
description: 'Foreground color, needs originalColors off',
},
{
name: 'originalColors',
type: 'boolean',
defaultValue: defaultParams.originalColors,
description: 'Use the sampled image’s original colors instead of colorFront',
options: ['true', 'false'],
},
{
name: 'contrast',
type: 'number',
min: 0,
max: 1,
defaultValue: defaultParams.contrast,
description: 'Contrast applied to the sampled image',
},
{
name: 'inverted',
type: 'boolean',
defaultValue: defaultParams.inverted,
description: 'Inverts the image luminance, needs contrast > 0',
options: ['true', 'false'],
},
{
name: 'smoothness',
type: 'number',
min: 0,
max: 1,
defaultValue: defaultParams.smoothness,
description: 'Smoothing applied to the luminance that shapes the strokes; the image colors stay sharp',
},
{
name: 'colorSmoothness',
type: 'number',
min: 0,
max: 1,
defaultValue: defaultParams.colorSmoothness,
description: 'Smoothing applied to the sampled image colors, needs originalColors on',
},
{
name: 'strokeWidth',
type: 'number',
min: 0.05,
max: 1,
defaultValue: defaultParams.strokeWidth,
description: 'Stroke width relative to the grid cell; at 1 the strokes fill the cell completely',
},
Comment thread
cursor[bot] marked this conversation as resolved.
{
name: 'softness',
type: 'number',
min: 0,
max: 1,
defaultValue: defaultParams.softness,
description:
'Softness of the stroke edges as a fraction of the grid cell; at 1 the stripes blur out into flat tone',
},
{
name: 'keepGaps',
type: 'boolean',
defaultValue: defaultParams.keepGaps,
description: 'Keeps a two pixel gap between neighbouring strokes; off, they merge where they meet',
options: ['true', 'false'],
},
{
name: 'keepStrokes',
type: 'boolean',
defaultValue: defaultParams.keepStrokes,
description: 'Keeps strokes at a two pixel minimum width; off, they fade away in the lightest areas',
options: ['true', 'false'],
},
{
name: 'grid',
type: 'enum',
defaultValue: defaultParams.grid,
description: 'Grid type',
options: ['lines', 'radial', 'waves', 'noise'],
},
{
name: 'gridSize',
type: 'number',
min: 0,
max: 1,
defaultValue: defaultParams.gridSize,
description:
'Grid size relative to the canvas (the grid lives in object space, so it does not follow the image box)',
},
{
name: 'gridOffset',
type: 'number',
min: -1,
max: 1,
defaultValue: defaultParams.gridOffset,
description:
'Grid offset along the grid Y axis; one grid cell at either end for the lines and waves grids, canvas units for the radial distance from the image center to the ring center, and a quarter of that for noise',
},
{
name: 'gridRotation',
type: 'number',
min: 0,
max: 360,
defaultValue: defaultParams.gridRotation,
description: 'The grid rotation around the image center, with the radial grid needs a nonzero grid offset',
},
{
name: 'gridAngleDistortion',
type: 'number',
min: 0,
max: 1,
defaultValue: defaultParams.gridAngleDistortion,
description:
'Changing the grid rotation on the darker image areas, with the radial grid needs a nonzero grid offset',
},
{
name: 'gridNoiseDistortion',
type: 'number',
min: 0,
max: 1,
defaultValue: defaultParams.gridNoiseDistortion,
description: 'Applying noise to the grid on the darker image areas',
},
{
name: 'grainMixer',
type: 'number',
min: 0,
max: 1,
defaultValue: defaultParams.grainMixer,
description: 'Strength of grain distortion applied to strokes',
},
{
name: 'grainMixerSize',
type: 'number',
min: 0,
max: 1,
defaultValue: defaultParams.grainMixerSize,
description: 'The scale applied to the grain distortion, needs grainMixer > 0',
},
{
name: 'grainOverlay',
type: 'number',
min: 0,
max: 1,
defaultValue: defaultParams.grainOverlay,
description: 'Post-processing b/w grainy overlay',
},
{
name: 'grainOverlaySize',
type: 'number',
min: 0,
max: 1,
defaultValue: defaultParams.grainOverlaySize,
description: 'The scale applied to the grain overlay, needs grainOverlay > 0',
},
...staticImageCommonParams,
],
};
2 changes: 2 additions & 0 deletions docs/src/shader-defs/shader-defs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { waterDef } from './water-def';
import { wavesDef } from './waves-def';
import { halftoneDotsDef } from './halftone-dots-def';
import { halftoneCmykDef } from './halftone-cmyk-def';
import { halftoneLinesDef } from './halftone-lines-def';
import { heatmapDef } from './heatmap-def';
import { gemSmokeDef } from './gem-smoke-def';
import { lensDistortionDef } from './lens-distortion-def';
Expand Down Expand Up @@ -59,6 +60,7 @@ export const shaderDefs: ShaderDef[] = [
waterDef,
halftoneDotsDef,
halftoneCmykDef,
halftoneLinesDef,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shader omitted from homepage catalog

Medium Severity

halftoneLinesDef is registered and the /halftone-lines route exists, but home-thumbnails.ts has no Image Filters entry and docs/public/shaders/ has no halftone-lines.webp. The homepage catalog is the primary discovery list, so this shader stays hidden next to halftone dots and halftone cmyk.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1b489e6. Configure here.

heatmapDef,
gemSmokeDef,
lensDistortionDef,
Expand Down
4 changes: 4 additions & 0 deletions packages/shaders-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ export { GemSmoke, gemSmokePresets } from './shaders/gem-smoke.js';
export type { GemSmokeProps } from './shaders/gem-smoke.js';
export type { GemSmokeUniforms, GemSmokeParams } from '@paper-design/shaders';

export { HalftoneLines, halftoneLinesPresets } from './shaders/halftone-lines.js';
export type { HalftoneLinesProps } from './shaders/halftone-lines.js';
export type { HalftoneLinesUniforms, HalftoneLinesParams } from '@paper-design/shaders';

export { isPaperShaderElement, getShaderColorFromString } from '@paper-design/shaders';
export type { PaperShaderElement, ShaderFit, ShaderSizingParams, ShaderSizingUniforms } from '@paper-design/shaders';

Expand Down
Loading
Loading