function loadFromUrl(url) { return new Promise(function (resolve, reject) { var xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.responseType = "arraybuffer"; xhr.onload = function () { if (this.status >= 200 && this.status < 300) { resolve({ type: xhr.getResponseHeader("Content-Type"), data: xhr.response }); } else { reject({ status: this.status, statusText: xhr.statusText }); } }; xhr.onerror = function () { reject({ status: this.status, statusText: xhr.statusText }); }; xhr.send(); }); } function _arrayBufferToBase64( buffer ) { var binary = ''; var bytes = new Uint8Array( buffer ); var len = bytes.byteLength; for (var i = 0; i < len; i++) { binary += String.fromCharCode( bytes[ i ] ); } return window.btoa( binary ); } function createCanvas(width, height) { const canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; return canvas; } class ImageEx { constructor(url) { this.url = url; this.loaded = loadFromUrl(url).then(result => { this.type = result.type; this.data = result.data; if(this.type === "image/gif") { return this.initGif(); } else { return this.initStatic(); } }) } initGif() { const reader = new GifReader(new Uint8Array(this.data)); this.width = reader.width; this.height = reader.height; this.frames = this.decodeFrames(reader); this.renderAllFrames(); } initStatic(){ // todo: in node, we wanna use this.data const img = new Image(); var arrayBufferView = new Uint8Array( this.data ); var blob = new Blob( [ arrayBufferView ], { type: this.type } ); var urlCreator = window.URL || window.webkitURL; var imageUrl = urlCreator.createObjectURL( blob ); img.src = imageUrl; return new Promise(resolve => { img.onload = () => { this.width = img.width; this.height = img.height; this.frames = [{ actualOffset: 0, actualDelay: Infinity, delay: Infinity }] this.spriteSheet = createCanvas(this.width, this.height); const spriteSheetCtx = this.spriteSheet.getContext("2d"); spriteSheetCtx.drawImage(img, 0, 0); resolve(); } }) } decodeFrames(reader) { const frames = []; let offset = 0; for(let i=0;i { return ctx.clearRect(0,0,canvas.width,canvas.height); } break; case 3: saved = ctx.getImageData(0,0,canvas.width,canvas.height); disposeFrame = () => { return ctx.putImageData(saved, 0, 0); } break; default: this.disposeFrame = null; } // draw current frame ctx.drawImage(frame.buffer, frame.x, frame.y); // draw the frame onto the sprite sheet spriteSheetCtx.drawImage(canvas, this.width * i, 0); } } createBufferCanvas(frame, width, height) { const canvas = createCanvas(frame.width,frame.height); const ctx = canvas.getContext("2d"); const imageData = ctx.createImageData(width, height); imageData.data.set(frame.pixels); ctx.putImageData(imageData, - frame.x, -frame.y); return canvas; } drawFrame(ctx, frameNum, x, y, args = {}) { console.log("this", this) const sx = frameNum * this.width + (args.sx || 0); const sy = args.sy || 0; const swidth = Math.min(args.swidth || this.width, this.width) - (args.sx || 0); const sheight = args.sheight || this.height; console.log(`Drawing frame ${frameNum} at`) console.log("sx",sx) console.log("sy",sy) console.log("sw",swidth) console.log("sh",sheight) console.log("x",x) console.log("y",y) console.log("w",args.width) console.log("h",args.height) ctx.drawImage(this.spriteSheet, sx, sy, swidth, sheight, x, y, args.width || swidth, args.height || sheight); //ctx.drawImage(this.spriteSheet, 0, 0, 112, 112, 0, 0, 112, 112) } } class CanvasEx { constructor(width, height) { this.width = width; this.height = height; this.frames = []; this.totalDuration = Infinity; } addFrame(actualDelay, delay) { if((actualDelay === undefined || actualDelay === null) && (delay === undefined || delay === null)) throw new Error("Delay has to be set!"); const canvas = createCanvas(this.width, this.height); const frame = { actualOffset: this.totalDuration, delay: delay ? delay : Math.max(Math.round(actualDelay/10), 2), actualDelay: actualDelay ? actualDelay : Math.max(delay * 10, 20), canvas, ctx: canvas.getContext("2d") } this.totalDuration += delay; this.frames.push(frame); } drawImage(img,x,y,args = {}) { let {sx,sy,swidth,sheight,width,height,beginAt = 0,endAt = Infinity} = args; console.log("Drawing image ",img) console.log("At ",x,y,args) beginAt = Math.max(0, beginAt); endAt = Math.min(Math.max(img.totalDuration || 0, this.totalDuration), endAt); if(img.frames && img.frames.length > 1) { if(this.frames.length > 1) throw new Error("Cannot render animations onto animated canvases!"); this.totalDuration = img.totalDuration; // we are drawing an animated image onto a static one. // for each frame in the image, create a frame on this one, cloning the original picture (if any), // render the original on each frame, and draw the frame on top. for(let i=this.frames.length;i 0) { this.frames[i].ctx.drawImage(this.frames[0].canvas, 0, 0); } } for(let i=0;i{ _cnv.drawImage(img, 0, 0); //img.drawFrame(_ctx, 0, 0, 0) return img2.loaded }).then(()=>{ _cnv.drawImage(img2, 0, 0, {width: 128, height: 128}); }).then(()=>{ console.log("Drawing CanvasEx to screen", _cnv) _cnv.drawFrame(_ctx, 0, 0, 0); console.log("Starting gif render") const GifEncoder = new GIF({ workers: 2, quality: 10, transparent: 'rgba(0,0,0,0)' }); for(let i=0;i<_cnv.frames.length;++i) { const frame = _cnv.frames[i]; console.log("Rendering frame "+i) GifEncoder.addFrame(frame.canvas, {delay: frame.delay}) } GifEncoder.on('finished', function(blob) { console.log("Done rendering!", URL.createObjectURL(blob)) document.getElementById("i").src = URL.createObjectURL(blob); }); GifEncoder.render(); }) var curFrm = 0; setInterval(()=>{ _ctx.clearRect(0,0,200,200); _cnv.drawFrame(_ctx, curFrm++ % _cnv.frames.length, 0, 0); }, 100) /*img2.loaded.then(() => { _ctx.drawImage(img2.spriteSheet, 0, 100) }) img.loaded.then(()=>_ctx.drawImage(img.spriteSheet,0,0))*/