diff --git a/CHANGELOG.md b/CHANGELOG.md index db9a79c..dccd758 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ + +## 3.0.0-rc.2 +- 新增支持使用一个或多个图片变换,如模糊,高亮等 + ## 3.0.0-rc.1 - 新增从内存或文件缓存获取图片数据接口getCacheImage - 新增图片预加载preLoadCache并返回文件缓存路径 diff --git a/README.md b/README.md index 1a01b36..0c1ef1c 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,9 @@ 本项目参考开源库 [Glide](https://github.com/bumptech/glide) 进行OpenHarmony的自研版本: -- 支持内存缓存,使用LRUCache算法,对图片数据进行内存缓存。 -- 支持通过initMemoryCache自定义策略内存缓存策略和大小。 +- 支持自定义内存缓存策略,支持设置内存缓存的大小。 - 支持磁盘二级缓存,对于下载图片会保存一份至磁盘当中。 -- 支持option自定义实现图片获取/网络下载 +- 支持自定义实现图片获取/网络下载 - 支持监听网络下载回调进度 - 继承Image的能力,支持option传入border,设置边框,圆角 - 继承Image的能力,支持option传入objectFit设置图片缩放,包括objectFit为auto时根据图片自适应高度 @@ -17,28 +16,27 @@ - 并发请求数量,支持请求排队队列的优先级 - 支持生命周期已销毁的图片,不再发起请求 - 自定义缓存key -- 自定义请求头规格 +- 自定义http网络请求头 - 支持writeCacheStrategy控制缓存的存入策略(只存入内存或文件缓存) - 支持preLoadCache预加载图片 - 支持onlyRetrieveFromCache仅用缓存加载 +- 支持使用一个或多个图片变换,如模糊,高亮等 待实现特性 - gif/webp动图显示与控制 - 内存降采样优化,节约内存的占用 - 支持自定义图片解码 -- 支持进行图片变换: 支持图像像素源图片变换效果。 注意:3.x版本相对2.x版本做了重大的重构,主要体现在: - 使用Image组件代替Canvas组件渲染 - 重构Dispatch分发逻辑,支持控制并发请求数,支持请求排队队列的优先级 -- 支持通过initMemoryCache自定义策略内存缓存策略和大小。 -- 支持option自定义实现图片获取/网络下载 +- 支持通过initMemoryCache自定义策略内存缓存策略和大小 +- 支持option自定义实现图片获取/网络下载 因此API及能力上,目前有部分差异,主要体现在: - 不支持drawLifeCycle接口,通过canvas自会图片 - mainScaleType,border等参数,新版本与系统Image保持一致 - gif/webp动图播放与控制 -- 支持进行图片变换: 支持图像像素源图片变换效果。 - 抗锯齿相关参数 ## 下载安装 @@ -122,34 +120,49 @@ ImageKnifeComponent({ ImageKnifeOption: } }).width(100).height(100) ``` +#### 7.支持option图片变换 +``` +ImageKnifeComponent({ ImageKnifeOption: +{ + loadSrc: $r("app.media.rabbit"), + border: {radius:50}, + transformation: new BlurTransformation(3) + } +}).width(100).height(100) +``` + + ## 接口说明 ### ImageKnifeOption参数列表 -| 参数名称 | 入参内容 | 功能简介 | -|-----------------------|-------------------------------|---------------| -| loadSrc | string、PixelMap、Resource | 主图展示 | -| placeholderSrc | PixelMap、Resource | 占位图图展示(可选) | -| errorholderSrc | PixelMap、Resource | 错误图展示(可选) | -| objectFit | ImageFit | 图片展示样式(可选) | -| writeCacheStrategy | WriteCacheStrategyType | 写入缓存策略(可选) | -| onlyRetrieveFromCache | boolean | 跳过网络和本地请求(可选) | -| customGetImage | (context: Context, src: string | 自定义网络(可选) | | Resource | 错误占位图数据源 | -| border | BorderOptions | 边框圆角(可选) | -| priority | taskpool.Priority | 加载优先级(可选) | -| context | common.UIAbilityContext | 上下文(可选) | -| progressListener | (progress: number)=>void | 进度(可选) | -| signature | ObjectKey | 自定义缓存关键字 | -| headerOption | Array | 设置请求头 | +| 参数名称 | 入参内容 | 功能简介 | +|-----------------------|-------------------------------|-----------------| +| loadSrc | string、PixelMap、Resource | 主图展示 | +| placeholderSrc | PixelMap、Resource | 占位图图展示(可选) | +| errorholderSrc | PixelMap、Resource | 错误图展示(可选) | +| objectFit | ImageFit | 图片展示样式(可选) | +| writeCacheStrategy | WriteCacheStrategyType | 写入缓存策略(可选) | +| onlyRetrieveFromCache | boolean | 是否跳过网络和本地请求(可选) | +| customGetImage | (context: Context, src: string | 自定义下载图片(可选) | | Resource | 错误占位图数据源 | +| border | BorderOptions | 边框圆角(可选) | +| priority | taskpool.Priority | 加载优先级(可选) | +| context | common.UIAbilityContext | 上下文(可选) | +| progressListener | (progress: number)=>void | 进度(可选) | +| signature | ObjectKey | 自定义缓存关键字(可选) | +| headerOption | Array | 设置请求头(可选) | +| transformation | PixelMapTransformation | 图片变换(可选) | ### ImageKnife接口 -| 参数名称 | 入参内容 | 功能简介 | -|--------------|---------------------------|---------------| -| preLoadCache | url:string | 预加载并返回文件缓存路径 | -| getCacheImage | url: string, cacheType: CacheType | 从内存或文件缓存中获取资源 | -| addHeader | key: string, value: Object | 全局添加请求头属性 | -| serHeaderOptions | Array | 全局设置请求头 | -| deleteHeader | key: string | 全局删除请求头 | -| setEngineKeyImpl | CustomEngineKeyImpl | 全局配置缓存key | +| 参数名称 | 入参内容 | 功能简介 | +|------------------|---------------------------|---------------| +| 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 | CustomEngineKeyImpl | 全局配置缓存key | ## 约束与限制 在下述版本验证通过: diff --git a/entry/src/main/ets/common/CustomEngineKeyImpl.ets b/entry/src/main/ets/common/CustomEngineKeyImpl.ets index 6bbb9a1..5af4cba 100644 --- a/entry/src/main/ets/common/CustomEngineKeyImpl.ets +++ b/entry/src/main/ets/common/CustomEngineKeyImpl.ets @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Huawei Device Co., Ltd. + * 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 @@ -13,34 +13,48 @@ * limitations under the License. */ import util from '@ohos.util'; -import { IEngineKey, ObjectKey } from '@ohos/imageknife'; +import { IEngineKey, ImageKnifeOption, ObjectKey , PixelMapTransformation } from '@ohos/imageknife'; import { SparkMD5 } from '@ohos/imageknife/src/main/ets/3rd_party/sparkmd5/spark-md5'; //全局自定义key demo export class CustomEngineKeyImpl implements IEngineKey { - private keyCache: util.LRUCache = new util.LRUCache(1024) + private memoryKeyCache: util.LRUCache = new util.LRUCache(1024) + private fileKeyCache: util.LRUCache = new util.LRUCache(1024) - // 生成内存缓存 - generateCacheKey(loadSrc: string | PixelMap | Resource, signature?: ObjectKey | undefined): string { - let key = "loadSrc=" + this.generateKey(loadSrc) + ";"; - if (signature) { - key += "signature=" + signature.getKey() + ";" + // 生成内存缓存key + generateMemoryKey(loadSrc: string | PixelMap | Resource, imageKnifeOption: ImageKnifeOption): string { + let src = "loadSrc=" + (typeof loadSrc == "string" ? loadSrc : JSON.stringify(loadSrc)) + ";" + if (imageKnifeOption.signature) { + src += "signature=" + imageKnifeOption.signature.getKey() + ";" } - return key; + if (imageKnifeOption.transformation) { + src += "transformation=" + this.getTransformation(imageKnifeOption.transformation) + ";" + } + return this.generateKey(src, this.memoryKeyCache) + } + + // 生成文件缓存key + generateFileKey(loadSrc: string | PixelMap | Resource, signature?: ObjectKey | undefined): string { + let src = "loadSrc=" + (typeof loadSrc == "string" ? loadSrc : JSON.stringify(loadSrc)) + ";" + if (signature) { + src += "signature=" + signature.getKey() + ";" + } + return this.generateKey(src, this.fileKeyCache) } // key缓存策略,避免无意义的 JSON.stringify - private generateKey(key: string | PixelMap | Resource): string { - let keyCache = typeof key == "string" ? key : JSON.stringify(key) - let result = this.keyCache.get(keyCache) + private generateKey(keyCache: string, cache: util.LRUCache): string { + let result = cache.get(keyCache) if (result != undefined) { return result } else { result = SparkMD5.hashBinary(keyCache) - this.keyCache.put(keyCache, result) + cache.put(keyCache, result) return result } } - + private getTransformation(transformation: PixelMapTransformation): string { + return transformation.getName() + } } diff --git a/entry/src/main/ets/pages/ImageTransformation.ets b/entry/src/main/ets/pages/ImageTransformation.ets new file mode 100644 index 0000000..5fd511d --- /dev/null +++ b/entry/src/main/ets/pages/ImageTransformation.ets @@ -0,0 +1,89 @@ +import { BlurTransformation, BrightnessTransformation, ImageKnifeComponent, ImageKnifeOption, + MultiTransTransformation, + PixelMapTransformation } from '@ohos/imageknife' +import { collections } from '@kit.ArkTS' + +@Entry +@Component +struct ImageTransformation { + @State imageKnifeOption: ImageKnifeOption = { + loadSrc: "http://g.hiphotos.baidu.com/image/pic/item/6d81800a19d8bc3e770bd00d868ba61ea9d345f2.jpg", + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + objectFit: ImageFit.Contain + } + isBlur: boolean = false + isBrightness: boolean = false + + build() { + Column() { + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'checkbox1', group: 'checkboxGroup' }) + .selectedColor(0x39a2db) + .shape(CheckBoxShape.ROUNDED_SQUARE) + .onChange((value: boolean) => { + this.isBlur = value + this.upedateImageKnifeOption() + }) + .mark({ + strokeColor: Color.Black, + size: 50, + strokeWidth: 5 + }) + .unselectedColor(Color.Red) + .width(30) + .height(30) + Text('模糊效果').fontSize(20) + } + + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'checkbox2', group: 'checkboxGroup' }) + .selectedColor(0x39a2db) + .shape(CheckBoxShape.ROUNDED_SQUARE) + .onChange((value: boolean) => { + this.isBrightness = value + this.upedateImageKnifeOption() + }) + .width(30) + .height(30) + Text('高亮效果').fontSize(20) + } + + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'checkbox3', group: 'checkboxGroup' }) + .selectedColor(0x39a2db) + .shape(CheckBoxShape.ROUNDED_SQUARE) + .onChange((value: boolean) => { + console.info('Checkbox3 change is' + value) + }) + .width(30) + .height(30) + Text('Checkbox3').fontSize(20) + } + + ImageKnifeComponent({ + imageKnifeOption: this.imageKnifeOption + }).width(300).height(300) + } + .height('100%') + .width('100%') + } + + upedateImageKnifeOption() { + let transformations: collections.Array = new collections.Array() + if (this.isBlur){ + transformations.push(new BlurTransformation(5)) + } + if (this.isBrightness){ + transformations.push(new BrightnessTransformation(0.2)) + } + + this.imageKnifeOption = { + loadSrc: "http://g.hiphotos.baidu.com/image/pic/item/6d81800a19d8bc3e770bd00d868ba61ea9d345f2.jpg", + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + objectFit: ImageFit.Contain, + transformation: transformations.length > 0 ? new MultiTransTransformation(transformations) : undefined + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/Index.ets b/entry/src/main/ets/pages/Index.ets index e07b959..e489b8e 100644 --- a/entry/src/main/ets/pages/Index.ets +++ b/entry/src/main/ets/pages/Index.ets @@ -87,6 +87,13 @@ struct Index { }); }) + Button("图片变换").margin({top:10}).onClick(()=>{ + router.push({ + uri: 'pages/ImageTransformation', + + }); + }) + } .width('100%') diff --git a/entry/src/main/ets/pages/ListPage.ets b/entry/src/main/ets/pages/ListPage.ets index d74fbcf..2864bfd 100644 --- a/entry/src/main/ets/pages/ListPage.ets +++ b/entry/src/main/ets/pages/ListPage.ets @@ -32,7 +32,7 @@ struct ListPage { Row() { List({ space: 10 }) { ForEach(this.datas, (item: string) => { - ImageKnifeComponent({ ImageKnifeOption: this.ImageKnifeOption }).height(200).width(200) + ImageKnifeComponent({ imageKnifeOption: this.ImageKnifeOption }).height(200).width(200) }, (item: string) => item) } .width('100%') diff --git a/entry/src/main/ets/pages/LongImagePage.ets b/entry/src/main/ets/pages/LongImagePage.ets index 7ad15c1..e8c5716 100644 --- a/entry/src/main/ets/pages/LongImagePage.ets +++ b/entry/src/main/ets/pages/LongImagePage.ets @@ -25,7 +25,7 @@ struct LongImagePage { // Image($r("app.media.aaa")).objectFit(ImageFit.Auto).width(200) ImageKnifeComponent({ - ImageKnifeOption: { + imageKnifeOption: { loadSrc:"https://wx2.sinaimg.cn/mw690/006HyQKGgy1hnqp08dw09j30u04twu0x.jpg", //src:$r("app.media.aaa"), // placeholderSrc: $r("app.media.loading"), diff --git a/entry/src/main/ets/pages/ManyPhotoShowPage.ets b/entry/src/main/ets/pages/ManyPhotoShowPage.ets index 9b2e685..0be47d9 100644 --- a/entry/src/main/ets/pages/ManyPhotoShowPage.ets +++ b/entry/src/main/ets/pages/ManyPhotoShowPage.ets @@ -36,7 +36,7 @@ struct ManyPhotoShowPage { // loadSrc: item.thumbnail, // mainScaleType: ScaleType.FIT_XY, // } }) - ImageKnifeComponent({ImageKnifeOption:{ + ImageKnifeComponent({imageKnifeOption:{ loadSrc: item.thumbnail, //src:"https://www.openharmony.cn/_nuxt/img/logo.dcf95b3.png", // src: this.localFile, diff --git a/entry/src/main/ets/pages/SignatureTestPage.ets b/entry/src/main/ets/pages/SignatureTestPage.ets index 11281cb..4ada805 100644 --- a/entry/src/main/ets/pages/SignatureTestPage.ets +++ b/entry/src/main/ets/pages/SignatureTestPage.ets @@ -43,7 +43,7 @@ struct SignatureTestPage { signature: new ObjectKey("1") } }).margin({ top: 5, left: 3 }) - ImageKnifeComponent({ ImageKnifeOption: this.imageKnifeOption1 }).width(300).height(300) + ImageKnifeComponent({ imageKnifeOption: this.imageKnifeOption1 }).width(300).height(300) }.width('100%').backgroundColor(Color.Pink) Text("key每次变化:时间戳").fontSize(15) @@ -56,7 +56,7 @@ struct SignatureTestPage { signature: new ObjectKey(new Date().getTime().toString()) } }).margin({ top: 5, left: 3 }) - ImageKnifeComponent({ ImageKnifeOption: this.imageKnifeOption2 }).width(300).height(300) + ImageKnifeComponent({ imageKnifeOption: this.imageKnifeOption2 }).width(300).height(300) }.width('100%').backgroundColor(Color.Pink) } diff --git a/entry/src/main/ets/pages/SingleImage.ets b/entry/src/main/ets/pages/SingleImage.ets index 5521c0d..d6f7658 100644 --- a/entry/src/main/ets/pages/SingleImage.ets +++ b/entry/src/main/ets/pages/SingleImage.ets @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ImageKnifeComponent } from '@ohos/imageknife'; +import { ImageKnifeComponent,BlurTransformation } from '@ohos/imageknife'; import fs from '@ohos.file.fs'; @Entry @@ -37,7 +37,7 @@ struct SingleImage { .fontSize(30) .fontWeight(FontWeight.Bold) ImageKnifeComponent({ - ImageKnifeOption: { + imageKnifeOption: { loadSrc: $r("app.media.app_icon"), placeholderSrc: $r("app.media.loading"), errorholderSrc: $r("app.media.app_icon"), @@ -48,7 +48,7 @@ struct SingleImage { .fontSize(30) .fontWeight(FontWeight.Bold) ImageKnifeComponent({ - ImageKnifeOption: { + imageKnifeOption: { loadSrc: this.localFile, placeholderSrc: $r("app.media.loading"), errorholderSrc: $r("app.media.app_icon"), @@ -59,7 +59,7 @@ struct SingleImage { .fontSize(30) .fontWeight(FontWeight.Bold) ImageKnifeComponent({ - ImageKnifeOption: { + imageKnifeOption: { loadSrc:"https://www.openharmony.cn/_nuxt/img/logo.dcf95b3.png", placeholderSrc: $r("app.media.loading"), errorholderSrc: $r("app.media.app_icon"), @@ -71,12 +71,13 @@ struct SingleImage { .fontSize(30) .fontWeight(FontWeight.Bold) ImageKnifeComponent({ - ImageKnifeOption: { + imageKnifeOption: { loadSrc: "https://file.atomgit.com/uploads/user/1704857786989_8994.jpeg", placeholderSrc: $r("app.media.loading"), errorholderSrc: $r("app.media.app_icon"), objectFit: ImageFit.Contain, - customGetImage: custom + customGetImage: custom, + transformation: new BlurTransformation(10) } }).width(100).height(100) } diff --git a/entry/src/main/ets/pages/TestHeader.ets b/entry/src/main/ets/pages/TestHeader.ets index b1e79c1..93670a6 100644 --- a/entry/src/main/ets/pages/TestHeader.ets +++ b/entry/src/main/ets/pages/TestHeader.ets @@ -31,7 +31,7 @@ struct TestPrefetchToFileCachePage { build() { Column() { ImageKnifeComponent({ - ImageKnifeOption: this.imageKnifeOption + imageKnifeOption: this.imageKnifeOption }).width(300).height(300) } .height('100%') .width('100%') diff --git a/entry/src/main/ets/pages/TestImageFlash.ets b/entry/src/main/ets/pages/TestImageFlash.ets index 3dc0bb0..cd1ab28 100644 --- a/entry/src/main/ets/pages/TestImageFlash.ets +++ b/entry/src/main/ets/pages/TestImageFlash.ets @@ -59,19 +59,19 @@ export struct MsgItem { build(){ if (this.count % 2 == 0 && this.count <6){ ImageKnifeComponent({ - ImageKnifeOption:{ + imageKnifeOption:{ loadSrc:$r("app.media.startIcon") } }) }else if (this.count > 6 && this.count - 6 < this.data.length){ ImageKnifeComponent({ - ImageKnifeOption:{ + imageKnifeOption:{ loadSrc:this.data[this.count - 6], } }) }else { ImageKnifeComponent({ - ImageKnifeOption:{ + imageKnifeOption:{ loadSrc:$r("app.media.loading") } }) diff --git a/entry/src/main/ets/pages/TestIsUrlExist.ets b/entry/src/main/ets/pages/TestIsUrlExist.ets index ca1db6c..8728557 100644 --- a/entry/src/main/ets/pages/TestIsUrlExist.ets +++ b/entry/src/main/ets/pages/TestIsUrlExist.ets @@ -73,7 +73,7 @@ struct TestIsUrlExist { }.margin({ top: 10 }) ImageKnifeComponent({ - ImageKnifeOption: this.imageKnifeOption + imageKnifeOption: this.imageKnifeOption }).width(200).height(200).margin({ top: 10 }) Image(this.source).margin({ top: 10 }) .width(200).height(200) diff --git a/entry/src/main/ets/pages/TestPrefetchToFileCache.ets b/entry/src/main/ets/pages/TestPrefetchToFileCache.ets index 8b881ba..3248b11 100644 --- a/entry/src/main/ets/pages/TestPrefetchToFileCache.ets +++ b/entry/src/main/ets/pages/TestPrefetchToFileCache.ets @@ -34,7 +34,7 @@ struct TestPrefetchToFileCachePage { this.imageKnifeOption.loadSrc = "https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658" }) ImageKnifeComponent({ - ImageKnifeOption: this.imageKnifeOption + imageKnifeOption: this.imageKnifeOption }).width(300).height(300).margin({top:30}) } .height('100%') .width('100%') diff --git a/entry/src/main/ets/pages/TransformPage.ets b/entry/src/main/ets/pages/TransformPage.ets index bda54b2..b462c4c 100644 --- a/entry/src/main/ets/pages/TransformPage.ets +++ b/entry/src/main/ets/pages/TransformPage.ets @@ -31,7 +31,7 @@ struct TransformPage { build() { Column() { - ImageKnifeComponent({ ImageKnifeOption: this.ImageKnifeOption }).height(200).width(200) + ImageKnifeComponent({ imageKnifeOption: this.ImageKnifeOption }).height(200).width(200) .transform(this.matrix1) // Image($r('app.media.rabbit')).objectFit(ImageFit.Contain).height(200).width(200).transform(this.matrix1) Button("放大").onClick(()=>{ diff --git a/entry/src/main/ets/pages/User.ets b/entry/src/main/ets/pages/User.ets index e58319b..00c571d 100644 --- a/entry/src/main/ets/pages/User.ets +++ b/entry/src/main/ets/pages/User.ets @@ -63,7 +63,7 @@ export struct UserAvatar { Row() { // Image(this.imageKnifeOption.loadSrc) - ImageKnifeComponent({ ImageKnifeOption: this.ImageKnifeOption }) + ImageKnifeComponent({ imageKnifeOption: this.ImageKnifeOption }) .borderRadius(this.radius) .clip(true) .width(this.calcImgSize) diff --git a/entry/src/main/resources/base/profile/main_pages.json b/entry/src/main/resources/base/profile/main_pages.json index 07a38d5..2d070ef 100644 --- a/entry/src/main/resources/base/profile/main_pages.json +++ b/entry/src/main/resources/base/profile/main_pages.json @@ -11,6 +11,7 @@ "pages/SignatureTestPage", "pages/TestPrefetchToFileCache", "pages/TestIsUrlExist", - "pages/TestHeader" + "pages/TestHeader", + "pages/ImageTransformation" ] } \ No newline at end of file diff --git a/library/index.ets b/library/index.ets index 488697f..eb1a473 100644 --- a/library/index.ets +++ b/library/index.ets @@ -16,6 +16,12 @@ export { ObjectKey } from './src/main/ets/model/ObjectKey' export { ImageKnifeData , ReadCacheStrategyType} from "./src/main/ets/model/ImageKnifeData" - +export { PixelMapTransformation } from './src/main/ets/transform/PixelMapTransformation' + +export { MultiTransTransformation } from './src/main/ets/transform/MultiTransTransformation' + +export { BrightnessTransformation } from './src/main/ets/transform/BrightnessTransformation' + +export { BlurTransformation } from './src/main/ets/transform/BlurTransformation' diff --git a/library/src/main/ets/ImageKnife.ets b/library/src/main/ets/ImageKnife.ets index 362cde5..0a2cc5e 100644 --- a/library/src/main/ets/ImageKnife.ets +++ b/library/src/main/ets/ImageKnife.ets @@ -129,7 +129,7 @@ export class ImageKnife { let imageKnifeOption = new ImageKnifeOption() imageKnifeOption.loadSrc = loadSrc let engineKeyImpl: IEngineKey = new DefaultEngineKey() - let keys = engineKeyImpl.generateCacheKey(loadSrc) + let keys = engineKeyImpl.generateMemoryKey(loadSrc, imageKnifeOption) let cachePath = ImageKnife.getInstance().getFileCache().getFileToPath(keys) if(cachePath == null || cachePath == "" || cachePath == undefined) { let request = new ImageKnifeRequest( @@ -156,26 +156,33 @@ export class ImageKnife { * @param cacheType 缓存策略 * @returns 图片数据 */ - getCacheImage(loadSrc: string, cacheType: ReadCacheStrategyType = ReadCacheStrategyType.Default):Promise { - return new Promise((resolve,reject)=>{ - let engineKeyImpl: IEngineKey = new DefaultEngineKey() - if(cacheType == ReadCacheStrategyType.Memory) { - resolve(this.ReadMemoryCache(loadSrc,engineKeyImpl)) + getCacheImage(loadSrc: string, + cacheType: ReadCacheStrategyType = ReadCacheStrategyType.Default): Promise { + let option: ImageKnifeOption = { + loadSrc: loadSrc + } + let engineKeyImpl: IEngineKey = new DefaultEngineKey() + + return new Promise((resolve, reject) => { + if (cacheType == ReadCacheStrategyType.Memory) { + resolve(this.readMemoryCache(loadSrc, option, engineKeyImpl)) } else if (cacheType == ReadCacheStrategyType.File) { - this.ReadFileCache(loadSrc,engineKeyImpl,resolve) + this.readFileCache(loadSrc, engineKeyImpl, resolve) } else { - let data = this.ReadMemoryCache(loadSrc,engineKeyImpl) - data == undefined ? this.ReadFileCache(loadSrc,engineKeyImpl,resolve) : data + let data = this.readMemoryCache(loadSrc, option, engineKeyImpl) + data == undefined ? this.readFileCache(loadSrc, engineKeyImpl, resolve) : data } }) } - ReadMemoryCache(loadSrc:string,engineKey:IEngineKey): ImageKnifeData | undefined{ - let keys = engineKey.generateCacheKey(loadSrc) + + private readMemoryCache(loadSrc: string,option: ImageKnifeOption, engineKey: IEngineKey): ImageKnifeData | undefined { + let keys = engineKey.generateMemoryKey(loadSrc,option) return ImageKnife.getInstance() .loadFromMemoryCache(keys) } - ReadFileCache(loadSrc:string,engineKey:IEngineKey,onComplete:(data:ImageKnifeData | undefined)=>void){ - let keys = engineKey.generateCacheKey(loadSrc) + + 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) { let fileTypeUtil = new FileTypeUtil(); diff --git a/library/src/main/ets/ImageKnifeDispatcher.ets b/library/src/main/ets/ImageKnifeDispatcher.ets index aeaa2de..c6295d1 100644 --- a/library/src/main/ets/ImageKnifeDispatcher.ets +++ b/library/src/main/ets/ImageKnifeDispatcher.ets @@ -34,6 +34,7 @@ import util from '@ohos.util'; import { IEngineKey } from './key/IEngineKey'; import { DefaultEngineKey } from './key/DefaultEngineKey'; import { HeaderOptions } from './ImageKnifeOption'; +import { PixelMapTransformation } from './transform/PixelMapTransformation'; export class ImageKnifeDispatcher { // 最大并发 @@ -47,7 +48,7 @@ export class ImageKnifeDispatcher { showFromMemomry(request: ImageKnifeRequest, imageSrc: string | PixelMap | Resource, requestSource: ImageKnifeRequestSource): boolean { let memoryCache: ImageKnifeData | undefined = ImageKnife.getInstance() - .loadFromMemoryCache(this.engineKeyImpl.generateCacheKey(imageSrc, request.ImageKnifeOption.signature)) + .loadFromMemoryCache(this.engineKeyImpl.generateMemoryKey(imageSrc, request.ImageKnifeOption)) if (memoryCache !== undefined) { // 画主图 if (request.requestState === ImageKnifeRequestState.PROGRESS) { @@ -96,12 +97,13 @@ export class ImageKnifeDispatcher { * 获取和显示图片 */ getAndShowImage(currentRequest: ImageKnifeRequest, imageSrc: string | PixelMap | Resource, requestSource: ImageKnifeRequestSource): void { - let key: string = this.engineKeyImpl.generateCacheKey(imageSrc, currentRequest.ImageKnifeOption.signature) - let requestList: List | undefined = this.executingJobMap.get(key) + let keyMemory: string = this.engineKeyImpl.generateMemoryKey(imageSrc, currentRequest.ImageKnifeOption) + let keyFile: string = this.engineKeyImpl.generateFileKey(imageSrc, currentRequest.ImageKnifeOption.signature) + let requestList: List | undefined = this.executingJobMap.get(keyMemory) if (requestList == undefined) { requestList = new List() requestList.add({ request: currentRequest, source: requestSource }) - this.executingJobMap.set(key, requestList) + this.executingJobMap.set(keyMemory, requestList) } else { requestList.add({ request: currentRequest, source: requestSource }) return @@ -110,11 +112,12 @@ export class ImageKnifeDispatcher { let request: RequestJobRequest = { context: currentRequest.context, src: imageSrc, - key: key, + key: keyFile, headers:currentRequest.ImageKnifeOption.headerOption, allHeaders:currentRequest.headers, customGetImage: currentRequest.ImageKnifeOption.customGetImage, onlyRetrieveFromCache: currentRequest.ImageKnifeOption.onlyRetrieveFromCache, + transformation:currentRequest.ImageKnifeOption.transformation, requestSource } // 启动线程下载和解码主图 @@ -140,15 +143,15 @@ export class ImageKnifeDispatcher { } } }); - this.executingJobMap.remove(key) + this.executingJobMap.remove(keyMemory) } else { - LogUtil.log("error: no requestlist need to draw for key = " + key) + LogUtil.log("error: no requestlist need to draw for key = " + keyMemory) } } // 保存文件缓存 if (requestJobResult.bufferSize > 0 && currentRequest.ImageKnifeOption.writeCacheStrategy !== WriteCacheStrategyType.Memory) { - ImageKnife.getInstance().saveWithoutWriteFile(key, requestJobResult.bufferSize) + ImageKnife.getInstance().saveWithoutWriteFile(keyFile, requestJobResult.bufferSize) } let ImageKnifeData: ImageKnifeData = { @@ -160,7 +163,7 @@ export class ImageKnifeDispatcher { // 保存内存缓存 if(currentRequest.ImageKnifeOption.writeCacheStrategy !== WriteCacheStrategyType.File) { ImageKnife.getInstance() - .saveMemoryCache(this.engineKeyImpl.generateCacheKey(imageSrc, currentRequest.ImageKnifeOption.signature), + .saveMemoryCache(this.engineKeyImpl.generateMemoryKey(imageSrc, currentRequest.ImageKnifeOption), ImageKnifeData) } if (requestList !== undefined) { @@ -186,11 +189,11 @@ export class ImageKnifeDispatcher { }); - this.executingJobMap.remove(key) + this.executingJobMap.remove(keyMemory) this.dispatchNextJob() } else { - LogUtil.log("error: no requestlist need to draw for key = " + key) + LogUtil.log("error: no requestlist need to draw for key = " + keyMemory) } // }) @@ -359,6 +362,11 @@ async function requestJob(request: RequestJobRequest): Promise Promise, onlyRetrieveFromCache?: boolean requestSource:ImageKnifeRequestSource + transformation?: PixelMapTransformation } diff --git a/library/src/main/ets/ImageKnifeOption.ets b/library/src/main/ets/ImageKnifeOption.ets index 21c4185..ac04132 100644 --- a/library/src/main/ets/ImageKnifeOption.ets +++ b/library/src/main/ets/ImageKnifeOption.ets @@ -16,6 +16,8 @@ import taskpool from '@ohos.taskpool'; import common from '@ohos.app.ability.common' import { ObjectKey } from './model/ObjectKey'; import { WriteCacheStrategyType } from './model/ImageKnifeData'; +import { PixelMapTransformation } from './transform/PixelMapTransformation'; + export interface HeaderOptions { key: string; value: Object; @@ -50,6 +52,8 @@ export class ImageKnifeOption { progressListener?: (progress: number)=>void; + transformation?: PixelMapTransformation + constructor() { } diff --git a/library/src/main/ets/components/ImageKnifeComponent.ets b/library/src/main/ets/components/ImageKnifeComponent.ets index 5c17595..739f947 100644 --- a/library/src/main/ets/components/ImageKnifeComponent.ets +++ b/library/src/main/ets/components/ImageKnifeComponent.ets @@ -17,14 +17,10 @@ import { ImageKnifeRequest, ImageKnifeRequestState } from '../ImageKnifeRequest' import common from '@ohos.app.ability.common'; import { ImageKnife } from '../ImageKnife'; import { LogUtil } from '../utils/LogUtil'; -import componentUtils from '@ohos.arkui.componentUtils' -import util from '@ohos.util' -import inspector from '@ohos.arkui.inspector' -import { ImageKnifeData } from '../model/ImageKnifeData' @Component export struct ImageKnifeComponent { - @Watch('watchImageKnifeOption') @ObjectLink ImageKnifeOption: ImageKnifeOption; + @Watch('watchImageKnifeOption') @ObjectLink imageKnifeOption: ImageKnifeOption; @State pixelMap: PixelMap | string | undefined = undefined @State adaptiveWidth: Length = '100%' @State adaptiveHeight: Length = '100%' @@ -66,10 +62,10 @@ export struct ImageKnifeComponent { build() { Image(this.pixelMap) - .objectFit(this.ImageKnifeOption.objectFit === undefined ? ImageFit.Contain : this.ImageKnifeOption.objectFit) + .objectFit(this.imageKnifeOption.objectFit === undefined ? ImageFit.Contain : this.imageKnifeOption.objectFit) .width(this.adaptiveWidth) .height(this.adaptiveHeight) - .border(this.ImageKnifeOption.border) + .border(this.imageKnifeOption.border) .onSizeChange((oldValue:SizeOptions, newValue:SizeOptions) => { this.currentWidth = newValue.width as number this.currentHeight = newValue.height as number @@ -106,8 +102,8 @@ export struct ImageKnifeComponent { getRequest(width: number, height: number): ImageKnifeRequest { if (this.request == undefined) { this.request = new ImageKnifeRequest( - this.ImageKnifeOption, - this.ImageKnifeOption.context !== undefined ? this.ImageKnifeOption.context : this.getCurrentContext(), + this.imageKnifeOption, + this.imageKnifeOption.context !== undefined ? this.imageKnifeOption.context : this.getCurrentContext(), width, height, this.componentVersion, @@ -118,7 +114,7 @@ export struct ImageKnifeComponent { } this.pixelMap = pixelMap if (typeof this.pixelMap !== 'string') { - if (this.ImageKnifeOption.objectFit === ImageFit.Auto) { + if (this.imageKnifeOption.objectFit === ImageFit.Auto) { let info = await this.pixelMap.getImageInfo() this.adaptiveWidth = this.currentWidth diff --git a/library/src/main/ets/key/DefaultEngineKey.ets b/library/src/main/ets/key/DefaultEngineKey.ets index 6c66985..9ed379e 100644 --- a/library/src/main/ets/key/DefaultEngineKey.ets +++ b/library/src/main/ets/key/DefaultEngineKey.ets @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Huawei Device Co., Ltd. + * 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 @@ -14,34 +14,51 @@ */ import util from '@ohos.util'; import { SparkMD5 } from '../3rd_party/sparkmd5/spark-md5'; -import { ObjectKey } from '../model/ObjectKey'; +import { ImageKnifeOption } from '../ImageKnifeOption'; import { IEngineKey } from './IEngineKey'; +import { ObjectKey } from '../model/ObjectKey'; +import { PixelMapTransformation } from '../transform/PixelMapTransformation'; export class DefaultEngineKey implements IEngineKey { + private memoryKeyCache: util.LRUCache = new util.LRUCache(1024) + private fileKeyCache: util.LRUCache = new util.LRUCache(1024) - private keyCache: util.LRUCache = new util.LRUCache(1024) - - // 生成内存缓存 - generateCacheKey(loadSrc: string | PixelMap | Resource, signature?: ObjectKey | undefined): string { - let key = "loadSrc=" + this.generateKey(loadSrc) + ";"; - if (signature) { - key += "signature=" + signature.getKey() + ";" + // 生成内存缓存key + generateMemoryKey(loadSrc: string | PixelMap | Resource, imageKnifeOption: ImageKnifeOption): string { + let src = "loadSrc=" + (typeof loadSrc == "string" ? loadSrc : JSON.stringify(loadSrc)) + ";" + if (imageKnifeOption.signature) { + src += "signature=" + imageKnifeOption.signature.getKey() + ";" } - return key; + if (imageKnifeOption.transformation) { + src += "transformation=" + this.getTransformation(imageKnifeOption.transformation) + ";" + } + return this.generateKey(src, this.memoryKeyCache) + } + + // 生成文件缓存key + generateFileKey(loadSrc: string | PixelMap | Resource, signature?: ObjectKey | undefined): string { + let src = "loadSrc=" + (typeof loadSrc == "string" ? loadSrc : JSON.stringify(loadSrc)) + ";" + if (signature) { + src += "signature=" + signature.getKey() + ";" + } + return this.generateKey(src, this.fileKeyCache) } // key缓存策略,避免无意义的 JSON.stringify - private generateKey(key: string | PixelMap | Resource): string { - let keyCache = typeof key == "string" ? key : JSON.stringify(key) - let result = this.keyCache.get(keyCache) + private generateKey(keyCache: string, cache: util.LRUCache): string { + let result = cache.get(keyCache) if (result != undefined) { return result } else { result = SparkMD5.hashBinary(keyCache) - this.keyCache.put(keyCache, result) + cache.put(keyCache, result) return result } } + + private getTransformation(transformation: PixelMapTransformation): string { + return transformation.getName() + } } diff --git a/library/src/main/ets/key/IEngineKey.ets b/library/src/main/ets/key/IEngineKey.ets index d832d79..71e30d9 100644 --- a/library/src/main/ets/key/IEngineKey.ets +++ b/library/src/main/ets/key/IEngineKey.ets @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Huawei Device Co., Ltd. + * 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 @@ -12,11 +12,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { ImageKnifeOption } from '../ImageKnifeOption' import { ObjectKey } from '../model/ObjectKey' export interface IEngineKey { - // 生成缓存key - generateCacheKey(loadSrc: string | PixelMap | Resource, signature?: ObjectKey | undefined): string + // 生成内存缓存key + generateMemoryKey(loadSrc: string | PixelMap | Resource, imageKnifeOption: ImageKnifeOption): string + + // 生成文件缓存key + generateFileKey(loadSrc: string | PixelMap | Resource, signature?: ObjectKey | undefined): string } diff --git a/library/src/main/ets/transform/BaseTransformation.ets b/library/src/main/ets/transform/BaseTransformation.ets new file mode 100644 index 0000000..ced2f06 --- /dev/null +++ b/library/src/main/ets/transform/BaseTransformation.ets @@ -0,0 +1,24 @@ +/* + * 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. + */ + +/** + * 图片变换接口 + */ +export interface BaseTransformation { + + transform(context: Context, toTransform: T, width: number, height: number): Promise; + + getName(): string +} \ No newline at end of file diff --git a/library/src/main/ets/transform/BlurTransformation.ets b/library/src/main/ets/transform/BlurTransformation.ets new file mode 100644 index 0000000..07de719 --- /dev/null +++ b/library/src/main/ets/transform/BlurTransformation.ets @@ -0,0 +1,38 @@ +/* + * 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 { PixelMapTransformation } from './PixelMapTransformation'; +import effectKit from '@ohos.effectKit'; + +/** + * 图片变换:模糊效果 + */ +@Sendable +export class BlurTransformation extends PixelMapTransformation { + private radius: number // 模糊半径,单位是像素。模糊效果与所设置的值成正比,值越大效果越明显。 + + constructor(radius: number) { + super() + this.radius = radius + } + + async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise { + let headFilter = effectKit.createEffect(toTransform); + if (headFilter != null) { + return await headFilter.blur(this.radius).getEffectPixelMap() + } + return toTransform + } +} + diff --git a/library/src/main/ets/transform/BrightnessTransformation.ets b/library/src/main/ets/transform/BrightnessTransformation.ets new file mode 100644 index 0000000..1b68426 --- /dev/null +++ b/library/src/main/ets/transform/BrightnessTransformation.ets @@ -0,0 +1,38 @@ +/* + * 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 { PixelMapTransformation } from './PixelMapTransformation'; +import effectKit from '@ohos.effectKit'; + +/** + * 图片变换:高亮效果 + */ +@Sendable +export class BrightnessTransformation extends PixelMapTransformation { + private bright: number // 高亮程度,取值范围在0-1之间,取值为0时图像保持不变。 + + constructor(bright: number) { + super() + this.bright = bright + } + + async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise { + let headFilter = effectKit.createEffect(toTransform); + if (headFilter != null) { + return await headFilter.brightness(this.bright).getEffectPixelMap() + } + return toTransform + } +} + diff --git a/library/src/main/ets/transform/MultiTransTransformation.ets b/library/src/main/ets/transform/MultiTransTransformation.ets new file mode 100644 index 0000000..459afbc --- /dev/null +++ b/library/src/main/ets/transform/MultiTransTransformation.ets @@ -0,0 +1,46 @@ +/* + * 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 { PixelMapTransformation } from './PixelMapTransformation'; +import { collections } from '@kit.ArkTS'; + +/** + * 多个图片变换 + */ +@Sendable +export class MultiTransTransformation extends PixelMapTransformation { + private transformations: collections.Array + + constructor(transformations: collections.Array) { + super() + this.transformations = transformations + } + + async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise { + let res = toTransform + for (let i = 0; i < this.transformations.length; i++) { + res = await this.transformations[i].transform(context, res, width, height) + } + return res + } + + getName(): string { + let res: string = "" + this.transformations.forEach((transformation) => { + res += transformation.getName() + "&" + }) + return res + } +} \ No newline at end of file diff --git a/library/src/main/ets/transform/PixelMapTransformation.ets b/library/src/main/ets/transform/PixelMapTransformation.ets new file mode 100644 index 0000000..ca002a6 --- /dev/null +++ b/library/src/main/ets/transform/PixelMapTransformation.ets @@ -0,0 +1,29 @@ +/* + * 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 { BaseTransformation } from './BaseTransformation'; + +/** + * 基于PixelMap的图片变换 + */ +@Sendable +export abstract class PixelMapTransformation implements BaseTransformation{ + transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise { + throw new Error('Method not implemented.'); + } + + getName(): string { + return this.constructor.name + } +} \ No newline at end of file