|
1 | 1 | <script lang="ts">
|
2 | 2 | import type { HTMLCanvasAttributes } from 'svelte/elements';
|
3 |
| - import { rgb2rgba } from './rgb2rgba'; |
4 |
| - type Props = Omit<HTMLCanvasAttributes, 'width' | 'height'> & { |
| 3 | + type Props = HTMLCanvasAttributes & { |
5 | 4 | data: Uint8Array;
|
6 | 5 | width: number;
|
7 | 6 | height: number;
|
8 | 7 | };
|
9 | 8 | let { data, width, height, ...props }: Props = $props();
|
10 | 9 | let canvas: HTMLCanvasElement | undefined = $state();
|
| 10 | +
|
11 | 11 | $effect(() => {
|
12 |
| - const ctx = canvas?.getContext('2d'); |
13 |
| - if (!ctx) return; |
14 |
| - const imageData = ctx.createImageData(width, height); |
15 |
| - imageData.data.set(rgb2rgba(data)); |
16 |
| - ctx.putImageData(imageData, 0, 0); |
| 12 | + if (!canvas) return; |
| 13 | + const gl = canvas.getContext('webgl2'); |
| 14 | + if (!gl) return; |
| 15 | +
|
| 16 | + const vertexShaderSource = `\ |
| 17 | +#version 300 es |
| 18 | +
|
| 19 | +// an attribute is an input (in) to a vertex shader. |
| 20 | +// It will receive data from a buffer |
| 21 | +in vec2 a_position; |
| 22 | +
|
| 23 | +in vec2 a_texCoord; |
| 24 | +
|
| 25 | +// Used to pass in the resolution of the canvas |
| 26 | +uniform vec2 u_resolution; |
| 27 | +
|
| 28 | +out vec2 v_texCoord; |
| 29 | +
|
| 30 | +// all shaders have a main function |
| 31 | +void main() { |
| 32 | + // convert the position from pixels to 0.0 to 1.0 |
| 33 | + vec2 zeroToOne = a_position / u_resolution; |
| 34 | +
|
| 35 | + // convert from 0->1 to 0->2 |
| 36 | + vec2 zeroToTwo = zeroToOne * 2.0; |
| 37 | +
|
| 38 | + // convert from 0->2 to -1->+1 (clipspace) |
| 39 | + vec2 clipSpace = zeroToTwo - 1.0; |
| 40 | +
|
| 41 | + // invert y axis |
| 42 | + clipSpace.y *= -1.0; |
| 43 | +
|
| 44 | + gl_Position = vec4(clipSpace, 0, 1); |
| 45 | +
|
| 46 | + // pass the texCoord to the fragment shader |
| 47 | + // The GPU will interpolate this value between points |
| 48 | + v_texCoord = a_texCoord; |
| 49 | +} |
| 50 | +`; |
| 51 | +
|
| 52 | + const fragmentShaderSource = `\ |
| 53 | +#version 300 es |
| 54 | +precision highp float; |
| 55 | + |
| 56 | +// our texture |
| 57 | +uniform sampler2D u_image; |
| 58 | + |
| 59 | +// the texCoords passed in from the vertex shader. |
| 60 | +in vec2 v_texCoord; |
| 61 | + |
| 62 | +// we need to declare an output for the fragment shader |
| 63 | +out vec4 outColor; |
| 64 | + |
| 65 | +void main() { |
| 66 | + // Look up a color from the texture. |
| 67 | + outColor = texture(u_image, v_texCoord); |
| 68 | +}`; |
| 69 | + const vertexShader = gl.createShader(gl.VERTEX_SHADER); |
| 70 | + if (!vertexShader) return; |
| 71 | + gl.shaderSource(vertexShader, vertexShaderSource); |
| 72 | + gl.compileShader(vertexShader); |
| 73 | + const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); |
| 74 | + if (!fragmentShader) return; |
| 75 | + gl.shaderSource(fragmentShader, fragmentShaderSource); |
| 76 | + gl.compileShader(fragmentShader); |
| 77 | +
|
| 78 | + const program = gl.createProgram(); |
| 79 | + gl.attachShader(program, vertexShader); |
| 80 | + gl.attachShader(program, fragmentShader); |
| 81 | + gl.linkProgram(program); |
| 82 | +
|
| 83 | + // look up where the vertex data needs to go. |
| 84 | + const positionAttributeLocation = gl.getAttribLocation(program, 'a_position'); |
| 85 | + const texCoordAttributeLocation = gl.getAttribLocation(program, 'a_texCoord'); |
| 86 | +
|
| 87 | + // look up uniform locations |
| 88 | + const resolutionUniformLocation = gl.getUniformLocation(program, 'u_resolution'); |
| 89 | + const imageLocation = gl.getUniformLocation(program, 'u_image'); |
| 90 | +
|
| 91 | + // Create a buffer and put a single pixel space rectangle in |
| 92 | + // it (2 triangles) |
| 93 | + // Create a buffer and put three 2d clip space points in it |
| 94 | + const positionBuffer = gl.createBuffer(); |
| 95 | +
|
| 96 | + // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer) |
| 97 | + gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); |
| 98 | +
|
| 99 | + // Create a vertex array object (attribute state) |
| 100 | + const vao = gl.createVertexArray(); |
| 101 | +
|
| 102 | + // and make it the one we're currently working with |
| 103 | + gl.bindVertexArray(vao); |
| 104 | +
|
| 105 | + // Turn on the attribute |
| 106 | + gl.enableVertexAttribArray(positionAttributeLocation); |
| 107 | +
|
| 108 | + // Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER) |
| 109 | + gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0); |
| 110 | +
|
| 111 | + // Tell WebGL how to convert from clip space to pixels |
| 112 | + gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); |
| 113 | +
|
| 114 | + // Clear the canvas |
| 115 | + gl.clearColor(0, 0, 0, 0); |
| 116 | + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); |
| 117 | +
|
| 118 | + // provide texture coordinates for the rectangle. |
| 119 | + const texCoordBuffer = gl.createBuffer(); |
| 120 | + gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer); |
| 121 | + gl.bufferData( |
| 122 | + gl.ARRAY_BUFFER, |
| 123 | + new Float32Array([0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0]), |
| 124 | + gl.STATIC_DRAW |
| 125 | + ); |
| 126 | + gl.enableVertexAttribArray(texCoordAttributeLocation); |
| 127 | + gl.vertexAttribPointer(texCoordAttributeLocation, 2, gl.FLOAT, false, 0, 0); |
| 128 | +
|
| 129 | + // Create a texture. |
| 130 | + const texture = gl.createTexture(); |
| 131 | +
|
| 132 | + // make unit 0 the active texture uint |
| 133 | + // (ie, the unit all other texture commands will affect |
| 134 | + gl.activeTexture(gl.TEXTURE0 + 0); |
| 135 | +
|
| 136 | + // Bind it to texture unit 0' 2D bind point |
| 137 | + gl.bindTexture(gl.TEXTURE_2D, texture); |
| 138 | +
|
| 139 | + // Set the parameters so we don't need mips and so we're not filtering |
| 140 | + // and we don't repeat |
| 141 | + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); |
| 142 | + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); |
| 143 | + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); |
| 144 | + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); |
| 145 | +
|
| 146 | + // Upload the image into the texture. |
| 147 | + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, width, height, 0, gl.RGB, gl.UNSIGNED_BYTE, data); |
| 148 | +
|
| 149 | + // Tell it to use our program (pair of shaders) |
| 150 | + gl.useProgram(program); |
| 151 | +
|
| 152 | + // Bind the attribute/buffer set we want. |
| 153 | + gl.bindVertexArray(vao); |
| 154 | +
|
| 155 | + // Pass in the canvas resolution so we can convert from |
| 156 | + // pixels to clipspace in the shader |
| 157 | + gl.uniform2f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height); |
| 158 | +
|
| 159 | + // Tell the shader to get the texture from texture unit 0 |
| 160 | + gl.uniform1i(imageLocation, 0); |
| 161 | +
|
| 162 | + // Bind the position buffer so gl.bufferData that will be called |
| 163 | + // in setRectangle puts data in the position buffer |
| 164 | + gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); |
| 165 | +
|
| 166 | + function setRectangle( |
| 167 | + gl: WebGL2RenderingContext, |
| 168 | + x: number, |
| 169 | + y: number, |
| 170 | + width: number, |
| 171 | + height: number |
| 172 | + ) { |
| 173 | + const x1 = x; |
| 174 | + const x2 = x + width; |
| 175 | + const y1 = y; |
| 176 | + const y2 = y + height; |
| 177 | + gl.bufferData( |
| 178 | + gl.ARRAY_BUFFER, |
| 179 | + new Float32Array([x1, y1, x2, y1, x1, y2, x1, y2, x2, y1, x2, y2]), |
| 180 | + gl.STATIC_DRAW |
| 181 | + ); |
| 182 | + } |
| 183 | + // Set a rectangle the same size as the image. |
| 184 | + setRectangle(gl, 0, 0, width, height); |
| 185 | + // draw |
| 186 | + gl.drawArrays(gl.TRIANGLES, 0, 6); |
17 | 187 | });
|
18 | 188 | </script>
|
19 | 189 |
|
|
0 commit comments