You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
functiondraw(frame?: CanvasImageSource){returnnewPromise<void>((resolve)=>{constVIDEO_NATURAL_WIDTH=videoRef?.videoWidth;constVIDEO_NATURAL_HEIGHT=videoRef?.videoHeight;constVIDEO_NATURAL_ASPECT_RATIO=VIDEO_NATURAL_WIDTH/VIDEO_NATURAL_HEIGHT;constp=100;constwidth=Math.min(ctx.canvas.height*VIDEO_NATURAL_ASPECT_RATIO,ctx.canvas.width)-p;constheight=Math.min(width/VIDEO_NATURAL_ASPECT_RATIO,ctx.canvas.height);constleft=(ctx.canvas.width-width)/2;consttop=(ctx.canvas.height-height)/2;ctx?.drawImage(backgroundImageRef,0,0,ctx.canvas.width,ctx.canvas.height);ctx.imageSmoothingEnabled=true;ctx.imageSmoothingQuality="high";ctx?.drawImage(frame??videoRef,left,top,width,height);resolve();});}exportfunctionexportAsGif(){constdecodeWorker=newDecodeWorker();constgifEncoderWorker=newGifEncoderWorker();constgif=GIFEncoder({auto: false});decodeWorker.addEventListener("message",async({ data })=>{const{ type, ...rest}=data;if(type==="frame"){constframe: VideoFrame=rest.frame;awaitdraw(frame);frame.close();constuint8=ctx?.getImageData(0,0,1920,1080).data;gifEncoderWorker.postMessage({type: "encode",frame: uint8});}});gifEncoderWorker.addEventListener("message",({ data })=>{const{ type, ...rest}=data;if(type==="encoded"){constoutput=rest.output;frames.push(output);}});decodeWorker.postMessage({type: "start",url: $recording?.url});setTimeout(async()=>{constchunks=awaitPromise.all(frames);gif.writeHeader();// Now we can write each chunkfor(leti=0;i<chunks.length;i++){gif.stream.writeBytesView(chunks[i]);}// Finish the GIFgif.finish();// Close workersdecodeWorker.terminate();gifEncoderWorker.terminate();// Return bytesconstbuffer=gif.bytesView();consturl=URL.createObjectURL(newBlob([buffer],{type: "image/gif"}));console.log(url);},50_000);}
// gif-encoder.worker.tsimport{GIFEncoder,applyPalette,prequantize,quantize}from"gifenc";constFORMAT="rgb565";constMAX_COLORS=256;letisFirstFrame=true;functiononEncodeFrame({ frame }: {frame: Uint8Array|Uint8ClampedArray}){constencoder=GIFEncoder({auto: false});prequantize(frame);constpalette=quantize(frame,MAX_COLORS,{format: FORMAT});constindex=applyPalette(frame,palette,FORMAT);encoder.writeFrame(index,1920,1080,{ palette,first: isFirstFrame});constoutput=encoder.bytesView();self.postMessage({type: "encoded", output },{transfer: [output.buffer]});isFirstFrame=false;}constMESSAGE_HANLDER={encode: onEncodeFrame,default: ()=>{thrownewError("This type of message is not available");},};typeHandlers=keyoftypeofMESSAGE_HANLDER;self.addEventListener("message",(e)=>{const{ type, ...rest}: {type: Handlers}=e.data;consthandler=MESSAGE_HANLDER[type]??MESSAGE_HANLDER.default;handler(rest);});
The text was updated successfully, but these errors were encountered:
jrafaaael
changed the title
Some colors are being blurred(?
Some colors are being pixelated(?
Dec 21, 2023
From what I see, it looks like a standard quantization of an image with many colours down to an image with 256 colours, and that creates banding that shows on the colourful gradients. toDataURL generates an image with many more colours than 256, but GIF is limited to 256 colours.
There is not a very easy way around this, but here's some suggestions:
Use dithering, like floyd-steinberg dithering, as this will reduce the noticeable banding between colours. See an example here
Use a different quantizer than the one in gifenc; some might be better than others depending on the image. You could try some different ones here
If you have control over your backdrop while recording frames, you could use a flatter and less colourful one.
The examples show better what I'm trying to say:
gifenc
resultcanvas.toDataUrl()
resultCode:
The text was updated successfully, but these errors were encountered: