@johnfmorton/some-shade 0.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +68 -0
- package/dist/some-shade.cjs.js +225 -0
- package/dist/some-shade.es.js +529 -0
- package/dist/some-shade.umd.js +225 -0
- package/package.json +30 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { CSSResult } from 'lit';
|
|
2
|
+
import { LitElement } from 'lit';
|
|
3
|
+
import { PropertyValues } from 'lit';
|
|
4
|
+
import { TemplateResult } from 'lit';
|
|
5
|
+
|
|
6
|
+
export declare interface EffectDefinition {
|
|
7
|
+
name: string;
|
|
8
|
+
fragmentShader: string;
|
|
9
|
+
vertexShader: string;
|
|
10
|
+
uniforms: UniformDefinition[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export declare function get(name: string): EffectDefinition | undefined;
|
|
14
|
+
|
|
15
|
+
export declare function list(): string[];
|
|
16
|
+
|
|
17
|
+
export declare function register(effect: EffectDefinition): void;
|
|
18
|
+
|
|
19
|
+
export declare class SomeShadeImage extends LitElement {
|
|
20
|
+
static styles: CSSResult;
|
|
21
|
+
src: string;
|
|
22
|
+
effect: string;
|
|
23
|
+
dotRadius: number;
|
|
24
|
+
gridSize: number;
|
|
25
|
+
angleC: number;
|
|
26
|
+
angleM: number;
|
|
27
|
+
angleY: number;
|
|
28
|
+
angleK: number;
|
|
29
|
+
duotoneColor: string;
|
|
30
|
+
angle: number;
|
|
31
|
+
threshold: number;
|
|
32
|
+
sortDirection: number;
|
|
33
|
+
sortSpan: number;
|
|
34
|
+
dotOffsetX: number;
|
|
35
|
+
dotOffsetY: number;
|
|
36
|
+
bgColor: string;
|
|
37
|
+
private _webglAvailable;
|
|
38
|
+
private _canvas;
|
|
39
|
+
private _gl;
|
|
40
|
+
private _programInfo;
|
|
41
|
+
private _textureInfo;
|
|
42
|
+
private _quadBuffer;
|
|
43
|
+
private _currentEffect;
|
|
44
|
+
private _image;
|
|
45
|
+
private _resizeObserver;
|
|
46
|
+
render(): TemplateResult<1>;
|
|
47
|
+
firstUpdated(): void;
|
|
48
|
+
updated(changed: PropertyValues): void;
|
|
49
|
+
disconnectedCallback(): void;
|
|
50
|
+
private _loadImage;
|
|
51
|
+
private _uploadTexture;
|
|
52
|
+
private _sizeCanvas;
|
|
53
|
+
private _handleResize;
|
|
54
|
+
private _setupProgram;
|
|
55
|
+
private _getUniformValues;
|
|
56
|
+
private _parseHexColor;
|
|
57
|
+
private _renderFrame;
|
|
58
|
+
private _cleanup;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export declare interface UniformDefinition {
|
|
62
|
+
name: string;
|
|
63
|
+
type: 'float' | 'vec2' | 'vec3' | 'vec4';
|
|
64
|
+
default: number | number[];
|
|
65
|
+
attribute?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export { }
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("lit"),i=require("lit/decorators.js");function y(e){return e.getContext("webgl",{alpha:!0,premultipliedAlpha:!1,preserveDrawingBuffer:!0})}function p(e,t,o){const a=e.createShader(t);if(!a)throw new Error("Failed to create shader");if(e.shaderSource(a,o),e.compileShader(a),!e.getShaderParameter(a,e.COMPILE_STATUS)){const n=e.getShaderInfoLog(a);throw e.deleteShader(a),new Error(`Shader compile error: ${n}`)}return a}function x(e,t,o){const a=p(e,e.VERTEX_SHADER,t),n=p(e,e.FRAGMENT_SHADER,o),r=e.createProgram();if(!r)throw new Error("Failed to create program");if(e.attachShader(r,a),e.attachShader(r,n),e.linkProgram(r),!e.getProgramParameter(r,e.LINK_STATUS)){const f=e.getProgramInfoLog(r);throw e.deleteProgram(r),new Error(`Program link error: ${f}`)}e.deleteShader(a),e.deleteShader(n);const u=new Map,b=e.getProgramParameter(r,e.ACTIVE_ATTRIBUTES);for(let f=0;f<b;f++){const l=e.getActiveAttrib(r,f);l&&u.set(l.name,e.getAttribLocation(r,l.name))}const _=new Map,S=e.getProgramParameter(r,e.ACTIVE_UNIFORMS);for(let f=0;f<S;f++){const l=e.getActiveUniform(r,f);if(l){const g=e.getUniformLocation(r,l.name);g&&_.set(l.name,g)}}return{program:r,attribLocations:u,uniformLocations:_}}function E(e,t,o){for(const[a,n]of Object.entries(o)){const r=t.uniformLocations.get(a);if(r){if(typeof n=="number")e.uniform1f(r,n);else if(Array.isArray(n))switch(n.length){case 2:e.uniform2fv(r,n);break;case 3:e.uniform3fv(r,n);break;case 4:e.uniform4fv(r,n);break}}}}function C(e,t){const o=e.createTexture();if(!o)throw new Error("Failed to create texture");return e.bindTexture(e.TEXTURE_2D,o),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,t),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.LINEAR),{texture:o,width:t.naturalWidth,height:t.naturalHeight}}function I(e,t){const o=new Float32Array([-1,-1,0,1,1,-1,1,1,-1,1,0,0,1,1,1,0]),a=e.createBuffer();if(!a)throw new Error("Failed to create buffer");e.bindBuffer(e.ARRAY_BUFFER,a),e.bufferData(e.ARRAY_BUFFER,o,e.STATIC_DRAW);const n=4*Float32Array.BYTES_PER_ELEMENT,r=t.attribLocations.get("a_position");r!==void 0&&r!==-1&&(e.enableVertexAttribArray(r),e.vertexAttribPointer(r,2,e.FLOAT,!1,n,0));const u=t.attribLocations.get("a_texCoord");return u!==void 0&&u!==-1&&(e.enableVertexAttribArray(u),e.vertexAttribPointer(u,2,e.FLOAT,!1,n,2*Float32Array.BYTES_PER_ELEMENT)),a}function R(e){e.drawArrays(e.TRIANGLE_STRIP,0,4)}const T=`precision mediump float;
|
|
2
|
+
|
|
3
|
+
varying vec2 v_texCoord;
|
|
4
|
+
|
|
5
|
+
uniform sampler2D u_image;
|
|
6
|
+
uniform vec2 u_resolution;
|
|
7
|
+
uniform float u_dotRadius;
|
|
8
|
+
uniform float u_gridSize;
|
|
9
|
+
uniform float u_angleC;
|
|
10
|
+
uniform float u_angleM;
|
|
11
|
+
uniform float u_angleY;
|
|
12
|
+
uniform float u_angleK;
|
|
13
|
+
|
|
14
|
+
float halftone(vec2 uv, float angle, float channelValue, float gridSize, float dotRadius) {
|
|
15
|
+
float rad = radians(angle);
|
|
16
|
+
float s = sin(rad);
|
|
17
|
+
float c = cos(rad);
|
|
18
|
+
mat2 rot = mat2(c, -s, s, c);
|
|
19
|
+
|
|
20
|
+
vec2 rotUV = rot * uv;
|
|
21
|
+
vec2 grid = fract(rotUV / gridSize) - 0.5;
|
|
22
|
+
|
|
23
|
+
float dist = length(grid) * gridSize;
|
|
24
|
+
float radius = dotRadius * sqrt(channelValue);
|
|
25
|
+
|
|
26
|
+
return smoothstep(radius + 0.5, radius - 0.5, dist);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
void main() {
|
|
30
|
+
vec2 uv = v_texCoord * u_resolution;
|
|
31
|
+
vec4 color = texture2D(u_image, v_texCoord);
|
|
32
|
+
|
|
33
|
+
float r = color.r;
|
|
34
|
+
float g = color.g;
|
|
35
|
+
float b = color.b;
|
|
36
|
+
|
|
37
|
+
// RGB to CMYK
|
|
38
|
+
float k = 1.0 - max(max(r, g), b);
|
|
39
|
+
float invK = 1.0 - k;
|
|
40
|
+
float cy = invK > 0.001 ? (invK - r) / invK : 0.0;
|
|
41
|
+
float ma = invK > 0.001 ? (invK - g) / invK : 0.0;
|
|
42
|
+
float ye = invK > 0.001 ? (invK - b) / invK : 0.0;
|
|
43
|
+
|
|
44
|
+
// Compute halftone dots for each channel
|
|
45
|
+
float cDot = halftone(uv, u_angleC, cy, u_gridSize, u_dotRadius);
|
|
46
|
+
float mDot = halftone(uv, u_angleM, ma, u_gridSize, u_dotRadius);
|
|
47
|
+
float yDot = halftone(uv, u_angleY, ye, u_gridSize, u_dotRadius);
|
|
48
|
+
float kDot = halftone(uv, u_angleK, k, u_gridSize, u_dotRadius);
|
|
49
|
+
|
|
50
|
+
// Subtractive color mixing on white paper
|
|
51
|
+
float outR = (1.0 - cDot) * (1.0 - kDot);
|
|
52
|
+
float outG = (1.0 - mDot) * (1.0 - kDot);
|
|
53
|
+
float outB = (1.0 - yDot) * (1.0 - kDot);
|
|
54
|
+
|
|
55
|
+
gl_FragColor = vec4(outR, outG, outB, color.a);
|
|
56
|
+
}
|
|
57
|
+
`,h=`attribute vec2 a_position;
|
|
58
|
+
attribute vec2 a_texCoord;
|
|
59
|
+
varying vec2 v_texCoord;
|
|
60
|
+
|
|
61
|
+
void main() {
|
|
62
|
+
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
63
|
+
v_texCoord = a_texCoord;
|
|
64
|
+
}
|
|
65
|
+
`,A={name:"halftone-cmyk",fragmentShader:T,vertexShader:h,uniforms:[{name:"u_dotRadius",type:"float",default:4,attribute:"dot-radius"},{name:"u_gridSize",type:"float",default:8,attribute:"grid-size"},{name:"u_angleC",type:"float",default:15,attribute:"angle-c"},{name:"u_angleM",type:"float",default:75,attribute:"angle-m"},{name:"u_angleY",type:"float",default:0,attribute:"angle-y"},{name:"u_angleK",type:"float",default:45,attribute:"angle-k"}]},w=`precision mediump float;
|
|
66
|
+
|
|
67
|
+
varying vec2 v_texCoord;
|
|
68
|
+
|
|
69
|
+
uniform sampler2D u_image;
|
|
70
|
+
uniform vec2 u_resolution;
|
|
71
|
+
uniform float u_dotRadius;
|
|
72
|
+
uniform float u_gridSize;
|
|
73
|
+
uniform vec3 u_duotoneColor;
|
|
74
|
+
uniform float u_angle;
|
|
75
|
+
|
|
76
|
+
void main() {
|
|
77
|
+
vec2 uv = v_texCoord * u_resolution;
|
|
78
|
+
vec4 color = texture2D(u_image, v_texCoord);
|
|
79
|
+
|
|
80
|
+
// Convert to luminance
|
|
81
|
+
float luma = dot(color.rgb, vec3(0.299, 0.587, 0.114));
|
|
82
|
+
float darkness = 1.0 - luma;
|
|
83
|
+
|
|
84
|
+
// Rotate grid
|
|
85
|
+
float rad = radians(u_angle);
|
|
86
|
+
float s = sin(rad);
|
|
87
|
+
float c = cos(rad);
|
|
88
|
+
vec2 rotUV = mat2(c, -s, s, c) * uv;
|
|
89
|
+
|
|
90
|
+
vec2 grid = fract(rotUV / u_gridSize) - 0.5;
|
|
91
|
+
float dist = length(grid) * u_gridSize;
|
|
92
|
+
float radius = u_dotRadius * sqrt(darkness);
|
|
93
|
+
float dot = smoothstep(radius + 0.5, radius - 0.5, dist);
|
|
94
|
+
|
|
95
|
+
// Mix duotone color (paper) with black (dots)
|
|
96
|
+
vec3 result = mix(u_duotoneColor, vec3(0.0), dot);
|
|
97
|
+
|
|
98
|
+
gl_FragColor = vec4(result, color.a);
|
|
99
|
+
}
|
|
100
|
+
`,U={name:"halftone-duotone",fragmentShader:w,vertexShader:h,uniforms:[{name:"u_dotRadius",type:"float",default:4,attribute:"dot-radius"},{name:"u_gridSize",type:"float",default:8,attribute:"grid-size"},{name:"u_duotoneColor",type:"vec3",default:[0,.6,.8],attribute:"duotone-color"},{name:"u_angle",type:"float",default:0,attribute:"angle"}]},P=`precision mediump float;
|
|
101
|
+
|
|
102
|
+
varying vec2 v_texCoord;
|
|
103
|
+
|
|
104
|
+
uniform sampler2D u_image;
|
|
105
|
+
uniform vec2 u_resolution;
|
|
106
|
+
uniform float u_threshold;
|
|
107
|
+
uniform float u_direction;
|
|
108
|
+
uniform float u_span;
|
|
109
|
+
|
|
110
|
+
float brightness(vec3 c) {
|
|
111
|
+
return dot(c, vec3(0.299, 0.587, 0.114));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
void main() {
|
|
115
|
+
vec2 uv = v_texCoord;
|
|
116
|
+
vec4 color = texture2D(u_image, uv);
|
|
117
|
+
float bri = brightness(color.rgb);
|
|
118
|
+
|
|
119
|
+
// Boundary pixel — pass through
|
|
120
|
+
if (bri < u_threshold) {
|
|
121
|
+
gl_FragColor = color;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
float rad = radians(u_direction);
|
|
126
|
+
vec2 dir = vec2(cos(rad), sin(rad));
|
|
127
|
+
vec2 step = dir / u_resolution;
|
|
128
|
+
|
|
129
|
+
int spanLen = int(u_span);
|
|
130
|
+
|
|
131
|
+
// Walk backward to find span start
|
|
132
|
+
int backCount = 0;
|
|
133
|
+
for (int i = 1; i < 256; i++) {
|
|
134
|
+
if (i >= spanLen) break;
|
|
135
|
+
vec2 sampleUV = uv - step * float(i);
|
|
136
|
+
if (sampleUV.x < 0.0 || sampleUV.x > 1.0 || sampleUV.y < 0.0 || sampleUV.y > 1.0) break;
|
|
137
|
+
float b = brightness(texture2D(u_image, sampleUV).rgb);
|
|
138
|
+
if (b < u_threshold) break;
|
|
139
|
+
backCount++;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Walk forward to find span end
|
|
143
|
+
int fwdCount = 0;
|
|
144
|
+
for (int i = 1; i < 256; i++) {
|
|
145
|
+
if (i >= spanLen) break;
|
|
146
|
+
vec2 sampleUV = uv + step * float(i);
|
|
147
|
+
if (sampleUV.x < 0.0 || sampleUV.x > 1.0 || sampleUV.y < 0.0 || sampleUV.y > 1.0) break;
|
|
148
|
+
float b = brightness(texture2D(u_image, sampleUV).rgb);
|
|
149
|
+
if (b < u_threshold) break;
|
|
150
|
+
fwdCount++;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
int totalSpan = backCount + 1 + fwdCount;
|
|
154
|
+
vec2 spanStartUV = uv - step * float(backCount);
|
|
155
|
+
|
|
156
|
+
// Count pixels in span that are darker than current (= rank)
|
|
157
|
+
int rank = 0;
|
|
158
|
+
for (int i = 0; i < 256; i++) {
|
|
159
|
+
if (i >= totalSpan) break;
|
|
160
|
+
vec2 sampleUV = spanStartUV + step * float(i);
|
|
161
|
+
float b = brightness(texture2D(u_image, sampleUV).rgb);
|
|
162
|
+
if (b < bri) rank++;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Resample at sorted position
|
|
166
|
+
vec2 sortedUV = spanStartUV + step * float(rank);
|
|
167
|
+
gl_FragColor = texture2D(u_image, sortedUV);
|
|
168
|
+
}
|
|
169
|
+
`,D={name:"pixel-sort",fragmentShader:P,vertexShader:h,uniforms:[{name:"u_threshold",type:"float",default:.5,attribute:"threshold"},{name:"u_direction",type:"float",default:0,attribute:"sort-direction"},{name:"u_span",type:"float",default:64,attribute:"sort-span"}]},k=`precision mediump float;
|
|
170
|
+
|
|
171
|
+
varying vec2 v_texCoord;
|
|
172
|
+
|
|
173
|
+
uniform sampler2D u_image;
|
|
174
|
+
uniform vec2 u_resolution;
|
|
175
|
+
uniform float u_dotRadius;
|
|
176
|
+
uniform float u_gridSize;
|
|
177
|
+
uniform vec2 u_dotOffset;
|
|
178
|
+
uniform vec3 u_bgColor;
|
|
179
|
+
|
|
180
|
+
void main() {
|
|
181
|
+
vec2 uv = v_texCoord * u_resolution;
|
|
182
|
+
|
|
183
|
+
// Which grid cell this fragment belongs to
|
|
184
|
+
vec2 cell = floor(uv / u_gridSize);
|
|
185
|
+
|
|
186
|
+
// Cell origin in pixel space
|
|
187
|
+
vec2 cellOrigin = cell * u_gridSize;
|
|
188
|
+
|
|
189
|
+
// Dot center within the cell, shifted by u_dotOffset (0–1 range)
|
|
190
|
+
vec2 dotCenter = cellOrigin + u_dotOffset * u_gridSize;
|
|
191
|
+
|
|
192
|
+
// 4×4 multi-sample average color across the cell
|
|
193
|
+
vec3 avg = vec3(0.0);
|
|
194
|
+
for (int y = 0; y < 4; y++) {
|
|
195
|
+
for (int x = 0; x < 4; x++) {
|
|
196
|
+
vec2 sampleUV = (cellOrigin + (vec2(float(x), float(y)) + 0.5) * (u_gridSize / 4.0)) / u_resolution;
|
|
197
|
+
avg += texture2D(u_image, sampleUV).rgb;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
avg /= 16.0;
|
|
201
|
+
|
|
202
|
+
// Wrapped (toroidal) distance from fragment to dot center within the cell
|
|
203
|
+
vec2 d = (uv - dotCenter) / u_gridSize;
|
|
204
|
+
d = fract(d + 0.5) - 0.5; // wrap to [-0.5, 0.5]
|
|
205
|
+
float dist = length(d) * u_gridSize;
|
|
206
|
+
|
|
207
|
+
// Anti-aliased dot
|
|
208
|
+
float mask = smoothstep(u_dotRadius + 0.5, u_dotRadius - 0.5, dist);
|
|
209
|
+
|
|
210
|
+
vec3 result = mix(u_bgColor, avg, mask);
|
|
211
|
+
|
|
212
|
+
gl_FragColor = vec4(result, 1.0);
|
|
213
|
+
}
|
|
214
|
+
`,L={name:"dot-grid",fragmentShader:k,vertexShader:h,uniforms:[{name:"u_dotRadius",type:"float",default:4,attribute:"dot-radius"},{name:"u_gridSize",type:"float",default:8,attribute:"grid-size"},{name:"u_dotOffset",type:"vec2",default:[.5,.5],attribute:"dot-offset"},{name:"u_bgColor",type:"vec3",default:[1,1,1],attribute:"bg-color"}]},m=new Map;function d(e){m.set(e.name,e)}function v(e){return m.get(e)}function z(){return Array.from(m.keys())}d(A);d(U);d(D);d(L);var F=Object.defineProperty,O=Object.getOwnPropertyDescriptor,s=(e,t,o,a)=>{for(var n=a>1?void 0:a?O(t,o):t,r=e.length-1,u;r>=0;r--)(u=e[r])&&(n=(a?u(t,o,n):u(n))||n);return a&&n&&F(t,o,n),n};exports.SomeShadeImage=class extends c.LitElement{constructor(){super(...arguments),this.src="",this.effect="halftone-cmyk",this.dotRadius=4,this.gridSize=8,this.angleC=15,this.angleM=75,this.angleY=0,this.angleK=45,this.duotoneColor="#0099cc",this.angle=0,this.threshold=.5,this.sortDirection=0,this.sortSpan=64,this.dotOffsetX=.5,this.dotOffsetY=.5,this.bgColor="#ffffff",this._webglAvailable=!0,this._canvas=null,this._gl=null,this._programInfo=null,this._textureInfo=null,this._quadBuffer=null,this._currentEffect=null,this._image=null,this._resizeObserver=null}render(){return this._webglAvailable?c.html`<canvas></canvas>`:c.html`<img .src=${this.src} alt="" />`}firstUpdated(){if(this._webglAvailable&&(this._canvas=this.shadowRoot.querySelector("canvas"),!!this._canvas)){if(this._gl=y(this._canvas),!this._gl){this._webglAvailable=!1,this.classList.add("webgl-unavailable");return}this._resizeObserver=new ResizeObserver(()=>this._handleResize()),this._resizeObserver.observe(this),this.src&&this._loadImage(this.src)}}updated(t){if(this._gl){if(t.has("src")&&this.src){this._loadImage(this.src);return}if(t.has("effect")){this._setupProgram(),this._renderFrame();return}this._renderFrame()}}disconnectedCallback(){var t;super.disconnectedCallback(),(t=this._resizeObserver)==null||t.disconnect(),this._cleanup()}_loadImage(t){const o=new Image;o.crossOrigin="anonymous",o.onload=()=>{this._image=o,this._uploadTexture(),this._sizeCanvas(),this._setupProgram(),this._renderFrame()},o.onerror=()=>{console.warn(`[some-shade] Failed to load image: ${t}`)},o.src=t}_uploadTexture(){!this._gl||!this._image||(this._textureInfo&&this._gl.deleteTexture(this._textureInfo.texture),this._textureInfo=C(this._gl,this._image))}_sizeCanvas(){if(!this._canvas||!this._textureInfo)return;const t=window.devicePixelRatio||1,o=this._textureInfo.width,a=this._textureInfo.height;this._canvas.width=o*t,this._canvas.height=a*t,this._canvas.style.aspectRatio=`${o} / ${a}`}_handleResize(){this._renderFrame()}_setupProgram(){if(!this._gl)return;const t=v(this.effect);if(!t){console.warn(`[some-shade] Unknown effect: ${this.effect}`);return}this._programInfo&&this._gl.deleteProgram(this._programInfo.program),this._currentEffect=t,this._programInfo=x(this._gl,t.vertexShader,t.fragmentShader),this._quadBuffer&&this._gl.deleteBuffer(this._quadBuffer),this._gl.useProgram(this._programInfo.program),this._quadBuffer=I(this._gl,this._programInfo)}_getUniformValues(){const t={};return this._textureInfo&&(t.u_resolution=[this._textureInfo.width*(window.devicePixelRatio||1),this._textureInfo.height*(window.devicePixelRatio||1)],t.u_dotRadius=this.dotRadius,t.u_gridSize=this.gridSize,this.effect==="halftone-cmyk"?(t.u_angleC=this.angleC,t.u_angleM=this.angleM,t.u_angleY=this.angleY,t.u_angleK=this.angleK):this.effect==="halftone-duotone"?(t.u_duotoneColor=this._parseHexColor(this.duotoneColor),t.u_angle=this.angle):this.effect==="pixel-sort"?(t.u_threshold=this.threshold,t.u_direction=this.sortDirection,t.u_span=this.sortSpan):this.effect==="dot-grid"&&(t.u_dotOffset=[this.dotOffsetX,this.dotOffsetY],t.u_bgColor=this._parseHexColor(this.bgColor))),t}_parseHexColor(t){const o=t.replace("#",""),a=parseInt(o.substring(0,2),16)/255,n=parseInt(o.substring(2,4),16)/255,r=parseInt(o.substring(4,6),16)/255;return[a,n,r]}_renderFrame(){const t=this._gl;if(!t||!this._programInfo||!this._textureInfo||!this._canvas)return;t.viewport(0,0,this._canvas.width,this._canvas.height),t.clearColor(0,0,0,0),t.clear(t.COLOR_BUFFER_BIT),t.useProgram(this._programInfo.program),t.activeTexture(t.TEXTURE0),t.bindTexture(t.TEXTURE_2D,this._textureInfo.texture);const o=this._programInfo.uniformLocations.get("u_image");o&&t.uniform1i(o,0),E(t,this._programInfo,this._getUniformValues()),t.bindBuffer(t.ARRAY_BUFFER,this._quadBuffer);const a=4*Float32Array.BYTES_PER_ELEMENT,n=this._programInfo.attribLocations.get("a_position");n!==void 0&&n!==-1&&(t.enableVertexAttribArray(n),t.vertexAttribPointer(n,2,t.FLOAT,!1,a,0));const r=this._programInfo.attribLocations.get("a_texCoord");r!==void 0&&r!==-1&&(t.enableVertexAttribArray(r),t.vertexAttribPointer(r,2,t.FLOAT,!1,a,2*Float32Array.BYTES_PER_ELEMENT)),R(t)}_cleanup(){this._gl&&(this._textureInfo&&this._gl.deleteTexture(this._textureInfo.texture),this._programInfo&&this._gl.deleteProgram(this._programInfo.program),this._quadBuffer&&this._gl.deleteBuffer(this._quadBuffer),this._gl=null,this._programInfo=null,this._textureInfo=null,this._quadBuffer=null)}};exports.SomeShadeImage.styles=c.css`
|
|
215
|
+
:host {
|
|
216
|
+
display: block;
|
|
217
|
+
position: relative;
|
|
218
|
+
overflow: hidden;
|
|
219
|
+
}
|
|
220
|
+
canvas, img {
|
|
221
|
+
display: block;
|
|
222
|
+
width: 100%;
|
|
223
|
+
height: auto;
|
|
224
|
+
}
|
|
225
|
+
`;s([i.property()],exports.SomeShadeImage.prototype,"src",2);s([i.property()],exports.SomeShadeImage.prototype,"effect",2);s([i.property({type:Number,attribute:"dot-radius"})],exports.SomeShadeImage.prototype,"dotRadius",2);s([i.property({type:Number,attribute:"grid-size"})],exports.SomeShadeImage.prototype,"gridSize",2);s([i.property({type:Number,attribute:"angle-c"})],exports.SomeShadeImage.prototype,"angleC",2);s([i.property({type:Number,attribute:"angle-m"})],exports.SomeShadeImage.prototype,"angleM",2);s([i.property({type:Number,attribute:"angle-y"})],exports.SomeShadeImage.prototype,"angleY",2);s([i.property({type:Number,attribute:"angle-k"})],exports.SomeShadeImage.prototype,"angleK",2);s([i.property({attribute:"duotone-color"})],exports.SomeShadeImage.prototype,"duotoneColor",2);s([i.property({type:Number})],exports.SomeShadeImage.prototype,"angle",2);s([i.property({type:Number})],exports.SomeShadeImage.prototype,"threshold",2);s([i.property({type:Number,attribute:"sort-direction"})],exports.SomeShadeImage.prototype,"sortDirection",2);s([i.property({type:Number,attribute:"sort-span"})],exports.SomeShadeImage.prototype,"sortSpan",2);s([i.property({type:Number,attribute:"dot-offset-x"})],exports.SomeShadeImage.prototype,"dotOffsetX",2);s([i.property({type:Number,attribute:"dot-offset-y"})],exports.SomeShadeImage.prototype,"dotOffsetY",2);s([i.property({attribute:"bg-color"})],exports.SomeShadeImage.prototype,"bgColor",2);s([i.state()],exports.SomeShadeImage.prototype,"_webglAvailable",2);exports.SomeShadeImage=s([i.customElement("some-shade-image")],exports.SomeShadeImage);exports.get=v;exports.list=z;exports.register=d;
|
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
import { css as y, LitElement as E, html as p } from "lit";
|
|
2
|
+
import { property as u, state as S, customElement as C } from "lit/decorators.js";
|
|
3
|
+
function R(t) {
|
|
4
|
+
return t.getContext("webgl", {
|
|
5
|
+
alpha: !0,
|
|
6
|
+
premultipliedAlpha: !1,
|
|
7
|
+
preserveDrawingBuffer: !0
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
function v(t, e, a) {
|
|
11
|
+
const n = t.createShader(e);
|
|
12
|
+
if (!n) throw new Error("Failed to create shader");
|
|
13
|
+
if (t.shaderSource(n, a), t.compileShader(n), !t.getShaderParameter(n, t.COMPILE_STATUS)) {
|
|
14
|
+
const r = t.getShaderInfoLog(n);
|
|
15
|
+
throw t.deleteShader(n), new Error(`Shader compile error: ${r}`);
|
|
16
|
+
}
|
|
17
|
+
return n;
|
|
18
|
+
}
|
|
19
|
+
function T(t, e, a) {
|
|
20
|
+
const n = v(t, t.VERTEX_SHADER, e), r = v(t, t.FRAGMENT_SHADER, a), o = t.createProgram();
|
|
21
|
+
if (!o) throw new Error("Failed to create program");
|
|
22
|
+
if (t.attachShader(o, n), t.attachShader(o, r), t.linkProgram(o), !t.getProgramParameter(o, t.LINK_STATUS)) {
|
|
23
|
+
const l = t.getProgramInfoLog(o);
|
|
24
|
+
throw t.deleteProgram(o), new Error(`Program link error: ${l}`);
|
|
25
|
+
}
|
|
26
|
+
t.deleteShader(n), t.deleteShader(r);
|
|
27
|
+
const f = /* @__PURE__ */ new Map(), b = t.getProgramParameter(o, t.ACTIVE_ATTRIBUTES);
|
|
28
|
+
for (let l = 0; l < b; l++) {
|
|
29
|
+
const d = t.getActiveAttrib(o, l);
|
|
30
|
+
d && f.set(d.name, t.getAttribLocation(o, d.name));
|
|
31
|
+
}
|
|
32
|
+
const m = /* @__PURE__ */ new Map(), x = t.getProgramParameter(o, t.ACTIVE_UNIFORMS);
|
|
33
|
+
for (let l = 0; l < x; l++) {
|
|
34
|
+
const d = t.getActiveUniform(o, l);
|
|
35
|
+
if (d) {
|
|
36
|
+
const g = t.getUniformLocation(o, d.name);
|
|
37
|
+
g && m.set(d.name, g);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return { program: o, attribLocations: f, uniformLocations: m };
|
|
41
|
+
}
|
|
42
|
+
function A(t, e, a) {
|
|
43
|
+
for (const [n, r] of Object.entries(a)) {
|
|
44
|
+
const o = e.uniformLocations.get(n);
|
|
45
|
+
if (o) {
|
|
46
|
+
if (typeof r == "number")
|
|
47
|
+
t.uniform1f(o, r);
|
|
48
|
+
else if (Array.isArray(r))
|
|
49
|
+
switch (r.length) {
|
|
50
|
+
case 2:
|
|
51
|
+
t.uniform2fv(o, r);
|
|
52
|
+
break;
|
|
53
|
+
case 3:
|
|
54
|
+
t.uniform3fv(o, r);
|
|
55
|
+
break;
|
|
56
|
+
case 4:
|
|
57
|
+
t.uniform4fv(o, r);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function w(t, e) {
|
|
64
|
+
const a = t.createTexture();
|
|
65
|
+
if (!a) throw new Error("Failed to create texture");
|
|
66
|
+
return t.bindTexture(t.TEXTURE_2D, a), t.texImage2D(t.TEXTURE_2D, 0, t.RGBA, t.RGBA, t.UNSIGNED_BYTE, e), t.texParameteri(t.TEXTURE_2D, t.TEXTURE_WRAP_S, t.CLAMP_TO_EDGE), t.texParameteri(t.TEXTURE_2D, t.TEXTURE_WRAP_T, t.CLAMP_TO_EDGE), t.texParameteri(t.TEXTURE_2D, t.TEXTURE_MIN_FILTER, t.LINEAR), t.texParameteri(t.TEXTURE_2D, t.TEXTURE_MAG_FILTER, t.LINEAR), { texture: a, width: e.naturalWidth, height: e.naturalHeight };
|
|
67
|
+
}
|
|
68
|
+
function I(t, e) {
|
|
69
|
+
const a = new Float32Array([
|
|
70
|
+
// pos.x pos.y tex.s tex.t
|
|
71
|
+
-1,
|
|
72
|
+
-1,
|
|
73
|
+
0,
|
|
74
|
+
1,
|
|
75
|
+
1,
|
|
76
|
+
-1,
|
|
77
|
+
1,
|
|
78
|
+
1,
|
|
79
|
+
-1,
|
|
80
|
+
1,
|
|
81
|
+
0,
|
|
82
|
+
0,
|
|
83
|
+
1,
|
|
84
|
+
1,
|
|
85
|
+
1,
|
|
86
|
+
0
|
|
87
|
+
]), n = t.createBuffer();
|
|
88
|
+
if (!n) throw new Error("Failed to create buffer");
|
|
89
|
+
t.bindBuffer(t.ARRAY_BUFFER, n), t.bufferData(t.ARRAY_BUFFER, a, t.STATIC_DRAW);
|
|
90
|
+
const r = 4 * Float32Array.BYTES_PER_ELEMENT, o = e.attribLocations.get("a_position");
|
|
91
|
+
o !== void 0 && o !== -1 && (t.enableVertexAttribArray(o), t.vertexAttribPointer(o, 2, t.FLOAT, !1, r, 0));
|
|
92
|
+
const f = e.attribLocations.get("a_texCoord");
|
|
93
|
+
return f !== void 0 && f !== -1 && (t.enableVertexAttribArray(f), t.vertexAttribPointer(f, 2, t.FLOAT, !1, r, 2 * Float32Array.BYTES_PER_ELEMENT)), n;
|
|
94
|
+
}
|
|
95
|
+
function U(t) {
|
|
96
|
+
t.drawArrays(t.TRIANGLE_STRIP, 0, 4);
|
|
97
|
+
}
|
|
98
|
+
const D = `precision mediump float;
|
|
99
|
+
|
|
100
|
+
varying vec2 v_texCoord;
|
|
101
|
+
|
|
102
|
+
uniform sampler2D u_image;
|
|
103
|
+
uniform vec2 u_resolution;
|
|
104
|
+
uniform float u_dotRadius;
|
|
105
|
+
uniform float u_gridSize;
|
|
106
|
+
uniform float u_angleC;
|
|
107
|
+
uniform float u_angleM;
|
|
108
|
+
uniform float u_angleY;
|
|
109
|
+
uniform float u_angleK;
|
|
110
|
+
|
|
111
|
+
float halftone(vec2 uv, float angle, float channelValue, float gridSize, float dotRadius) {
|
|
112
|
+
float rad = radians(angle);
|
|
113
|
+
float s = sin(rad);
|
|
114
|
+
float c = cos(rad);
|
|
115
|
+
mat2 rot = mat2(c, -s, s, c);
|
|
116
|
+
|
|
117
|
+
vec2 rotUV = rot * uv;
|
|
118
|
+
vec2 grid = fract(rotUV / gridSize) - 0.5;
|
|
119
|
+
|
|
120
|
+
float dist = length(grid) * gridSize;
|
|
121
|
+
float radius = dotRadius * sqrt(channelValue);
|
|
122
|
+
|
|
123
|
+
return smoothstep(radius + 0.5, radius - 0.5, dist);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
void main() {
|
|
127
|
+
vec2 uv = v_texCoord * u_resolution;
|
|
128
|
+
vec4 color = texture2D(u_image, v_texCoord);
|
|
129
|
+
|
|
130
|
+
float r = color.r;
|
|
131
|
+
float g = color.g;
|
|
132
|
+
float b = color.b;
|
|
133
|
+
|
|
134
|
+
// RGB to CMYK
|
|
135
|
+
float k = 1.0 - max(max(r, g), b);
|
|
136
|
+
float invK = 1.0 - k;
|
|
137
|
+
float cy = invK > 0.001 ? (invK - r) / invK : 0.0;
|
|
138
|
+
float ma = invK > 0.001 ? (invK - g) / invK : 0.0;
|
|
139
|
+
float ye = invK > 0.001 ? (invK - b) / invK : 0.0;
|
|
140
|
+
|
|
141
|
+
// Compute halftone dots for each channel
|
|
142
|
+
float cDot = halftone(uv, u_angleC, cy, u_gridSize, u_dotRadius);
|
|
143
|
+
float mDot = halftone(uv, u_angleM, ma, u_gridSize, u_dotRadius);
|
|
144
|
+
float yDot = halftone(uv, u_angleY, ye, u_gridSize, u_dotRadius);
|
|
145
|
+
float kDot = halftone(uv, u_angleK, k, u_gridSize, u_dotRadius);
|
|
146
|
+
|
|
147
|
+
// Subtractive color mixing on white paper
|
|
148
|
+
float outR = (1.0 - cDot) * (1.0 - kDot);
|
|
149
|
+
float outG = (1.0 - mDot) * (1.0 - kDot);
|
|
150
|
+
float outB = (1.0 - yDot) * (1.0 - kDot);
|
|
151
|
+
|
|
152
|
+
gl_FragColor = vec4(outR, outG, outB, color.a);
|
|
153
|
+
}
|
|
154
|
+
`, c = `attribute vec2 a_position;
|
|
155
|
+
attribute vec2 a_texCoord;
|
|
156
|
+
varying vec2 v_texCoord;
|
|
157
|
+
|
|
158
|
+
void main() {
|
|
159
|
+
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
160
|
+
v_texCoord = a_texCoord;
|
|
161
|
+
}
|
|
162
|
+
`, P = {
|
|
163
|
+
name: "halftone-cmyk",
|
|
164
|
+
fragmentShader: D,
|
|
165
|
+
vertexShader: c,
|
|
166
|
+
uniforms: [
|
|
167
|
+
{ name: "u_dotRadius", type: "float", default: 4, attribute: "dot-radius" },
|
|
168
|
+
{ name: "u_gridSize", type: "float", default: 8, attribute: "grid-size" },
|
|
169
|
+
{ name: "u_angleC", type: "float", default: 15, attribute: "angle-c" },
|
|
170
|
+
{ name: "u_angleM", type: "float", default: 75, attribute: "angle-m" },
|
|
171
|
+
{ name: "u_angleY", type: "float", default: 0, attribute: "angle-y" },
|
|
172
|
+
{ name: "u_angleK", type: "float", default: 45, attribute: "angle-k" }
|
|
173
|
+
]
|
|
174
|
+
}, k = `precision mediump float;
|
|
175
|
+
|
|
176
|
+
varying vec2 v_texCoord;
|
|
177
|
+
|
|
178
|
+
uniform sampler2D u_image;
|
|
179
|
+
uniform vec2 u_resolution;
|
|
180
|
+
uniform float u_dotRadius;
|
|
181
|
+
uniform float u_gridSize;
|
|
182
|
+
uniform vec3 u_duotoneColor;
|
|
183
|
+
uniform float u_angle;
|
|
184
|
+
|
|
185
|
+
void main() {
|
|
186
|
+
vec2 uv = v_texCoord * u_resolution;
|
|
187
|
+
vec4 color = texture2D(u_image, v_texCoord);
|
|
188
|
+
|
|
189
|
+
// Convert to luminance
|
|
190
|
+
float luma = dot(color.rgb, vec3(0.299, 0.587, 0.114));
|
|
191
|
+
float darkness = 1.0 - luma;
|
|
192
|
+
|
|
193
|
+
// Rotate grid
|
|
194
|
+
float rad = radians(u_angle);
|
|
195
|
+
float s = sin(rad);
|
|
196
|
+
float c = cos(rad);
|
|
197
|
+
vec2 rotUV = mat2(c, -s, s, c) * uv;
|
|
198
|
+
|
|
199
|
+
vec2 grid = fract(rotUV / u_gridSize) - 0.5;
|
|
200
|
+
float dist = length(grid) * u_gridSize;
|
|
201
|
+
float radius = u_dotRadius * sqrt(darkness);
|
|
202
|
+
float dot = smoothstep(radius + 0.5, radius - 0.5, dist);
|
|
203
|
+
|
|
204
|
+
// Mix duotone color (paper) with black (dots)
|
|
205
|
+
vec3 result = mix(u_duotoneColor, vec3(0.0), dot);
|
|
206
|
+
|
|
207
|
+
gl_FragColor = vec4(result, color.a);
|
|
208
|
+
}
|
|
209
|
+
`, L = {
|
|
210
|
+
name: "halftone-duotone",
|
|
211
|
+
fragmentShader: k,
|
|
212
|
+
vertexShader: c,
|
|
213
|
+
uniforms: [
|
|
214
|
+
{ name: "u_dotRadius", type: "float", default: 4, attribute: "dot-radius" },
|
|
215
|
+
{ name: "u_gridSize", type: "float", default: 8, attribute: "grid-size" },
|
|
216
|
+
{ name: "u_duotoneColor", type: "vec3", default: [0, 0.6, 0.8], attribute: "duotone-color" },
|
|
217
|
+
{ name: "u_angle", type: "float", default: 0, attribute: "angle" }
|
|
218
|
+
]
|
|
219
|
+
}, z = `precision mediump float;
|
|
220
|
+
|
|
221
|
+
varying vec2 v_texCoord;
|
|
222
|
+
|
|
223
|
+
uniform sampler2D u_image;
|
|
224
|
+
uniform vec2 u_resolution;
|
|
225
|
+
uniform float u_threshold;
|
|
226
|
+
uniform float u_direction;
|
|
227
|
+
uniform float u_span;
|
|
228
|
+
|
|
229
|
+
float brightness(vec3 c) {
|
|
230
|
+
return dot(c, vec3(0.299, 0.587, 0.114));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
void main() {
|
|
234
|
+
vec2 uv = v_texCoord;
|
|
235
|
+
vec4 color = texture2D(u_image, uv);
|
|
236
|
+
float bri = brightness(color.rgb);
|
|
237
|
+
|
|
238
|
+
// Boundary pixel — pass through
|
|
239
|
+
if (bri < u_threshold) {
|
|
240
|
+
gl_FragColor = color;
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
float rad = radians(u_direction);
|
|
245
|
+
vec2 dir = vec2(cos(rad), sin(rad));
|
|
246
|
+
vec2 step = dir / u_resolution;
|
|
247
|
+
|
|
248
|
+
int spanLen = int(u_span);
|
|
249
|
+
|
|
250
|
+
// Walk backward to find span start
|
|
251
|
+
int backCount = 0;
|
|
252
|
+
for (int i = 1; i < 256; i++) {
|
|
253
|
+
if (i >= spanLen) break;
|
|
254
|
+
vec2 sampleUV = uv - step * float(i);
|
|
255
|
+
if (sampleUV.x < 0.0 || sampleUV.x > 1.0 || sampleUV.y < 0.0 || sampleUV.y > 1.0) break;
|
|
256
|
+
float b = brightness(texture2D(u_image, sampleUV).rgb);
|
|
257
|
+
if (b < u_threshold) break;
|
|
258
|
+
backCount++;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Walk forward to find span end
|
|
262
|
+
int fwdCount = 0;
|
|
263
|
+
for (int i = 1; i < 256; i++) {
|
|
264
|
+
if (i >= spanLen) break;
|
|
265
|
+
vec2 sampleUV = uv + step * float(i);
|
|
266
|
+
if (sampleUV.x < 0.0 || sampleUV.x > 1.0 || sampleUV.y < 0.0 || sampleUV.y > 1.0) break;
|
|
267
|
+
float b = brightness(texture2D(u_image, sampleUV).rgb);
|
|
268
|
+
if (b < u_threshold) break;
|
|
269
|
+
fwdCount++;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
int totalSpan = backCount + 1 + fwdCount;
|
|
273
|
+
vec2 spanStartUV = uv - step * float(backCount);
|
|
274
|
+
|
|
275
|
+
// Count pixels in span that are darker than current (= rank)
|
|
276
|
+
int rank = 0;
|
|
277
|
+
for (int i = 0; i < 256; i++) {
|
|
278
|
+
if (i >= totalSpan) break;
|
|
279
|
+
vec2 sampleUV = spanStartUV + step * float(i);
|
|
280
|
+
float b = brightness(texture2D(u_image, sampleUV).rgb);
|
|
281
|
+
if (b < bri) rank++;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Resample at sorted position
|
|
285
|
+
vec2 sortedUV = spanStartUV + step * float(rank);
|
|
286
|
+
gl_FragColor = texture2D(u_image, sortedUV);
|
|
287
|
+
}
|
|
288
|
+
`, F = {
|
|
289
|
+
name: "pixel-sort",
|
|
290
|
+
fragmentShader: z,
|
|
291
|
+
vertexShader: c,
|
|
292
|
+
uniforms: [
|
|
293
|
+
{ name: "u_threshold", type: "float", default: 0.5, attribute: "threshold" },
|
|
294
|
+
{ name: "u_direction", type: "float", default: 0, attribute: "sort-direction" },
|
|
295
|
+
{ name: "u_span", type: "float", default: 64, attribute: "sort-span" }
|
|
296
|
+
]
|
|
297
|
+
}, V = `precision mediump float;
|
|
298
|
+
|
|
299
|
+
varying vec2 v_texCoord;
|
|
300
|
+
|
|
301
|
+
uniform sampler2D u_image;
|
|
302
|
+
uniform vec2 u_resolution;
|
|
303
|
+
uniform float u_dotRadius;
|
|
304
|
+
uniform float u_gridSize;
|
|
305
|
+
uniform vec2 u_dotOffset;
|
|
306
|
+
uniform vec3 u_bgColor;
|
|
307
|
+
|
|
308
|
+
void main() {
|
|
309
|
+
vec2 uv = v_texCoord * u_resolution;
|
|
310
|
+
|
|
311
|
+
// Which grid cell this fragment belongs to
|
|
312
|
+
vec2 cell = floor(uv / u_gridSize);
|
|
313
|
+
|
|
314
|
+
// Cell origin in pixel space
|
|
315
|
+
vec2 cellOrigin = cell * u_gridSize;
|
|
316
|
+
|
|
317
|
+
// Dot center within the cell, shifted by u_dotOffset (0–1 range)
|
|
318
|
+
vec2 dotCenter = cellOrigin + u_dotOffset * u_gridSize;
|
|
319
|
+
|
|
320
|
+
// 4×4 multi-sample average color across the cell
|
|
321
|
+
vec3 avg = vec3(0.0);
|
|
322
|
+
for (int y = 0; y < 4; y++) {
|
|
323
|
+
for (int x = 0; x < 4; x++) {
|
|
324
|
+
vec2 sampleUV = (cellOrigin + (vec2(float(x), float(y)) + 0.5) * (u_gridSize / 4.0)) / u_resolution;
|
|
325
|
+
avg += texture2D(u_image, sampleUV).rgb;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
avg /= 16.0;
|
|
329
|
+
|
|
330
|
+
// Wrapped (toroidal) distance from fragment to dot center within the cell
|
|
331
|
+
vec2 d = (uv - dotCenter) / u_gridSize;
|
|
332
|
+
d = fract(d + 0.5) - 0.5; // wrap to [-0.5, 0.5]
|
|
333
|
+
float dist = length(d) * u_gridSize;
|
|
334
|
+
|
|
335
|
+
// Anti-aliased dot
|
|
336
|
+
float mask = smoothstep(u_dotRadius + 0.5, u_dotRadius - 0.5, dist);
|
|
337
|
+
|
|
338
|
+
vec3 result = mix(u_bgColor, avg, mask);
|
|
339
|
+
|
|
340
|
+
gl_FragColor = vec4(result, 1.0);
|
|
341
|
+
}
|
|
342
|
+
`, O = {
|
|
343
|
+
name: "dot-grid",
|
|
344
|
+
fragmentShader: V,
|
|
345
|
+
vertexShader: c,
|
|
346
|
+
uniforms: [
|
|
347
|
+
{ name: "u_dotRadius", type: "float", default: 4, attribute: "dot-radius" },
|
|
348
|
+
{ name: "u_gridSize", type: "float", default: 8, attribute: "grid-size" },
|
|
349
|
+
{ name: "u_dotOffset", type: "vec2", default: [0.5, 0.5], attribute: "dot-offset" },
|
|
350
|
+
{ name: "u_bgColor", type: "vec3", default: [1, 1, 1], attribute: "bg-color" }
|
|
351
|
+
]
|
|
352
|
+
}, h = /* @__PURE__ */ new Map();
|
|
353
|
+
function _(t) {
|
|
354
|
+
h.set(t.name, t);
|
|
355
|
+
}
|
|
356
|
+
function B(t) {
|
|
357
|
+
return h.get(t);
|
|
358
|
+
}
|
|
359
|
+
function X() {
|
|
360
|
+
return Array.from(h.keys());
|
|
361
|
+
}
|
|
362
|
+
_(P);
|
|
363
|
+
_(L);
|
|
364
|
+
_(F);
|
|
365
|
+
_(O);
|
|
366
|
+
var N = Object.defineProperty, M = Object.getOwnPropertyDescriptor, s = (t, e, a, n) => {
|
|
367
|
+
for (var r = n > 1 ? void 0 : n ? M(e, a) : e, o = t.length - 1, f; o >= 0; o--)
|
|
368
|
+
(f = t[o]) && (r = (n ? f(e, a, r) : f(r)) || r);
|
|
369
|
+
return n && r && N(e, a, r), r;
|
|
370
|
+
};
|
|
371
|
+
let i = class extends E {
|
|
372
|
+
constructor() {
|
|
373
|
+
super(...arguments), this.src = "", this.effect = "halftone-cmyk", this.dotRadius = 4, this.gridSize = 8, this.angleC = 15, this.angleM = 75, this.angleY = 0, this.angleK = 45, this.duotoneColor = "#0099cc", this.angle = 0, this.threshold = 0.5, this.sortDirection = 0, this.sortSpan = 64, this.dotOffsetX = 0.5, this.dotOffsetY = 0.5, this.bgColor = "#ffffff", this._webglAvailable = !0, this._canvas = null, this._gl = null, this._programInfo = null, this._textureInfo = null, this._quadBuffer = null, this._currentEffect = null, this._image = null, this._resizeObserver = null;
|
|
374
|
+
}
|
|
375
|
+
render() {
|
|
376
|
+
return this._webglAvailable ? p`<canvas></canvas>` : p`<img .src=${this.src} alt="" />`;
|
|
377
|
+
}
|
|
378
|
+
firstUpdated() {
|
|
379
|
+
if (this._webglAvailable && (this._canvas = this.shadowRoot.querySelector("canvas"), !!this._canvas)) {
|
|
380
|
+
if (this._gl = R(this._canvas), !this._gl) {
|
|
381
|
+
this._webglAvailable = !1, this.classList.add("webgl-unavailable");
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
this._resizeObserver = new ResizeObserver(() => this._handleResize()), this._resizeObserver.observe(this), this.src && this._loadImage(this.src);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
updated(t) {
|
|
388
|
+
if (this._gl) {
|
|
389
|
+
if (t.has("src") && this.src) {
|
|
390
|
+
this._loadImage(this.src);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
if (t.has("effect")) {
|
|
394
|
+
this._setupProgram(), this._renderFrame();
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
this._renderFrame();
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
disconnectedCallback() {
|
|
401
|
+
var t;
|
|
402
|
+
super.disconnectedCallback(), (t = this._resizeObserver) == null || t.disconnect(), this._cleanup();
|
|
403
|
+
}
|
|
404
|
+
_loadImage(t) {
|
|
405
|
+
const e = new Image();
|
|
406
|
+
e.crossOrigin = "anonymous", e.onload = () => {
|
|
407
|
+
this._image = e, this._uploadTexture(), this._sizeCanvas(), this._setupProgram(), this._renderFrame();
|
|
408
|
+
}, e.onerror = () => {
|
|
409
|
+
console.warn(`[some-shade] Failed to load image: ${t}`);
|
|
410
|
+
}, e.src = t;
|
|
411
|
+
}
|
|
412
|
+
_uploadTexture() {
|
|
413
|
+
!this._gl || !this._image || (this._textureInfo && this._gl.deleteTexture(this._textureInfo.texture), this._textureInfo = w(this._gl, this._image));
|
|
414
|
+
}
|
|
415
|
+
_sizeCanvas() {
|
|
416
|
+
if (!this._canvas || !this._textureInfo) return;
|
|
417
|
+
const t = window.devicePixelRatio || 1, e = this._textureInfo.width, a = this._textureInfo.height;
|
|
418
|
+
this._canvas.width = e * t, this._canvas.height = a * t, this._canvas.style.aspectRatio = `${e} / ${a}`;
|
|
419
|
+
}
|
|
420
|
+
_handleResize() {
|
|
421
|
+
this._renderFrame();
|
|
422
|
+
}
|
|
423
|
+
_setupProgram() {
|
|
424
|
+
if (!this._gl) return;
|
|
425
|
+
const t = B(this.effect);
|
|
426
|
+
if (!t) {
|
|
427
|
+
console.warn(`[some-shade] Unknown effect: ${this.effect}`);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
this._programInfo && this._gl.deleteProgram(this._programInfo.program), this._currentEffect = t, this._programInfo = T(this._gl, t.vertexShader, t.fragmentShader), this._quadBuffer && this._gl.deleteBuffer(this._quadBuffer), this._gl.useProgram(this._programInfo.program), this._quadBuffer = I(this._gl, this._programInfo);
|
|
431
|
+
}
|
|
432
|
+
_getUniformValues() {
|
|
433
|
+
const t = {};
|
|
434
|
+
return this._textureInfo && (t.u_resolution = [
|
|
435
|
+
this._textureInfo.width * (window.devicePixelRatio || 1),
|
|
436
|
+
this._textureInfo.height * (window.devicePixelRatio || 1)
|
|
437
|
+
], t.u_dotRadius = this.dotRadius, t.u_gridSize = this.gridSize, this.effect === "halftone-cmyk" ? (t.u_angleC = this.angleC, t.u_angleM = this.angleM, t.u_angleY = this.angleY, t.u_angleK = this.angleK) : this.effect === "halftone-duotone" ? (t.u_duotoneColor = this._parseHexColor(this.duotoneColor), t.u_angle = this.angle) : this.effect === "pixel-sort" ? (t.u_threshold = this.threshold, t.u_direction = this.sortDirection, t.u_span = this.sortSpan) : this.effect === "dot-grid" && (t.u_dotOffset = [this.dotOffsetX, this.dotOffsetY], t.u_bgColor = this._parseHexColor(this.bgColor))), t;
|
|
438
|
+
}
|
|
439
|
+
_parseHexColor(t) {
|
|
440
|
+
const e = t.replace("#", ""), a = parseInt(e.substring(0, 2), 16) / 255, n = parseInt(e.substring(2, 4), 16) / 255, r = parseInt(e.substring(4, 6), 16) / 255;
|
|
441
|
+
return [a, n, r];
|
|
442
|
+
}
|
|
443
|
+
_renderFrame() {
|
|
444
|
+
const t = this._gl;
|
|
445
|
+
if (!t || !this._programInfo || !this._textureInfo || !this._canvas) return;
|
|
446
|
+
t.viewport(0, 0, this._canvas.width, this._canvas.height), t.clearColor(0, 0, 0, 0), t.clear(t.COLOR_BUFFER_BIT), t.useProgram(this._programInfo.program), t.activeTexture(t.TEXTURE0), t.bindTexture(t.TEXTURE_2D, this._textureInfo.texture);
|
|
447
|
+
const e = this._programInfo.uniformLocations.get("u_image");
|
|
448
|
+
e && t.uniform1i(e, 0), A(t, this._programInfo, this._getUniformValues()), t.bindBuffer(t.ARRAY_BUFFER, this._quadBuffer);
|
|
449
|
+
const a = 4 * Float32Array.BYTES_PER_ELEMENT, n = this._programInfo.attribLocations.get("a_position");
|
|
450
|
+
n !== void 0 && n !== -1 && (t.enableVertexAttribArray(n), t.vertexAttribPointer(n, 2, t.FLOAT, !1, a, 0));
|
|
451
|
+
const r = this._programInfo.attribLocations.get("a_texCoord");
|
|
452
|
+
r !== void 0 && r !== -1 && (t.enableVertexAttribArray(r), t.vertexAttribPointer(r, 2, t.FLOAT, !1, a, 2 * Float32Array.BYTES_PER_ELEMENT)), U(t);
|
|
453
|
+
}
|
|
454
|
+
_cleanup() {
|
|
455
|
+
this._gl && (this._textureInfo && this._gl.deleteTexture(this._textureInfo.texture), this._programInfo && this._gl.deleteProgram(this._programInfo.program), this._quadBuffer && this._gl.deleteBuffer(this._quadBuffer), this._gl = null, this._programInfo = null, this._textureInfo = null, this._quadBuffer = null);
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
i.styles = y`
|
|
459
|
+
:host {
|
|
460
|
+
display: block;
|
|
461
|
+
position: relative;
|
|
462
|
+
overflow: hidden;
|
|
463
|
+
}
|
|
464
|
+
canvas, img {
|
|
465
|
+
display: block;
|
|
466
|
+
width: 100%;
|
|
467
|
+
height: auto;
|
|
468
|
+
}
|
|
469
|
+
`;
|
|
470
|
+
s([
|
|
471
|
+
u()
|
|
472
|
+
], i.prototype, "src", 2);
|
|
473
|
+
s([
|
|
474
|
+
u()
|
|
475
|
+
], i.prototype, "effect", 2);
|
|
476
|
+
s([
|
|
477
|
+
u({ type: Number, attribute: "dot-radius" })
|
|
478
|
+
], i.prototype, "dotRadius", 2);
|
|
479
|
+
s([
|
|
480
|
+
u({ type: Number, attribute: "grid-size" })
|
|
481
|
+
], i.prototype, "gridSize", 2);
|
|
482
|
+
s([
|
|
483
|
+
u({ type: Number, attribute: "angle-c" })
|
|
484
|
+
], i.prototype, "angleC", 2);
|
|
485
|
+
s([
|
|
486
|
+
u({ type: Number, attribute: "angle-m" })
|
|
487
|
+
], i.prototype, "angleM", 2);
|
|
488
|
+
s([
|
|
489
|
+
u({ type: Number, attribute: "angle-y" })
|
|
490
|
+
], i.prototype, "angleY", 2);
|
|
491
|
+
s([
|
|
492
|
+
u({ type: Number, attribute: "angle-k" })
|
|
493
|
+
], i.prototype, "angleK", 2);
|
|
494
|
+
s([
|
|
495
|
+
u({ attribute: "duotone-color" })
|
|
496
|
+
], i.prototype, "duotoneColor", 2);
|
|
497
|
+
s([
|
|
498
|
+
u({ type: Number })
|
|
499
|
+
], i.prototype, "angle", 2);
|
|
500
|
+
s([
|
|
501
|
+
u({ type: Number })
|
|
502
|
+
], i.prototype, "threshold", 2);
|
|
503
|
+
s([
|
|
504
|
+
u({ type: Number, attribute: "sort-direction" })
|
|
505
|
+
], i.prototype, "sortDirection", 2);
|
|
506
|
+
s([
|
|
507
|
+
u({ type: Number, attribute: "sort-span" })
|
|
508
|
+
], i.prototype, "sortSpan", 2);
|
|
509
|
+
s([
|
|
510
|
+
u({ type: Number, attribute: "dot-offset-x" })
|
|
511
|
+
], i.prototype, "dotOffsetX", 2);
|
|
512
|
+
s([
|
|
513
|
+
u({ type: Number, attribute: "dot-offset-y" })
|
|
514
|
+
], i.prototype, "dotOffsetY", 2);
|
|
515
|
+
s([
|
|
516
|
+
u({ attribute: "bg-color" })
|
|
517
|
+
], i.prototype, "bgColor", 2);
|
|
518
|
+
s([
|
|
519
|
+
S()
|
|
520
|
+
], i.prototype, "_webglAvailable", 2);
|
|
521
|
+
i = s([
|
|
522
|
+
C("some-shade-image")
|
|
523
|
+
], i);
|
|
524
|
+
export {
|
|
525
|
+
i as SomeShadeImage,
|
|
526
|
+
B as get,
|
|
527
|
+
X as list,
|
|
528
|
+
_ as register
|
|
529
|
+
};
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
(function(n,d){typeof exports=="object"&&typeof module<"u"?d(exports,require("lit"),require("lit/decorators.js")):typeof define=="function"&&define.amd?define(["exports","lit","lit/decorators.js"],d):(n=typeof globalThis<"u"?globalThis:n||self,d(n.SomeShade={},n.Lit,n.LitDecorators))})(this,(function(n,d,s){"use strict";function S(e){return e.getContext("webgl",{alpha:!0,premultipliedAlpha:!1,preserveDrawingBuffer:!0})}function g(e,t,o){const i=e.createShader(t);if(!i)throw new Error("Failed to create shader");if(e.shaderSource(i,o),e.compileShader(i),!e.getShaderParameter(i,e.COMPILE_STATUS)){const a=e.getShaderInfoLog(i);throw e.deleteShader(i),new Error(`Shader compile error: ${a}`)}return i}function y(e,t,o){const i=g(e,e.VERTEX_SHADER,t),a=g(e,e.FRAGMENT_SHADER,o),r=e.createProgram();if(!r)throw new Error("Failed to create program");if(e.attachShader(r,i),e.attachShader(r,a),e.linkProgram(r),!e.getProgramParameter(r,e.LINK_STATUS)){const l=e.getProgramInfoLog(r);throw e.deleteProgram(r),new Error(`Program link error: ${l}`)}e.deleteShader(i),e.deleteShader(a);const f=new Map,L=e.getProgramParameter(r,e.ACTIVE_ATTRIBUTES);for(let l=0;l<L;l++){const c=e.getActiveAttrib(r,l);c&&f.set(c.name,e.getAttribLocation(r,c.name))}const v=new Map,z=e.getProgramParameter(r,e.ACTIVE_UNIFORMS);for(let l=0;l<z;l++){const c=e.getActiveUniform(r,l);if(c){const b=e.getUniformLocation(r,c.name);b&&v.set(c.name,b)}}return{program:r,attribLocations:f,uniformLocations:v}}function E(e,t,o){for(const[i,a]of Object.entries(o)){const r=t.uniformLocations.get(i);if(r){if(typeof a=="number")e.uniform1f(r,a);else if(Array.isArray(a))switch(a.length){case 2:e.uniform2fv(r,a);break;case 3:e.uniform3fv(r,a);break;case 4:e.uniform4fv(r,a);break}}}}function x(e,t){const o=e.createTexture();if(!o)throw new Error("Failed to create texture");return e.bindTexture(e.TEXTURE_2D,o),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,t),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.LINEAR),{texture:o,width:t.naturalWidth,height:t.naturalHeight}}function C(e,t){const o=new Float32Array([-1,-1,0,1,1,-1,1,1,-1,1,0,0,1,1,1,0]),i=e.createBuffer();if(!i)throw new Error("Failed to create buffer");e.bindBuffer(e.ARRAY_BUFFER,i),e.bufferData(e.ARRAY_BUFFER,o,e.STATIC_DRAW);const a=4*Float32Array.BYTES_PER_ELEMENT,r=t.attribLocations.get("a_position");r!==void 0&&r!==-1&&(e.enableVertexAttribArray(r),e.vertexAttribPointer(r,2,e.FLOAT,!1,a,0));const f=t.attribLocations.get("a_texCoord");return f!==void 0&&f!==-1&&(e.enableVertexAttribArray(f),e.vertexAttribPointer(f,2,e.FLOAT,!1,a,2*Float32Array.BYTES_PER_ELEMENT)),i}function I(e){e.drawArrays(e.TRIANGLE_STRIP,0,4)}const R=`precision mediump float;
|
|
2
|
+
|
|
3
|
+
varying vec2 v_texCoord;
|
|
4
|
+
|
|
5
|
+
uniform sampler2D u_image;
|
|
6
|
+
uniform vec2 u_resolution;
|
|
7
|
+
uniform float u_dotRadius;
|
|
8
|
+
uniform float u_gridSize;
|
|
9
|
+
uniform float u_angleC;
|
|
10
|
+
uniform float u_angleM;
|
|
11
|
+
uniform float u_angleY;
|
|
12
|
+
uniform float u_angleK;
|
|
13
|
+
|
|
14
|
+
float halftone(vec2 uv, float angle, float channelValue, float gridSize, float dotRadius) {
|
|
15
|
+
float rad = radians(angle);
|
|
16
|
+
float s = sin(rad);
|
|
17
|
+
float c = cos(rad);
|
|
18
|
+
mat2 rot = mat2(c, -s, s, c);
|
|
19
|
+
|
|
20
|
+
vec2 rotUV = rot * uv;
|
|
21
|
+
vec2 grid = fract(rotUV / gridSize) - 0.5;
|
|
22
|
+
|
|
23
|
+
float dist = length(grid) * gridSize;
|
|
24
|
+
float radius = dotRadius * sqrt(channelValue);
|
|
25
|
+
|
|
26
|
+
return smoothstep(radius + 0.5, radius - 0.5, dist);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
void main() {
|
|
30
|
+
vec2 uv = v_texCoord * u_resolution;
|
|
31
|
+
vec4 color = texture2D(u_image, v_texCoord);
|
|
32
|
+
|
|
33
|
+
float r = color.r;
|
|
34
|
+
float g = color.g;
|
|
35
|
+
float b = color.b;
|
|
36
|
+
|
|
37
|
+
// RGB to CMYK
|
|
38
|
+
float k = 1.0 - max(max(r, g), b);
|
|
39
|
+
float invK = 1.0 - k;
|
|
40
|
+
float cy = invK > 0.001 ? (invK - r) / invK : 0.0;
|
|
41
|
+
float ma = invK > 0.001 ? (invK - g) / invK : 0.0;
|
|
42
|
+
float ye = invK > 0.001 ? (invK - b) / invK : 0.0;
|
|
43
|
+
|
|
44
|
+
// Compute halftone dots for each channel
|
|
45
|
+
float cDot = halftone(uv, u_angleC, cy, u_gridSize, u_dotRadius);
|
|
46
|
+
float mDot = halftone(uv, u_angleM, ma, u_gridSize, u_dotRadius);
|
|
47
|
+
float yDot = halftone(uv, u_angleY, ye, u_gridSize, u_dotRadius);
|
|
48
|
+
float kDot = halftone(uv, u_angleK, k, u_gridSize, u_dotRadius);
|
|
49
|
+
|
|
50
|
+
// Subtractive color mixing on white paper
|
|
51
|
+
float outR = (1.0 - cDot) * (1.0 - kDot);
|
|
52
|
+
float outG = (1.0 - mDot) * (1.0 - kDot);
|
|
53
|
+
float outB = (1.0 - yDot) * (1.0 - kDot);
|
|
54
|
+
|
|
55
|
+
gl_FragColor = vec4(outR, outG, outB, color.a);
|
|
56
|
+
}
|
|
57
|
+
`,m=`attribute vec2 a_position;
|
|
58
|
+
attribute vec2 a_texCoord;
|
|
59
|
+
varying vec2 v_texCoord;
|
|
60
|
+
|
|
61
|
+
void main() {
|
|
62
|
+
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
63
|
+
v_texCoord = a_texCoord;
|
|
64
|
+
}
|
|
65
|
+
`,T={name:"halftone-cmyk",fragmentShader:R,vertexShader:m,uniforms:[{name:"u_dotRadius",type:"float",default:4,attribute:"dot-radius"},{name:"u_gridSize",type:"float",default:8,attribute:"grid-size"},{name:"u_angleC",type:"float",default:15,attribute:"angle-c"},{name:"u_angleM",type:"float",default:75,attribute:"angle-m"},{name:"u_angleY",type:"float",default:0,attribute:"angle-y"},{name:"u_angleK",type:"float",default:45,attribute:"angle-k"}]},A={name:"halftone-duotone",fragmentShader:`precision mediump float;
|
|
66
|
+
|
|
67
|
+
varying vec2 v_texCoord;
|
|
68
|
+
|
|
69
|
+
uniform sampler2D u_image;
|
|
70
|
+
uniform vec2 u_resolution;
|
|
71
|
+
uniform float u_dotRadius;
|
|
72
|
+
uniform float u_gridSize;
|
|
73
|
+
uniform vec3 u_duotoneColor;
|
|
74
|
+
uniform float u_angle;
|
|
75
|
+
|
|
76
|
+
void main() {
|
|
77
|
+
vec2 uv = v_texCoord * u_resolution;
|
|
78
|
+
vec4 color = texture2D(u_image, v_texCoord);
|
|
79
|
+
|
|
80
|
+
// Convert to luminance
|
|
81
|
+
float luma = dot(color.rgb, vec3(0.299, 0.587, 0.114));
|
|
82
|
+
float darkness = 1.0 - luma;
|
|
83
|
+
|
|
84
|
+
// Rotate grid
|
|
85
|
+
float rad = radians(u_angle);
|
|
86
|
+
float s = sin(rad);
|
|
87
|
+
float c = cos(rad);
|
|
88
|
+
vec2 rotUV = mat2(c, -s, s, c) * uv;
|
|
89
|
+
|
|
90
|
+
vec2 grid = fract(rotUV / u_gridSize) - 0.5;
|
|
91
|
+
float dist = length(grid) * u_gridSize;
|
|
92
|
+
float radius = u_dotRadius * sqrt(darkness);
|
|
93
|
+
float dot = smoothstep(radius + 0.5, radius - 0.5, dist);
|
|
94
|
+
|
|
95
|
+
// Mix duotone color (paper) with black (dots)
|
|
96
|
+
vec3 result = mix(u_duotoneColor, vec3(0.0), dot);
|
|
97
|
+
|
|
98
|
+
gl_FragColor = vec4(result, color.a);
|
|
99
|
+
}
|
|
100
|
+
`,vertexShader:m,uniforms:[{name:"u_dotRadius",type:"float",default:4,attribute:"dot-radius"},{name:"u_gridSize",type:"float",default:8,attribute:"grid-size"},{name:"u_duotoneColor",type:"vec3",default:[0,.6,.8],attribute:"duotone-color"},{name:"u_angle",type:"float",default:0,attribute:"angle"}]},w={name:"pixel-sort",fragmentShader:`precision mediump float;
|
|
101
|
+
|
|
102
|
+
varying vec2 v_texCoord;
|
|
103
|
+
|
|
104
|
+
uniform sampler2D u_image;
|
|
105
|
+
uniform vec2 u_resolution;
|
|
106
|
+
uniform float u_threshold;
|
|
107
|
+
uniform float u_direction;
|
|
108
|
+
uniform float u_span;
|
|
109
|
+
|
|
110
|
+
float brightness(vec3 c) {
|
|
111
|
+
return dot(c, vec3(0.299, 0.587, 0.114));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
void main() {
|
|
115
|
+
vec2 uv = v_texCoord;
|
|
116
|
+
vec4 color = texture2D(u_image, uv);
|
|
117
|
+
float bri = brightness(color.rgb);
|
|
118
|
+
|
|
119
|
+
// Boundary pixel — pass through
|
|
120
|
+
if (bri < u_threshold) {
|
|
121
|
+
gl_FragColor = color;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
float rad = radians(u_direction);
|
|
126
|
+
vec2 dir = vec2(cos(rad), sin(rad));
|
|
127
|
+
vec2 step = dir / u_resolution;
|
|
128
|
+
|
|
129
|
+
int spanLen = int(u_span);
|
|
130
|
+
|
|
131
|
+
// Walk backward to find span start
|
|
132
|
+
int backCount = 0;
|
|
133
|
+
for (int i = 1; i < 256; i++) {
|
|
134
|
+
if (i >= spanLen) break;
|
|
135
|
+
vec2 sampleUV = uv - step * float(i);
|
|
136
|
+
if (sampleUV.x < 0.0 || sampleUV.x > 1.0 || sampleUV.y < 0.0 || sampleUV.y > 1.0) break;
|
|
137
|
+
float b = brightness(texture2D(u_image, sampleUV).rgb);
|
|
138
|
+
if (b < u_threshold) break;
|
|
139
|
+
backCount++;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Walk forward to find span end
|
|
143
|
+
int fwdCount = 0;
|
|
144
|
+
for (int i = 1; i < 256; i++) {
|
|
145
|
+
if (i >= spanLen) break;
|
|
146
|
+
vec2 sampleUV = uv + step * float(i);
|
|
147
|
+
if (sampleUV.x < 0.0 || sampleUV.x > 1.0 || sampleUV.y < 0.0 || sampleUV.y > 1.0) break;
|
|
148
|
+
float b = brightness(texture2D(u_image, sampleUV).rgb);
|
|
149
|
+
if (b < u_threshold) break;
|
|
150
|
+
fwdCount++;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
int totalSpan = backCount + 1 + fwdCount;
|
|
154
|
+
vec2 spanStartUV = uv - step * float(backCount);
|
|
155
|
+
|
|
156
|
+
// Count pixels in span that are darker than current (= rank)
|
|
157
|
+
int rank = 0;
|
|
158
|
+
for (int i = 0; i < 256; i++) {
|
|
159
|
+
if (i >= totalSpan) break;
|
|
160
|
+
vec2 sampleUV = spanStartUV + step * float(i);
|
|
161
|
+
float b = brightness(texture2D(u_image, sampleUV).rgb);
|
|
162
|
+
if (b < bri) rank++;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Resample at sorted position
|
|
166
|
+
vec2 sortedUV = spanStartUV + step * float(rank);
|
|
167
|
+
gl_FragColor = texture2D(u_image, sortedUV);
|
|
168
|
+
}
|
|
169
|
+
`,vertexShader:m,uniforms:[{name:"u_threshold",type:"float",default:.5,attribute:"threshold"},{name:"u_direction",type:"float",default:0,attribute:"sort-direction"},{name:"u_span",type:"float",default:64,attribute:"sort-span"}]},U={name:"dot-grid",fragmentShader:`precision mediump float;
|
|
170
|
+
|
|
171
|
+
varying vec2 v_texCoord;
|
|
172
|
+
|
|
173
|
+
uniform sampler2D u_image;
|
|
174
|
+
uniform vec2 u_resolution;
|
|
175
|
+
uniform float u_dotRadius;
|
|
176
|
+
uniform float u_gridSize;
|
|
177
|
+
uniform vec2 u_dotOffset;
|
|
178
|
+
uniform vec3 u_bgColor;
|
|
179
|
+
|
|
180
|
+
void main() {
|
|
181
|
+
vec2 uv = v_texCoord * u_resolution;
|
|
182
|
+
|
|
183
|
+
// Which grid cell this fragment belongs to
|
|
184
|
+
vec2 cell = floor(uv / u_gridSize);
|
|
185
|
+
|
|
186
|
+
// Cell origin in pixel space
|
|
187
|
+
vec2 cellOrigin = cell * u_gridSize;
|
|
188
|
+
|
|
189
|
+
// Dot center within the cell, shifted by u_dotOffset (0–1 range)
|
|
190
|
+
vec2 dotCenter = cellOrigin + u_dotOffset * u_gridSize;
|
|
191
|
+
|
|
192
|
+
// 4×4 multi-sample average color across the cell
|
|
193
|
+
vec3 avg = vec3(0.0);
|
|
194
|
+
for (int y = 0; y < 4; y++) {
|
|
195
|
+
for (int x = 0; x < 4; x++) {
|
|
196
|
+
vec2 sampleUV = (cellOrigin + (vec2(float(x), float(y)) + 0.5) * (u_gridSize / 4.0)) / u_resolution;
|
|
197
|
+
avg += texture2D(u_image, sampleUV).rgb;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
avg /= 16.0;
|
|
201
|
+
|
|
202
|
+
// Wrapped (toroidal) distance from fragment to dot center within the cell
|
|
203
|
+
vec2 d = (uv - dotCenter) / u_gridSize;
|
|
204
|
+
d = fract(d + 0.5) - 0.5; // wrap to [-0.5, 0.5]
|
|
205
|
+
float dist = length(d) * u_gridSize;
|
|
206
|
+
|
|
207
|
+
// Anti-aliased dot
|
|
208
|
+
float mask = smoothstep(u_dotRadius + 0.5, u_dotRadius - 0.5, dist);
|
|
209
|
+
|
|
210
|
+
vec3 result = mix(u_bgColor, avg, mask);
|
|
211
|
+
|
|
212
|
+
gl_FragColor = vec4(result, 1.0);
|
|
213
|
+
}
|
|
214
|
+
`,vertexShader:m,uniforms:[{name:"u_dotRadius",type:"float",default:4,attribute:"dot-radius"},{name:"u_gridSize",type:"float",default:8,attribute:"grid-size"},{name:"u_dotOffset",type:"vec2",default:[.5,.5],attribute:"dot-offset"},{name:"u_bgColor",type:"vec3",default:[1,1,1],attribute:"bg-color"}]},_=new Map;function h(e){_.set(e.name,e)}function p(e){return _.get(e)}function D(){return Array.from(_.keys())}h(T),h(A),h(w),h(U);var P=Object.defineProperty,k=Object.getOwnPropertyDescriptor,u=(e,t,o,i)=>{for(var a=i>1?void 0:i?k(t,o):t,r=e.length-1,f;r>=0;r--)(f=e[r])&&(a=(i?f(t,o,a):f(a))||a);return i&&a&&P(t,o,a),a};n.SomeShadeImage=class extends d.LitElement{constructor(){super(...arguments),this.src="",this.effect="halftone-cmyk",this.dotRadius=4,this.gridSize=8,this.angleC=15,this.angleM=75,this.angleY=0,this.angleK=45,this.duotoneColor="#0099cc",this.angle=0,this.threshold=.5,this.sortDirection=0,this.sortSpan=64,this.dotOffsetX=.5,this.dotOffsetY=.5,this.bgColor="#ffffff",this._webglAvailable=!0,this._canvas=null,this._gl=null,this._programInfo=null,this._textureInfo=null,this._quadBuffer=null,this._currentEffect=null,this._image=null,this._resizeObserver=null}render(){return this._webglAvailable?d.html`<canvas></canvas>`:d.html`<img .src=${this.src} alt="" />`}firstUpdated(){if(this._webglAvailable&&(this._canvas=this.shadowRoot.querySelector("canvas"),!!this._canvas)){if(this._gl=S(this._canvas),!this._gl){this._webglAvailable=!1,this.classList.add("webgl-unavailable");return}this._resizeObserver=new ResizeObserver(()=>this._handleResize()),this._resizeObserver.observe(this),this.src&&this._loadImage(this.src)}}updated(t){if(this._gl){if(t.has("src")&&this.src){this._loadImage(this.src);return}if(t.has("effect")){this._setupProgram(),this._renderFrame();return}this._renderFrame()}}disconnectedCallback(){var t;super.disconnectedCallback(),(t=this._resizeObserver)==null||t.disconnect(),this._cleanup()}_loadImage(t){const o=new Image;o.crossOrigin="anonymous",o.onload=()=>{this._image=o,this._uploadTexture(),this._sizeCanvas(),this._setupProgram(),this._renderFrame()},o.onerror=()=>{console.warn(`[some-shade] Failed to load image: ${t}`)},o.src=t}_uploadTexture(){!this._gl||!this._image||(this._textureInfo&&this._gl.deleteTexture(this._textureInfo.texture),this._textureInfo=x(this._gl,this._image))}_sizeCanvas(){if(!this._canvas||!this._textureInfo)return;const t=window.devicePixelRatio||1,o=this._textureInfo.width,i=this._textureInfo.height;this._canvas.width=o*t,this._canvas.height=i*t,this._canvas.style.aspectRatio=`${o} / ${i}`}_handleResize(){this._renderFrame()}_setupProgram(){if(!this._gl)return;const t=p(this.effect);if(!t){console.warn(`[some-shade] Unknown effect: ${this.effect}`);return}this._programInfo&&this._gl.deleteProgram(this._programInfo.program),this._currentEffect=t,this._programInfo=y(this._gl,t.vertexShader,t.fragmentShader),this._quadBuffer&&this._gl.deleteBuffer(this._quadBuffer),this._gl.useProgram(this._programInfo.program),this._quadBuffer=C(this._gl,this._programInfo)}_getUniformValues(){const t={};return this._textureInfo&&(t.u_resolution=[this._textureInfo.width*(window.devicePixelRatio||1),this._textureInfo.height*(window.devicePixelRatio||1)],t.u_dotRadius=this.dotRadius,t.u_gridSize=this.gridSize,this.effect==="halftone-cmyk"?(t.u_angleC=this.angleC,t.u_angleM=this.angleM,t.u_angleY=this.angleY,t.u_angleK=this.angleK):this.effect==="halftone-duotone"?(t.u_duotoneColor=this._parseHexColor(this.duotoneColor),t.u_angle=this.angle):this.effect==="pixel-sort"?(t.u_threshold=this.threshold,t.u_direction=this.sortDirection,t.u_span=this.sortSpan):this.effect==="dot-grid"&&(t.u_dotOffset=[this.dotOffsetX,this.dotOffsetY],t.u_bgColor=this._parseHexColor(this.bgColor))),t}_parseHexColor(t){const o=t.replace("#",""),i=parseInt(o.substring(0,2),16)/255,a=parseInt(o.substring(2,4),16)/255,r=parseInt(o.substring(4,6),16)/255;return[i,a,r]}_renderFrame(){const t=this._gl;if(!t||!this._programInfo||!this._textureInfo||!this._canvas)return;t.viewport(0,0,this._canvas.width,this._canvas.height),t.clearColor(0,0,0,0),t.clear(t.COLOR_BUFFER_BIT),t.useProgram(this._programInfo.program),t.activeTexture(t.TEXTURE0),t.bindTexture(t.TEXTURE_2D,this._textureInfo.texture);const o=this._programInfo.uniformLocations.get("u_image");o&&t.uniform1i(o,0),E(t,this._programInfo,this._getUniformValues()),t.bindBuffer(t.ARRAY_BUFFER,this._quadBuffer);const i=4*Float32Array.BYTES_PER_ELEMENT,a=this._programInfo.attribLocations.get("a_position");a!==void 0&&a!==-1&&(t.enableVertexAttribArray(a),t.vertexAttribPointer(a,2,t.FLOAT,!1,i,0));const r=this._programInfo.attribLocations.get("a_texCoord");r!==void 0&&r!==-1&&(t.enableVertexAttribArray(r),t.vertexAttribPointer(r,2,t.FLOAT,!1,i,2*Float32Array.BYTES_PER_ELEMENT)),I(t)}_cleanup(){this._gl&&(this._textureInfo&&this._gl.deleteTexture(this._textureInfo.texture),this._programInfo&&this._gl.deleteProgram(this._programInfo.program),this._quadBuffer&&this._gl.deleteBuffer(this._quadBuffer),this._gl=null,this._programInfo=null,this._textureInfo=null,this._quadBuffer=null)}},n.SomeShadeImage.styles=d.css`
|
|
215
|
+
:host {
|
|
216
|
+
display: block;
|
|
217
|
+
position: relative;
|
|
218
|
+
overflow: hidden;
|
|
219
|
+
}
|
|
220
|
+
canvas, img {
|
|
221
|
+
display: block;
|
|
222
|
+
width: 100%;
|
|
223
|
+
height: auto;
|
|
224
|
+
}
|
|
225
|
+
`,u([s.property()],n.SomeShadeImage.prototype,"src",2),u([s.property()],n.SomeShadeImage.prototype,"effect",2),u([s.property({type:Number,attribute:"dot-radius"})],n.SomeShadeImage.prototype,"dotRadius",2),u([s.property({type:Number,attribute:"grid-size"})],n.SomeShadeImage.prototype,"gridSize",2),u([s.property({type:Number,attribute:"angle-c"})],n.SomeShadeImage.prototype,"angleC",2),u([s.property({type:Number,attribute:"angle-m"})],n.SomeShadeImage.prototype,"angleM",2),u([s.property({type:Number,attribute:"angle-y"})],n.SomeShadeImage.prototype,"angleY",2),u([s.property({type:Number,attribute:"angle-k"})],n.SomeShadeImage.prototype,"angleK",2),u([s.property({attribute:"duotone-color"})],n.SomeShadeImage.prototype,"duotoneColor",2),u([s.property({type:Number})],n.SomeShadeImage.prototype,"angle",2),u([s.property({type:Number})],n.SomeShadeImage.prototype,"threshold",2),u([s.property({type:Number,attribute:"sort-direction"})],n.SomeShadeImage.prototype,"sortDirection",2),u([s.property({type:Number,attribute:"sort-span"})],n.SomeShadeImage.prototype,"sortSpan",2),u([s.property({type:Number,attribute:"dot-offset-x"})],n.SomeShadeImage.prototype,"dotOffsetX",2),u([s.property({type:Number,attribute:"dot-offset-y"})],n.SomeShadeImage.prototype,"dotOffsetY",2),u([s.property({attribute:"bg-color"})],n.SomeShadeImage.prototype,"bgColor",2),u([s.state()],n.SomeShadeImage.prototype,"_webglAvailable",2),n.SomeShadeImage=u([s.customElement("some-shade-image")],n.SomeShadeImage),n.get=p,n.list=D,n.register=h,Object.defineProperty(n,Symbol.toStringTag,{value:"Module"})}));
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@johnfmorton/some-shade",
|
|
3
|
+
"version": "0.1.0-beta.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/some-shade.cjs.js",
|
|
6
|
+
"module": "./dist/some-shade.es.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/some-shade.es.js",
|
|
12
|
+
"require": "./dist/some-shade.cjs.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"lit": "^3.2.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"typescript": "^5.5.0",
|
|
23
|
+
"vite": "^6.0.0",
|
|
24
|
+
"vite-plugin-dts": "^4.0.0"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"dev": "vite build --watch",
|
|
28
|
+
"build": "vite build"
|
|
29
|
+
}
|
|
30
|
+
}
|