diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cdaab2..4cd6cbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ +## 3.0.0-rc.4 +- 支持hsp多包图片资源 +- 新增putCache写入缓存接口 +- 修复入参为pixelMap图片不显示问题 + ## 3.0.0-rc.3 - 将请求默认并行从64调整到8,减少对taskpool execute内存消耗 - 补充option参数:placeholderObjectFit,errorholderObjectFit分别支持占位图填充效果和错误图填充效果 diff --git a/README.md b/README.md index 162c2cc..40972e0 100644 --- a/README.md +++ b/README.md @@ -23,23 +23,27 @@ - 支持使用一个或多个图片变换,如模糊,高亮等 待实现特性 + - gif/webp动图显示与控制 - 内存降采样优化,节约内存的占用 - 支持自定义图片解码 注意:3.x版本相对2.x版本做了重大的重构,主要体现在: + - 使用Image组件代替Canvas组件渲染 - 重构Dispatch分发逻辑,支持控制并发请求数,支持请求排队队列的优先级 - 支持通过initMemoryCache自定义策略内存缓存策略和大小 - 支持option自定义实现图片获取/网络下载 因此API及能力上,目前有部分差异,主要体现在: + - 不支持drawLifeCycle接口,通过canvas自会图片 - mainScaleType,border等参数,新版本与系统Image保持一致 - gif/webp动图播放与控制 - 抗锯齿相关参数 ## 下载安装 + ``` ohpm install @ohos/imageknife @@ -48,7 +52,9 @@ await ImageKnife.getInstance().initFileCache(context, 256, 256 * 1024 * 1024) ``` ## 使用说明 + #### 1.显示本地资源图片 + ``` ImageKnifeComponent({ ImageKnifeOption: { @@ -61,6 +67,7 @@ ImageKnifeComponent({ ``` #### 2.显示本地context files下文件 + ``` ImageKnifeComponent({ ImageKnifeOption: { @@ -73,6 +80,7 @@ ImageKnifeComponent({ ``` #### 3.显示网络图片 + ``` ImageKnifeComponent({ ImageKnifeOption: { @@ -85,6 +93,7 @@ ImageKnifeComponent({ ``` #### 4.自定义下载图片 + ``` ImageKnifeComponent({ ImageKnifeOption: { @@ -106,6 +115,7 @@ async function custom(context: Context, src: string | PixelMap | Resource): Prom ``` #### 5.监听网络下载进度 + ``` ImageKnifeComponent({ ImageKnifeOption: { @@ -114,7 +124,9 @@ ImageKnifeComponent({ } }).width(100).height(100) ``` + #### 6.支持option传入border,设置边框,圆角 + ``` ImageKnifeComponent({ ImageKnifeOption: { @@ -123,7 +135,9 @@ ImageKnifeComponent({ ImageKnifeOption: } }).width(100).height(100) ``` + #### 7.支持option图片变换 + ``` ImageKnifeComponent({ ImageKnifeOption: { @@ -134,44 +148,48 @@ ImageKnifeComponent({ ImageKnifeOption: }).width(100).height(100) ``` - ## 接口说明 + ### ImageKnifeOption参数列表 -| 参数名称 | 入参内容 | 功能简介 | -|-----------------------|---------------------------|-----------------| -| loadSrc | string、PixelMap、Resource | 主图展示 | -| placeholderSrc | PixelMap、Resource | 占位图图展示(可选) | -| errorholderSrc | PixelMap、Resource | 错误图展示(可选) | -| objectFit | ImageFit | 主图填充效果(可选) | -| placeholderObjectFit | ImageFit | 占位图填充效果(可选) | -| errorholderObjectFit | ImageFit | 错误图填充效果(可选) | -| writeCacheStrategy | CacheStrategyType | 写入缓存策略(可选) | -| onlyRetrieveFromCache | boolean | 是否跳过网络和本地请求(可选) | +| 参数名称 | 入参内容 | 功能简介 | +|-----------------------|--------------------------------|-----------------| +| loadSrc | string、PixelMap、Resource | 主图展示 | +| placeholderSrc | PixelMap、Resource | 占位图图展示(可选) | +| errorholderSrc | PixelMap、Resource | 错误图展示(可选) | +| objectFit | ImageFit | 主图填充效果(可选) | +| placeholderObjectFit | ImageFit | 占位图填充效果(可选) | +| errorholderObjectFit | ImageFit | 错误图填充效果(可选) | +| writeCacheStrategy | CacheStrategyType | 写入缓存策略(可选) | +| onlyRetrieveFromCache | boolean | 是否跳过网络和本地请求(可选) | | customGetImage | (context: Context, src: string | 自定义下载图片(可选) | | Resource | 错误占位图数据源 | -| border | BorderOptions | 边框圆角(可选) | -| priority | taskpool.Priority | 加载优先级(可选) | -| context | common.UIAbilityContext | 上下文(可选) | -| progressListener | (progress: number)=>void | 进度(可选) | -| signature | String | 自定义缓存关键字(可选) | -| headerOption | Array | 设置请求头(可选) | -| transformation | PixelMapTransformation | 图片变换(可选) | +| border | BorderOptions | 边框圆角(可选) | +| priority | taskpool.Priority | 加载优先级(可选) | +| context | common.UIAbilityContext | 上下文(可选) | +| progressListener | (progress: number)=>void | 进度(可选) | +| signature | String | 自定义缓存关键字(可选) | +| headerOption | Array | 设置请求头(可选) | +| transformation | PixelMapTransformation | 图片变换(可选) | ### ImageKnife接口 -| 参数名称 | 入参内容 | 功能简介 | -|------------------|---------------------------|---------------| -| initMemoryCache | newMemoryCache: IMemoryCache | 自定义内存缓存策略 | -| initFileCache | context: Context, size: number, memory: number | 初始化文件缓存数量和大小 | -| preLoadCache | url:string | 预加载并返回文件缓存路径 | -| getCacheImage | url: string, cacheType: CacheType | 从内存或文件缓存中获取资源 | -| addHeader | key: string, value: Object | 全局添加http请求头 | -| setHeaderOptions | Array | 全局设置http请求头 | -| deleteHeader | key: string | 全局删除http请求头 | -| setEngineKeyImpl | IEngineKey | 全局配置缓存key生成策略 | + +| 参数名称 | 入参内容 | 功能简介 | +|------------------|-------------------------------------------------------------------------------------------------------|---------------| +| initMemoryCache | newMemoryCache: IMemoryCache | 自定义内存缓存策略 | +| initFileCache | context: Context, size: number, memory: number | 初始化文件缓存数量和大小 | +| preLoadCache | loadSrc: string I ImageKnifeOption | 预加载并返回文件缓存路径 | +| getCacheImage | loadSrc: string, cacheType: CacheStrategy = CacheStrategy.Default, signature?: string) | 从内存或文件缓存中获取资源 | +| addHeader | key: string, value: Object | 全局添加http请求头 | +| setHeaderOptions | Array | 全局设置http请求头 | +| deleteHeader | key: string | 全局删除http请求头 | +| setEngineKeyImpl | IEngineKey | 全局配置缓存key生成策略 | +| putCacheImage | url: string, pixelMap: PixelMap, cacheType: CacheStrategy = CacheStrategy.Default, signature?: string | 写入内存磁盘缓存 | ## 约束与限制 + 在下述版本验证通过: DevEco Studio 5.0 Canary3(5.0.3.221)--SDK:API12 + ## 贡献代码 使用过程中发现任何问题都可以提 [issue](https://gitee.com/openharmony-tpc/ImageKnife/issues) @@ -182,4 +200,5 @@ DevEco Studio 5.0 Canary3(5.0.3.221)--SDK:API12 本项目基于 [Apache License 2.0](https://gitee.com/openharmony-tpc/ImageKnife/blob/master/LICENSE) ,请自由的享受和参与开源。 ## 遗留问题 + - 添加组件闪动问题 \ No newline at end of file diff --git a/entry/src/main/ets/pages/SingleImage.ets b/entry/src/main/ets/pages/SingleImage.ets index d6f7658..99dbbc6 100644 --- a/entry/src/main/ets/pages/SingleImage.ets +++ b/entry/src/main/ets/pages/SingleImage.ets @@ -14,13 +14,14 @@ */ import { ImageKnifeComponent,BlurTransformation } from '@ohos/imageknife'; import fs from '@ohos.file.fs'; +import image from '@ohos.multimedia.image'; @Entry @Component struct SingleImage { scroller: Scroller = new Scroller; localFile: string = getContext(this).filesDir + "/icon.png" - + @State pixelMap:PixelMap | undefined = undefined; aboutToAppear(): void { // 拷贝本地文件 let icon: Uint8Array = getContext(this).resourceManager.getMediaContentSync($r("app.media.startIcon")); @@ -28,6 +29,9 @@ struct SingleImage { fs.writeSync(file.fd, icon.buffer); fs.fsyncSync(file.fd); fs.closeSync(file); + getContext().resourceManager.getMediaContentSync( $r("app.media.aaa")) + .buffer as ArrayBuffer; + } build() { @@ -78,13 +82,36 @@ struct SingleImage { objectFit: ImageFit.Contain, customGetImage: custom, transformation: new BlurTransformation(10) - } - }).width(100).height(100) + } + }).width(100).height(100) + Text("自定义下载") + .fontSize(30) + .fontWeight(FontWeight.Bold) + ImageKnifeComponent({ + imageKnifeOption: { + loadSrc: this.pixelMap, + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + objectFit: ImageFit.Contain, + } + }).width(100).height(100) } .width('100%') } .height('100%') } + + changePic(buffer: ArrayBuffer){ + let imageSource: image.ImageSource = image.createImageSource(buffer); + if (imageSource) { + let decodingOptions: image.DecodingOptions = { + editable: true, + } + imageSource.createPixelMap(decodingOptions,(err,pixelMap)=>{ + this.pixelMap = pixelMap; + }) + } + } } // 自定义下载方法 diff --git a/library/src/main/ets/ImageKnife.ets b/library/src/main/ets/ImageKnife.ets index 91bcabe..916f11a 100644 --- a/library/src/main/ets/ImageKnife.ets +++ b/library/src/main/ets/ImageKnife.ets @@ -73,15 +73,17 @@ export class ImageKnife { addHeader(key: string, value: Object) { this.headerMap.set(key, value); } + /** * 全局设置请求头调用方法 * @param options 请求头数组 */ - serHeaderOptions(options:Array) { - options.forEach((value)=>{ - this.headerMap.set(value.key,value.value) + serHeaderOptions(options: Array) { + options.forEach((value) => { + this.headerMap.set(value.key, value.value) }) } + /** * 删除单个请求头属性 * @param key 请求头属性 @@ -89,6 +91,7 @@ export class ImageKnife { deleteHeader(key: string) { this.headerMap.delete(key); } + /** * 设置自定义的内存缓存 * @param newMemoryCache 自定义内存缓存 @@ -125,22 +128,26 @@ export class ImageKnife { this.fileCache?.put(key, data) } - getFileCache(): FileCache{ + getFileCache(): FileCache { return this.fileCache as FileCache } + /** * 预加载到文件缓存 * @param loadSrc 图片地址url * @returns 返回文件缓存路径 */ - preLoadCache(loadSrc: string): Promise { - return new Promise((resolve,reject)=>{ + preLoadCache(loadSrc: string | ImageKnifeOption): Promise { + return new Promise((resolve, reject) => { let imageKnifeOption = new ImageKnifeOption() - imageKnifeOption.loadSrc = loadSrc - let engineKeyImpl: IEngineKey = new DefaultEngineKey() - let fileKey = engineKeyImpl.generateFileKey(loadSrc) + if (loadSrc instanceof ImageKnifeOption) { + imageKnifeOption = loadSrc + } else { + imageKnifeOption.loadSrc = loadSrc; + } + let fileKey = this.getEngineKeyImpl().generateFileKey(imageKnifeOption.loadSrc, imageKnifeOption.signature) let cachePath = ImageKnife.getInstance().getFileCache().getFileToPath(fileKey) - if(cachePath == null || cachePath == "" || cachePath == undefined) { + if (cachePath == null || cachePath == "" || cachePath == undefined) { let request = new ImageKnifeRequest( imageKnifeOption, imageKnifeOption.context !== undefined ? imageKnifeOption.context : getContext(this) as common.UIAbilityContext, @@ -159,18 +166,21 @@ export class ImageKnife { } }) } + /** * 从内存或文件缓存中获取图片数据 * @param url 图片地址url * @param cacheType 缓存策略 * @returns 图片数据 + * @param signature key自定义信息 */ getCacheImage(loadSrc: string, - cacheType: CacheStrategy = CacheStrategy.Default): Promise { + cacheType: CacheStrategy = CacheStrategy.Default, signature?: string): Promise { let option: ImageKnifeOption = { - loadSrc: loadSrc + loadSrc: loadSrc, + signature:signature } - let engineKeyImpl: IEngineKey = new DefaultEngineKey() + let engineKeyImpl: IEngineKey = this.getEngineKeyImpl(); return new Promise((resolve, reject) => { if (cacheType == CacheStrategy.Memory) { @@ -184,24 +194,58 @@ export class ImageKnife { }) } - private readMemoryCache(loadSrc: string,option: ImageKnifeOption, engineKey: IEngineKey): ImageKnifeData | undefined { + /** + * + * @param url 图片地址url + * @param pixelMap 图片 + * @param cacheType 缓存策略 + * @param signature key自定义信息 + */ + putCacheImage(url: string, pixelMap: PixelMap, cacheType: CacheStrategy = CacheStrategy.Default, signature?: string) { + let memoryKey = this.getEngineKeyImpl() + .generateMemoryKey(url, ImageKnifeRequestSource.SRC, { loadSrc: url, signature: signature }); + let fileKey = this.getEngineKeyImpl().generateFileKey(url, signature); + let imageKnifeData: ImageKnifeData = { source: pixelMap, imageWidth: 0, imageHeight: 0 }; + switch (cacheType) { + case CacheStrategy.Default: + this.saveMemoryCache(memoryKey, imageKnifeData); + this.saveFileCache(fileKey, this.pixelMapToArrayBuffer(pixelMap)); + break; + case CacheStrategy.File: + this.saveFileCache(fileKey, this.pixelMapToArrayBuffer(pixelMap)); + break + case CacheStrategy.Memory: + this.saveMemoryCache(memoryKey, imageKnifeData); + break + } + } + + private pixelMapToArrayBuffer(pixelMap: PixelMap): ArrayBuffer { + let imageInfo = pixelMap.getImageInfoSync(); + let readBuffer: ArrayBuffer = new ArrayBuffer(imageInfo.size.height * imageInfo.size.width * 4); + pixelMap.readPixelsToBufferSync(readBuffer); + return readBuffer + + } + + private readMemoryCache(loadSrc: string, option: ImageKnifeOption, engineKey: IEngineKey): ImageKnifeData | undefined { let memoryKey = engineKey.generateMemoryKey(loadSrc, ImageKnifeRequestSource.SRC, option) return ImageKnife.getInstance() .loadFromMemoryCache(memoryKey) } - private readFileCache(loadSrc:string,engineKey:IEngineKey,onComplete:(data:ImageKnifeData | undefined)=>void){ + private readFileCache(loadSrc: string, engineKey: IEngineKey, onComplete: (data: ImageKnifeData | undefined) => void) { let keys = engineKey.generateFileKey(loadSrc) let buffer = ImageKnife.getInstance().loadFromFileCache(keys) - if(buffer != undefined) { + if (buffer != undefined) { let fileTypeUtil = new FileTypeUtil(); let typeValue = fileTypeUtil.getFileType(buffer); if (typeValue === 'gif' || typeValue === 'webp') { let base64Help = new util.Base64Helper() - let base64str = "data:image/" + typeValue + ";base64," + base64Help.encodeToStringSync(new Uint8Array(buffer)) + let base64str = "data:image/" + typeValue + ";base64," + base64Help.encodeToStringSync(new Uint8Array(buffer)) onComplete({ - source:base64str, + source: base64str, imageWidth: 0, imageHeight: 0 }) @@ -215,7 +259,7 @@ export class ImageKnife { imageSource.createPixelMap(decodingOptions) .then((pixelmap: PixelMap) => { onComplete({ - source:pixelmap, + source: pixelmap, imageWidth: 0, imageHeight: 0 }) @@ -225,6 +269,7 @@ export class ImageKnife { onComplete(undefined) } } + /** * 清除所有文件缓存 * @returns @@ -268,4 +313,7 @@ export class ImageKnife { this.dispatcher.setEngineKeyImpl(impl); } + getEngineKeyImpl(): IEngineKey { + return this.dispatcher.getEngineKeyImpl(); + } } \ No newline at end of file diff --git a/library/src/main/ets/ImageKnifeDispatcher.ets b/library/src/main/ets/ImageKnifeDispatcher.ets index 5c23f0d..0eb75ba 100644 --- a/library/src/main/ets/ImageKnifeDispatcher.ets +++ b/library/src/main/ets/ImageKnifeDispatcher.ets @@ -32,7 +32,12 @@ import { FileTypeUtil } from './utils/FileTypeUtil'; import util from '@ohos.util'; import { IEngineKey } from './key/IEngineKey'; import { DefaultEngineKey } from './key/DefaultEngineKey'; -import { ImageKnifeRequestWithSource , ImageKnifeRequestSource , RequestJobResult , RequestJobRequest } from './model/ImageKnifeData' +import { + ImageKnifeRequestWithSource, + ImageKnifeRequestSource, + RequestJobResult, + RequestJobRequest +} from './model/ImageKnifeData' export class ImageKnifeDispatcher { // 最大并发 @@ -45,8 +50,19 @@ export class ImageKnifeDispatcher { private engineKey: IEngineKey = new DefaultEngineKey(); showFromMemomry(request: ImageKnifeRequest, imageSrc: string | PixelMap | Resource, requestSource: ImageKnifeRequestSource): boolean { - let memoryCache: ImageKnifeData | undefined = ImageKnife.getInstance() - .loadFromMemoryCache(this.engineKey.generateMemoryKey(imageSrc, requestSource, request.imageKnifeOption)) + let memoryCache: ImageKnifeData | undefined; + if ((typeof (request.imageKnifeOption.loadSrc as image.PixelMap).isEditable) == 'boolean') { + memoryCache = { + source: request.imageKnifeOption.loadSrc as image.PixelMap, + imageWidth: 0, + imageHeight: 0, + } + } else { + memoryCache = ImageKnife.getInstance() + .loadFromMemoryCache(this.engineKey.generateMemoryKey(imageSrc, requestSource, request.imageKnifeOption)); + } + + if (memoryCache !== undefined) { // 画主图 if (request.requestState === ImageKnifeRequestState.PROGRESS) { @@ -95,7 +111,7 @@ export class ImageKnifeDispatcher { * 获取和显示图片 */ getAndShowImage(currentRequest: ImageKnifeRequest, imageSrc: string | PixelMap | Resource, requestSource: ImageKnifeRequestSource): void { - let memoryKey: string = this.engineKey.generateMemoryKey(imageSrc,requestSource, currentRequest.imageKnifeOption) + let memoryKey: string = this.engineKey.generateMemoryKey(imageSrc, requestSource, currentRequest.imageKnifeOption) let requestList: List | undefined = this.executingJobMap.get(memoryKey) if (requestList == undefined) { requestList = new List() @@ -109,12 +125,13 @@ export class ImageKnifeDispatcher { let request: RequestJobRequest = { context: currentRequest.context, src: imageSrc, - headers:currentRequest.imageKnifeOption.headerOption, - allHeaders:currentRequest.headers, + headers: currentRequest.imageKnifeOption.headerOption, + allHeaders: currentRequest.headers, customGetImage: currentRequest.imageKnifeOption.customGetImage, onlyRetrieveFromCache: currentRequest.imageKnifeOption.onlyRetrieveFromCache, - transformation:currentRequest.imageKnifeOption.transformation, - writeCacheStrategy: ImageKnife.getInstance().isFileCacheInit() ? currentRequest.imageKnifeOption.writeCacheStrategy : CacheStrategy.Memory, // 未初始化文件缓存时,不写文件缓存 + transformation: currentRequest.imageKnifeOption.transformation, + writeCacheStrategy: ImageKnife.getInstance() + .isFileCacheInit() ? currentRequest.imageKnifeOption.writeCacheStrategy : CacheStrategy.Memory, // 未初始化文件缓存时,不写文件缓存 engineKey: this.engineKey, signature: currentRequest.imageKnifeOption.signature, requestSource @@ -160,7 +177,7 @@ export class ImageKnifeDispatcher { } // 保存内存缓存 - if(currentRequest.imageKnifeOption.writeCacheStrategy !== CacheStrategy.File) { + if (currentRequest.imageKnifeOption.writeCacheStrategy !== CacheStrategy.File) { ImageKnife.getInstance() .saveMemoryCache(this.engineKey.generateMemoryKey(imageSrc, requestSource, currentRequest.imageKnifeOption), ImageKnifeData) @@ -215,6 +232,10 @@ export class ImageKnifeDispatcher { setEngineKeyImpl(impl: IEngineKey): void { this.engineKey = impl; } + + getEngineKeyImpl(): IEngineKey { + return this.engineKey; + } } /** @@ -259,18 +280,18 @@ async function requestJob(request: RequestJobRequest): Promise = {} - if(request.headers != undefined) { - request.headers.forEach((value)=>{ + 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)=>{ + } else if (request.allHeaders.size > 0) { + request.allHeaders.forEach((value, key) => { headerObj[key] = value }) } let promise = httpRequest.requestInStream(request.src, { - header:headerObj, + header: headerObj, method: http.RequestMethod.GET, connectTimeout: 6000, readTimeout: 6000, @@ -321,10 +342,17 @@ async function requestJob(request: RequestJobRequest): Promise