diff --git a/CHANGELOG.md b/CHANGELOG.md index 29cc918..794a2f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ + +## 3.1.1-rc.0 +- 重构代码:抽取ImageKnifeDispatcher子线程requestJob相关代码到ImageKnifeLoader中,降低函数复杂度 + ## 3.1.0 - 部分静态webp图片有delay属性导致识别成动图,改用getFrameCount识别 - 修复加载错误图后未去请求排队队列中的请求 diff --git a/library/oh-package.json5 b/library/oh-package.json5 index bcc090f..ca33846 100644 --- a/library/oh-package.json5 +++ b/library/oh-package.json5 @@ -14,7 +14,7 @@ "main": "index.ets", "repository": "https://gitee.com/openharmony-tpc/ImageKnife", "type": "module", - "version": "3.1.0", + "version": "3.1.1-rc.0", "dependencies": { "@ohos/gpu_transform": "^1.0.2" }, diff --git a/library/src/main/ets/ImageKnifeDispatcher.ets b/library/src/main/ets/ImageKnifeDispatcher.ets index 2791464..f321402 100644 --- a/library/src/main/ets/ImageKnifeDispatcher.ets +++ b/library/src/main/ets/ImageKnifeDispatcher.ets @@ -18,18 +18,13 @@ import { IJobQueue } from './utils/IJobQueue' import List from '@ohos.util.List'; import LightWeightMap from '@ohos.util.LightWeightMap'; import { LogUtil } from './utils/LogUtil'; -import buffer from '@ohos.buffer'; -import { FileCache } from './utils/FileCache'; -import fs from '@ohos.file.fs'; import { ImageKnife } from './ImageKnife'; import { ImageKnifeData, CacheStrategy } from './model/ImageKnifeData'; -import http from '@ohos.net.http'; import image from '@ohos.multimedia.image'; import emitter from '@ohos.events.emitter'; import { Constants } from './utils/Constants'; import taskpool from '@ohos.taskpool'; import { FileTypeUtil } from './utils/FileTypeUtil'; -import util from '@ohos.util'; import { IEngineKey } from './key/IEngineKey'; import { DefaultEngineKey } from './key/DefaultEngineKey'; import { @@ -38,8 +33,9 @@ import { RequestJobResult, RequestJobRequest } from './model/ImageKnifeData' -import { combineArrayBuffers } from './model/utils'; import { BusinessError } from '@kit.BasicServicesKit'; +import { ImageKnifeLoader } from './ImageKnifeLoader' + export class ImageKnifeDispatcher { // 最大并发 @@ -52,7 +48,7 @@ export class ImageKnifeDispatcher { private engineKey: IEngineKey = new DefaultEngineKey(); showFromMemomry(request: ImageKnifeRequest, imageSrc: string | PixelMap | Resource, requestSource: ImageKnifeRequestSource,isAnimator?: boolean): boolean { - LogUtil.log("ImageKnife_DataTime_showFromMemomry.start:" + request.imageKnifeOption.loadSrc) + LogUtil.log("ImageKnife_DataTime_showFromMemomry.start:" + request.imageKnifeOption.loadSrc + "requestSource=" + requestSource + " isAnimator=" + isAnimator) let memoryCache: ImageKnifeData | undefined; if ((typeof (request.imageKnifeOption.loadSrc as image.PixelMap).isEditable) == 'boolean') { memoryCache = { @@ -65,14 +61,12 @@ export class ImageKnifeDispatcher { .loadFromMemoryCache(this.engineKey.generateMemoryKey(imageSrc, requestSource, request.imageKnifeOption,isAnimator)); } - if (memoryCache !== undefined) { - LogUtil.log("ImageKnife_DataTime_showFromMemomry.end_true:" + request.imageKnifeOption.loadSrc) // 画主图 if (request.requestState === ImageKnifeRequestState.PROGRESS) { // 回调请求开始 if (requestSource === ImageKnifeRequestSource.SRC && request.imageKnifeOption.onLoadListener?.onLoadStart !== undefined) { - request.imageKnifeOption.onLoadListener?.onLoadStart() + request.imageKnifeOption.onLoadListener.onLoadStart() LogUtil.log("ImageKnife_DataTime_MemoryCache_onLoadStart:" + request.imageKnifeOption.loadSrc) } LogUtil.log("ImageKnife_DataTime_MemoryCache_showPixelMap.start:" + request.imageKnifeOption.loadSrc) @@ -83,22 +77,22 @@ export class ImageKnifeDispatcher { request.requestState = ImageKnifeRequestState.COMPLETE // 回调请求开结束 if (request.imageKnifeOption.onLoadListener?.onLoadSuccess !== undefined) { - request.imageKnifeOption.onLoadListener?.onLoadSuccess(memoryCache.source,memoryCache) + request.imageKnifeOption.onLoadListener.onLoadSuccess(memoryCache.source,memoryCache) LogUtil.log("ImageKnife_DataTime_MemoryCache_onLoadSuccess:" + request.imageKnifeOption.loadSrc) } } else if (requestSource == ImageKnifeRequestSource.ERROR_HOLDER) { request.requestState = ImageKnifeRequestState.ERROR } } + LogUtil.log("ImageKnife_DataTime_showFromMemomry.end_hasmemory:" + request.imageKnifeOption.loadSrc) return true } - LogUtil.log("ImageKnife_DataTime_showFromMemomry.end_false:" + request.imageKnifeOption.loadSrc) + LogUtil.log("ImageKnife_DataTime_showFromMemomry.end_nomemory:" + request.imageKnifeOption.loadSrc) return false } enqueue(request: ImageKnifeRequest,isAnimator?: boolean): void { - //1.内存有的话直接渲染 if (this.showFromMemomry(request, request.imageKnifeOption.loadSrc, ImageKnifeRequestSource.SRC,isAnimator)) { return @@ -204,9 +198,9 @@ export class ImageKnifeDispatcher { emitter.on(Constants.PROGRESS_EMITTER + memoryKey, (data) => { this.progressCallBack(requestList! , data?.data?.value as number) }); - } + } - LogUtil.log("ImageKnife_DataTime_getAndShowImage_execute.start:" + currentRequest.imageKnifeOption.loadSrc) + LogUtil.log("ImageKnife_DataTime_getAndShowImage_execute.start(subthread):" + currentRequest.imageKnifeOption.loadSrc) taskpool.execute(task).then((res: Object) => { this.doTaskCallback(res as RequestJobResult | undefined, requestList!, currentRequest, memoryKey, imageSrc, requestSource,isAnimator); if (isWatchProgress){ @@ -214,8 +208,9 @@ export class ImageKnifeDispatcher { } LogUtil.log("ImageKnife_DataTime_getAndShowImage_execute.end:"+currentRequest.imageKnifeOption.loadSrc) LogUtil.log("ImageKnife_DataTime_getAndShowImage.end:"+currentRequest.imageKnifeOption.loadSrc) - }).catch((err:BusinessError)=>{ - LogUtil.error("Fail to execute in sub thread src=" + imageSrc + " err=" + err) + }).catch((err: BusinessError) => { + LogUtil.error("Fail to requestJob in sub thread src=" + imageSrc + " err=" + err) + LogUtil.log("ImageKnife_DataTime_getAndShowImage.end:" + currentRequest.imageKnifeOption.loadSrc) if (isWatchProgress){ emitter.off(Constants.PROGRESS_EMITTER + memoryKey) } @@ -223,12 +218,14 @@ export class ImageKnifeDispatcher { this.dispatchNextJob(); }) } else { //主线程请求 + LogUtil.log("ImageKnife_DataTime_getAndShowImage_execute.start(mainthread):" + currentRequest.imageKnifeOption.loadSrc) requestJob(request, requestList).then((res: RequestJobResult | undefined) => { this.doTaskCallback(res, requestList!, currentRequest, memoryKey, imageSrc, requestSource,isAnimator); LogUtil.log("ImageKnife_DataTime_getAndShowImage_execute.end:"+currentRequest.imageKnifeOption.loadSrc) LogUtil.log("ImageKnife_DataTime_getAndShowImage.end:"+currentRequest.imageKnifeOption.loadSrc) - }).catch((err:BusinessError)=>{ - LogUtil.error("Fail to execute in main thread src=" + imageSrc + " err=" + err) + }).catch((err: BusinessError) => { + LogUtil.error("Fail to requestJob in main thread src=" + imageSrc + " err=" + err) + LogUtil.log("ImageKnife_DataTime_getAndShowImage.end:" + currentRequest.imageKnifeOption.loadSrc) this.executingJobMap.remove(memoryKey); this.dispatchNextJob(); }) @@ -257,6 +254,7 @@ export class ImageKnifeDispatcher { } let pixelmap = requestJobResult.pixelMap; if (pixelmap === undefined) { + LogUtil.log("ImageKnife_DataTime_getAndShowImage_CallBack.pixelmap undefined:"+currentRequest.imageKnifeOption.loadSrc) requestList.forEach((requestWithSource: ImageKnifeRequestWithSource) => { // 回调请求失败 if (requestWithSource.source === ImageKnifeRequestSource.SRC && @@ -311,8 +309,6 @@ export class ImageKnifeDispatcher { LogUtil.log("ImageKnife_DataTime_getAndShowImage_saveMemoryCache.end:"+currentRequest.imageKnifeOption.loadSrc) } if (requestList !== undefined) { - - // todo 判断request生命周期,已销毁的不需要再绘制 // key相同的request,一起绘制 requestList.forEach((requestWithSource: ImageKnifeRequestWithSource) => { if (requestWithSource.request.requestState !== ImageKnifeRequestState.DESTROY) { @@ -340,8 +336,8 @@ export class ImageKnifeDispatcher { } } else { if (requestWithSource.source == ImageKnifeRequestSource.SRC && requestWithSource.request.imageKnifeOption.onLoadListener?.onLoadCancel) { - // 回调请求成功 - requestWithSource.request.imageKnifeOption.onLoadListener.onLoadCancel("component has destroyed") + // 回调请求成功 + requestWithSource.request.imageKnifeOption.onLoadListener.onLoadCancel("component has destroyed") } } }); @@ -360,11 +356,13 @@ export class ImageKnifeDispatcher { while (true) { let request = this.jobQueue.pop() if (request === undefined) { + LogUtil.log("ImageKnife_DataTime_dispatchNextJob.end:no any job") break // 队列已无任务 } else if (request.requestState === ImageKnifeRequestState.PROGRESS) { + LogUtil.log("ImageKnife_DataTime_dispatchNextJob.start executeJob:" + request.imageKnifeOption.loadSrc) this.executeJob(request) - LogUtil.log("ImageKnife_DataTime_dispatchNextJob.end:" + request.imageKnifeOption.loadSrc) + LogUtil.log("ImageKnife_DataTime_dispatchNextJob.end executeJob:" + request.imageKnifeOption.loadSrc) break }else if (request.requestState == ImageKnifeRequestState.DESTROY && request.imageKnifeOption.onLoadListener?.onLoadCancel) { request.imageKnifeOption.onLoadListener.onLoadCancel("component has destroyed") @@ -395,293 +393,44 @@ export class ImageKnifeDispatcher { */ @Concurrent async function requestJob(request: RequestJobRequest, requestList?: List): Promise { - LogUtil.log("ImageKnife_DataTime_requestJob.start:" + request.src) - let resBuf: ArrayBuffer | undefined - let bufferSize: number = 0 - let loadError: string = ''; - - class RequestData { - receiveSize: number = 2000 - totalSize: number = 2000 - } + LogUtil.log("ImageKnife_DataTime_requestJob.start:" + request.src + " requestSource=" + request.requestSource) let src = typeof request.src == "number" ? request.resName != undefined ? request.resName : request.src + "" : request.src - // 生成文件key - let fileKey = request.engineKey.generateFileKey(src, request.signature,request.isAnimator) + // 生成文件缓存key + let fileKey = request.engineKey.generateFileKey(src, request.signature, request.isAnimator) - // 判断自定义下载 - if (request.customGetImage !== undefined && request.requestSource == ImageKnifeRequestSource.SRC && typeof request.src == "string") { - // 先从文件缓存获取 - resBuf = FileCache.getFileCacheByFile(request.context, fileKey , request.fileCacheFolder) - if (resBuf === undefined) { - LogUtil.log("customGetImage customGetImage"); - resBuf = await request.customGetImage(request.context, request.src) - loadError = resBuf == undefined ? "customGetImage loadFile" : loadError - // 保存文件缓存 - if (resBuf !== undefined && request.writeCacheStrategy !== CacheStrategy.Memory) { - let copyBuf = buffer.concat([buffer.from(resBuf)]).buffer; // IDE有bug,不能直接获取resBuf.byteLength - bufferSize = copyBuf.byteLength - FileCache.saveFileCacheOnlyFile(request.context, fileKey, resBuf , request.fileCacheFolder) - } - } - } - else { - if (typeof request.src === 'string') { - if (request.src.indexOf("http://") == 0 || request.src.indexOf("https://") == 0) { //从网络下载 - // 先从文件缓存获取 - resBuf = FileCache.getFileCacheByFile(request.context, fileKey , request.fileCacheFolder) - if (resBuf === undefined && request.onlyRetrieveFromCache != true) { - LogUtil.log("ImageKnife_DataTime_requestJob_httpRequest.start:"+request.src) - let httpRequest = http.createHttp(); - let progress: number = 0 - let arrayBuffers = new Array() - const headerObj: Record = {} - if (request.headers != undefined) { - request.headers.forEach((value) => { - headerObj[value.key] = value.value - }) - } else if (request.allHeaders.size > 0) { - request.allHeaders.forEach((value, key) => { - headerObj[key] = value - }) - } - httpRequest.on("dataReceive", (data: ArrayBuffer) => { - arrayBuffers.push(data) - }); - - if (request.isWatchProgress) { - httpRequest.on('dataReceiveProgress', (data: RequestData) => { - // 下载进度 - if (data != undefined && (typeof data.receiveSize == 'number') && (typeof data.totalSize == 'number')) { - let percent = Math.round(((data.receiveSize * 1.0) / (data.totalSize * 1.0)) * 100) - if (progress !== percent) { - progress = percent - if (requestList === undefined) { - // 子线程 - emitter.emit(Constants.PROGRESS_EMITTER + request.memoryKey, { data: { "value": progress } }) - }else { - // 主线程请求 - requestList!.forEach((requestWithSource: ImageKnifeRequestWithSource) => { - if (requestWithSource.request.imageKnifeOption.progressListener !== undefined && requestWithSource.source === ImageKnifeRequestSource.SRC) { - requestWithSource.request.imageKnifeOption.progressListener(progress) - } - }) - } - } - } - }) - } - let promise = httpRequest.requestInStream(request.src, { - header: headerObj, - method: http.RequestMethod.GET, - expectDataType: http.HttpDataType.ARRAY_BUFFER, - connectTimeout: 60000, - readTimeout: 0, - // usingProtocol:http.HttpProtocol.HTTP1_1 - // header: new Header('application/json') - }); - - await promise.then((data: number) => { - if (data == 200 || data == 206 || data == 204) { - resBuf = combineArrayBuffers(arrayBuffers) - } else { - loadError = "HttpDownloadClient has error, http code =" + JSON.stringify(data) - } - }).catch((err: Error) => { - loadError = err.message; - LogUtil.error("requestInStream ERROR : err = " + JSON.stringify(err)); - }); - LogUtil.log("ImageKnife_DataTime_requestJob_httpRequest.end:"+request.src) - // 保存文件缓存 - if (resBuf !== undefined && request.writeCacheStrategy !== CacheStrategy.Memory) { - LogUtil.log("ImageKnife_DataTime_requestJob_saveFileCacheOnlyFile.start:"+request.src) - let copyBuf = combineArrayBuffers(arrayBuffers); // IDE有bug,不能直接获取resBuf.byteLength - bufferSize = copyBuf.byteLength - FileCache.saveFileCacheOnlyFile(request.context, fileKey, resBuf , request.fileCacheFolder) - LogUtil.log("ImageKnife_DataTime_requestJob_saveFileCacheOnlyFile.end:"+request.src) - } - } - else { - LogUtil.log("success get image from filecache for key = " + fileKey); - loadError = "success get image from filecache for key = " + fileKey; - } - } else if (request.src.startsWith('datashare://') || request.src.startsWith('file://')) { - await fs.open(request.src, fs.OpenMode.READ_ONLY).then(async (file) => { - await fs.stat(file.fd).then(async (stat) =>{ - let buf = new ArrayBuffer(stat.size); - await fs.read(file.fd, buf).then((readLen) => { - resBuf = buf; - fs.close(file.fd); - }).catch((err:BusinessError) => { - loadError = 'LoadDataShareFileClient fs.read err happened uri=' + request.src + " err.msg=" + err?.message + " err.code=" + err?.code; - }) - }).catch((err:BusinessError) => { - loadError = 'LoadDataShareFileClient fs.stat err happened uri=' + request.src + " err.msg=" + err?.message + " err.code=" + err?.code; - }) - }).catch((err:BusinessError) => { - loadError ='LoadDataShareFileClient fs.open err happened uri=' + request.src + " err.msg=" + err?.message + " err.code=" + err?.code; - }) - } else { //从本地文件获取 - try { - let stat = fs.statSync(request.src); - if (stat.size > 0) { - let file = fs.openSync(request.src, fs.OpenMode.READ_ONLY); - resBuf = new ArrayBuffer(stat.size); - fs.readSync(file.fd, resBuf); - fs.closeSync(file); - } - } catch (err) { - if (typeof err == 'string') { - loadError = err; - } else { - loadError = err.message; - } - } - } - } else if (typeof request.src == "number") { //从资源文件获取 - let manager = request.context.createModuleContext(request.moduleName).resourceManager - if (resBuf == undefined && request.onlyRetrieveFromCache != true && request.requestSource == ImageKnifeRequestSource.SRC) { - if(request.src == -1) { - let resName = request.resName as string - resBuf = (await manager.getMediaByName(resName.substring(10))).buffer as ArrayBuffer - } else { - resBuf = manager.getMediaContentSync(request.src).buffer as ArrayBuffer - } - } else if (resBuf == undefined && request.requestSource != ImageKnifeRequestSource.SRC) { - if(request.src == -1) { - let resName = request.resName as string - resBuf = (await manager.getMediaByName(resName.substring(10))).buffer as ArrayBuffer - } else { - resBuf = manager.getMediaContentSync(request.src).buffer as ArrayBuffer - } - } - } + //获取图片资源 + let resBuf: ArrayBuffer + try { + LogUtil.log("ImageKnife_DataTime_requestJob.getImageArrayBuffer.start:" + request.src) + resBuf = await ImageKnifeLoader.getImageArrayBuffer(request, requestList, fileKey) + LogUtil.log("ImageKnife_DataTime_requestJob.getImageArrayBuffer.end:" + request.src) + } catch (error) { + LogUtil.error("ImageKnife_DataTime_requestJob.end: getImageArrayBuffer error " + request.src + " err=" + error) + return ImageKnifeLoader.makeEmptyResult(error) } - - if (resBuf == undefined) { - LogUtil.log("ImageKnife_DataTime_requestJob.end_undefined:"+request.src) - return { - pixelMap: undefined, - bufferSize: 0, - fileKey: '', - loadFail: loadError, - } - } - LogUtil.log("ImageKnife_DataTime_requestJob_createPixelMap.start:"+request.src) - let fileTypeUtil = new FileTypeUtil(); - let typeValue = fileTypeUtil.getFileType(resBuf); + // 获取图片类型 + let typeValue = new FileTypeUtil().getFileType(resBuf); if(typeValue == null) { - return { - pixelMap: undefined, - bufferSize: 0, - fileKey: '', - loadFail: "request is not a valid image source", - } + LogUtil.log("ImageKnife_DataTime_requestJob.end: getFileType is null " + request.src) + return ImageKnifeLoader.makeEmptyResult("request is not a valid image source") } - let imageSource: image.ImageSource = image.createImageSource(resBuf); - let decodingOptions: image.DecodingOptions = { - editable: request.requestSource === ImageKnifeRequestSource.SRC && request.transformation !== undefined ? true : false, - } - if(request.isAnimator) { - if (typeValue === 'gif' || typeValue === 'webp') { - let pixelMapList: Array = [] - let delayList: Array = [] - await imageSource.createPixelMapList(decodingOptions).then(async (pixelList: Array) => { - //sdk的api接口发生变更:从.getDelayTime() 变为.getDelayTimeList() - await imageSource.getDelayTimeList().then(delayTimes => { - if (pixelList.length > 0) { - for (let i = 0; i < pixelList.length; i++) { - pixelMapList.push(pixelList[i]); - if (i < delayTimes.length) { - delayList.push(delayTimes[i]); - } else { - delayList.push(delayTimes[delayTimes.length - 1]) - } - } - imageSource.release(); - } - }) - }) - return { - pixelMap: "", - bufferSize: bufferSize, - fileKey: fileKey, - type: typeValue, - pixelMapList, - delayList - } - } else { - return { - pixelMap: undefined, - bufferSize: 0, - fileKey: '', - loadFail: "ImageKnifeAnimatorComponent组件仅支持动态图", - } - } - } - let resPixelmap: PixelMap | undefined = undefined - if (typeValue === 'gif' || typeValue === 'webp') { - let frameCount = await imageSource.getFrameCount() - if(frameCount == undefined || frameCount == 1) { - } else { - let size = (await imageSource.getImageInfo()).size - let base64Help = new util.Base64Helper() - let base64str = "data:image/" + typeValue + ";base64," + base64Help.encodeToStringSync(new Uint8Array(resBuf)) - LogUtil.log("ImageKnife_DataTime_requestJob_createPixelMap.end_GIF:"+request.src) - LogUtil.log("ImageKnife_DataTime_requestJob.end_GIF:"+request.src) - return { - pixelMap: base64str, - bufferSize: bufferSize, - fileKey: fileKey, - size:size, - type:typeValue - }; - } - } else if(typeValue == "svg") { - let size = (await imageSource.getImageInfo()).size - let scale = size.height / size.width - let hValue = Math.round(request.componentHeight); - let wValue = Math.round(request.componentWidth); - let defaultSize: image.Size = { - height: vp2px(wValue) * scale, - width: vp2px(wValue) - }; - let opts: image.DecodingOptions = { - editable: true, - desiredSize: defaultSize - }; - await imageSource.createPixelMap(opts) - .then((pixelmap: PixelMap) => { - resPixelmap = pixelmap - imageSource.release() - }) - return { - pixelMap: resPixelmap, - bufferSize: bufferSize, - fileKey: fileKey, - type:typeValue - }; - } - let size = (await imageSource.getImageInfo()).size - await imageSource.createPixelMap(decodingOptions) - .then((pixelmap: PixelMap) => { - resPixelmap = pixelmap - imageSource.release() - }) + // 解析图片 + LogUtil.log("ImageKnife_DataTime_requestJob.parseImage.start:" + request.src) + let result: RequestJobResult = await ImageKnifeLoader.parseImage(resBuf, typeValue, fileKey, request) + LogUtil.log("ImageKnife_DataTime_requestJob.parseImage.end:" + request.src) // 图形变化 - if (request.requestSource === ImageKnifeRequestSource.SRC && request.transformation !== undefined) { - resPixelmap = await request.transformation?.transform(request.context, resPixelmap!, request.componentWidth, request.componentHeight); + if (request.requestSource === ImageKnifeRequestSource.SRC && request.transformation !== undefined && result?.pixelMap !== undefined && typeof result.pixelMap !== 'string') { + LogUtil.log("ImageKnife_DataTime_requestJob.transform.start:" + request.src) + result.pixelMap = await request.transformation?.transform(request.context, result.pixelMap, request.componentWidth, request.componentHeight); + LogUtil.log("ImageKnife_DataTime_requestJob.transform.end:" + request.src) } - LogUtil.log("ImageKnife_DataTime_requestJob_createPixelMap.end:"+request.src) - LogUtil.log("ImageKnife_DataTime_requestJob.end:"+request.src) - return { - pixelMap: resPixelmap, - bufferSize: bufferSize, - fileKey: fileKey, - size:size, - type:typeValue - }; + + LogUtil.log("ImageKnife_DataTime_requestJob.end:" + request.src) + return result } + + diff --git a/library/src/main/ets/ImageKnifeLoader.ets b/library/src/main/ets/ImageKnifeLoader.ets new file mode 100644 index 0000000..8b0be11 --- /dev/null +++ b/library/src/main/ets/ImageKnifeLoader.ets @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2024 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 { + CacheStrategy, + ImageKnifeRequestSource, + ImageKnifeRequestWithSource, RequestJobRequest } from './model/ImageKnifeData'; +import List from '@ohos.util.List' +import { FileCache } from './utils/FileCache'; +import { LogUtil } from './utils/LogUtil'; +import { Constants } from './utils/Constants'; +import http from '@ohos.net.http'; +import { combineArrayBuffers } from './model/utils'; +import { BusinessError } from '@kit.BasicServicesKit'; +import fs from '@ohos.file.fs'; +import emitter from '@ohos.events.emitter'; +import image from '@ohos.multimedia.image'; +import { RequestJobResult } from './model/ImageKnifeData' +import util from '@ohos.util'; + +class RequestData { + receiveSize: number = 2000 + totalSize: number = 2000 +} + +/** + * ImageKnifeDispatcher 抽取出来的方法,因@Concurrent只能import方法,故抽取到另一个类 + */ +export class ImageKnifeLoader { + static async parseImage(resBuf: ArrayBuffer, typeValue: string, fileKey: string, + request: RequestJobRequest): Promise { + if(request.isAnimator) { + return ImageKnifeLoader.parseForAnimatorComponent(resBuf ,typeValue ,fileKey, request) + } + + if (typeValue === 'gif' || typeValue === 'webp') { + return ImageKnifeLoader.parseAnimatorImage(resBuf ,typeValue ,fileKey , request) + } else if(typeValue == "svg") { + return ImageKnifeLoader.parseSvgImage(resBuf ,typeValue ,fileKey , request) + } + + return ImageKnifeLoader.parseNormalImage(resBuf, typeValue, fileKey, request) + } + + static makeEmptyResult(error: string): RequestJobResult{ + return { + pixelMap: undefined, + bufferSize: 0, + fileKey: '', + loadFail: error, + } + } + + static async parseNormalImage(resBuf: ArrayBuffer, typeValue: string, fileKey: string, request: RequestJobRequest):Promise { + let resPixelmap: PixelMap | undefined = undefined + let decodingOptions: image.DecodingOptions = { + editable: request.requestSource === ImageKnifeRequestSource.SRC && request.transformation !== undefined ? true : false, + } + let imageSource: image.ImageSource = image.createImageSource(resBuf) + if (imageSource === undefined){ + return ImageKnifeLoader.makeEmptyResult("image.createImageSource failed") + } + + let size = (await imageSource.getImageInfo()).size + await imageSource.createPixelMap(decodingOptions) + .then((pixelmap: PixelMap) => { + resPixelmap = pixelmap + imageSource.release() + }).catch((error: BusinessError) => { + imageSource.release() + return ImageKnifeLoader.makeEmptyResult(JSON.stringify(error)) + }) + + return { + pixelMap: resPixelmap, + bufferSize: resBuf.byteLength, + fileKey: fileKey, + size:size, + type:typeValue + }; + } + static async parseSvgImage(resBuf: ArrayBuffer, typeValue: string, fileKey: string, + request: RequestJobRequest): Promise { + let resPixelmap: PixelMap | undefined = undefined + let imageSource: image.ImageSource = image.createImageSource(resBuf) + if (imageSource === undefined){ + return ImageKnifeLoader.makeEmptyResult("image.createImageSource failed") + } + + let size = (await imageSource.getImageInfo()).size + let scale = size.height / size.width + let hValue = Math.round(request.componentHeight); + let wValue = Math.round(request.componentWidth); + let defaultSize: image.Size = { + height: vp2px(wValue) * scale, + width: vp2px(wValue) + }; + let opts: image.DecodingOptions = { + editable: true, + desiredSize: defaultSize + }; + await imageSource.createPixelMap(opts) + .then((pixelmap: PixelMap) => { + resPixelmap = pixelmap + imageSource.release() + }).catch((error: BusinessError) => { + imageSource.release() + return ImageKnifeLoader.makeEmptyResult(JSON.stringify(error)) + }) + + return { + pixelMap: resPixelmap, + bufferSize: resBuf.byteLength, + fileKey: fileKey, + type:typeValue + }; + } + static async parseAnimatorImage(resBuf: ArrayBuffer, typeValue: string, + fileKey: string,request: RequestJobRequest): Promise { + let imageSource: image.ImageSource = image.createImageSource(resBuf) + if (imageSource === undefined){ + return ImageKnifeLoader.makeEmptyResult("image.createImageSource failed") + } + + let frameCount = await imageSource.getFrameCount() + let size = (await imageSource.getImageInfo()).size + imageSource.release() + + if(frameCount == undefined || frameCount == 1) { + } else { + let base64str = "data:image/" + typeValue + ";base64," + new util.Base64Helper().encodeToStringSync(new Uint8Array(resBuf)) + return { + pixelMap: base64str, + bufferSize: resBuf.byteLength, + fileKey: fileKey, + size:size, + type:typeValue + }; + } + return ImageKnifeLoader.parseNormalImage(resBuf, typeValue, fileKey, request) + } + // 为AnimatorComponent解析动图 + static async parseForAnimatorComponent(resBuf: ArrayBuffer, typeValue: string, fileKey: string,request: RequestJobRequest): Promise { + if (typeValue === 'gif' || typeValue === 'webp') { + let imageSource: image.ImageSource = image.createImageSource(resBuf); + if (imageSource === undefined){ + return ImageKnifeLoader.makeEmptyResult("image.createImageSource failed") + } + let decodingOptions: image.DecodingOptions = { + editable: request.requestSource === ImageKnifeRequestSource.SRC && request.transformation !== undefined ? true : false, + } + let pixelMapList: Array = [] + let delayList: Array = [] + await imageSource.createPixelMapList(decodingOptions).then(async (pixelList: Array) => { + //sdk的api接口发生变更:从.getDelayTime() 变为.getDelayTimeList() + await imageSource.getDelayTimeList().then(delayTimes => { + if (pixelList.length > 0) { + for (let i = 0; i < pixelList.length; i++) { + pixelMapList.push(pixelList[i]); + if (i < delayTimes.length) { + delayList.push(delayTimes[i]); + } else { + delayList.push(delayTimes[delayTimes.length - 1]) + } + } + imageSource.release(); + } + }) + }).catch((error: BusinessError) => { + imageSource.release() + return ImageKnifeLoader.makeEmptyResult(JSON.stringify(error)) + }) + return { + pixelMap: "", + bufferSize: resBuf.byteLength, + fileKey: fileKey, + type: typeValue, + pixelMapList, + delayList + } + } else { + return ImageKnifeLoader.makeEmptyResult("ImageKnifeAnimatorComponent组件仅支持动态图") + } + } + + // 获取图片资源 + static async getImageArrayBuffer(request: RequestJobRequest, requestList: List | undefined,fileKey:string): Promise { + let resBuf: ArrayBuffer | undefined + + // 判断自定义下载 + if (request.customGetImage !== undefined && request.requestSource == ImageKnifeRequestSource.SRC && typeof request.src == "string") { + // 先从文件缓存获取 + resBuf = FileCache.getFileCacheByFile(request.context, fileKey , request.fileCacheFolder) + if (resBuf === undefined) { + LogUtil.log("start customGetImage src=" + request.src) + try { + resBuf = await request.customGetImage(request.context, request.src) + LogUtil.log("end customGetImage src=" + request.src) + } catch (err) { + throw new Error('customGetImage loadFile failed! err = ' + err) + } + if (resBuf === undefined) { + throw new Error('customGetImage loadFile failed!') + } + // 保存文件缓存 + if (request.writeCacheStrategy !== CacheStrategy.Memory) { + LogUtil.log("ImageKnife_DataTime_requestJob_saveFileCacheOnlyFile.start:" + request.src) + FileCache.saveFileCacheOnlyFile(request.context, fileKey, resBuf, request.fileCacheFolder) + LogUtil.log("ImageKnife_DataTime_requestJob_saveFileCacheOnlyFile.end:" + request.src) + } + } + } + else { + if (typeof request.src === 'string') { + if (request.src.indexOf("http://") == 0 || request.src.indexOf("https://") == 0) { //从网络下载 + // 先从文件缓存获取 + resBuf = FileCache.getFileCacheByFile(request.context, fileKey , request.fileCacheFolder) + if (resBuf !== undefined){ + LogUtil.log("success get image from filecache for key = " + fileKey + " src = " + request.src) + } + else if (request.onlyRetrieveFromCache != true) { + LogUtil.log("HttpDownloadClient.start:" + request.src) + let httpRequest = http.createHttp(); + let progress: number = 0 + let arrayBuffers = new Array() + const headerObj: Record = {} + if (request.headers != undefined) { + request.headers.forEach((value) => { + headerObj[value.key] = value.value + }) + } else if (request.allHeaders.size > 0) { + request.allHeaders.forEach((value, key) => { + headerObj[key] = value + }) + } + httpRequest.on("dataReceive", (data: ArrayBuffer) => { + arrayBuffers.push(data) + }); + + if (request.isWatchProgress) { + httpRequest.on('dataReceiveProgress', (data: RequestData) => { + // 下载进度 + if (data != undefined && (typeof data.receiveSize == 'number') && (typeof data.totalSize == 'number')) { + let percent = Math.round(((data.receiveSize * 1.0) / (data.totalSize * 1.0)) * 100) + if (progress !== percent) { + progress = percent + if (requestList === undefined) { + // 子线程 + emitter.emit(Constants.PROGRESS_EMITTER + request.memoryKey, { data: { "value": progress } }) + }else { + // 主线程请求 + requestList!.forEach((requestWithSource: ImageKnifeRequestWithSource) => { + if (requestWithSource.request.imageKnifeOption.progressListener !== undefined && requestWithSource.source === ImageKnifeRequestSource.SRC) { + requestWithSource.request.imageKnifeOption.progressListener(progress) + } + }) + } + } + } + }) + } + let promise = httpRequest.requestInStream(request.src, { + header: headerObj, + method: http.RequestMethod.GET, + expectDataType: http.HttpDataType.ARRAY_BUFFER, + connectTimeout: 60000, + readTimeout: 0, + // usingProtocol:http.HttpProtocol.HTTP1_1 + // header: new Header('application/json') + }); + + await promise.then((data: number) => { + if (data == 200 || data == 206 || data == 204) { + resBuf = combineArrayBuffers(arrayBuffers) + } else { + throw new Error("HttpDownloadClient has error, http code =" + JSON.stringify(data)) + } + }).catch((err: Error) => { + throw new Error("HttpDownloadClient download ERROR : err = " + JSON.stringify(err)) + }); + LogUtil.log("HttpDownloadClient.end:" + request.src) + // 保存文件缓存 + if (resBuf !== undefined && request.writeCacheStrategy !== CacheStrategy.Memory) { + LogUtil.log("ImageKnife_DataTime_requestJob_saveFileCacheOnlyFile.start:"+request.src) + FileCache.saveFileCacheOnlyFile(request.context, fileKey, resBuf , request.fileCacheFolder) + LogUtil.log("ImageKnife_DataTime_requestJob_saveFileCacheOnlyFile.end:"+request.src) + } + } + else { + throw new Error('onlyRetrieveFromCache,do not fetch image src = ' + request.src) + } + } else if (request.src.startsWith('datashare://') || request.src.startsWith('file://')) { + await fs.open(request.src, fs.OpenMode.READ_ONLY).then(async (file) => { + await fs.stat(file.fd).then(async (stat) =>{ + let buf = new ArrayBuffer(stat.size); + await fs.read(file.fd, buf).then((readLen) => { + resBuf = buf; + fs.closeSync(file.fd); + }).catch((err:BusinessError) => { + throw new Error('LoadDataShareFileClient fs.read err happened uri=' + request.src + " err.msg=" + err?.message + " err.code=" + err?.code) + }) + }).catch((err:BusinessError) => { + throw new Error('LoadDataShareFileClient fs.stat err happened uri=' + request.src + " err.msg=" + err?.message + " err.code=" + err?.code) + }) + }).catch((err:BusinessError) => { + throw new Error('LoadDataShareFileClient fs.open err happened uri=' + request.src + " err.msg=" + err?.message + " err.code=" + err?.code) + }) + } else { //从本地文件获取 + try { + let stat = fs.statSync(request.src); + if (stat.size > 0) { + let file = fs.openSync(request.src, fs.OpenMode.READ_ONLY); + resBuf = new ArrayBuffer(stat.size); + fs.readSync(file.fd, resBuf); + fs.closeSync(file); + } + } catch (err) { + throw new Error(err) + } + } + } else if (typeof request.src == "number") { //从资源文件获取 + let manager = request.context.createModuleContext(request.moduleName).resourceManager + if (resBuf == undefined && request.onlyRetrieveFromCache != true && request.requestSource == ImageKnifeRequestSource.SRC) { + if(request.src == -1) { + let resName = request.resName as string + resBuf = (await manager.getMediaByName(resName.substring(10))).buffer as ArrayBuffer + } else { + resBuf = manager.getMediaContentSync(request.src).buffer as ArrayBuffer + } + } else if (resBuf == undefined && request.requestSource != ImageKnifeRequestSource.SRC) { + if(request.src == -1) { + let resName = request.resName as string + resBuf = (await manager.getMediaByName(resName.substring(10))).buffer as ArrayBuffer + } else { + resBuf = manager.getMediaContentSync(request.src).buffer as ArrayBuffer + } + } + } + } + + if (resBuf === undefined){ + throw new Error('getImageArrayBuffer undefined') + } + return resBuf + } +} \ No newline at end of file diff --git a/library/src/main/ets/utils/Tools.ets b/library/src/main/ets/utils/Tools.ets deleted file mode 100644 index a51faa8..0000000 --- a/library/src/main/ets/utils/Tools.ets +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2024 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 { SparkMD5 } from '../3rd_party/sparkmd5/spark-md5' -import util from '@ohos.util' - -export class Tools { - private static keyCache: util.LRUCache = new util.LRUCache(1024) - public static generateMemoryKey(key: string | PixelMap | Resource): string{ - return typeof key == "string"? key : JSON.stringify(key) - } - // 生成唯一的key - public static generateKey(key: string | PixelMap | Resource): string{ - let keyCache = typeof key == "string"? key : JSON.stringify(key) - let result = Tools.keyCache.get(keyCache) - if(result != undefined) { - return result - } else { - result = SparkMD5.hashBinary(keyCache) - Tools.keyCache.put(keyCache,result) - return result - } - } -} \ No newline at end of file