-
Notifications
You must be signed in to change notification settings - Fork 146
Halftone lines shader #234
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 87 commits
33b0c5f
0cf9762
2917012
777ade9
7ef44c0
41df7b3
2272dbb
852955b
4db5009
8e9af67
00fbb82
81c38af
abc8d05
7429f5e
935ac64
42cea26
b48e0ef
d046bdc
5a78358
8f8b7bd
e031f8d
a58c3c8
e19fa8d
059e3e2
6081d42
1299796
6faf74a
e7b4fc3
ceb3e5f
93a95b7
de4f70f
69b9b0b
4b58c9d
3f458c9
42ee0e1
99a2555
1101618
75ccfbb
543d1be
c8c1b87
78e776d
4ba32cd
d013141
6d26168
be3c3c0
1db44a9
949db84
0dfac04
9c8e9d1
6d1e6a7
ebc396d
75e8b82
ca62a0a
1f79c24
73a1a7b
14a18bc
57b6228
606ddf3
1190fff
d49d0e9
978c99d
3a0c33a
91ccdcb
494525c
cc938ef
ecba100
0941a18
35e92c2
6ee6f74
7f6834d
c604564
336c72f
819786e
67e3db4
52866db
d53ec64
da9e17e
02c4ce2
f1ea14c
1b489e6
18d7ea1
15db35c
84ee819
bc53282
a55d87a
bbd4715
d9803b7
cf057f8
2fd8981
1212925
0860ff8
b4ab0cf
c2cf3ee
903487f
d281990
20adf8c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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} />; | ||
| } |
| 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}</>; | ||
| } |
| 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; |
| 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', | ||
| }, | ||
| { | ||
| 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, | ||
| ], | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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'; | ||
|
|
@@ -59,6 +60,7 @@ export const shaderDefs: ShaderDef[] = [ | |
| waterDef, | ||
| halftoneDotsDef, | ||
| halftoneCmykDef, | ||
| halftoneLinesDef, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shader omitted from homepage catalogMedium Severity
Reviewed by Cursor Bugbot for commit 1b489e6. Configure here. |
||
| heatmapDef, | ||
| gemSmokeDef, | ||
| lensDistortionDef, | ||
|
|
||


Uh oh!
There was an error while loading. Please reload this page.