From 887d740b48ab2af13308f5a83293996f78489924 Mon Sep 17 00:00:00 2001 From: zhoulisheng1 Date: Thu, 6 Apr 2023 10:14:50 +0800 Subject: [PATCH] =?UTF-8?q?1.js-binary-schema-parser=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E6=BA=90=E7=A0=81=E4=BE=9D=E8=B5=96=E6=96=B9=E5=BC=8F=E5=BC=95?= =?UTF-8?q?=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhoulisheng1 --- .../imageknife/utils/gif/GIFParseImpl.ets | 4 +- .../gif/jsbinaryschemaparser/lib/index.js | 71 ++++++ .../jsbinaryschemaparser/lib/parsers/uint8.js | 114 +++++++++ .../jsbinaryschemaparser/lib/schemas/gif.js | 220 ++++++++++++++++++ .../imageknife/utils/gif/parse/index.js | 149 ++++++------ 5 files changed, 486 insertions(+), 72 deletions(-) create mode 100644 imageknife/src/main/ets/components/imageknife/utils/gif/jsbinaryschemaparser/lib/index.js create mode 100644 imageknife/src/main/ets/components/imageknife/utils/gif/jsbinaryschemaparser/lib/parsers/uint8.js create mode 100644 imageknife/src/main/ets/components/imageknife/utils/gif/jsbinaryschemaparser/lib/schemas/gif.js diff --git a/imageknife/src/main/ets/components/imageknife/utils/gif/GIFParseImpl.ets b/imageknife/src/main/ets/components/imageknife/utils/gif/GIFParseImpl.ets index 96d6ea4..03b87dc 100644 --- a/imageknife/src/main/ets/components/imageknife/utils/gif/GIFParseImpl.ets +++ b/imageknife/src/main/ets/components/imageknife/utils/gif/GIFParseImpl.ets @@ -12,11 +12,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { IParseGif } from './IParseGif' +import type { IParseGif } from './IParseGif' import { GIFFrame } from './GIFFrame' import { LoadType } from '../../../../../../../GifWorker' import { parseBufferToFrame } from './parse/GIFParse' -import {LogUtil} from '../../utils/LogUtil' +import { LogUtil } from '../../utils/LogUtil' import image from '@ohos.multimedia.image' export class GIFParseImpl implements IParseGif { diff --git a/imageknife/src/main/ets/components/imageknife/utils/gif/jsbinaryschemaparser/lib/index.js b/imageknife/src/main/ets/components/imageknife/utils/gif/jsbinaryschemaparser/lib/index.js new file mode 100644 index 0000000..94dd639 --- /dev/null +++ b/imageknife/src/main/ets/components/imageknife/utils/gif/jsbinaryschemaparser/lib/index.js @@ -0,0 +1,71 @@ +"use strict"; + + + + +var parse = function parse(stream, schema) { + var result = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + var parent = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : result; + + if (Array.isArray(schema)) { + schema.forEach(function (partSchema) { + return parse(stream, partSchema, result, parent); + }); + } else if (typeof schema === 'function') { + schema(stream, result, parent, parse); + } else { + var key = Object.keys(schema)[0]; + + if (Array.isArray(schema[key])) { + parent[key] = {}; + parse(stream, schema[key], result, parent[key]); + } else { + parent[key] = schema[key](stream, result, parent, parse); + } + } + + return result; +}; + + + +var conditional = function conditional(schema, conditionFunc) { + return function (stream, result, parent, parse) { + if (conditionFunc(stream, result, parent)) { + parse(stream, schema, result, parent); + } + }; +}; + + + +var loop = function loop(schema, continueFunc) { + return function (stream, result, parent, parse) { + var arr = []; + var lastStreamPos = stream.pos; + + while (continueFunc(stream, result, parent)) { + var newParent = {}; + parse(stream, schema, result, newParent); // cases when whole file is parsed but no termination is there and stream position is not getting updated as well + // it falls into infinite recursion, null check to avoid the same + + if (stream.pos === lastStreamPos) { + break; + } + + lastStreamPos = stream.pos; + arr.push(newParent); + } + + return arr; + }; +}; + + + +export{ + loop, + conditional, + parse +} + diff --git a/imageknife/src/main/ets/components/imageknife/utils/gif/jsbinaryschemaparser/lib/parsers/uint8.js b/imageknife/src/main/ets/components/imageknife/utils/gif/jsbinaryschemaparser/lib/parsers/uint8.js new file mode 100644 index 0000000..82c16d7 --- /dev/null +++ b/imageknife/src/main/ets/components/imageknife/utils/gif/jsbinaryschemaparser/lib/parsers/uint8.js @@ -0,0 +1,114 @@ +"use strict"; + + +// Default stream and parsers for Uint8TypedArray data type +var buildStream = function buildStream(uint8Data) { + return { + data: uint8Data, + pos: 0 + }; +}; + + + +var readByte = function readByte() { + return function (stream) { + return stream.data[stream.pos++]; + }; +}; + + + +var peekByte = function peekByte() { + var offset = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; + return function (stream) { + return stream.data[stream.pos + offset]; + }; +}; + + + +var readBytes = function readBytes(length) { + return function (stream) { + return stream.data.subarray(stream.pos, stream.pos += length); + }; +}; + + +var peekBytes = function peekBytes(length) { + return function (stream) { + return stream.data.subarray(stream.pos, stream.pos + length); + }; +}; + + +var readString = function readString(length) { + return function (stream) { + return Array.from(readBytes(length)(stream)).map(function (value) { + return String.fromCharCode(value); + }).join(''); + }; +}; + + + +var readUnsigned = function readUnsigned(littleEndian) { + return function (stream) { + var bytes = readBytes(2)(stream); + return littleEndian ? (bytes[1] << 8) + bytes[0] : (bytes[0] << 8) + bytes[1]; + }; +}; + + + +var readArray = function readArray(byteSize, totalOrFunc) { + return function (stream, result, parent) { + var total = typeof totalOrFunc === 'function' ? totalOrFunc(stream, result, parent) : totalOrFunc; + var parser = readBytes(byteSize); + var arr = new Array(total); + + for (var i = 0; i < total; i++) { + arr[i] = parser(stream); + } + + return arr; + }; +}; + + +var subBitsTotal = function subBitsTotal(bits, startIndex, length) { + var result = 0; + + for (var i = 0; i < length; i++) { + result += bits[startIndex + i] && Math.pow(2, length - i - 1); + } + + return result; +}; + +var readBits = function readBits(schema) { + return function (stream) { + var _byte = readByte()(stream); // convert the byte to bit array + + + var bits = new Array(8); + + for (var i = 0; i < 8; i++) { + bits[7 - i] = !!(_byte & 1 << i); + } // convert the bit array to values based on the schema + + + return Object.keys(schema).reduce(function (res, key) { + var def = schema[key]; + + if (def.length) { + res[key] = subBitsTotal(bits, def.index, def.length); + } else { + res[key] = bits[def.index]; + } + + return res; + }, {}); + }; +}; +export {buildStream,readByte,peekByte,readBytes,peekBytes,readString,readUnsigned,readArray,readBits} diff --git a/imageknife/src/main/ets/components/imageknife/utils/gif/jsbinaryschemaparser/lib/schemas/gif.js b/imageknife/src/main/ets/components/imageknife/utils/gif/jsbinaryschemaparser/lib/schemas/gif.js new file mode 100644 index 0000000..1e4c6e8 --- /dev/null +++ b/imageknife/src/main/ets/components/imageknife/utils/gif/jsbinaryschemaparser/lib/schemas/gif.js @@ -0,0 +1,220 @@ +"use strict"; + + +import { + loop, + conditional, + parse +} from "../"; + +import {buildStream,readByte,peekByte,readBytes,peekBytes,readString,readUnsigned,readArray,readBits} from "../parsers/uint8"; + +// a set of 0x00 terminated subblocks +var subBlocksSchema = { + blocks: function blocks(stream) { + var terminator = 0x00; + var chunks = []; + var streamSize = stream.data.length; + var total = 0; + + for (var size = (0, readByte)()(stream); size !== terminator; size = (0, readByte)()(stream)) { + // size becomes undefined for some case when file is corrupted and terminator is not proper + // null check to avoid recursion + if (!size) break; // catch corrupted files with no terminator + + if (stream.pos + size >= streamSize) { + var availableSize = streamSize - stream.pos; + chunks.push((0, readBytes)(availableSize)(stream)); + total += availableSize; + break; + } + + chunks.push((0, readBytes)(size)(stream)); + total += size; + } + + var result = new Uint8Array(total); + var offset = 0; + + for (var i = 0; i < chunks.length; i++) { + result.set(chunks[i], offset); + offset += chunks[i].length; + } + + return result; + } +}; // global control extension + +var gceSchema = (0, conditional)({ + gce: [{ + codes: (0, readBytes)(2) + }, { + byteSize: (0, readByte)() + }, { + extras: (0, readBits)({ + future: { + index: 0, + length: 3 + }, + disposal: { + index: 3, + length: 3 + }, + userInput: { + index: 6 + }, + transparentColorGiven: { + index: 7 + } + }) + }, { + delay: (0, readUnsigned)(true) + }, { + transparentColorIndex: (0, readByte)() + }, { + terminator: (0, readByte)() + }] +}, function (stream) { + var codes = (0, peekBytes)(2)(stream); + return codes[0] === 0x21 && codes[1] === 0xf9; +}); // image pipeline block + +var imageSchema = (0, conditional)({ + image: [{ + code: (0, readByte)() + }, { + descriptor: [{ + left: (0, readUnsigned)(true) + }, { + top: (0, readUnsigned)(true) + }, { + width: (0, readUnsigned)(true) + }, { + height: (0, readUnsigned)(true) + }, { + lct: (0, readBits)({ + exists: { + index: 0 + }, + interlaced: { + index: 1 + }, + sort: { + index: 2 + }, + future: { + index: 3, + length: 2 + }, + size: { + index: 5, + length: 3 + } + }) + }] + }, (0, conditional)({ + lct: (0, readArray)(3, function (stream, result, parent) { + return Math.pow(2, parent.descriptor.lct.size + 1); + }) + }, function (stream, result, parent) { + return parent.descriptor.lct.exists; + }), { + data: [{ + minCodeSize: (0, readByte)() + }, subBlocksSchema] + }] +}, function (stream) { + return (0, peekByte)()(stream) === 0x2c; +}); // plain text block + +var textSchema = (0, conditional)({ + text: [{ + codes: (0, readBytes)(2) + }, { + blockSize: (0, readByte)() + }, { + preData: function preData(stream, result, parent) { + return (0, readBytes)(parent.text.blockSize)(stream); + } + }, subBlocksSchema] +}, function (stream) { + var codes = (0, peekBytes)(2)(stream); + return codes[0] === 0x21 && codes[1] === 0x01; +}); // application block + +var applicationSchema = (0, conditional)({ + application: [{ + codes: (0, readBytes)(2) + }, { + blockSize: (0, readByte)() + }, { + id: function id(stream, result, parent) { + return (0, readString)(parent.blockSize)(stream); + } + }, subBlocksSchema] +}, function (stream) { + var codes = (0, peekBytes)(2)(stream); + return codes[0] === 0x21 && codes[1] === 0xff; +}); // comment block + +var commentSchema = (0, conditional)({ + comment: [{ + codes: (0, readBytes)(2) + }, subBlocksSchema] +}, function (stream) { + var codes = (0, peekBytes)(2)(stream); + return codes[0] === 0x21 && codes[1] === 0xfe; +}); +var schema = [{ + header: [{ + signature: (0, readString)(3) + }, { + version: (0, readString)(3) + }] +}, { + lsd: [{ + width: (0, readUnsigned)(true) + }, { + height: (0, readUnsigned)(true) + }, { + gct: (0, readBits)({ + exists: { + index: 0 + }, + resolution: { + index: 1, + length: 3 + }, + sort: { + index: 4 + }, + size: { + index: 5, + length: 3 + } + }) + }, { + backgroundColorIndex: (0, readByte)() + }, { + pixelAspectRatio: (0, readByte)() + }] +}, (0, conditional)({ + gct: (0, readArray)(3, function (stream, result) { + return Math.pow(2, result.lsd.gct.size + 1); + }) +}, function (stream, result) { + return result.lsd.gct.exists; +}), // content frames +{ + frames: (0, loop)([gceSchema, applicationSchema, commentSchema, imageSchema, textSchema], function (stream) { + var nextCode = (0, peekByte)()(stream); // rather than check for a terminator, we should check for the existence + // of an ext or image block to avoid infinite loops + //var terminator = 0x3B; + //return nextCode !== terminator; + + return nextCode === 0x21 || nextCode === 0x2c; + }) +}]; +var _default = schema; + +export {_default} \ No newline at end of file diff --git a/imageknife/src/main/ets/components/imageknife/utils/gif/parse/index.js b/imageknife/src/main/ets/components/imageknife/utils/gif/parse/index.js index a9134e3..bfe3199 100644 --- a/imageknife/src/main/ets/components/imageknife/utils/gif/parse/index.js +++ b/imageknife/src/main/ets/components/imageknife/utils/gif/parse/index.js @@ -13,102 +13,111 @@ * limitations under the License. */ -var _gif = _interopRequireDefault(require("js-binary-schema-parser/lib/schemas/gif")); +import { _default } from '../jsbinaryschemaparser/lib/schemas/gif' +import { conditional, loop, parse } from '../jsbinaryschemaparser/lib/index' +import { + buildStream, + peekByte, + peekBytes, + readArray, + readBits, + readByte, + readBytes, + readString, + readUnsigned +} from '../jsbinaryschemaparser/lib/parsers/uint8' +import { deinterlace } from './deinterlace' +import { lzw } from './lzw' -var _jsBinarySchemaParser = require("js-binary-schema-parser"); +var _gif = _interopRequireDefault(_default); -var _uint = require("js-binary-schema-parser/lib/parsers/uint8"); - -import {deinterlace} from './deinterlace' -import {lzw} from './lzw' - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } +function _interopRequireDefault(obj) { + return { "default": obj }; +} export function parseGIF(arrayBuffer) { - var byteData = new Uint8Array(arrayBuffer); - return (0, _jsBinarySchemaParser.parse)((0, _uint.buildStream)(byteData), _gif["default"]); + var byteData = new Uint8Array(arrayBuffer); + return (parse)((buildStream)(byteData), _gif["default"]); }; - export function generatePatch(image) { - var totalPixels = image.pixels.length; - var patchData = new Uint8ClampedArray(totalPixels * 4); + var totalPixels = image.pixels.length; + var patchData = new Uint8ClampedArray(totalPixels * 4); - for (var i = 0; i < totalPixels; i++) { - var pos = i * 4; - var colorIndex = image.pixels[i]; - var color = image.colorTable[colorIndex] || [0, 0, 0]; - patchData[pos] = color[2]; - patchData[pos + 1] = color[1]; - patchData[pos + 2] = color[0]; - patchData[pos + 3] = colorIndex !== image.transparentIndex ? 255 : 0; - } + for (var i = 0; i < totalPixels; i++) { + var pos = i * 4; + var colorIndex = image.pixels[i]; + var color = image.colorTable[colorIndex] || [0, 0, 0]; + patchData[pos] = color[2]; + patchData[pos + 1] = color[1]; + patchData[pos + 2] = color[0]; + patchData[pos + 3] = colorIndex !== image.transparentIndex ? 255 : 0; + } - return patchData; + return patchData; }; export function decompressFrame(frame, gct, buildImagePatch) { - if (!frame.image) { + if (!frame.image) { - return; - } - - var image = frame.image; // get the number of pixels - - var totalPixels = image.descriptor.width * image.descriptor.height; // do lzw decompression - - var pixels = lzw(image.data.minCodeSize, image.data.blocks, totalPixels); // deal with interlacing if necessary - - if (image.descriptor.lct.interlaced) { - pixels = deinterlace(pixels, image.descriptor.width); - } - - var resultImage = { - pixels: pixels, - dims: { - top: frame.image.descriptor.top, - left: frame.image.descriptor.left, - width: frame.image.descriptor.width, - height: frame.image.descriptor.height + return; } - }; // color table - if (image.descriptor.lct && image.descriptor.lct.exists) { - resultImage['colorTable'] = image.lct; - } else { - resultImage['colorTable'] = gct; - } // add per frame relevant gce information + var image = frame.image; // get the number of pixels + var totalPixels = image.descriptor.width * image.descriptor.height; // do lzw decompression - if (frame.gce) { - resultImage['delay'] = (frame.gce.delay || 10) * 10; // convert to ms + var pixels = lzw(image.data.minCodeSize, image.data.blocks, totalPixels); // deal with interlacing if necessary - resultImage['disposalType'] = frame.gce.extras.disposal; // transparency - - if (frame.gce.extras.transparentColorGiven) { - resultImage['transparentIndex'] = frame.gce.transparentColorIndex; + if (image.descriptor.lct.interlaced) { + pixels = deinterlace(pixels, image.descriptor.width); } - } // create canvas usable imagedata if desired + + var resultImage = { + pixels: pixels, + dims: { + top: frame.image.descriptor.top, + left: frame.image.descriptor.left, + width: frame.image.descriptor.width, + height: frame.image.descriptor.height + } + }; // color table + + if (image.descriptor.lct && image.descriptor.lct.exists) { + resultImage['colorTable'] = image.lct; + } else { + resultImage['colorTable'] = gct; + } // add per frame relevant gce information - if (buildImagePatch) { - resultImage['patch'] = generatePatch(resultImage); - resultImage['colorTable'] = null - resultImage['transparentIndex'] = null - resultImage['pixels'] = null - } + if (frame.gce) { + resultImage['delay'] = (frame.gce.delay || 10) * 10; // convert to ms - return resultImage; + resultImage['disposalType'] = frame.gce.extras.disposal; // transparency + + if (frame.gce.extras.transparentColorGiven) { + resultImage['transparentIndex'] = frame.gce.transparentColorIndex; + } + } // create canvas usable imagedata if desired + + + if (buildImagePatch) { + resultImage['patch'] = generatePatch(resultImage); + resultImage['colorTable'] = null + resultImage['transparentIndex'] = null + resultImage['pixels'] = null + } + + return resultImage; }; - export function decompressFrames(parsedGif, buildImagePatches) { - return parsedGif.frames.filter(function (f) { - return f.image; - }).map(function (f) { - return decompressFrame(f, parsedGif.gct, buildImagePatches); - }); + return parsedGif.frames.filter(function (f) { + return f.image; + }).map(function (f) { + return decompressFrame(f, parsedGif.gct, buildImagePatches); + }); };