UNPKG

image-in-browser

Version:

Package for encoding / decoding images, transforming images, applying filters, drawing primitives on images on the client side (no need for server Node.js)

287 lines 10 kB
import { ColorRgba8 } from '../color/color-rgba8.js'; import { InputBuffer } from '../common/input-buffer.js'; import { Draw } from '../draw/draw.js'; import { LibError } from '../error/lib-error.js'; import { MemoryImage } from '../image/image.js'; import { ImageFormat } from './image-format.js'; import { VP8 } from './webp/vp8.js'; import { VP8L } from './webp/vp8l.js'; import { WebPFormat } from './webp/webp-format.js'; import { WebPFrame } from './webp/webp-frame.js'; import { WebPInfoInternal } from './webp/webp-info-internal.js'; export class WebPDecoder { get info() { return this._info; } get format() { return ImageFormat.webp; } get numFrames() { return this._info !== undefined ? this._info.numFrames : 0; } constructor(bytes) { if (bytes !== undefined) { this.startDecode(bytes); } } decodeFrameInternal(input, frameIndex = 0) { const webp = new WebPInfoInternal(); if (!this.getInfo(input, webp)) { return undefined; } if (webp.format === WebPFormat.undefined) { return undefined; } webp.frame = frameIndex; webp.frameCount = this._info.numFrames; if (webp.hasAnimation) { if (frameIndex >= webp.frames.length || frameIndex < 0) { return undefined; } const f = webp.frames[frameIndex]; const frameData = input.subarray(f.frameSize, f.framePosition); return this.decodeFrameInternal(frameData, frameIndex); } else { const data = input.subarray(webp.vp8Size, webp.vp8Position); if (webp.format === WebPFormat.lossless) { return new VP8L(data, webp).decode(); } else if (webp.format === WebPFormat.lossy) { return new VP8(data, webp).decode(); } } return undefined; } getHeader(input) { let tag = input.readString(4); if (tag !== 'RIFF') { return false; } input.readUint32(); tag = input.readString(4); if (tag !== 'WEBP') { return false; } return true; } getInfo(input, webp) { while (!input.isEOS) { const tag = input.readString(4); const size = input.readUint32(); const diskSize = ((size + 1) >>> 1) << 1; const p = input.position; switch (tag) { case 'VP8X': if (!this.getVp8xInfo(input, webp)) { return false; } break; case 'VP8 ': webp.vp8Position = input.position; webp.vp8Size = size; webp.format = WebPFormat.lossy; break; case 'VP8L': webp.vp8Position = input.position; webp.vp8Size = size; webp.format = WebPFormat.lossless; break; case 'ALPH': webp.alphaData = new InputBuffer({ buffer: input.buffer, bigEndian: input.bigEndian, }); webp.alphaData.offset = input.offset; webp.alphaSize = size; input.skip(diskSize); break; case 'ANIM': webp.format = WebPFormat.animated; if (!this.getAnimInfo(input, webp)) { return false; } break; case 'ANMF': if (!this.getAnimFrameInfo(input, webp, size)) { return false; } break; case 'ICCP': webp.iccpData = input.readRange(size).toUint8Array(); break; case 'EXIF': webp.exifData = input.readString(size); break; case 'XMP ': webp.xmpData = input.readString(size); break; default: input.skip(diskSize); break; } const remainder = diskSize - (input.position - p); if (remainder > 0) { input.skip(remainder); } } if (!webp.hasAlpha) { webp.hasAlpha = webp.alphaData !== undefined; } return webp.format !== WebPFormat.undefined; } getVp8xInfo(input, webp) { const b = input.read(); if ((b & 0xc0) !== 0) { return false; } const alpha = (b >>> 4) & 0x1; const anim = (b >>> 1) & 0x1; if ((b & 0x1) !== 0) { return false; } if (input.readUint24() !== 0) { return false; } const w = input.readUint24() + 1; const h = input.readUint24() + 1; webp.width = w; webp.height = h; webp.hasAnimation = anim !== 0; webp.hasAlpha = alpha !== 0; return true; } getAnimInfo(input, webp) { const c = input.readUint32(); const a = c & 0xff; const r = (c >>> 8) & 0xff; const g = (c >>> 16) & 0xff; const b = (c >>> 24) & 0xff; webp.backgroundColor = new ColorRgba8(r, g, b, a); webp.animLoopCount = input.readUint16(); return true; } getAnimFrameInfo(input, webp, size) { const frame = new WebPFrame(input, size); if (!frame.isValid) { return false; } webp.frames.push(frame); return true; } isValidFile(bytes) { this._input = new InputBuffer({ buffer: bytes, }); if (!this.getHeader(this._input)) { return false; } return true; } startDecode(bytes) { this._input = new InputBuffer({ buffer: bytes, }); if (!this.getHeader(this._input)) { return undefined; } this._info = new WebPInfoInternal(); if (!this.getInfo(this._input, this._info)) { return undefined; } switch (this._info.format) { case WebPFormat.animated: { this._info.frameCount = this._info.frames.length; return this._info; } case WebPFormat.lossless: { this._input.offset = this._info.vp8Position; const vp8l = new VP8L(this._input, this._info); if (!vp8l.decodeHeader()) { return undefined; } this._info.frameCount = this._info.frames.length; return this._info; } case WebPFormat.lossy: { this._input.offset = this._info.vp8Position; const vp8 = new VP8(this._input, this._info); if (!vp8.decodeHeader()) { return undefined; } this._info.frameCount = this._info.frames.length; return this._info; } case WebPFormat.undefined: throw new LibError('Unknown format for WebP'); } return undefined; } decode(opt) { var _a; if (this.startDecode(opt.bytes) === undefined) { return undefined; } if (!this._info.hasAnimation || opt.frameIndex !== undefined) { return this.decodeFrame((_a = opt.frameIndex) !== null && _a !== void 0 ? _a : 0); } let firstImage = undefined; let lastImage = undefined; for (let i = 0; i < this._info.numFrames; ++i) { this._info.frame = i; const frame = this._info.frames[i]; const image = this.decodeFrame(i); if (image === undefined) { continue; } image.frameDuration = frame.duration; if (firstImage === undefined || lastImage === undefined) { firstImage = new MemoryImage({ width: this._info.width, height: this._info.height, numChannels: image.numChannels, format: image.format, frameDuration: image.frameDuration, }); lastImage = firstImage; } else { lastImage = MemoryImage.from(lastImage); if (frame.clearFrame) { lastImage.clear(); } } Draw.compositeImage({ dst: lastImage, src: image, dstX: frame.x, dstY: frame.y, }); firstImage.addFrame(lastImage); } return firstImage; } decodeFrame(frameIndex) { if (this._input === undefined || this._info === undefined) { return undefined; } if (this._info.hasAnimation) { if (frameIndex >= this._info.frames.length || frameIndex < 0) { return undefined; } const f = this._info.frames[frameIndex]; const frameData = this._input.subarray(f.frameSize, f.framePosition); return this.decodeFrameInternal(frameData, frameIndex); } if (this._info.format === WebPFormat.lossless) { const data = this._input.subarray(this._info.vp8Size, this._info.vp8Position); return new VP8L(data, this._info).decode(); } else if (this._info.format === WebPFormat.lossy) { const data = this._input.subarray(this._info.vp8Size, this._info.vp8Position); return new VP8(data, this._info).decode(); } return undefined; } } //# sourceMappingURL=webp-decoder.js.map