forked from floraachy/ImageKnife
403 lines
17 KiB
Plaintext
403 lines
17 KiB
Plaintext
/*
|
||
* 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 './cache/FileCache';
|
||
import { LogUtil } from './utils/LogUtil';
|
||
import { Constants } from './utils/Constants';
|
||
import http from '@ohos.net.http';
|
||
import { combineArrayBuffers } from './utils/ArrayBufferUtils';
|
||
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';
|
||
import { DownsampleStrategy } from './downsampling/DownsampleStartegy';
|
||
import { Downsampler } from './downsampling/Downsampler';
|
||
|
||
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<RequestJobResult> {
|
||
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<RequestJobResult> {
|
||
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
|
||
try {
|
||
if ((request.downsampType !== DownsampleStrategy.NONE) &&
|
||
request.requestSource == ImageKnifeRequestSource.SRC) {
|
||
decodingOptions = ImageKnifeLoader.getDownsamplerDecodingOptions(typeValue, request, size, ImageKnifeRequestSource.SRC)
|
||
}
|
||
} catch (err) {
|
||
return ImageKnifeLoader.makeEmptyResult(err)
|
||
}
|
||
|
||
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<RequestJobResult> {
|
||
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
|
||
};
|
||
try {
|
||
if ((request.downsampType !== DownsampleStrategy.NONE) &&
|
||
request.requestSource == ImageKnifeRequestSource.SRC) {
|
||
opts = ImageKnifeLoader.getDownsamplerDecodingOptions(typeValue, request, size)
|
||
}
|
||
} catch (err) {
|
||
return ImageKnifeLoader.makeEmptyResult(err)
|
||
}
|
||
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<RequestJobResult> {
|
||
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<RequestJobResult> {
|
||
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<PixelMap> = []
|
||
let delayList: Array<number> = []
|
||
await imageSource.createPixelMapList(decodingOptions).then(async (pixelList: Array<PixelMap>) => {
|
||
//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<ImageKnifeRequestWithSource> | undefined,fileKey:string): Promise<ArrayBuffer> {
|
||
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<ArrayBuffer>()
|
||
const headerObj: Record<string, object> = {}
|
||
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
|
||
}
|
||
|
||
static getDownsamplerDecodingOptions(typeValue: string, request: RequestJobRequest, size: Size,
|
||
SRC?: ImageKnifeRequestSource):image.DecodingOptions {
|
||
let reqSize =
|
||
new Downsampler().calculateScaling(typeValue, size.width, size.height, request.targetWidth, request.targetHeight,
|
||
request.downsampType)
|
||
if (typeValue == "svg") {
|
||
return {
|
||
editable: true,
|
||
desiredSize: {
|
||
height: vp2px(reqSize.height),
|
||
width: vp2px(reqSize.width)
|
||
}
|
||
|
||
}
|
||
} else {
|
||
return {
|
||
editable: request.requestSource === SRC && request.transformation !== undefined ? true : false,
|
||
desiredSize:{
|
||
width: reqSize.width,
|
||
height: reqSize.height
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|