@react-gifs/tools
Version:
Set of react tools for GIFs renering
404 lines (349 loc) • 18.7 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var React = require('react');
var gifuctJs = require('gifuct-js');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
const reducer = (state, action = {}) => {
if (typeof action === "function") {
action = action(state);
}
const isLoadedChange = action.loaded && state.loaded !== action.loaded;
const {
playing,
...rest
} = action;
const copy = { ...state,
...rest
};
if (isLoadedChange) {
Object.assign(copy, {
playing: copy.autoPlay && copy.loaded
});
}
if (action.delays != null || action.frames != null) {
Object.assign(copy, {
length: copy.frames.length
});
if (process.env.NODE_ENV !== "production") {
if (copy.frames.length !== copy.delays.length) throw Error("frames and delays have different sizes");
}
}
if (action.index != null || action.frames != null) {
Object.assign(copy, {
index: copy.length === 0 ? 0 : (copy.length + copy.index) % copy.length
});
}
if (action.playing != null) {
Object.assign(copy, copy.loaded ? {
playing
} : {
autoPlay: playing
});
}
return copy;
};
const initializer = stateOrFn => reducer({
autoPlay: true,
playing: false,
frames: [],
delays: [],
index: 0,
length: 0,
loaded: false
}, stateOrFn);
const usePlayerState = stateOrFn => {
return React.useReducer(reducer, stateOrFn, initializer);
};
var code = "function e(e){var r={exports:{}};return e(r,r.exports),r.exports}var r=e((function(e,r){Object.defineProperty(r,\"__esModule\",{value:!0}),r.loop=r.conditional=r.parse=void 0;r.parse=function e(r,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:n;if(Array.isArray(t))t.forEach((function(t){return e(r,t,n,a)}));else if(\"function\"==typeof t)t(r,n,a,e);else{var i=Object.keys(t)[0];Array.isArray(t[i])?(a[i]={},e(r,t[i],n,a[i])):a[i]=t[i](r,n,a,e)}return n};r.conditional=function(e,r){return function(t,n,a,i){r(t,n,a)&&i(t,e,n,a)}};r.loop=function(e,r){return function(t,n,a,i){for(var o=[];r(t,n,a);){var s={};i(t,e,n,s),o.push(s)}return o}}})),t=e((function(e,r){Object.defineProperty(r,\"__esModule\",{value:!0}),r.readBits=r.readArray=r.readUnsigned=r.readString=r.peekBytes=r.readBytes=r.peekByte=r.readByte=r.buildStream=void 0;r.buildStream=function(e){return{data:e,pos:0}};var t=function(){return function(e){return e.data[e.pos++]}};r.readByte=t;r.peekByte=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0;return function(r){return r.data[r.pos+e]}};var n=function(e){return function(r){return r.data.subarray(r.pos,r.pos+=e)}};r.readBytes=n;r.peekBytes=function(e){return function(r){return r.data.subarray(r.pos,r.pos+e)}};r.readString=function(e){return function(r){return Array.from(n(e)(r)).map((function(e){return String.fromCharCode(e)})).join(\"\")}};r.readUnsigned=function(e){return function(r){var t=n(2)(r);return e?(t[1]<<8)+t[0]:(t[0]<<8)+t[1]}};r.readArray=function(e,r){return function(t,a,i){for(var o=\"function\"==typeof r?r(t,a,i):r,s=n(e),d=new Array(o),c=0;c<o;c++)d[c]=s(t);return d}};r.readBits=function(e){return function(r){for(var t=function(e){return e.data[e.pos++]}(r),n=new Array(8),a=0;a<8;a++)n[7-a]=!!(t&1<<a);return Object.keys(e).reduce((function(r,t){var a=e[t];return a.length?r[t]=function(e,r,t){for(var n=0,a=0;a<t;a++)n+=e[r+a]&&Math.pow(2,t-a-1);return n}(n,a.index,a.length):r[t]=n[a.index],r}),{})}}})),n=e((function(e,n){Object.defineProperty(n,\"__esModule\",{value:!0}),n.default=void 0;var a={blocks:function(e){for(var r=[],n=e.data.length,a=0,i=(0,t.readByte)()(e);0!==i;i=(0,t.readByte)()(e)){if(e.pos+i>=n){var o=n-e.pos;r.push((0,t.readBytes)(o)(e)),a+=o;break}r.push((0,t.readBytes)(i)(e)),a+=i}for(var s=new Uint8Array(a),d=0,c=0;c<r.length;c++)s.set(r[c],d),d+=r[c].length;return s}},i=(0,r.conditional)({gce:[{codes:(0,t.readBytes)(2)},{byteSize:(0,t.readByte)()},{extras:(0,t.readBits)({future:{index:0,length:3},disposal:{index:3,length:3},userInput:{index:6},transparentColorGiven:{index:7}})},{delay:(0,t.readUnsigned)(!0)},{transparentColorIndex:(0,t.readByte)()},{terminator:(0,t.readByte)()}]},(function(e){var r=(0,t.peekBytes)(2)(e);return 33===r[0]&&249===r[1]})),o=(0,r.conditional)({image:[{code:(0,t.readByte)()},{descriptor:[{left:(0,t.readUnsigned)(!0)},{top:(0,t.readUnsigned)(!0)},{width:(0,t.readUnsigned)(!0)},{height:(0,t.readUnsigned)(!0)},{lct:(0,t.readBits)({exists:{index:0},interlaced:{index:1},sort:{index:2},future:{index:3,length:2},size:{index:5,length:3}})}]},(0,r.conditional)({lct:(0,t.readArray)(3,(function(e,r,t){return Math.pow(2,t.descriptor.lct.size+1)}))},(function(e,r,t){return t.descriptor.lct.exists})),{data:[{minCodeSize:(0,t.readByte)()},a]}]},(function(e){return 44===(0,t.peekByte)()(e)})),s=(0,r.conditional)({text:[{codes:(0,t.readBytes)(2)},{blockSize:(0,t.readByte)()},{preData:function(e,r,n){return(0,t.readBytes)(n.text.blockSize)(e)}},a]},(function(e){var r=(0,t.peekBytes)(2)(e);return 33===r[0]&&1===r[1]})),d=(0,r.conditional)({application:[{codes:(0,t.readBytes)(2)},{blockSize:(0,t.readByte)()},{id:function(e,r,n){return(0,t.readString)(n.blockSize)(e)}},a]},(function(e){var r=(0,t.peekBytes)(2)(e);return 33===r[0]&&255===r[1]})),c=(0,r.conditional)({comment:[{codes:(0,t.readBytes)(2)},a]},(function(e){var r=(0,t.peekBytes)(2)(e);return 33===r[0]&&254===r[1]})),u=[{header:[{signature:(0,t.readString)(3)},{version:(0,t.readString)(3)}]},{lsd:[{width:(0,t.readUnsigned)(!0)},{height:(0,t.readUnsigned)(!0)},{gct:(0,t.readBits)({exists:{index:0},resolution:{index:1,length:3},sort:{index:4},size:{index:5,length:3}})},{backgroundColorIndex:(0,t.readByte)()},{pixelAspectRatio:(0,t.readByte)()}]},(0,r.conditional)({gct:(0,t.readArray)(3,(function(e,r){return Math.pow(2,r.lsd.gct.size+1)}))},(function(e,r){return r.lsd.gct.exists})),{frames:(0,r.loop)([i,d,c,o,s],(function(e){var r=(0,t.peekByte)()(e);return 33===r||44===r}))}];n.default=u})),a=e((function(e,r){Object.defineProperty(r,\"__esModule\",{value:!0}),r.deinterlace=void 0;r.deinterlace=function(e,r){for(var t=new Array(e.length),n=e.length/r,a=function(n,a){var i=e.slice(a*r,(a+1)*r);t.splice.apply(t,[n*r,r].concat(i))},i=[0,4,2,1],o=[8,8,4,2],s=0,d=0;d<4;d++)for(var c=i[d];c<n;c+=o[d])a(c,s),s++;return t}})),i=e((function(e,r){Object.defineProperty(r,\"__esModule\",{value:!0}),r.lzw=void 0;r.lzw=function(e,r,t){var n,a,i,o,s,d,c,u,l,f,p,g,y,h,v,m,x=4096,B=t,w=new Array(t),b=new Array(x),k=new Array(x),A=new Array(4097);for(s=(a=1<<(f=e))+1,n=a+2,c=-1,i=(1<<(o=f+1))-1,u=0;u<a;u++)b[u]=0,k[u]=u;for(p=g=y=h=v=m=0,l=0;l<B;){if(0===h){if(g<o){p+=r[m]<<g,g+=8,m++;continue}if(u=p&i,p>>=o,g-=o,u>n||u==s)break;if(u==a){i=(1<<(o=f+1))-1,n=a+2,c=-1;continue}if(-1==c){A[h++]=k[u],c=u,y=u;continue}for(d=u,u==n&&(A[h++]=y,u=c);u>a;)A[h++]=k[u],u=b[u];y=255&k[u],A[h++]=y,n<x&&(b[n]=c,k[n]=y,0==(++n&i)&&n<x&&(o++,i+=n)),c=d}h--,w[v++]=A[h],l++}for(l=v;l<B;l++)w[l]=0;return w}})),o=e((function(e,o){Object.defineProperty(o,\"__esModule\",{value:!0}),o.decompressFrames=o.decompressFrame=o.parseGIF=void 0;var s,d=(s=n)&&s.__esModule?s:{default:s};o.parseGIF=function(e){var n=new Uint8Array(e);return(0,r.parse)((0,t.buildStream)(n),d.default)};var c=function(e,r,t){if(e.image){var n=e.image,o=n.descriptor.width*n.descriptor.height,s=(0,i.lzw)(n.data.minCodeSize,n.data.blocks,o);n.descriptor.lct.interlaced&&(s=(0,a.deinterlace)(s,n.descriptor.width));var d={pixels:s,dims:{top:e.image.descriptor.top,left:e.image.descriptor.left,width:e.image.descriptor.width,height:e.image.descriptor.height}};return n.descriptor.lct&&n.descriptor.lct.exists?d.colorTable=n.lct:d.colorTable=r,e.gce&&(d.delay=10*(e.gce.delay||10),d.disposalType=e.gce.extras.disposal,e.gce.extras.transparentColorGiven&&(d.transparentIndex=e.gce.transparentColorIndex)),t&&(d.patch=function(e){for(var r=e.pixels.length,t=new Uint8ClampedArray(4*r),n=0;n<r;n++){var a=4*n,i=e.pixels[n],o=e.colorTable[i]||[0,0,0];t[a]=o[0],t[a+1]=o[1],t[a+2]=o[2],t[a+3]=i!==e.transparentIndex?255:0}return t}(d)),d}console.warn(\"gif frame does not have associated image.\")};o.decompressFrame=c;o.decompressFrames=function(e,r){return e.frames.filter((function(e){return e.image})).map((function(t){return c(t,e.gct,r)}))}}));const s=(e,r,t)=>{const{width:n,height:a,top:i,left:o}=r.dims,s=i*t.width+o;for(let i=0;i<a;i++)for(let a=0;a<n;a++){const o=i*n+a,d=r.pixels[o];if(d!==r.transparentIndex){const n=s+i*t.width+a,o=r.colorTable[d]||[0,0,0];e[4*n]=o[0],e[4*n+1]=o[1],e[4*n+2]=o[2],e[4*n+3]=255}}return e},d=new Map;self.addEventListener(\"message\",(e=>{const{type:r,src:t}=e.data||e;switch(r){case\"parse\":if(!d.has(t)){const e=new AbortController,r={signal:e.signal};d.set(t,e),((e,{signal:r})=>fetch(e,{signal:r}).then((e=>{if(!e.headers.get(\"Content-Type\").includes(\"image/gif\"))throw Error(`Wrong content type: \"${e.headers.get(\"Content-Type\")}\"`);return e.arrayBuffer()})).then((e=>o.parseGIF(e))).then((e=>((e=>{let r=null;for(const t of e.frames)r=t.gce?t.gce:r,\"image\"in t&&!(\"gce\"in t)&&(t.gce=r)})(e),e))).then((e=>Promise.all([o.decompressFrames(e,!1),{width:e.lsd.width,height:e.lsd.height}]))).then((([e,r])=>{const t=[],n=r.width*r.height*4;for(let a=0;a<e.length;++a){const i=e[a],o=0===a||2===e[a-1].disposalType?new Uint8ClampedArray(n):t[a-1].slice();t.push(s(o,i,r))}return{...r,loaded:!0,delays:e.map((e=>e.delay)),frames:t}})))(t,r).then((e=>{self.postMessage(Object.assign(e,{src:t}),e.frames.map((e=>e.buffer)))})).catch((e=>{self.postMessage({src:t,error:e,loaded:!0})})).finally((()=>{d.delete(t)}))}break;case\"cancel\":if(d.has(t)){d.get(t).abort(),d.delete(t)}}}));\n";
function WorkerFactory(options) {
const blob = new Blob([code], {
type: 'application/javascript'
});
const url = URL.createObjectURL(blob);
const worker = new Worker(url, options);
URL.revokeObjectURL(url);
return worker;
}
const validateAndFix = gif => {
let currentGce = null;
for (const frame of gif.frames) {
currentGce = frame.gce ? frame.gce : currentGce; // fix loosing graphic control extension for same frames
if ("image" in frame && !("gce" in frame)) {
frame.gce = currentGce;
}
}
};
const parse = (src, {
signal
}) => fetch(src, {
signal
}).then(resp => {
if (!resp.headers.get("Content-Type").includes("image/gif")) throw Error(`Wrong content type: "${resp.headers.get("Content-Type")}"`);
return resp.arrayBuffer();
}).then(buffer => gifuctJs.parseGIF(buffer)).then(gif => {
validateAndFix(gif);
return gif;
}).then(gif => Promise.all([gifuctJs.decompressFrames(gif, false), {
width: gif.lsd.width,
height: gif.lsd.height
}])).then(([frames, options]) => {
const readyFrames = [];
const size = options.width * options.height * 4;
for (let i = 0; i < frames.length; ++i) {
const frame = frames[i];
const typedArray = i === 0 || frames[i - 1].disposalType === 2 ? new Uint8ClampedArray(size) : readyFrames[i - 1].slice();
readyFrames.push(putPixels(typedArray, frame, options));
}
return { ...options,
loaded: true,
delays: frames.map(frame => frame.delay),
frames: readyFrames
};
});
const putPixels = (typedArray, frame, gifSize) => {
const {
width,
height,
top: dy,
left: dx
} = frame.dims;
const offset = dy * gifSize.width + dx;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const pPos = y * width + x;
const colorIndex = frame.pixels[pPos];
if (colorIndex !== frame.transparentIndex) {
const taPos = offset + y * gifSize.width + x;
const color = frame.colorTable[colorIndex] || [0, 0, 0];
typedArray[taPos * 4] = color[0];
typedArray[taPos * 4 + 1] = color[1];
typedArray[taPos * 4 + 2] = color[2];
typedArray[taPos * 4 + 3] = 255;
}
}
}
return typedArray;
};
const genearate = info => {
return { ...info,
frames: info.frames.map(buffer => {
const image = new ImageData(info.width, info.height);
image.data.set(new Uint8ClampedArray(buffer));
return image;
})
};
};
const createSingleton = (constructor, destructor) => {
const ref = {};
return () => {
if (!ref.instance) {
ref.instance = constructor();
}
React.useLayoutEffect(() => {
if (ref.timeout) {
clearTimeout(ref.timeout);
delete ref.timeout;
} else {
ref.usageCount = (ref.usageCount || 0) + 1;
}
return () => {
ref.timeout = setTimeout(() => {
ref.usageCount = ref.usageCount - 1;
if (ref.usageCount === 0) {
destructor && destructor(ref.instance);
delete ref.instance;
delete ref.timeout;
}
});
};
}, [ref, destructor]);
return ref.instance;
};
};
const useUpdatedRef = value => {
const ref = React.useRef(value);
React.useEffect(() => {
ref.current = value;
}, [value]);
return ref;
};
const useEventCallback = callback => {
const ref = useUpdatedRef(callback);
return React.useCallback(arg => ref.current && ref.current(arg), []);
};
const useRaf = (callback, pause) => {
const cb = useEventCallback(callback);
React.useLayoutEffect(() => {
if (!pause) {
let id;
let prev = null;
const handleUpdate = () => {
id = requestAnimationFrame(now => {
const dt = now - (prev || now);
prev = now;
cb(dt);
handleUpdate();
});
};
handleUpdate();
return () => cancelAnimationFrame(id);
}
}, [pause, cb]);
};
const useAsyncEffect = (fn, deps) => {
const cb = useEventCallback(fn);
React.useEffect(() => {
const controller = new AbortController();
const dest = cb(controller);
return () => {
controller.abort();
dest && dest();
};
}, [...deps]);
};
const useParser = (src, callback) => {
const cb = useEventCallback(callback);
useAsyncEffect(controller => {
if (typeof src === "string") {
parse(src, {
signal: controller.signal
}).then(raw => genearate(raw)).then(info => cb(info)).catch(error => cb({
error,
loaded: true
}));
}
}, [src]);
};
const useWorkerSingleton = /*#__PURE__*/createSingleton(() => new WorkerFactory(), worker => worker.terminate());
const useWorkerParser = (src, callback) => {
const cb = useEventCallback(callback);
const worker = useWorkerSingleton();
React.useEffect(() => {
if (typeof src === "string") {
const handler = e => {
const message = e.data || e;
if (message.src === src) {
const data = message.error ? message : genearate(message);
cb(data);
}
};
worker.addEventListener("message", handler);
worker.postMessage({
src,
type: "parse"
});
return () => {
worker.postMessage({
src,
type: "cancel"
});
worker.removeEventListener("message", handler);
};
}
}, [worker, src]);
};
const usePlayback = (state, updater) => {
const delay = React.useRef(0);
useRaf(dt => {
const {
delays,
index: currentIndex
} = state;
delay.current += dt;
if (delay.current > delays[currentIndex]) {
delay.current = delay.current % delays[currentIndex];
updater();
}
}, !state.playing);
};
const calcArgs = (fit, frameSize, canvasSize) => {
switch (fit) {
case "fill":
return [0, 0, frameSize.width, frameSize.height, 0, 0, canvasSize.width, canvasSize.height];
case "contain":
{
const ratio = Math.min(canvasSize.width / frameSize.width, canvasSize.height / frameSize.height);
const centerX = (canvasSize.width - frameSize.width * ratio) / 2;
const centerY = (canvasSize.height - frameSize.height * ratio) / 2;
return [0, 0, frameSize.width, frameSize.height, centerX, centerY, frameSize.width * ratio, frameSize.height * ratio];
}
case "cover":
{
const ratio = Math.max(canvasSize.width / frameSize.width, canvasSize.height / frameSize.height);
const centerX = (canvasSize.width - frameSize.width * ratio) / 2;
const centerY = (canvasSize.height - frameSize.height * ratio) / 2;
return [0, 0, frameSize.width, frameSize.height, centerX, centerY, frameSize.width * ratio, frameSize.height * ratio];
}
default:
return [0, 0];
}
};
const combine = (...refs) => value => {
refs.forEach(ref => {
if (typeof ref === "function") {
ref(value);
} else if (ref != null) {
ref.current = value;
}
});
};
const useCanvasSingleton = /*#__PURE__*/createSingleton(() => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = 0;
canvas.height = 0;
return ctx;
});
const Canvas = /*#__PURE__*/React.forwardRef(function Canvas({
index,
frames,
width,
height,
fit,
className,
style
}, ref) {
const canvasRef = React.useRef();
const ctx = React.useRef();
const tempCtx = useCanvasSingleton();
React.useLayoutEffect(() => {
if (canvasRef.current) {
ctx.current = canvasRef.current.getContext("2d");
}
}, [canvasRef]);
React.useLayoutEffect(() => {
if (canvasRef.current) {
canvasRef.current.width = width;
canvasRef.current.height = height;
}
}, [canvasRef, width, height]);
React.useEffect(() => {
const imageData = frames[index];
if (imageData) {
if (tempCtx.canvas.width < imageData.width || tempCtx.canvas.height < imageData.height) {
tempCtx.canvas.width = imageData.width;
tempCtx.canvas.height = imageData.height;
}
if (width > 0 && height > 0) {
ctx.current.clearRect(0, 0, width, height);
tempCtx.clearRect(0, 0, tempCtx.canvas.width, tempCtx.canvas.height);
}
tempCtx.putImageData(imageData, 0, 0);
ctx.current.drawImage(tempCtx.canvas, ...calcArgs(fit, imageData, {
width,
height
}));
}
}, [index, frames, width, height, fit]);
return /*#__PURE__*/React__default['default'].createElement("canvas", {
ref: combine(canvasRef, ref),
className: className,
style: style
});
});
exports.Canvas = Canvas;
exports.useParser = useParser;
exports.usePlayback = usePlayback;
exports.usePlayerState = usePlayerState;
exports.useWorkerParser = useWorkerParser;