forked from floraachy/ImageKnife
1.新增gif解析操作
Signed-off-by: zhoulisheng1 <zhoulisheng1@huawei.com>
This commit is contained in:
parent
82ad36f241
commit
80f912ba60
|
@ -15,14 +15,15 @@
|
||||||
import {ImageKnifeComponent} from '@ohos/imageknife'
|
import {ImageKnifeComponent} from '@ohos/imageknife'
|
||||||
import {ImageKnifeOption} from '@ohos/imageknife'
|
import {ImageKnifeOption} from '@ohos/imageknife'
|
||||||
import {RotateImageTransformation} from '@ohos/imageknife'
|
import {RotateImageTransformation} from '@ohos/imageknife'
|
||||||
|
import ArkWorker from '@ohos.worker'
|
||||||
@Entry
|
@Entry
|
||||||
@Component
|
@Component
|
||||||
struct TestGifDontAnimatePage {
|
struct TestGifDontAnimatePage {
|
||||||
|
private globalGifWorker:any = undefined
|
||||||
@State imageKnifeOption1: ImageKnifeOption =
|
@State imageKnifeOption1: ImageKnifeOption =
|
||||||
{
|
{
|
||||||
loadSrc: $r('app.media.jpgSample'),
|
loadSrc: $r('app.media.jpgSample'),
|
||||||
size: { width: 300, height: 300 },
|
size: { width: '300', height: '300' },
|
||||||
placeholderSrc: $r('app.media.icon_loading'),
|
placeholderSrc: $r('app.media.icon_loading'),
|
||||||
errorholderSrc: $r('app.media.icon_failed'),
|
errorholderSrc: $r('app.media.icon_failed'),
|
||||||
margin:{left:15,top:15,right:15,bottom:15}
|
margin:{left:15,top:15,right:15,bottom:15}
|
||||||
|
@ -36,8 +37,8 @@ struct TestGifDontAnimatePage {
|
||||||
Button('本地资源gif')
|
Button('本地资源gif')
|
||||||
.onClick(() => {
|
.onClick(() => {
|
||||||
this.imageKnifeOption1 = {
|
this.imageKnifeOption1 = {
|
||||||
loadSrc: $r('app.media.gifSample'),
|
loadSrc: $r('app.media.testGif2'),
|
||||||
size: { width: 300, height: 300 },
|
size: { width: '300', height: '300' },
|
||||||
placeholderSrc: $r('app.media.icon_loading'),
|
placeholderSrc: $r('app.media.icon_loading'),
|
||||||
errorholderSrc: $r('app.media.icon_failed'),
|
errorholderSrc: $r('app.media.icon_failed'),
|
||||||
margin: { left: 15, top: 15, right: 15, bottom: 15 }
|
margin: { left: 15, top: 15, right: 15, bottom: 15 }
|
||||||
|
@ -48,7 +49,7 @@ struct TestGifDontAnimatePage {
|
||||||
.onClick(()=>{
|
.onClick(()=>{
|
||||||
this.imageKnifeOption1 = {
|
this.imageKnifeOption1 = {
|
||||||
loadSrc: $r('app.media.gifSample'),
|
loadSrc: $r('app.media.gifSample'),
|
||||||
size: { width: 300, height: 300 },
|
size: { width: '300', height: '300' },
|
||||||
placeholderSrc: $r('app.media.icon_loading'),
|
placeholderSrc: $r('app.media.icon_loading'),
|
||||||
errorholderSrc: $r('app.media.icon_failed'),
|
errorholderSrc: $r('app.media.icon_failed'),
|
||||||
margin:{left:15,top:15,right:15,bottom:15},
|
margin:{left:15,top:15,right:15,bottom:15},
|
||||||
|
@ -63,7 +64,7 @@ struct TestGifDontAnimatePage {
|
||||||
.onClick(()=>{
|
.onClick(()=>{
|
||||||
this.imageKnifeOption1 = {
|
this.imageKnifeOption1 = {
|
||||||
loadSrc: 'https://pic.ibaotu.com/gif/18/17/16/51u888piCtqj.gif!fwpaa70/fw/700',
|
loadSrc: 'https://pic.ibaotu.com/gif/18/17/16/51u888piCtqj.gif!fwpaa70/fw/700',
|
||||||
size: { width: 300, height: 300 },
|
size: { width: '300', height: '300' },
|
||||||
placeholderSrc: $r('app.media.icon_loading'),
|
placeholderSrc: $r('app.media.icon_loading'),
|
||||||
errorholderSrc: $r('app.media.icon_failed'),
|
errorholderSrc: $r('app.media.icon_failed'),
|
||||||
margin:{left:15,top:15,right:15,bottom:15}
|
margin:{left:15,top:15,right:15,bottom:15}
|
||||||
|
@ -73,7 +74,7 @@ struct TestGifDontAnimatePage {
|
||||||
.onClick(()=>{
|
.onClick(()=>{
|
||||||
this.imageKnifeOption1 = {
|
this.imageKnifeOption1 = {
|
||||||
loadSrc: 'https://pic.ibaotu.com/gif/18/17/16/51u888piCtqj.gif!fwpaa70/fw/700',
|
loadSrc: 'https://pic.ibaotu.com/gif/18/17/16/51u888piCtqj.gif!fwpaa70/fw/700',
|
||||||
size: { width: 300, height: 300 },
|
size: { width: '300', height: '300' },
|
||||||
placeholderSrc: $r('app.media.icon_loading'),
|
placeholderSrc: $r('app.media.icon_loading'),
|
||||||
errorholderSrc: $r('app.media.icon_failed'),
|
errorholderSrc: $r('app.media.icon_failed'),
|
||||||
margin:{left:15,top:15,right:15,bottom:15},
|
margin:{left:15,top:15,right:15,bottom:15},
|
||||||
|
@ -91,8 +92,16 @@ struct TestGifDontAnimatePage {
|
||||||
}
|
}
|
||||||
|
|
||||||
aboutToAppear() {
|
aboutToAppear() {
|
||||||
console.log('aboutToAppear()')
|
this.globalGifWorker = new ArkWorker.Worker('entry/ets/pages/workers/gifParseWorker.ts', {
|
||||||
|
type: 'classic',
|
||||||
|
name: 'ImageKnifeParseGIF'
|
||||||
|
})
|
||||||
|
globalThis.ImageKnife.setGifWorker(this.globalGifWorker)
|
||||||
|
}
|
||||||
|
aboutToDisappear(){
|
||||||
|
if(this.globalGifWorker){
|
||||||
|
this.globalGifWorker.terminate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var ImageKnife = globalThis.ImageKnife
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Huawei Device Co., Ltd.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the 'License');
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an 'AS IS' BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
import arkWorker from '@ohos.worker';
|
||||||
|
import { gifHandler } from '@ohos/imageknife/src/main/ets/components/imageknife/utils/gif/worker/GifWorker'
|
||||||
|
|
||||||
|
arkWorker.parentPort.onmessage = gifHandler;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Huawei Device Co., Ltd.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
export class GIFFrame {
|
||||||
|
|
||||||
|
// 显示帧 width 宽 height 高 top上边距 left左边距
|
||||||
|
dims: {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
top: number;
|
||||||
|
left: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当前帧的像素数据指向的颜色数组 只为了生成patch,非必要
|
||||||
|
colorTable?: [number, number, number][]
|
||||||
|
|
||||||
|
// 当前帧到下一帧的间隔时长
|
||||||
|
delay: number
|
||||||
|
|
||||||
|
// 当前帧绘制要求 0保留 1在上一帧绘制此帧 2恢复画布背景 3.将画布恢复到绘制当前图像之前的先前状态
|
||||||
|
disposalType: number
|
||||||
|
|
||||||
|
// Uint8CampedArray颜色转换后的补片信息用于绘制 必要
|
||||||
|
patch?: Uint8ClampedArray
|
||||||
|
|
||||||
|
// drawPixelMap 如果像素转换为PixelMap后使用PixelMap展示, patch和drawPixelMap 2选1
|
||||||
|
drawPixelMap?
|
||||||
|
|
||||||
|
// 当前帧每个像素的颜色表查找索引 只为了生成patch,非必要
|
||||||
|
pixels?: number[]
|
||||||
|
|
||||||
|
// 表示透明度的可选颜色索引
|
||||||
|
transparentIndex: number
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Huawei Device Co., Ltd.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
import { IParseGif } from './IParseGif'
|
||||||
|
import { GIFFrame } from './GIFFrame'
|
||||||
|
import { LoadType } from './worker/GifWorker'
|
||||||
|
import { parseBufferToFrame } from './parse/GIFParse'
|
||||||
|
import image from '@ohos.multimedia.image'
|
||||||
|
|
||||||
|
export class GIFParseImpl implements IParseGif {
|
||||||
|
parseGifs(imageinfo: ArrayBuffer, callback: (data?, err?) => void, worker?,runMainThread?:boolean) {
|
||||||
|
let resolveWorker = worker;
|
||||||
|
console.log('parseGifs resolveWorker1 is null =' + (resolveWorker == null))
|
||||||
|
if (!resolveWorker) {
|
||||||
|
resolveWorker = globalThis.ImageKnife.getGifWorker();
|
||||||
|
}
|
||||||
|
console.log('parseGifs resolveWorker2 is null =' + (resolveWorker == null))
|
||||||
|
|
||||||
|
if (!!resolveWorker && !runMainThread) {
|
||||||
|
console.log('parseGifs in worker thread!')
|
||||||
|
this.useWorkerParse(resolveWorker, imageinfo, (data, err) => {
|
||||||
|
if (err) {
|
||||||
|
callback(undefined, err)
|
||||||
|
} else {
|
||||||
|
this.createPixelMapAll(data).then((pixelmaps) => {
|
||||||
|
if (pixelmaps.length == data.length) {
|
||||||
|
for (let i = 0;i < data.length; i++) {
|
||||||
|
let frame = data[i];
|
||||||
|
frame['drawPixelMap'] = pixelmaps[i];
|
||||||
|
frame['patch'] = null;
|
||||||
|
}
|
||||||
|
callback(data, undefined)
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
callback(undefined, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.log('parseGifs in main thread!')
|
||||||
|
let frames = parseBufferToFrame(imageinfo)
|
||||||
|
console.log('frames length =' + frames.length)
|
||||||
|
this.createPixelMapAll(frames).then((pixelmaps) => {
|
||||||
|
if (pixelmaps.length == frames.length) {
|
||||||
|
for (let i = 0;i < frames.length; i++) {
|
||||||
|
let frame = frames[i];
|
||||||
|
frame['drawPixelMap'] = pixelmaps[i];
|
||||||
|
frame['patch'] = null;
|
||||||
|
}
|
||||||
|
console.log('parseGifs in main thread! callback is done!')
|
||||||
|
callback(frames, undefined)
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
console.log('parseGifs in main thread! err =' + err)
|
||||||
|
callback(undefined, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private createPixelMapAll(frames): Promise<PixelMap[]> {
|
||||||
|
let promises = []
|
||||||
|
let filterCriteria = (item) => {
|
||||||
|
if (!item['drawPixelMap']) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frames.filter(filterCriteria, frames).flatMap((frame) => {
|
||||||
|
promises.push(image.createPixelMap(frame.patch.buffer, {
|
||||||
|
'size': {
|
||||||
|
'height': frame.dims.height as number,
|
||||||
|
'width': frame.dims.width as number
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
return Promise.all(promises)
|
||||||
|
}
|
||||||
|
|
||||||
|
private useWorkerParse(worker: any, buffer: ArrayBuffer, callback: (data?, err?) => void) {
|
||||||
|
|
||||||
|
worker.onerror = function (data) {
|
||||||
|
callback(undefined, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
worker.onmessageerror = function (event) {
|
||||||
|
callback(undefined, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
worker.onexit = function () {
|
||||||
|
console.log('gifWork worker.onexit!')
|
||||||
|
}
|
||||||
|
|
||||||
|
worker.onmessage = (e) => {
|
||||||
|
var data = e.data;
|
||||||
|
switch (data.type) {
|
||||||
|
case LoadType.loadBufferByWorker:
|
||||||
|
let pages = data.data;
|
||||||
|
if (this.gifDecodeCorrect(pages)) {
|
||||||
|
let images = this.recDecodedData(pages);
|
||||||
|
callback(images, undefined)
|
||||||
|
} else {
|
||||||
|
callback(undefined, 'GIF Worker Decoder Data Is Error!')
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var obj = { type: LoadType.loadBufferByWorker, data: buffer }
|
||||||
|
worker.postMessage(obj, [buffer])
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private gifDecodeCorrect(frames) {
|
||||||
|
if (
|
||||||
|
(frames.patch.length == frames.dims.length) &&
|
||||||
|
(frames.patch.length == frames.delay.length) &&
|
||||||
|
(frames.patch.length == frames.disposalType.length) &&
|
||||||
|
(frames.patch.length == frames.patch.length) &&
|
||||||
|
(frames.patch.length == frames.transparentIndex.length)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 子线程数据回传处理
|
||||||
|
private recDecodedData(pages): GIFFrame[] {
|
||||||
|
let images = []
|
||||||
|
for (let i = 0; i < pages.patch.length; i++) {
|
||||||
|
let frame = {}
|
||||||
|
frame['dims'] = pages.dims[i]
|
||||||
|
pages.dims[i] = null
|
||||||
|
frame['delay'] = pages.delay[i]
|
||||||
|
pages.delay[i] = null
|
||||||
|
frame['disposalType'] = pages.disposalType[i]
|
||||||
|
pages.disposalType[i] = null
|
||||||
|
let uint8ClampedArray = new Uint8ClampedArray(pages.patch[i])
|
||||||
|
frame['patch'] = uint8ClampedArray
|
||||||
|
pages.patch[i] = null
|
||||||
|
frame['transparentIndex'] = pages.transparentIndex[i]
|
||||||
|
pages.transparentIndex[i] = null
|
||||||
|
images.push(frame)
|
||||||
|
}
|
||||||
|
pages = null
|
||||||
|
return images;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2021 Huawei Device Co., Ltd.
|
* Copyright (C) 2022 Huawei Device Co., Ltd.
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
export interface IParseGif{
|
||||||
export class PixelMapPack {
|
// gif解析
|
||||||
pixelMap:PixelMap
|
parseGifs(imageinfo:ArrayBuffer,callback:(data?,err?)=>void,worker?)
|
||||||
}
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Huawei Device Co., Ltd.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the 'License');
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an 'AS IS' BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
import { decompressFrames, parseGIF } from './index'
|
||||||
|
import { fixLoseGCE} from '../utils/ParseHelperUtils'
|
||||||
|
import { GIFFrame } from '../GIFFrame'
|
||||||
|
|
||||||
|
export function parseBufferToFrame(arraybuffer: ArrayBuffer): GIFFrame[] {
|
||||||
|
let gif = parseGIF(arraybuffer)
|
||||||
|
fixLoseGCE(gif)
|
||||||
|
let origins = decompressFrames(gif, true)
|
||||||
|
return origins as GIFFrame[];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Huawei Device Co., Ltd.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the 'License');
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an 'AS IS' BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deinterlace function from https://github.com/shachaf/jsgif
|
||||||
|
*/
|
||||||
|
export function deinterlace(pixels, width) {
|
||||||
|
var newPixels = new Array(pixels.length);
|
||||||
|
var rows = pixels.length / width;
|
||||||
|
|
||||||
|
var cpRow = function cpRow(toRow, fromRow) {
|
||||||
|
var fromPixels = pixels.slice(fromRow * width, (fromRow + 1) * width);
|
||||||
|
newPixels.splice.apply(newPixels, [toRow * width, width].concat(fromPixels));
|
||||||
|
}; // See appendix E.
|
||||||
|
|
||||||
|
|
||||||
|
var offsets = [0, 4, 2, 1];
|
||||||
|
var steps = [8, 8, 4, 2];
|
||||||
|
var fromRow = 0;
|
||||||
|
|
||||||
|
for (var pass = 0; pass < 4; pass++) {
|
||||||
|
for (var toRow = offsets[pass]; toRow < rows; toRow += steps[pass]) {
|
||||||
|
cpRow(toRow, fromRow);
|
||||||
|
fromRow++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newPixels;
|
||||||
|
};
|
|
@ -0,0 +1,114 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Huawei Device Co., Ltd.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the 'License');
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an 'AS IS' BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var _gif = _interopRequireDefault(require("js-binary-schema-parser/lib/schemas/gif"));
|
||||||
|
|
||||||
|
var _jsBinarySchemaParser = require("js-binary-schema-parser");
|
||||||
|
|
||||||
|
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 }; }
|
||||||
|
|
||||||
|
export function parseGIF(arrayBuffer) {
|
||||||
|
var byteData = new Uint8Array(arrayBuffer);
|
||||||
|
return (0, _jsBinarySchemaParser.parse)((0, _uint.buildStream)(byteData), _gif["default"]);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export function generatePatch(image) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return patchData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function decompressFrame(frame, gct, buildImagePatch) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}; // 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 (frame.gce) {
|
||||||
|
resultImage['delay'] = (frame.gce.delay || 10) * 10; // convert to ms
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,139 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Huawei Device Co., Ltd.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the 'License');
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an 'AS IS' BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* javascript port of java LZW decompression
|
||||||
|
* Original java author url: https://gist.github.com/devunwired/4479231
|
||||||
|
*/
|
||||||
|
export function lzw(minCodeSize, data, pixelCount) {
|
||||||
|
var MAX_STACK_SIZE = 4096;
|
||||||
|
var nullCode = -1;
|
||||||
|
var npix = pixelCount;
|
||||||
|
var available;
|
||||||
|
var clear;
|
||||||
|
var codeMask;
|
||||||
|
var codeSize;
|
||||||
|
var endOfInformation;
|
||||||
|
var inCode;
|
||||||
|
var oldCode;
|
||||||
|
var bits;
|
||||||
|
var code;
|
||||||
|
var i;
|
||||||
|
var datum;
|
||||||
|
var dataSize;
|
||||||
|
var first;
|
||||||
|
var top;
|
||||||
|
var bi;
|
||||||
|
var pi;
|
||||||
|
var dstPixels = new Array(pixelCount);
|
||||||
|
var prefix = new Array(MAX_STACK_SIZE);
|
||||||
|
var suffix = new Array(MAX_STACK_SIZE);
|
||||||
|
var pixelStack = new Array(MAX_STACK_SIZE + 1); // Initialize GIF data stream decoder.
|
||||||
|
|
||||||
|
dataSize = minCodeSize;
|
||||||
|
clear = 1 << dataSize;
|
||||||
|
endOfInformation = clear + 1;
|
||||||
|
available = clear + 2;
|
||||||
|
oldCode = nullCode;
|
||||||
|
codeSize = dataSize + 1;
|
||||||
|
codeMask = (1 << codeSize) - 1;
|
||||||
|
|
||||||
|
for (code = 0; code < clear; code++) {
|
||||||
|
prefix[code] = 0;
|
||||||
|
suffix[code] = code;
|
||||||
|
} // Decode GIF pixel stream.
|
||||||
|
|
||||||
|
|
||||||
|
var datum, bits, count, first, top, pi, bi;
|
||||||
|
datum = bits = count = first = top = pi = bi = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < npix;) {
|
||||||
|
if (top === 0) {
|
||||||
|
if (bits < codeSize) {
|
||||||
|
// get the next byte
|
||||||
|
datum += data[bi] << bits;
|
||||||
|
bits += 8;
|
||||||
|
bi++;
|
||||||
|
continue;
|
||||||
|
} // Get the next code.
|
||||||
|
|
||||||
|
|
||||||
|
code = datum & codeMask;
|
||||||
|
datum >>= codeSize;
|
||||||
|
bits -= codeSize; // Interpret the code
|
||||||
|
|
||||||
|
if (code > available || code == endOfInformation) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code == clear) {
|
||||||
|
// Reset decoder.
|
||||||
|
codeSize = dataSize + 1;
|
||||||
|
codeMask = (1 << codeSize) - 1;
|
||||||
|
available = clear + 2;
|
||||||
|
oldCode = nullCode;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldCode == nullCode) {
|
||||||
|
pixelStack[top++] = suffix[code];
|
||||||
|
oldCode = code;
|
||||||
|
first = code;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
inCode = code;
|
||||||
|
|
||||||
|
if (code == available) {
|
||||||
|
pixelStack[top++] = first;
|
||||||
|
code = oldCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (code > clear) {
|
||||||
|
pixelStack[top++] = suffix[code];
|
||||||
|
code = prefix[code];
|
||||||
|
}
|
||||||
|
|
||||||
|
first = suffix[code] & 0xff;
|
||||||
|
pixelStack[top++] = first; // add a new string to the table, but only if space is available
|
||||||
|
// if not, just continue with current table until a clear code is found
|
||||||
|
// (deferred clear code implementation as per GIF spec)
|
||||||
|
|
||||||
|
if (available < MAX_STACK_SIZE) {
|
||||||
|
prefix[available] = oldCode;
|
||||||
|
suffix[available] = first;
|
||||||
|
available++;
|
||||||
|
|
||||||
|
if ((available & codeMask) === 0 && available < MAX_STACK_SIZE) {
|
||||||
|
codeSize++;
|
||||||
|
codeMask += available;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
oldCode = inCode;
|
||||||
|
} // Pop a pixel off the pixel stack.
|
||||||
|
|
||||||
|
|
||||||
|
top--;
|
||||||
|
dstPixels[pi++] = pixelStack[top];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = pi; i < npix; i++) {
|
||||||
|
dstPixels[i] = 0; // clear missing pixels
|
||||||
|
}
|
||||||
|
|
||||||
|
return dstPixels;
|
||||||
|
};
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Huawei Device Co., Ltd.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the 'License');
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an 'AS IS' BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
export function fixLoseGCE(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Huawei Device Co., Ltd.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the 'License');
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an 'AS IS' BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
import arkWorker from '@ohos.worker';
|
||||||
|
import { parseBufferToFrame } from '../parse/GIFParse'
|
||||||
|
|
||||||
|
export enum LoadType {
|
||||||
|
loadBufferByWorker = "loadBufferByWorker"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send or Receive Format Data Such as: {type: yourResolveType, data: yourDataJson, error?: yourErrorInfo }
|
||||||
|
export function gifHandler(e) {
|
||||||
|
let data = e.data;
|
||||||
|
switch (data.type) {
|
||||||
|
case LoadType.loadBufferByWorker:
|
||||||
|
loadBufferByWorker(data.data, data.type);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadBufferByWorker(buffer: ArrayBuffer, recType: string) {
|
||||||
|
let images = parseBufferToFrame(buffer);
|
||||||
|
let dimss = [];
|
||||||
|
let delays = [];
|
||||||
|
let disposalTypes = [];
|
||||||
|
let patchs = [];
|
||||||
|
let transparentIndexs = [];
|
||||||
|
for (let i = 0; i < images.length; i++) {
|
||||||
|
dimss.push(images[i].dims)
|
||||||
|
delays.push(images[i].delay)
|
||||||
|
disposalTypes.push(images[i].disposalType)
|
||||||
|
patchs.push(images[i].patch.buffer)
|
||||||
|
transparentIndexs.push(images[i].transparentIndex)
|
||||||
|
}
|
||||||
|
let frame = {
|
||||||
|
dims: dimss,
|
||||||
|
// 当前帧到下一帧的间隔时长
|
||||||
|
delay: delays,
|
||||||
|
// 当前帧绘制要求 0保留 1在上一帧绘制此帧 2恢复画布背景 3.将画布恢复到绘制当前图像之前的先前状态
|
||||||
|
disposalType: disposalTypes,
|
||||||
|
// Uint8CampedArray颜色转换后的补片信息用于绘制
|
||||||
|
patch: patchs,
|
||||||
|
// 表示透明度的可选颜色索引
|
||||||
|
transparentIndex: transparentIndexs
|
||||||
|
}
|
||||||
|
let dataObj = { type: recType, data: frame }
|
||||||
|
arkWorker.parentPort.postMessage(dataObj, patchs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue