Toggle navigation
MeasureThat.net
Create a benchmark
Tools
Feedback
FAQ
Register
Log In
giftest
(version: 0)
Comparing performance of:
f vs g
Created:
one year ago
by:
Guest
Jump to the latest result
Script Preparation code:
class GifReader { constructor(buf) { this.buf = buf; this.p = 0; if ( this.buf[this.p++] !== 0x47 || this.buf[this.p++] !== 0x49 || this.buf[this.p++] !== 0x46 || this.buf[this.p++] !== 0x38 || this.buf[this.p++] !== 0x39 || // (this.buf[this.p++] + 1 & 0xFD) !== 0x38 || this.buf[this.p++] !== 0x61 ) { throw new Error("Invalid GIF 87a/89a header."); } this.width = this.buf[this.p++] | this.buf[this.p++] << 8; this.height = this.buf[this.p++] | this.buf[this.p++] << 8; var pf0 = this.buf[this.p++]; var globalPaletteFlag = pf0 >> 7; var numGlobalColorsPow2 = (pf0 & 0x7) + 1; var numGlobalColors = 1 << numGlobalColorsPow2; this.backgroundIndex = this.buf[this.p++]; // const aspectRatio = this.buf[this.p++]; this.buf[this.p++]; var globalPaletteOffset = null; var globalPaletteSize = null; if (globalPaletteFlag) { globalPaletteOffset = this.p; globalPaletteSize = numGlobalColors; this.p += numGlobalColors * 3; } let noEOF = true; let delay = 0; this.frames = []; var transparentIndex = null; var disposal = 0; this.loopCount = null; while (noEOF && this.p < this.buf.length) { switch (this.buf[this.p++]) { case 0x21: switch (this.buf[this.p++]) { case 0xFF: if ( this.buf[this.p] !== 0x0B || this.buf[this.p + 1] == 0x4E && this.buf[this.p + 2] == 0x45 && this.buf[this.p + 3] == 0x54 && this.buf[this.p + 4] == 0x53 && this.buf[this.p + 5] == 0x43 && this.buf[this.p + 6] == 0x41 && this.buf[this.p + 7] == 0x50 && this.buf[this.p + 8] == 0x45 && this.buf[this.p + 9] == 0x32 && this.buf[this.p + 10] == 0x2E && this.buf[this.p + 11] == 0x30 && this.buf[this.p + 12] == 0x03 && this.buf[this.p + 13] == 0x01 && this.buf[this.p + 16] == 0 ) { this.p += 14; this.loopCount = this.buf[this.p++] | this.buf[this.p++] << 8; this.p++; } else { this.p += 12; while (true) { var blockSize = this.buf[this.p++]; if (!(blockSize >= 0)) { throw Error("Invalid block size"); } if (blockSize === 0) { break; } this.p += blockSize; } } break; case 0xF9: if (this.buf[this.p++] !== 0x4 || this.buf[this.p + 4] !== 0) { throw new Error("Invalid graphics extension block."); } var pf1 = this.buf[this.p++]; delay = this.buf[this.p++] | this.buf[this.p++] << 8; transparentIndex = this.buf[this.p++]; if ((pf1 & 1) === 0) { transparentIndex = null; } disposal = pf1 >> 2 & 0x7; this.p++; break; case 0x01: case 0xFE: while (true) { var blockSize = this.buf[this.p++]; if (!(blockSize >= 0)) { throw Error("Invalid block size"); } if (blockSize === 0) { break; } this.p += blockSize; } break; default: throw new Error(`Unknown graphic control label: 0x${ this.buf[this.p - 1].toString(16) }`); } break; case 0x2C: var x = this.buf[this.p++] | this.buf[this.p++] << 8; var y = this.buf[this.p++] | this.buf[this.p++] << 8; var w = this.buf[this.p++] | this.buf[this.p++] << 8; var h = this.buf[this.p++] | this.buf[this.p++] << 8; var pf2 = this.buf[this.p++]; var localPaletteFlag = pf2 >> 7; var interlaceFlag = pf2 >> 6 & 1; var numLocalColorsPow2 = (pf2 & 0x7) + 1; var numLocalColors = 1 << numLocalColorsPow2; var paletteOffset = globalPaletteOffset; var paletteSize = globalPaletteSize; var hasLocalPalette = false; if (localPaletteFlag) { var hasLocalPalette = true; paletteOffset = this.p; paletteSize = numLocalColors; this.p += numLocalColors * 3; } var dataOffset = this.p; this.p++; while (true) { var blockSize = this.buf[this.p++]; if (!(blockSize >= 0)) { throw Error("Invalid block size"); } if (blockSize === 0) { break; } this.p += blockSize; } this.frames.push({ x: x, y: y, width: w, height: h, has_local_palette: hasLocalPalette, palette_offset: paletteOffset, palette_size: paletteSize, data_offset: dataOffset, data_length: this.p - dataOffset, transparent_index: transparentIndex, interlaced: !!interlaceFlag, delay: delay, disposal: disposal, }); break; case 0x3B: noEOF = false; break; default: throw new Error(`Unknown gif block: 0x${ this.buf[this.p - 1].toString(16) }`); break; } } } numFrames() { return this.frames.length; }; getLoopCount() { return this.loopCount; }; getBackgroundIndex() { return this.backgroundIndex; } frameInfo(frameIndex) { if (frameIndex < 0 || frameIndex >= this.frames.length) { throw new Error("Frame index out of range."); } return this.frames[frameIndex]; }; decodeAndBlitFrameRGBA(frameIndex) { const pixels = new Uint8Array(this.width * this.height * 4); const frame = this.frameInfo(frameIndex); const numPixels = frame.width * frame.height; const indexStream = new Uint8Array(numPixels); GifReader.LZWOutputIndexStream(this.buf, frame.data_offset, indexStream, numPixels); const paletteOffset = frame.palette_offset; const trans = frame.transparent_index ?? 256; const frameWidth = frame.width; const framestride = this.width - frameWidth; const opbeg = 4 * (frame.y * this.width + frame.x); const opend = opbeg + 4 * this.width * frame.height; let xleft = frameWidth; let op = opbeg; let scanstride = 4 * (framestride + (frame.interlaced && this.width * 7)); let interlaceskip = 8; for (let i = 0, il = indexStream.length; i < il; ++i) { const index = indexStream[i]; if (xleft === 0) { op += scanstride; xleft = frameWidth; if (op >= opend) { scanstride = 4 * (framestride + this.width * (interlaceskip - 1)); op = opbeg + (frameWidth + framestride) * (interlaceskip << 1); interlaceskip >>= 1; } } const bufIndex = paletteOffset + index * 3; pixels[op++] = this.buf[bufIndex]; pixels[op++] = this.buf[bufIndex + 1]; pixels[op++] = this.buf[bufIndex + 2]; pixels[op++] = index === trans ? 0 : 255; --xleft; } return pixels; }; static LZWOutputIndexStream(codeStream, p, output, outputLength) { var minCodeSize = codeStream[p++]; var clearCode = 1 << minCodeSize; var eoiCode = clearCode + 1; var nextCode = eoiCode + 1; var curCodeSize = minCodeSize + 1; var codeMask = (1 << curCodeSize) - 1; var curShift = 0; var cur = 0; var op = 0; var subblockSize = codeStream[p++]; var codeTable = new Int32Array(4096); var prevCode = null; while (true) { while (curShift < 16) { if (subblockSize === 0) { break; } cur |= codeStream[p++] << curShift; curShift += 8; if (subblockSize === 1) { subblockSize = codeStream[p++]; } else { --subblockSize; } } if (curShift < curCodeSize) { break; } var code = cur & codeMask; cur >>= curCodeSize; curShift -= curCodeSize; if (code === clearCode) { nextCode = eoiCode + 1; curCodeSize = minCodeSize + 1; codeMask = (1 << curCodeSize) - 1; prevCode = null; continue; } else if (code === eoiCode) { break; } var chaseCode = code < nextCode ? code : prevCode; var chaseLength = 0; var chase = chaseCode; while (chase > clearCode) { chase = codeTable[chase] >> 8; ++chaseLength; } var k = chase; var opEnd = op + chaseLength + (chaseCode !== code ? 1 : 0); if (opEnd > outputLength) { console.log("Warning, gif stream longer than expected."); return; } output[op++] = k; op += chaseLength; var b = op; if (chaseCode !== code) { output[op++] = k; } chase = chaseCode; while (chaseLength--) { chase = codeTable[chase]; output[--b] = chase & 0xFF; chase >>= 8; } if (prevCode !== null && nextCode < 4096) { codeTable[nextCode++] = prevCode << 8 | k; if (nextCode >= codeMask + 1 && curCodeSize < 12) { ++curCodeSize; codeMask = codeMask << 1 | 1; } } prevCode = code; } if (op !== outputLength) { console.log("Warning, gif stream shorter than expected."); } return output; } } class GifWriter { constructor(buf, width, height, gopts = {}) { if (width <= 0 || height <= 0 || width > 0XFFFF || height > 0xFFFF) { throw new Error("Width/Height invalid."); } this.buf = buf; this.p = 0; this.globalPalette = gopts.palette ?? null; this.ended = false; const loopCount = gopts.loop ?? null; const gpNumColors = GifWriter.checkPaletteLength(this.globalPalette).length; const gpNumColorsPow2 = GifWriter.calculateMinCodeSize(gpNumColors); const background = (this.globalPalette && gopts.background) ?? null; if (background >= gpNumColors) { throw new Error("Background index out of range."); } if (background === 0) { throw new Error("Background index explicitly passed as 0."); } this.buf[this.p++] = 0x47; this.buf[this.p++] = 0x49; this.buf[this.p++] = 0x46; this.buf[this.p++] = 0x38; this.buf[this.p++] = 0x39; this.buf[this.p++] = 0x61; this.buf[this.p++] = width & 0xFF; this.buf[this.p++] = width >> 8 & 0xFF; this.buf[this.p++] = height & 0xFF; this.buf[this.p++] = height >> 8 & 0xFF; this.buf[this.p++] = !!this.globalPalette << 7 | (gpNumColorsPow2 - 1); this.buf[this.p++] = background; this.buf[this.p++] = 0; if (this.globalPalette) { for (let i = 0, il = this.globalPalette.length; i < il; ++i) { const rgb = this.globalPalette[i]; this.buf[this.p++] = rgb >> 16 & 0xFF; this.buf[this.p++] = rgb >> 8 & 0xFF; this.buf[this.p++] = rgb & 0xFF; } } if (loopCount !== null) { if (loopCount < 0 || loopCount > 0xFFFF) { throw new Error("Loop count invalid."); } this.buf[this.p++] = 0x21; this.buf[this.p++] = 0xFF; this.buf[this.p++] = 0x0B; this.buf[this.p++] = 0x4E; this.buf[this.p++] = 0x45; this.buf[this.p++] = 0x54; this.buf[this.p++] = 0x53; this.buf[this.p++] = 0x43; this.buf[this.p++] = 0x41; this.buf[this.p++] = 0x50; this.buf[this.p++] = 0x45; this.buf[this.p++] = 0x32; this.buf[this.p++] = 0x2E; this.buf[this.p++] = 0x30; this.buf[this.p++] = 0x03; this.buf[this.p++] = 0x01; this.buf[this.p++] = loopCount & 0xFF; this.buf[this.p++] = loopCount >> 8 & 0xFF; this.buf[this.p++] = 0x00; } } addFrame(x, y, w, h, indexedPixels, opts = {}) { if (this.ended) { --this.p; this.ended = false; } if (x < 0 || y < 0 || x > 0xFFFF || y > 0xFFFF) { throw new Error("x/y invalid."); } if (w <= 0 || h <= 0 || w > 0xFFFF || h > 0xFFFF) { throw new Error("Width/Height invalid."); } if (indexedPixels.length < w * h) { throw new Error("Not enough pixels for the frame size."); } const usingLocalPalette = !!opts.palette; const palette = opts.palette ?? this.globalPalette; if (!palette) { throw new Error("Must supply either a local or global palette."); } const numColors = GifWriter.checkPaletteLength(palette).length; const minCodeSize = GifWriter.calculateMinCodeSize(numColors); const delay = opts.delay ?? 0; const disposal = opts.disposal ?? 0; if (disposal < 0 || disposal > 3) { throw new Error("Disposal out of range."); } const useTrancparency = !!opts.transparent; const transparentIndex = opts.transparent ?? 0; if (transparentIndex < 0 || transparentIndex >= numColors) { throw new Error("Transparent color index."); } if (disposal !== 0 || useTrancparency || delay !== 0) { this.buf[this.p++] = 0x21; this.buf[this.p++] = 0xF9; this.buf[this.p++] = 4; this.buf[this.p++] = disposal << 2 | useTrancparency; this.buf[this.p++] = delay & 0xFF; this.buf[this.p++] = delay >> 8 & 0xFF; this.buf[this.p++] = transparentIndex; this.buf[this.p++] = 0; } this.buf[this.p++] = 0x2C; this.buf[this.p++] = x & 0xFF; this.buf[this.p++] = x >> 8 & 0xFF; this.buf[this.p++] = y & 0xFF; this.buf[this.p++] = y >> 8 & 0xFF; this.buf[this.p++] = w & 0xFF; this.buf[this.p++] = w >> 8 & 0xFF; this.buf[this.p++] = h & 0xFF; this.buf[this.p++] = h >> 8 & 0xFF; this.buf[this.p++] = usingLocalPalette ? (0x80 | (minCodeSize - 1)) : 0; if (usingLocalPalette) { for (let i = 0, il = palette.length; i < il; ++i) { const rgb = palette[i]; this.buf[this.p++] = rgb >> 16 & 0xFF; this.buf[this.p++] = rgb >> 8 & 0xFF; this.buf[this.p++] = rgb & 0xFF; } } // this.p = GifWriter.OutputLZWCodeStream(this.buf, this.p, minCodeSize < 2 ? 2 : minCodeSize, indexedPixels); this.OutputLZWCodeStream(indexedPixels, minCodeSize < 2 ? 2 : minCodeSize); return this.p; }; end() { if (!this.ended) { this.buf[this.p++] = 0x3B; this.ended = true; } return this.p; }; getOutputBuffer() { return this.buf; }; setOutputBuffer(v) { this.buf = v; }; getOutputBufferPosition() { return this.p; }; setOutputBufferPosition(v) { this.p = v; }; emitBytesToBuffer(bitBlockSize) { while (this.curShift >= bitBlockSize) { this.buf[this.p++] = this.cur & 0xFF; this.cur >>= 8; this.curShift -= 8; if (this.p === this.curSubblock + 256) { this.buf[this.curSubblock] = 255; this.curSubblock = this.p++; } } } emitCode(c) { this.cur |= c << this.curShift; this.curShift += this.curCodeSize; this.emitBytesToBuffer(8); } OutputLZWCodeStream(indexStream, minCodeSize) { this.buf[this.p++] = minCodeSize; const clearCode = 1 << minCodeSize; const codeMask = clearCode - 1; const eoiCode = clearCode + 1; this.curSubblock = this.p++; this.curCodeSize = minCodeSize + 1; this.curShift = 0; this.cur = 0; let nextCode = eoiCode + 1; let ibCode = indexStream[0] & codeMask; let codeTable = {}; this.emitCode(clearCode); for (let i = 1, il = indexStream.length; i < il; ++i) { const k = indexStream[i] & codeMask; const curKey = ibCode << 8 | k; const curCode = codeTable[curKey]; if (curCode === undefined) { this.cur |= ibCode << this.curShift; this.curShift += this.curCodeSize; while (this.curShift >= 8) { this.buf[this.p++] = this.cur & 0xFF; this.cur >>= 8; this.curShift -= 8; if (this.p === this.curSubblock + 256) { this.buf[this.curSubblock] = 255; this.curSubblock = this.p++; } } if (nextCode === 4096) { this.emitCode(clearCode); nextCode = eoiCode + 1; this.curCodeSize = minCodeSize + 1; codeTable = {}; } else { if (nextCode >= (1 << this.curCodeSize)) { ++this.curCodeSize; } codeTable[curKey] = nextCode++; } ibCode = k; } else { ibCode = curCode; } } this.emitCode(ibCode); this.emitCode(eoiCode); this.emitBytesToBuffer(1); if (this.curSubblock + 1 === this.p) { this.buf[this.curSubblock] = 0; } else { this.buf[this.curSubblock] = this.p - this.curSubblock - 1; this.buf[this.p++] = 0; } return this.p; } static checkPaletteLength(palette) { const numColors = palette.length; if (numColors < 2 || numColors > 256 || numColors & (numColors - 1)) { throw new Error("Invalid code/color length, must be power of 2 and 2 .. 256."); } return palette; } static calculateMinCodeSize(numColors) { let minCodeSize = 0; while (numColors >>= 1) { ++minCodeSize; } return minCodeSize; } } class GifWriter2 { constructor(buf, width, height, gopts = {}) { if (width <= 0 || height <= 0 || width > 0XFFFF || height > 0xFFFF) { throw new Error("Width/Height invalid."); } this.buf = buf; this.p = 0; this.globalPalette = gopts.palette ?? null; this.ended = false; const loopCount = gopts.loop ?? null; const gpNumColors = GifWriter.checkPaletteLength(this.globalPalette).length; const gpNumColorsPow2 = GifWriter.calculateMinCodeSize(gpNumColors); const background = (this.globalPalette && gopts.background) ?? null; if (background >= gpNumColors) { throw new Error("Background index out of range."); } if (background === 0) { throw new Error("Background index explicitly passed as 0."); } this.buf[this.p++] = 0x47; this.buf[this.p++] = 0x49; this.buf[this.p++] = 0x46; this.buf[this.p++] = 0x38; this.buf[this.p++] = 0x39; this.buf[this.p++] = 0x61; this.buf[this.p++] = width & 0xFF; this.buf[this.p++] = width >> 8 & 0xFF; this.buf[this.p++] = height & 0xFF; this.buf[this.p++] = height >> 8 & 0xFF; this.buf[this.p++] = !!this.globalPalette << 7 | (gpNumColorsPow2 - 1); this.buf[this.p++] = background; this.buf[this.p++] = 0; if (this.globalPalette) { for (let i = 0, il = this.globalPalette.length; i < il; ++i) { const rgb = this.globalPalette[i]; this.buf[this.p++] = rgb >> 16 & 0xFF; this.buf[this.p++] = rgb >> 8 & 0xFF; this.buf[this.p++] = rgb & 0xFF; } } if (loopCount !== null) { if (loopCount < 0 || loopCount > 0xFFFF) { throw new Error("Loop count invalid."); } this.buf[this.p++] = 0x21; this.buf[this.p++] = 0xFF; this.buf[this.p++] = 0x0B; this.buf[this.p++] = 0x4E; this.buf[this.p++] = 0x45; this.buf[this.p++] = 0x54; this.buf[this.p++] = 0x53; this.buf[this.p++] = 0x43; this.buf[this.p++] = 0x41; this.buf[this.p++] = 0x50; this.buf[this.p++] = 0x45; this.buf[this.p++] = 0x32; this.buf[this.p++] = 0x2E; this.buf[this.p++] = 0x30; this.buf[this.p++] = 0x03; this.buf[this.p++] = 0x01; this.buf[this.p++] = loopCount & 0xFF; this.buf[this.p++] = loopCount >> 8 & 0xFF; this.buf[this.p++] = 0x00; } } addFrame(x, y, w, h, indexedPixels, opts = {}) { if (this.ended) { --this.p; this.ended = false; } if (x < 0 || y < 0 || x > 0xFFFF || y > 0xFFFF) { throw new Error("x/y invalid."); } if (w <= 0 || h <= 0 || w > 0xFFFF || h > 0xFFFF) { throw new Error("Width/Height invalid."); } if (indexedPixels.length < w * h) { throw new Error("Not enough pixels for the frame size."); } const usingLocalPalette = !!opts.palette; const palette = opts.palette ?? this.globalPalette; if (!palette) { throw new Error("Must supply either a local or global palette."); } const numColors = GifWriter.checkPaletteLength(palette).length; const minCodeSize = GifWriter.calculateMinCodeSize(numColors); const delay = opts.delay ?? 0; const disposal = opts.disposal ?? 0; if (disposal < 0 || disposal > 3) { throw new Error("Disposal out of range."); } const useTrancparency = !!opts.transparent; const transparentIndex = opts.transparent ?? 0; if (transparentIndex < 0 || transparentIndex >= numColors) { throw new Error("Transparent color index."); } if (disposal !== 0 || useTrancparency || delay !== 0) { this.buf[this.p++] = 0x21; this.buf[this.p++] = 0xF9; this.buf[this.p++] = 4; this.buf[this.p++] = disposal << 2 | useTrancparency; this.buf[this.p++] = delay & 0xFF; this.buf[this.p++] = delay >> 8 & 0xFF; this.buf[this.p++] = transparentIndex; this.buf[this.p++] = 0; } this.buf[this.p++] = 0x2C; this.buf[this.p++] = x & 0xFF; this.buf[this.p++] = x >> 8 & 0xFF; this.buf[this.p++] = y & 0xFF; this.buf[this.p++] = y >> 8 & 0xFF; this.buf[this.p++] = w & 0xFF; this.buf[this.p++] = w >> 8 & 0xFF; this.buf[this.p++] = h & 0xFF; this.buf[this.p++] = h >> 8 & 0xFF; this.buf[this.p++] = usingLocalPalette ? (0x80 | (minCodeSize - 1)) : 0; if (usingLocalPalette) { for (let i = 0, il = palette.length; i < il; ++i) { const rgb = palette[i]; this.buf[this.p++] = rgb >> 16 & 0xFF; this.buf[this.p++] = rgb >> 8 & 0xFF; this.buf[this.p++] = rgb & 0xFF; } } // this.p = GifWriter.OutputLZWCodeStream(this.buf, this.p, minCodeSize < 2 ? 2 : minCodeSize, indexedPixels); this.OutputLZWCodeStream(indexedPixels, minCodeSize < 2 ? 2 : minCodeSize); return this.p; }; end() { if (!this.ended) { this.buf[this.p++] = 0x3B; this.ended = true; } return this.p; }; getOutputBuffer() { return this.buf; }; setOutputBuffer(v) { this.buf = v; }; getOutputBufferPosition() { return this.p; }; setOutputBufferPosition(v) { this.p = v; }; emitBytesToBuffer(bitBlockSize) { while (this.curShift >= bitBlockSize) { this.buf[this.p++] = this.cur & 0xFF; this.cur >>= 8; this.curShift -= 8; if (this.p === this.curSubblock + 256) { this.buf[this.curSubblock] = 255; this.curSubblock = this.p++; } } } emitCode(c) { this.cur |= c << this.curShift; this.curShift += this.curCodeSize; this.emitBytesToBuffer(8); } OutputLZWCodeStream(indexStream, minCodeSize) { this.buf[this.p++] = minCodeSize; const clearCode = 1 << minCodeSize; const codeMask = clearCode - 1; const eoiCode = clearCode + 1; this.curSubblock = this.p++; this.curCodeSize = minCodeSize + 1; this.curShift = 0; this.cur = 0; let nextCode = eoiCode + 1; let ibCode = indexStream[0] & codeMask; let codeTable = {}; this.emitCode(clearCode); for (let i = 1, il = indexStream.length; i < il; ++i) { const k = indexStream[i] & codeMask; const curKey = ibCode << 8 | k; const curCode = codeTable[curKey]; if (curCode === undefined) { this.emitCode(ibCode); if (nextCode === 4096) { this.emitCode(clearCode); nextCode = eoiCode + 1; this.curCodeSize = minCodeSize + 1; codeTable = {}; } else { if (nextCode >= (1 << this.curCodeSize)) { ++this.curCodeSize; } codeTable[curKey] = nextCode++; } ibCode = k; } else { ibCode = curCode; } } this.emitCode(ibCode); this.emitCode(eoiCode); this.emitBytesToBuffer(1); if (this.curSubblock + 1 === this.p) { this.buf[this.curSubblock] = 0; } else { this.buf[this.curSubblock] = this.p - this.curSubblock - 1; this.buf[this.p++] = 0; } return this.p; } static checkPaletteLength(palette) { const numColors = palette.length; if (numColors < 2 || numColors > 256 || numColors & (numColors - 1)) { throw new Error("Invalid code/color length, must be power of 2 and 2 .. 256."); } return palette; } static calculateMinCodeSize(numColors) { let minCodeSize = 0; while (numColors >>= 1) { ++minCodeSize; } return minCodeSize; } } function makeMapPalette(buffer, offset, size) { const paletteMap = new Map(); for (let i = offset, limit = offset + size * 3; i < limit; i += 3) { const color = buffer[i] << 16 | buffer[i + 1] << 8 | buffer[i + 2]; if (!paletteMap.has(color)) { paletteMap.set(color, paletteMap.size); } } return paletteMap; } function makeResizedPaletteFromMap(paletteMap) { const oldPaletteSize = paletteMap.size; let newPaletteSize = 2; while (newPaletteSize < oldPaletteSize) newPaletteSize <<= 1; const palette = [...paletteMap.keys()]; palette.length = newPaletteSize; // palette.fill(0, oldPaletteSize, newPaletteSize); return palette; } function colorDistance(c1, c2) { const r = (c1 & 0xFF) - (c2 & 0xFF); const g = (c1 >> 8 & 0xFF) - (c2 >> 8 & 0xFF); const b = (c1 >> 16 & 0xFF) - (c2 >> 16 & 0xFF); return r * r + g * g + b * b; } function h(srcCtx, destCtx) { const offset = (srcCtx.canvas.width - srcCtx.canvas.height) * 0.5; const srcX = offset < 0 ? 0 : offset; const srcY = offset > 0 ? 0 : -offset; const srcSize = srcCtx.canvas.width < srcCtx.canvas.height ? srcCtx.canvas.width : srcCtx.canvas.height; const destSize = destCtx.canvas.width; destCtx.drawImage(srcCtx.canvas, srcX, srcY, srcSize, srcSize, 0, 0, destSize, destSize); } function calculateNewSizeAndPosition(x, y, width, height, gifWidth, gifHeight, newSize) { const size = gifWidth < gifHeight ? gifWidth : gifHeight; const offset = (gifWidth - gifHeight) * 0.5; const offsetX = offset < 0 ? 0 : offset; const offsetY = offset > 0 ? 0 : -offset; const left = x; const right = x + width; const top = y; const bottom = y + height; const leftX = offsetX; const rightX = offsetX + size; const leftY = offsetY; const rightY = offsetY + size; const f0 = left < leftX; const f1 = left < rightX; const f2 = right > leftX; const f3 = right > rightX; const f4 = top < leftY; const f5 = top < rightY; const f6 = bottom > leftY; const f7 = bottom > rightY; const fsx = f0 | f1 << 1 | f2 << 2 | f3 << 3; const fsy = f4 | f5 << 1 | f6 << 2 | f7 << 3; let state; switch (fsx) { case 0b0110: case 0b0111: case 0b1110: case 0b1111: state |= 1; break; case 0b0011: case 0b1100: state |= 2; break; default: state |= 4; } switch (fsy) { case 0b0110: case 0b0111: case 0b1110: case 0b1111: state |= 1; break; case 0b0011: case 0b1100: state |= 2; break; default: state |= 4; } if (state >> 2) { throw new Error("Invalid state"); } if (state >> 1) { return { cropX: -1, cropY: -1, cropWidth: 1, cropHeight: 1 }; } const cropX = x > offsetX ? x - offsetX : 0; const cropWidth = Math.min(x + width, offsetX + size) - cropX - offsetX; const cropY = y > offsetY ? y - offsetY : 0; const cropHeight = Math.min(y + height, offsetY + size) - cropY - offsetY; const scale = newSize / size; return { cropX: Math.round(cropX * scale), cropY: Math.round(cropY * scale), cropWidth: Math.round(cropWidth * scale), cropHeight: Math.round(cropHeight * scale), }; } function processPixels(frameData, x, y, width, height, size, palette, paletteMap) { const pixels = new Uint8Array(width * height); if (x === -1) { pixels[0] = 0; return pixels; } for (let i = y, pixelIndex = 0; i < y + height; ++i) { for (let j = x; j < x + width; ++j, ++pixelIndex) { const index = (i * size + j) * 4; const color = frameData[index] << 16 | frameData[index + 1] << 8 | frameData[index + 2]; pixels[pixelIndex] = paletteMap.get(color) ?? palette.reduce((closest, currentColor, index) => { const dist = colorDistance(color, currentColor); return dist < closest.dist ? { index, dist } : closest; }, { index: 0, dist: Infinity }).index; } } return pixels; } function f(imageURL) { if (imageURL === undefined) { imageURL = "https://techolay.net/sosyal/data/avatars/o/6/6412.jpg?1724340482"; } return fetch(imageURL) .then(response => response.arrayBuffer()) .then(buffer => { console.time("gif"); const bufArr = new Uint8Array(buffer); const gifReader = new GifReader(bufArr); const frameCount = gifReader.numFrames(); const { palette_offset, palette_size } = gifReader.frameInfo(0); const paletteMap = makeMapPalette(bufArr, palette_offset, palette_size); const palette = makeResizedPaletteFromMap(paletteMap); const size = 192; const newWidth = size; const newHeight = size; const gifWriter = new GifWriter( new Uint8Array(frameCount * (newWidth * newHeight + 0x1000)), newWidth, newHeight, { palette, loop: gifReader.getLoopCount() }, ); for (let frameIndex = 0; frameIndex < frameCount; ++frameIndex) { const { palette_offset, transparent_index, background_index, delay, disposal, width, height, x, y, } = gifReader.frameInfo(frameIndex); const frameData = gifReader.decodeAndBlitFrameRGBA(frameIndex); const transparentIndex = palette_offset + transparent_index * 3; const transparentColor = bufArr[transparentIndex] << 16 | bufArr[transparentIndex + 1] << 8 | bufArr[transparentIndex + 2]; const transparent = transparent_index ? paletteMap.get(transparentColor) : undefined; const backgroundIndex = palette_offset + background_index * 3; const backgroundColor = bufArr[backgroundIndex] << 16 | bufArr[backgroundIndex + 1] << 8 | bufArr[backgroundIndex + 2]; const background = background_index ? paletteMap.get(backgroundColor) : undefined; const originalCtx = new OffscreenCanvas(gifReader.width, gifReader.height).getContext("2d", { alpha: false, willReadFrequently: true }); const newCtx = new OffscreenCanvas(newWidth, newHeight).getContext("2d", { alpha: false, willReadFrequently: true }); originalCtx.putImageData(new ImageData(new Uint8ClampedArray(frameData.buffer), gifReader.width, gifReader.height), 0, 0); newCtx.imageSmoothingEnabled = false; // newCtx.imageSmoothingEnabled = true; h(originalCtx, newCtx); const newFrameData = newCtx.getImageData(0, 0, newWidth, newHeight).data; const { cropX, cropY, cropWidth, cropHeight, } = calculateNewSizeAndPosition(x, y, width, height, gifReader.width, gifReader.height, size); const pixels = processPixels(newFrameData, cropX, cropY, cropWidth, cropHeight, size, palette, paletteMap); gifWriter.addFrame( cropX !== -1 ? cropX : 0, cropY !== -1 ? cropY : 0, cropWidth, cropHeight, pixels, { delay, disposal, transparent: cropX !== -1 ? transparent : 0, }, ); } gifWriter.end(); const blob = new Blob([gifWriter.getOutputBuffer().subarray(0, gifWriter.getOutputBufferPosition())], { type: "image/gif" }); const reader = new FileReader(); reader.onload = event => { const dataUrl = reader.result; // console.log(dataUrl); // console.log(dataUrl); sha256(dataUrl, "f00dd8b1290f4fca899eec25c8022eeaf9a22a652c1d81d7b2c8561392ccf5e6"); }; reader.readAsDataURL(blob); console.timeEnd("gif"); }); } async function sha256(message, sha) { // encode as UTF-8 const msgBuffer = new TextEncoder().encode(message); // hash the message const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer); // convert ArrayBuffer to Array const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert bytes to hex string const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); const isShaEqual = hashHex === sha; if (isShaEqual) { console.log(`sha256: ${ isShaEqual }`); // console.log(`${ message }`); } else { console.log(`sha256: ${ hashHex }`); console.log(`${ message }`); } } function g(imageURL) { if (imageURL === undefined) { imageURL = "https://techolay.net/sosyal/data/avatars/o/6/6412.jpg?1724340482"; } return fetch(imageURL) .then(response => response.arrayBuffer()) .then(buffer => { console.time("gif"); const bufArr = new Uint8Array(buffer); const gifReader = new GifReader(bufArr); const frameCount = gifReader.numFrames(); const { palette_offset, palette_size } = gifReader.frameInfo(0); const paletteMap = makeMapPalette(bufArr, palette_offset, palette_size); const palette = makeResizedPaletteFromMap(paletteMap); const size = 192; const newWidth = size; const newHeight = size; const gifWriter = new GifWriter2( new Uint8Array(frameCount * (newWidth * newHeight + 0x1000)), newWidth, newHeight, { palette, loop: gifReader.getLoopCount() }, ); for (let frameIndex = 0; frameIndex < frameCount; ++frameIndex) { const { palette_offset, transparent_index, background_index, delay, disposal, width, height, x, y, } = gifReader.frameInfo(frameIndex); const frameData = gifReader.decodeAndBlitFrameRGBA(frameIndex); const transparentIndex = palette_offset + transparent_index * 3; const transparentColor = bufArr[transparentIndex] << 16 | bufArr[transparentIndex + 1] << 8 | bufArr[transparentIndex + 2]; const transparent = transparent_index ? paletteMap.get(transparentColor) : undefined; const backgroundIndex = palette_offset + background_index * 3; const backgroundColor = bufArr[backgroundIndex] << 16 | bufArr[backgroundIndex + 1] << 8 | bufArr[backgroundIndex + 2]; const background = background_index ? paletteMap.get(backgroundColor) : undefined; const originalCtx = new OffscreenCanvas(gifReader.width, gifReader.height).getContext("2d", { alpha: false, willReadFrequently: true }); const newCtx = new OffscreenCanvas(newWidth, newHeight).getContext("2d", { alpha: false, willReadFrequently: true }); originalCtx.putImageData(new ImageData(new Uint8ClampedArray(frameData.buffer), gifReader.width, gifReader.height), 0, 0); newCtx.imageSmoothingEnabled = false; // newCtx.imageSmoothingEnabled = true; h(originalCtx, newCtx); const newFrameData = newCtx.getImageData(0, 0, newWidth, newHeight).data; const { cropX, cropY, cropWidth, cropHeight, } = calculateNewSizeAndPosition(x, y, width, height, gifReader.width, gifReader.height, size); const pixels = processPixels(newFrameData, cropX, cropY, cropWidth, cropHeight, size, palette, paletteMap); gifWriter.addFrame( cropX !== -1 ? cropX : 0, cropY !== -1 ? cropY : 0, cropWidth, cropHeight, pixels, { delay, disposal, transparent: cropX !== -1 ? transparent : 0, }, ); } gifWriter.end(); const blob = new Blob([gifWriter.getOutputBuffer().subarray(0, gifWriter.getOutputBufferPosition())], { type: "image/gif" }); const reader = new FileReader(); reader.onload = event => { const dataUrl = reader.result; // console.log(dataUrl); // console.log(dataUrl); sha256(dataUrl, "f00dd8b1290f4fca899eec25c8022eeaf9a22a652c1d81d7b2c8561392ccf5e6"); }; reader.readAsDataURL(blob); console.timeEnd("gif"); }); }
Tests:
f
f();
g
g();
Rendered benchmark preparation results:
Suite status:
<idle, ready to run>
Run tests (2)
Previous results
Fork
Test case name
Result
f
g
Fastest:
N/A
Slowest:
N/A
Latest run results:
No previous run results
This benchmark does not have any results yet. Be the first one
to run it!
Comments
Confirm delete:
Do you really want to delete benchmark?