diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a69d5e..2c5d7cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,35 +1,107 @@ -## 2.3.0-rc.2 -- 加载优先级Priority类型改为taskpool.Priority类型 +## 3.0.1-rc.2 +- 修复自定义下载失败无失败回调 +- 增加全局配置自定义下载接口 +- 修复主图相同,错误图不同导致只显示一个错误图 +- heic格式图片文件魔数从第五位开始匹配 + +## 3.0.1-rc.1 +- 新增ImageKnifeAnimatorComponent控制动图组件 - 修复部分heif图无法解码 -## 2.3.0-rc.1 -- 修复file://格式图片无法显示 -- 修改uuid的生成方式,使用资源loadSrc,宽高以及转换效果的拼接字符串作为入参, 修复同资源图形转换错乱的问题 +## 3.0.1-rc.0 +- 文件缓存设置最大缓存数量改为无限制 -## 2.3.0-rc.0 -- 增加gif图duration的默认值,以及默认播放次数 +## 3.0.0 +- 修复图形变换的闪退问题 +- 自定义下载customGetImage改为仅主图支持 +- 修改网络请求requestInStream配置优先返回arraybuffer +- 新增ColorFilter属性 -## 2.2.0 -- 修复错误图绘制完后变成占位图 -- 提供图片加载成功/失败的事件 -- 修复懒加载在多次点击出现卡死的问题 +## 3.0.0-rc.9 +- 修复Resource类型$r(变量无法)加载 +- 成功回调增加图片格式 +- Image组件增加onComplete回调 +- 修复404链接无返回错误信息 +- onLoadListener增加请求取消回调 + +## 3.0.0-rc.8 +- svg解码单位改为px +- 修复预加载接口preLoadCache传ImageKnifeOption失效 +- 文件缓存初始化接口新增目录参数 +- 占位图从内存获取提前到判断队列前面 +- 图片改为不可拖拽 +- 修复getCacheImage默认内存获取后不返回数据 +- 成功回调返回GIF图宽高 + +## 3.0.0-rc.7 +- 修复成功回调获取不到宽高 +- 新增svg图片解码 +- 新增媒体图片file://格式 +- 修复头像超过设备高度图片闪动问题-消息列表底部头像闪动问题 + +## 3.0.0-rc.6 - 支持多种组合变换 +- 支持全局配置是否在子线程请求加载图片,默认在子线程 +- 文件缓存初始化增加默认值 +- 预加载接口新增返回加载错误信息 +- 加载队列改为使用堆Stack +- fileType图片格式新增heic格式 + +## 3.0.0-rc.5 +- 图片加载事件增加请求开始的回调,以及修复有缓存时,没有回调的bug +- 修复对已销毁组件不再下发请求的逻辑 +- 加载图片流程添加日志 +- 子线程写入文件缓存获取buffer优化 +- 成功回调增加返回图片分辨率宽高 +- 内存缓存时将pixelMap进行release释放 - 提供清理缓存能力 -- 修复preLoad接口失效 -- 修复多线程图片加载出现空白问题 + +## 3.0.0-rc.4 +- 支持hsp多包图片资源 +- 新增putCache写入缓存接口 +- 修复入参为pixelMap图片不显示问题 +- 网络请求减少拼接操作,修复网络加载速度慢 +- 提供图片加载成功/失败的事件 + +## 3.0.0-rc.3 +- 将请求默认并行从64调整到8,减少对taskpool execute内存消耗 +- 补充option参数:placeholderObjectFit,errorholderObjectFit分别支持占位图填充效果和错误图填充效果 + +## 3.0.0-rc.2 +- 新增支持使用一个或多个图片变换,如模糊,高亮等 + +## 3.0.0-rc.1 +- 新增从内存或文件缓存获取图片数据接口getCacheImage +- 新增图片预加载preLoadCache并返回文件缓存路径 +- ImageKnifeOption新增writeCacheStrategy存入策略(只存入内存或文件缓存) +- ImageKnifeOption新增onlyRetrieveFromCache仅用缓存加载 +- 新增单个和全局请求头 +- 补齐自定key特性 - 获取组件宽高改用onSizeChange (需要API12) -- svg解码宽高单位给位px -- 修复复用场景下从内存获取图片后又清空了画布导致图片不显示 -- 修复复用场景主图比占位图绘制快后下次不显示占位图问题 + +## 3.0.0-rc.0 +- 使用Image组件替换Canvas组件渲染,并重构大部分的实现逻辑,提升渲染性能 + +较2.x版本增强点: +- 使用Image组件代替Canvas组件渲染 +- 重构Dispatch分发逻辑,支持控制并发请求数,支持请求排队队列的优先级 +- 支持通过initMemoryCache自定义策略内存缓存策略和大小。 +- 支持option自定义实现图片获取/网络下载 +- 继承Image的能力,支持option传入border,设置边框,圆角 +- 继承Image的能力,支持option传入objectFit设置图片缩放 +- 修复发送消息时最近的两条消息头像闪动的问题 + +缺失特性 +- 不支持drawLifeCycle接口,通过canvas自会图片 +- mainScaleType,border等参数,新版本与系统Image保持一致 +- gif/webp动图播放与控制 +- signature自定义key的实现 +- 支持进行图片变换: 支持图像像素源图片变换效果。 +- 抗锯齿相关参数 ## 2.2.0-rc.2 -- ImageKnife支持下采样 - ImageKnife支持heic图片修改demo,按钮控制组件是否展示 -- 修复通过磁盘链接加载图片无法显示 - ImageKnife控制可视化区域图片 -- 修复占位图、错误图、重试图从内存获取之后进入子线程导致内存泄露 -- ImageKnifeComponent组件key属性改为id属性 -- 修改header图的存储标志位 ## 2.2.0-rc.1 - 修改ImageKnife跳过网络,点击默认,图片没有传入宽高,无显示bug @@ -283,12 +355,12 @@ 新增 - - 1.onClick事件属性 + - 1.onClick事件属性 - 删除 + 删除 - - 1.size(设置大小) - - 2.sizeAnimated 显式动画 + - 1.size(设置大小) + - 2.sizeAnimated 显式动画 - 3.backgroundColor背景色 - 4.margin 组件外间距 等属性,删除的属性将由通用属性提供支持,可支持在ImageKnifeComponent自定义组件上链式调用 ## 1.0.4 diff --git a/README.md b/README.md index f17bd4b..3f50768 100644 --- a/README.md +++ b/README.md @@ -4,630 +4,355 @@ ## 简介 -本项目基于开源库 [Glide](https://github.com/bumptech/glide) 进行OpenHarmony的自研版本: +本项目参考开源库 [Glide](https://github.com/bumptech/glide) 进行OpenHarmony的自研版本: -- 支持内存缓存,使用LRUCache算法,对图片数据进行内存缓存。 -- 支持磁盘缓存,对于下载图片会保存一份至磁盘当中。 -- 支持进行图片变换: 支持图像像素源图片变换效果。 -- 支持用户配置参数使用:( - 例如:配置是否开启一级内存缓存,配置磁盘缓存策略,配置仅使用缓存加载数据,配置图片变换效果,配置占位图,配置加载失败占位图等)。 -- 推荐使用ImageKnifeComponent组件配合ImageKnifeOption参数来实现功能。 -- 支持用户自定义配置实现能力参考ImageKnifeComponent组件中对于入参ImageKnifeOption的处理。 +- 支持自定义内存缓存策略,支持设置内存缓存的大小(默认LRU策略)。 +- 支持磁盘二级缓存,对于下载图片会保存一份至磁盘当中。 +- 支持自定义实现图片获取/网络下载 +- 支持监听网络下载回调进度 +- 继承Image的能力,支持option传入border,设置边框,圆角 +- 继承Image的能力,支持option传入objectFit设置图片缩放,包括objectFit为auto时根据图片自适应高度 +- 支持通过设置transform缩放图片 +- 并发请求数量,支持请求排队队列的优先级 +- 支持生命周期已销毁的图片,不再发起请求 +- 自定义缓存key +- 自定义http网络请求头 +- 支持writeCacheStrategy控制缓存的存入策略(只存入内存或文件缓存) +- 支持preLoadCache预加载图片 +- 支持onlyRetrieveFromCache仅用缓存加载 +- 支持使用一个或多个图片变换,如模糊,高亮等 - +待实现特性 + +- gif/webp动图显示与控制 +- 内存降采样优化,节约内存的占用 +- 支持自定义图片解码 + +注意:3.x版本相对2.x版本做了重大的重构,主要体现在: + +- 使用Image组件代替Canvas组件渲染 +- 重构Dispatch分发逻辑,支持控制并发请求数,支持请求排队队列的优先级 +- 支持通过initMemoryCache自定义策略内存缓存策略和大小 +- 支持option自定义实现图片获取/网络下载 + +因此API及能力上,目前有部分差异,主要体现在: + +- 不支持drawLifeCycle接口,通过canvas自会图片 +- mainScaleType,border等参数,新版本与系统Image保持一致 +- gif/webp动图播放与控制 +- 抗锯齿相关参数 ## 下载安装 ``` ohpm install @ohos/imageknife + +// 如果需要用文件缓存,需要提前初始化文件缓存 +await ImageKnife.getInstance().initFileCache(context, 256, 256 * 1024 * 1024) ``` -## X86模拟器配置 +## 使用说明 -[使用模拟器运行应用/服务](https://developer.huawei.com/consumer/cn/deveco-developer-suite/enabling/kit?currentPage=1&pageSize=100) +#### 1.显示本地资源图片 - -## 使用说明 - -### 1.依赖配置 -在entry\src\main\ets\entryability\EntryAbility.ts中做如下配置初始化全局ImageKnife实例: - -```typescript -import UIAbility from '@ohos.app.ability.UIAbility'; -import window from '@ohos.window'; -import { ImageKnife } from '@ohos/imageknife' - -export default class EntryAbility extends UIAbility { - onWindowStageCreate(windowStage: window.WindowStage) { - windowStage.loadContent('pages/Index', (err, data) => { - }); - // 初始化全局ImageKnife - ImageKnife.with(this.context); - // 后续访问ImageKnife请通过:ImageKnifeGlobal.getInstance().getImageKnife()方式 +``` +ImageKnifeComponent({ + ImageKnifeOption: { + loadSrc: $r("app.media.app_icon"), + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + objectFit: ImageFit.Auto } -} +}).width(100).height(100) ``` -### 2.加载普通图片 +#### 2.显示本地context files下文件 -接下来我们来写个简单实例看看: - -```extendtypescript -import { ImageKnifeComponent, ImageKnifeOption } from '@ohos/imageknife' - -@Entry -@Component -struct Index { - @State message: string = 'Hello World' - @State option: ImageKnifeOption = { - loadSrc: $r('app.media.icon') +``` +ImageKnifeComponent({ + ImageKnifeOption: { + loadSrc: this.localFile, + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + objectFit: ImageFit.Auto } +}).width(100).height(100) +``` - build() { - Row() { - Column() { - Text(this.message) - .fontSize(50) - .fontWeight(FontWeight.Bold) - ImageKnifeComponent({ imageKnifeOption: this.option }) - .width(300) - .height(300) - }.width('100%') - }.height('100%') +#### 3.显示网络图片 + +``` +ImageKnifeComponent({ + ImageKnifeOption: { + loadSrc:"https://www.openharmony.cn/_nuxt/img/logo.dcf95b3.png", + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + objectFit: ImageFit.Auto } -} +}).width(100).height(100) ``` -非常简单,仅需定义一个ImageKnifeOption数据对象,然后在你需要的UI位置,加入ImageKnifeComponent自定义组件就可以加载出一张图像了。 +#### 4.自定义下载图片 -### 3.加载SVG图片 - -加载svg其实和普通流程没有区别,只要将 `loadSrc: $r('app.media.jpgSample'),` `改成一张 loadSrc: $r('app.media.svgSample'),` -svg类型图片即可。 - -注:SVG文件需添加xml声明,应以" { - let result: DataFetchResult = new DataFetchResult(); - result.data = arraybuffer; //此处替换成自己网络获取的ArrayBuffer的逻辑 - return result; +async function custom(context: Context, src: string | PixelMap | Resource): Promise { + console.info("ImageKnife:: custom download:" + src) + // 举例写死从本地文件读取,也可以自己请求网络图片 + return context.resourceManager.getMediaContentSync($r("app.media.bb").id).buffer as ArrayBuffer } ``` -7.2 自定义网络栈加载全部图片 -``` -1.先执行ImageKnifeGlobal.getInstance().getImageKnife()?.replaceDataFetch(new CustomDataFetchClient()); 替换网络栈 -2.在CustomDataFetchClient这个类中也通过下面的逻辑替换成自己网络获取的ArrayBuffer的逻辑 - let result: DataFetchResult = new DataFetchResult(); - result.data = arraybuffer; //此处替换成自己网络获取的ArrayBuffer的逻辑 - return result; -``` -7.3 取消自定义网络栈加载全部图片 -``` -如果用户执行了自定义网络加载全部图片,后面又不想自定义网络栈加载全部图片了,可以通过下面的方式恢复 -ImageKnifeGlobal.getInstance().getImageKnife()?.replaceDataFetch(new CustomDataFetchClient()); -``` -### 8.监听图片加载成功与失败 +#### 5.监听网络下载进度 + ``` -ImageKnifeNextComponent({ - imageKnifeNextOption: { - loadSrc: 'http://e.hiphotos.baidu.com/image/pic/item/4e4a20a4462309f7e41f5cfe760e0cf3d6cad6ee.jpg', - onLoadListener: { - console.log('Load Successful: ' + data); - return data; - }, - onLoadFailed: (err) => { - console.error('Load Failed Reason: ' + err); - } +ImageKnifeComponent({ + ImageKnifeOption: { + loadSrc:"https://www.openharmony.cn/_nuxt/img/logo.dcf95b3.png", + progressListener:(progress:number)=>{console.info("ImageKinfe:: call back progress = " + progress)} } - } }).width(100).height(100) ``` -## 进阶使用 +#### 6.支持option传入border,设置边框,圆角 -如果简单的加载一张图像无法满足需求,我们可以看看ImageKnifeOption这个类提供了哪些扩展能力。 +``` +ImageKnifeComponent({ ImageKnifeOption: +{ + loadSrc: $r("app.media.rabbit"), + border: {radius:50} + } +}).width(100).height(100) +``` + +#### 7.支持option图片变换 + +``` +ImageKnifeComponent({ ImageKnifeOption: +{ + loadSrc: $r("app.media.rabbit"), + border: {radius:50}, + transformation: new BlurTransformation(3) + } +}).width(100).height(100) +``` +多种组合变换用法 + +``` +let transformations: collections.Array = new collections.Array(); +transformations.push(new BlurTransformation(5)); +transformations.push(new BrightnessTransformation(0.2)); +ImageKnifeComponent({ + { + loadSrc: $r('app.media.pngSample'), + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + objectFit: ImageFit.Contain, + border: { radius: { topLeft: 50, bottomRight: 50 } }, // 圆角设置 + transformation: transformations.length > 0 ? new MultiTransTransformation(transformations) : undefined // 图形变换组 +} +}).width(300) + .height(300) + .rotate({ angle: 90 }) // 旋转90度 + .contrast(12) // 对比度滤波器 +``` +其他变换相关属性,可叠加实现组合变换效果 + +圆形裁剪变换示例 + +``` +ImageKnifeComponent({ ImageKnifeOption: + { + loadSrc: $r('app.media.pngSample'), + objectFit: ImageFit.Cover, + border: { radius: 150 } +} +}).width(300) + .height(300) +``` + +圆形裁剪带边框变换示例 + +``` +ImageKnifeComponent({ ImageKnifeOption: + { + loadSrc: $r('app.media.pngSample'), + objectFit: ImageFit.Cover, + border: { radius: 150, color: Color.Red, width: 5 } +} +}).width(300) + .height(300) +``` + +对比度滤波变换示例 + +``` +ImageKnifeComponent({ + imageKnifeOption: { + loadSrc: $r('app.media.pngSample') + } +}).width(300) + .height(300) + .contrast(12) +``` + +旋转变换示例 + +``` +ImageKnifeComponent({ + imageKnifeOption: { + loadSrc: $r('app.media.pngSample') + } +}).width(300) + .height(300) + .rotate({angle:90}) + .backgroundColor(Color.Pink) +``` + +#### 8.监听图片加载成功与失败 + +``` +ImageKnifeComponent({ ImageKnifeOption: +{ + loadSrc: $r("app.media.rabbit"), + onLoadListener:{ + onLoadStart:()=>{ + this.starTime = new Date().getTime() + console.info("Load start: "); + }, + onLoadFailed: (err) => { + console.error("Load Failed Reason: " + err + " cost " + (new Date().getTime() - this.starTime) + " milliseconds"); + }, + onLoadSuccess: (data, imageData) => { + console.info("Load Successful: cost " + (new Date().getTime() - this.starTime) + " milliseconds"); + return data; + }, + onLoadCancel(err){ + console.info(err) + } + } + } +}).width(100).height(100) +``` +#### 9.ImageKnifeComponent - syncLoad +设置是否同步加载图片,默认是异步加载。建议加载尺寸较小的本地图片时将syncLoad设为true,因为耗时较短,在主线程上执行即可 +``` +ImageKnifeComponent({ + imageKnifeOption:{ + loadSrc:$r("app.media.pngSample"), + placeholderSrc:$r("app.media.loading") + },syncLoad:true + }) +``` +#### 10.ImageKnifeAnimatorComponent 示例 +``` +ImageKnifeAnimatorComponent({ + imageKnifeOption:{ + loadSrc:"https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658", + placeholderSrc:$r('app.media.loading'), + errorholderSrc:$r('app.media.failed') + },animatorOption:this.animatorOption + }).width(300).height(300).backgroundColor(Color.Orange).margin({top:30}) +``` +## 接口说明 +### ImageKnife组件 +| 组件名称 | 入参内容 | 功能简介 | +|-----------------------------|---------------------------------|--------| +| ImageKnifeComponent | ImageKnifeOption | 图片显示组件 | +| ImageKnifeAnimatorComponent | ImageKnifeOption、AnimatorOption | 动图控制组件 | + +### AnimatorOption参数列表 +| 参数名称 | 入参内容 | 功能简介 | +|-----------------------|-------------------------------------------------------|----------| +| state | AnimationStatus | 播放状态(可选) | +| iterations | number | 播放次数(可选) | +| reverse | boolean | 播放顺序(可选) | ### ImageKnifeOption参数列表 -| 参数名称 | 入参内容 | 功能简介 | -|------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------| -| loadSrc | string\ | PixelMap\ |Resource | 图片数据源 | -| mainScaleType | ScaleType | 设置主图展示样式(可选) | -| strategy | DiskStrategy | 设置磁盘缓存策略(可选) | -| dontAnimateFlag | boolean | gif加载展示一帧(可选) | -| placeholderSrc | string\ (占位图不支持gif图且不支持网络下载功能) | PixelMap\ |Resource | 占位图数据源 | -| placeholderScaleType | ScaleType | 设置占位图展示样式(可选) | -| errorholderSrc | PixelMap\ | Resource | 错误占位图数据源 | -| errorholderSrcScaleType | ScaleType | 设置失败占位图展示样式(可选) | -| retryholderSrc | PixelMap\ | Resource | 重试占位图数据源 | -| retryholderScaleType | ScaleType | 设置重试占位图展示样式(可选) | -| fallbackSrc | PixelMap\ | Resource | 后备回调符数据源 | -| thumbSizeMultiplier | number 范围(0,1] | 设置缩略图占比(可选) | -| thumbSizeDelay | number | 设置缩略图展示时间(可选) | -| thumbSizeMultiplierScaleType | ScaleType | 设置缩略图展示样式(可选) | -| displayProgress | boolean | 设置是否展示下载进度条(可选) | -| canRetryClick | boolean | 设置重试图层是否点击重试(可选) | -| onlyRetrieveFromCache | boolean | 仅使用缓存加载数据(可选) | -| isCacheable | boolean | 是否开启一级内存缓存(可选) | -| gif | {
// 返回一周期动画gif消耗的时间
loopFinish?: (loopTime?) => void
// gif播放速率相关
speedFactory?: number
// 直接展示gif第几帧数据
seekTo?: number
playTimes?: number
} | GIF播放控制能力(可选) | -| transformation | BaseTransform | 单个变换(可选) | -| transformations | Array> | 多个变换(可选) | -| allCacheInfoCallback | IAllCacheInfoCallback | 输出缓存相关内容和信息(可选) | -| signature | ObjectKey | 自定key(可选) | -| **drawLifeCycle** | **IDrawLifeCycle** | **用户自定义实现绘制方案(可选)** | -| imageSmoothingEnabled | boolean | 抗锯齿是否开启属性配置,设置为false时,imageSmoothingQuality失效 | -| imageSmoothingQuality | AntiAliasing | 抗锯齿属性配置 | -| autoPlay | boolean | GIF播放暂停控制(可选) | -| customGetImage | (Context, string) | 设置是否使用应用自定义的方式加载图片(可选) | -| onLoadListener | onLoadSuccess: (data: string | PixelMap | Resource | undefined) => void;onLoadFailed: (err: string) => void; | 监听图片加载成功/失败 | +| 参数名称 | 入参内容 | 功能简介 | +|-----------------------|-------------------------------------------------------|-----------------| +| 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 | 图片变换(可选) | +| drawingColorFilter | ColorFilter | drawing.ColorFilter | 图片变换(可选) | +| onComplete | (event:EventImage | undefined) => voi | 颜色滤镜效果(可选) | +| onLoadListener | onLoadStart: () => void、onLoadSuccess: (data: string | PixelMap | undefined) => void、onLoadFailed: (err: string) => void| 监听图片加载成功与失败 | -其他参数只需要在ImageKnifeOption对象上按需添加即可。 +### ImageKnife接口 -这里我们着重讲一下**自定义实现绘制方案**。为了增强绘制扩展能力,目前ImageKnifeComponent使用了Canvas的渲染能力作为基础。在此之上为了抽象组件绘制表达。我将图像的状态使用了 -**IDrawLifeCycle绘制生命周期进行表达**, +| 参数名称 | 入参内容 | 功能简介 | +|------------------|-------------------------------------------------------------------------------------------------------|---------------| +| 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请求头 | +| setCustomGetImage | customGetImage?: (context: Context, src: string | PixelMap | Resource) => Promise | 全局设置自定义下载 | +| setEngineKeyImpl | IEngineKey | 全局配置缓存key生成策略 | +| putCacheImage | url: string, pixelMap: PixelMap, cacheType: CacheStrategy = CacheStrategy.Default, signature?: string | 写入内存磁盘缓存 | +| removeMemoryCache| url: string | ImageKnifeOption | 清理指定内存缓存 | +| removeFileCache | url: string | ImageKnifeOption | 清理指定磁盘缓存 | +### 图形变换类型(需要为GPUImage添加依赖项) -大致流程 展示占位图->展示网络加载进度->展示缩略图->展示主图->展示重试图层->展示失败占位图 +| 类型 | 相关描述 | +| ---------------------------------- | ----------------------------- | +| BlurTransformation | 模糊处理 | +| BrightnessTransformation | 亮度滤波器 | +| CropSquareTransformation | 正方形剪裁 | +| CropTransformation | 自定义矩形剪裁 | +| GrayScaleTransformation | 灰度级滤波器 | +| InvertTransformation | 反转滤波器 | +| KuwaharaTransformation | 桑原滤波器(使用GPUIImage) | +| MaskTransformation | 遮罩 | +| PixelationTransformation | 像素化滤波器(使用GPUIImage) | +| SepiaTransformation | 乌墨色滤波器(使用GPUIImage) | +| SketchTransformation | 素描滤波器(使用GPUIImage) | +| SwirlTransformation | 扭曲滤波器(使用GPUIImage) | +| ToonTransformation | 动画滤波器(使用GPUIImage) | +| VignetterTransformation | 装饰滤波器(使用GPUIImage) | - - -ImageKnifeComponent内部,责任链实现。 用户参数设置->全局参数设置->自定义组件内部设置 - -采用责任链的好处是,用户可以通过自定义绘制,重新绘制图层。如果不想绘制也可以通过预制回调获取绘制流程信息。 - - - -### 场景1:默认的展示不满足需求,需要加个圆角效果。 - -代码如下: - -```typescript -import { ImageKnifeComponent } from '@ohos/imageknife' -import { ImageKnifeOption } from '@ohos/imageknife' -import { ImageKnifeDrawFactory } from '@ohos/imageknife' - -@Entry -@Component -struct Index { - @State imageKnifeOption1: ImageKnifeOption = { - // 加载一张本地的jpg资源(必选) - loadSrc: $r('app.media.jpgSample'), - // 占位图使用本地资源icon_loading(可选) - placeholderSrc: $r('app.media.icon_loading'), - // 失败占位图使用本地资源icon_failed(可选) - errorholderSrc: $r('app.media.icon_failed'), - // 绘制圆角30,边框5,边框"#ff00ff".用户自定义绘制(可选) - drawLifeCycle:ImageKnifeDrawFactory.createRoundLifeCycle(5,"#ff00ff",30) - }; - - build(){ - Scroll() { - Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { - ImageKnifeComponent({ imageKnifeOption: this.imageKnifeOption1 }) - .width(300) - .height(300) - } - } - .width('100%') - .height('100%') +## 下载安装GPUImage依赖 +方法一:在Terminal窗口中,执行如下命令安装三方包,DevEco Studio会自动在工程的oh-package.json5中自动添加三方包依赖。 +``` + ohpm install @ohos/gpu_transform +``` +方法二: 在工程的oh-package.json5中设置三方包依赖,配置示例如下: +``` + "dependencies": { + "@ohos/gpu_transform": "^1.0.2" } -} ``` - -`ImageKnifeDrawFactory.createRoundLifeCycle(5,"#ff00ff",30)` -我们深入查看源码可以发现,实际上是对IDrawLifeCycle接口的部分实现,这里我介绍一下IDrawLifeCycle。 - -* -*IDrawLifeCycle的返回值代表事件是否被消费,如果被消费接下来组件内部就不会处理,如果没被消费就会传递到下一个使用者。目前消费流程(用户自定义-> -全局配置定义->组件内部默认定义)** - -所以我们在当数据是一张PixelMap的时候(目前jpg png bmp webp -svg返回的都是PixelMap,gif返回GIFFrame数组),我们返回了true。消费了事件,代表这个绘制流程用户自定义完成。 - - - -由于IDrawLifeCycle实现较为冗长,我们封装了ImageKnifeDrawFactory工厂,提供了网络下载百分比效果、圆角、椭圆添加边框等能力。下面我们就再看看使用工厂封装之后的场景代码。 - -### 场景2: 网络下载百分比效果展示 - -当进行加载网络图片时,可能需要展示网络下载百分比动画。但是默认的动画又不能满足需求,这个时候我们就需要自定义网络下载百分比效果。代码如下: - -```typescript -import UIAbility from '@ohos.app.ability.UIAbility'; -import window from '@ohos.window'; -import { ImageKnifeGlobal,ImageKnife,ImageKnifeDrawFactory,LogUtil } from '@ohos/imageknife' -import abilityAccessCtrl,{Permissions} from '@ohos.abilityAccessCtrl'; -export default class EntryAbility extends UIAbility { - onWindowStageCreate(windowStage: window.WindowStage) { - //.. 删除不必要代码 - windowStage.loadContent('pages/index', (err, data) => { - }); - // 初始化ImageKnifeGlobal和ImageKnife - ImageKnife.with(this.context); - // 全局配置网络加载进度条 使用ImageKnifeGlobal.getInstance().getImageKnife()访问ImageKnife -ImageKnifeGlobal.getInstance().getImageKnife().setDefaultLifeCycle(ImageKnifeDrawFactory.createProgressLifeCycle("#10a5ff", 0.5)) - } -} -``` - -这里大家可能会问,为什么会将这个IDrawLifeCycle放在AbilityStage里面实现? - -这是因为网络下载百分比进度很多时候都是全局通用,如果有需要全局配置的自定义展示方案。推荐在AbilityStage里面,往ImageKnife的setDefaultLifeCycle函数中注入,即可将ImageKnifeComponent中的默认绘制方案替换。 - -在这里我们实现的效果如下图所示。 - - - -## 高级用法 - -以上简单使用和进阶使用都是经过一层自定义组件封装之后形成的,RequestOption封装成了ImageKnifeOption,绘制部分封装成了自定义组件ImageKnifeComponent。 - -如果用户其实并不关心绘制部分,或者说想用自己的通用方案对自定义组件ImageKnifeComponent重构都是可以的。 - -下面我们会着重指导用户如何复用图片加载逻辑,重构自定义组件ImageKnifeComponent。 - -首先我们先看看RequestOption构建的内容,如下所示: - -### 数据加载 - -#### RequestOption构建: - -请查阅下文接口内容:[RequestOption接口方法](#requestoption用户配置参数) - -了解了RequestOption的参数内容后,我们可以参考ImageKnifeComponent组件代码进行分析。 - -**从`imageKnifeExecute()`函数入口,首先我们需要构建一个RequestOption对象,`let request = new RequestOption()`, -接下来就是按需配置request对象的内容,最后使用 `ImageKnifeGlobal.getInstance().getImageKnife()?.call(request)`发送request执行任务即可。** - -是不是很简单,而其实最重要的内容是就是: **按需配置request对象的内容** 为了更好理解,我举例说明一下: - -#### 场景一: 简单加载一张图片 - -``` -let request = new RequestOption(); -// (必传) -request.load("图片url") - // (可选 整个request监听回调) - .addListener({callback:(err:BusinessError|string, data:ImageKnifeData) => { - // data 是ImageKnifeData对象 - if(data.isPixelMap()){ - // 这样就获取到了目标PixelMap - let pixelmap = data.drawPixleMap.imagePixelMap; - } - return false; - }) - - let compSize:Size = { - width: this.currentWidth, - height:this.currentHeight - } - // (必传)这里setImageViewSize函数必传组件大小,因为涉及到图片变换效果都需要适配图像源和组件大小 - request.setImageViewSize(compSize) - // 最后使用ImageKnife的call函数调用request即可 - let imageKnife:ImageKnife|undefined = ImageKnifeGlobal.getInstance().getImageKnife(); - if(imageKnife != undefined){ - imageKnife.call(request) - } - -``` - -**其他场景,可以按需加载** - -比如我需要配置 **占位图** 只需要 在request对象创建好之后,调用 **placeholder** 函数即可 - -``` -request.placeholder(this.imageKnifeOption.placeholderSrc, (data) => { - console.log('request.placeholder callback') - this.displayPlaceholder(data) -}) -``` - -再比如 我对缓存配置有要求,我要禁用内存缓存,调用 **skipMemoryCache** 函数即可 - -``` -request.skipMemoryCache(true) -``` - -这里只是简单介绍部分使用,更多的内容请参考 **按需加载** 原则,并且可以参考ImageKnifeComponent源码或者根据文档自行探索实现。 - -## 接口说明 - -### RequestOption用户配置参数 - -| 方法名 | 入参 | 接口描述 | -| ------------------------------------------------------------ | ------------------------------------------------------------ | -------------------------------------------------------- | -| load(src: string \| PixelMap \|Resource) | src:string\|PixelMap\|Resource | 用户加载图片源 | -| setImageViewSize(imageSize: { width: number, height: number }) | imageSize:{width: number, height: number } | 传入显示图片组件的大小,变换的时候需要作为参考 | -| diskCacheStrategy(strategy: DiskStrategy) | strategy:DiskStrategy | 配置磁盘缓存策略 NONE SOURCE RESULT ALL AUTOMATIC | -| placeholder(src: string \| PixelMap\|Resource, func?: AsyncSuccess) | src: string\|PixelMap\|Resource, func?: AsyncSuccess | 占位图,占位图回调数据ImageKnifeData | -| errorholder(src: PixelMap\|Resource, func?: AsyncSuccess) | src: PixelMap\|Resource, func?: AsyncSuccess | 错误占位图,错误占位图回调数据ImageKnifeData | -| retryholder(src: PixelMap\|Resource, func?: AsyncSuccess) | src: PixelMap\|Resource, func?: AsyncSuccess | 重试占位图,重试占位图回调数据ImageKnifeData | -| fallback(src: PixelMap\|Resource, func?: AsyncSuccess) | src: PixelMap\|Resource, func?: AsyncSuccess | 重试占位图,重试占位图回调数据ImageKnifeData | -| addListener(func: AsyncCallback) | func: AsyncCallback | 配置整个监听回调,数据正常加载返回,加载失败返回错误信息 | -| thumbnail(sizeMultiplier:number, func?: AsyncSuccess) | sizeMultiplier:number, func?: AsyncSuccess | 设置缩略图比例,缩略图返回后,加载并展示缩略图 | -| addProgressListener(func?: AsyncSuccess) | func?: AsyncSuccess | 设置网络下载百分比监听,返回数据加载百分比数值 | -| addAllCacheInfoCallback(func: IAllCacheInfoCallback) | func: IAllCacheInfoCallback | 设置获取所有缓存信息监听 | -| skipMemoryCache(skip: boolean) | skip: boolean | 配置是否跳过内存缓存 | -| retrieveDataFromCache(flag: boolean) | flag: boolean | 配置仅从缓存中加载数据 | -| signature | ObjectKey | 自定义key | - -同时支持[图片变换相关](#图片变换相关)接口。 - -### ImageKnife 启动器/门面类 - -| 方法名 | 入参 | 接口描述 | -|----------------------------------| ---------------------- | ------------------------------------------------------------ | -| call(request: RequestOption) | request: RequestOption | 根据用户配置参数具体执行加载流程 | -| preload(request: RequestOption) | request: RequestOption | 根据用户配置参数具体执行预加载流程 | -| pauseRequests() | | 全局暂停请求 | -| resumeRequests() | | 全局恢复暂停 | -| isUrlExist(url, cacheType, size) | url, CacheType, Size | 判断图片是否在 缓存和磁盘中存在,如果入参是缓存,需要传入值图片大小,参数 CacheType, Size(可选) | -| setMaxRequests(count: number) | count | 设置请求的最大并发数量 | -| removeAllMemoryCache() | | 清除全部内存缓存 | -| removeAllFileCache() | | 清除全部磁盘缓存 | -| removeMemoryCache(url: string) | url: string | 清除指定内存缓存 | -| removeFileCache(url: string) | url: string | 清除指定磁盘缓存 | -| getMemoryCacheKey(url: string) | url: string | 获取指定图片内存缓存key | -| getDiskCacheKey(url: string) | url: string | 获取指定图片磁盘缓存key | - -### 缓存策略相关 - -| 使用方法 | 类型 | 策略描述 | -|--------------------------------------------|-----------|----------------------| -| request.diskCacheStrategy(new ALL()) | ALL | 表示既缓存原始图片,也缓存转换过后的图片 | -| request.diskCacheStrategy(new AUTOMATIC()) | AUTOMATIC | 表示尝试对本地和远程图片使用适合的策略 | -| request.diskCacheStrategy(new DATA()) | DATA | 表示只缓存原始图片 | -| request.diskCacheStrategy(new NONE()) | NONE | 表示不缓存任何内容 | -| request.diskCacheStrategy(new RESOURCE()) | RESOURCE | 表示只缓存转换过后的图片 | - -### AntiAliasing类型展示效果 - -| 使用方法 | 类型 | 策略描述 | -|-------------------------|--------|-------------| -| AntiAliasing.FIT_HIGH | String | 图像抗锯齿设置为高画质 | -| AntiAliasing.FIT_MEDIUM | String | 图像抗锯齿设置为中画质 | -| AntiAliasing.FIT_LOW | String | 图像抗锯齿设置为低画质 | - -### CacheType类型展示效果 - -| 使用方法 | 类型 | 策略描述 | -|-------------------------|-----|-----------------------------------| -| CacheType.Default| int | 默认值,先从内存获取,无值则从磁盘获取 | -| CacheType.Cache| int | 缓存 | -| CacheType.Disk | int | 磁盘 | - -### ScaleType类型展示效果 - -| 使用方法 | 类型 | 策略描述 | -|-------------------------|-----|-----------------------------------| -| ScaleType.FIT_START | int | 图像位于用户设置组件左上角显示,图像会缩放至全部展示 | -| ScaleType.FIT_END | int | 图像位于用户设置组件右下角显示,图像会缩放至全部展示 | -| ScaleType.FIT_CENTER | int | 图像位于用户设置组件居中,图像会缩放至全部展示 | -| ScaleType.CENTER | int | 图像居中展示,不缩放 | -| ScaleType.CENTER_CROP | int | 图像的宽高长度,短的部分缩放至组件大小,超出的全部裁剪 | -| ScaleType.FIT_XY | int | 图像拉伸至组件大小 | -| ScaleType.CENTER_INSIDE | int | 如果图像大于组件则执行FIT_CENTER,小于组件则CENTER | -| ScaleType.NONE | int | 如果不想适配,直接展示原图大小 | -| ScaleType.AUTO_HEIGHT | int | 设置宽的时候,图片高度自适应 | -| ScaleType.AUTO_WIDTH | int | 设置高的时候,图片宽度自适应 | -| ScaleType.AUTO | int | 没有设置宽和高,图片按照自身宽高显示 | - -### 图片变换相关 - -| 使用方法 | 类型 | 相关描述 | -|--------------------------------|------------------------------------|--------------------------------| -| request.centerCrop() | CenterCrop | 可以根据图片文件,目标显示大小,进行对应centerCrop | -| request.centerInside() | CenterInside | 可以根据图片文件,目标显示大小,进行对应centerInside | -| request.fitCenter() | FitCenter | 可以根据图片文件,目标显示大小,进行对应fitCenter | -| request.blur() | BlurTransformation | 模糊处理(图片分辨率较大建议传递第二个参数将图片进行缩小) | -| request.brightnessFilter() | BrightnessFilterTransformation | 亮度滤波器 | -| request.contrastFilter() | ContrastFilterTransformation | 对比度滤波器 | -| request.cropCircle() | CropCircleTransformation | 圆形剪裁显示 | -| request.cropCircleWithBorder() | CropCircleWithBorderTransformation | 圆环展示 | -| request.cropSquare() | CropSquareTransformation | 正方形剪裁 | -| request.crop() | CropTransformation | 自定义矩形剪裁 | -| request.grayscale() | GrayscaleTransformation | 灰度级转换 | -| request.invertFilter() | InvertFilterTransformation | 反转滤波器 | -| request.pixelationFilter() | PixelationFilterTransformation | 像素化滤波器 | -| request.rotateImage() | RotateImageTransformation | 图片旋转 | -| request.roundedCorners() | RoundedCornersTransformation | 圆角剪裁 | -| request.sepiaFilter() | SepiaFilterTransformation | 乌墨色滤波器 | -| request.sketchFilter() | SketchFilterTransformation | 素描滤波器 | -| request.mask() | MaskTransformation | 遮罩 | -| request.swirlFilter() | SwirlFilterTransformation | 扭曲滤波器 | -| request.kuwaharaFilter() | KuwaharaFilterTransform | 桑原滤波器 | -| request.toonFilter() | ToonFilterTransform | 动画滤波器 | -| request.vignetteFilter() | VignetteFilterTransform | 装饰滤波器 | -| request.downsampleOf() | BaseDownsampling | 下采样 | - - - -### setLruCacheSize - -setLruCacheSize(size: number,memory:number): void - -设置图片文件缓存的大小上限,size单位为张数,memory单位为字节,提升再次加载同源图片的加载速度,特别是对网络图源会有较明显提升。 -如果不设置则默认为100张,100MB。缓存采用内置的LRU策略。 -size为0则代表不限制缓存张数,memory为0则代表不限制缓存大小。 -建议根据应用实际需求,设置合理缓存上限,数字过大可能导致内存占用过高,可能导致OOM异常。 - -**参数:** - -| 参数名 | 类型 | 必填 | 说明 | -| -------- | -------- | -------- |-------------------------| -| size | number | 是 | 图片文件的缓存张数,单位为张。只支持正整数,0 | -| memory | number | 是 | 图片文件的缓存大小,单位为字节。只支持正数,0 | - -**示例:** -```ts -//EntryAbility.ets -import { InitImageKnife } from '...imageknife' -export default class EntryAbility extends UIAbility { - onWindowStageCreate(windowStage: window.WindowStage) { - InitImageKnife.init(this.context); - let imageKnife: ImageKnife | undefined = ImageKnifeGlobal.getInstance().getImageKnife() - if (imageKnife != undefined) { - //设置全局内存缓存大小张数 - imageKnife.setLruCacheSize(100, 100 * 1204 * 1024) - } - } -} -``` - -### Queue - -| 方法名 | 入参 | 接口描述 | -| ----------------------------------- | ----------------------- | ------------------------------ | -| getQueueLength(): number | | 获取队列总长度 | -| add(request: RequestOption) | request:RequestOption | 在队列尾部插入元素 | -| pop(): RequestOption | undefined | | 删除队列头元素并返回该删除元素 | - ## 约束与限制 -在下述版本验证通过: -DevEco Studio 4.1(4.1.3.520)--SDK:API11( 4.1.0.63) -DevEco Studio 4.1(4.1.3.418)--SDK:API11( 4.1.0.56) -DevEco Studio 4.1(4.1.3.322)--SDK:API11( 4.1.0.36) -DevEco Studio 4.0(4.0.3.700)--SDK:API10( 4.0.10.15) - -HSP场景适配: - -在使用ImageKnifeComponent进行加载图片时, 提供的ImageKnifeOption配置类新增了可选参数context, 在HSP场景下需要传入正确的context, 才能保证三方库后续正确获取Resource资源。 - -在使用RquestOption进行加载图片时, 提供的RquestOption配置类新增了接口`setModuleContext(moduleCtx:common.UIAbilityContext)`, 在HSP场景下需要传入正确的context, 才能保证三方库后续正确获取Resource资源。 - -非HSP场景不影响原功能, ImageKnifeOption配置类新增的可选参数context可以不传, RquestOption配置类新增的接口可以不调用。 - -注意: - -基于性能优化的原因,2.1.2-rc.13及以后版本引用了API12 Sendable接口,至此以后的版本只支持API12。 - -## 目录结构 - -``` -/library/src/ -- main/ets/components - - cache # 缓存相关内容 - - diskstrategy # 缓存策略 - - key # 缓存key生成策略 - - Base64.ets # Base64算法 - - CustomMap.ets # 自定义Map封装 - - DiskCacheEntry.ets # 磁盘缓存entry - - DiskLruCache.ets # 磁盘LRU缓存策略 - - FileReader.ets # 文件读取相关 - - FileUtils.ets # 文件工具类 - - LruCache.ets # 内存LRU缓存策略 - - Md5.ets # MD5算法 - - - imageknife # imageknife主要内容 - - compress # 压缩相关 - - constants # 常量相关 - - Downsampling # 下采样相关 - - entry # 部分数据结构 - - holder # 占位图相关解析 - - interface # 接口相关 - - networkmanage # 网络相关 - - pngj # pngj相关 - - requestmanage # imageknife请求相关 - - resourcemanage # 本地资源解析相关 - - transform # 图片变换相关 - - utils # 工具类相关 - - ImageKnife.ets # imageknife门面,app持久化类 - - ImageKnifeData.ets # 数据封装 - - ImageKnifeComponent.ets # 自定义控件封装 - - ImageKnifeDrawFactory.ets # IDrawLifeCycle用户自定义实现 - - ImageKnifeOption.ets # 用户传参数封装 - - RequestOption.ets # 用户设置参数封装 - -/entry/src/ -- main/ets - - entryability - - CustomEngineKeyImpl.ets - - EntryAbility.ts - - pages # 测试page页面列表 - - basicTestFeatureAbilityPage.ets # 测试列表加载 - - basicTestFileIOPage.ets # 测试fileio - - basicTestMediaImage.ets # 测试媒体image - - basicTestResourceManagerPage.ets # 测试本地资源解析 - - compressPage.ets # 压缩页面 - - cropImagePage2.ets # 手势裁剪页面 - - DownsamplingPage.ets # 图片下采样测试 - - frescoImageTestCasePage.ets # 测试属性动画组件切换 - - frescoRetryTestCasePage.ets # 测试ImageKnifeComponent加载失败重试 - - svgTestCasePage.ets # 测试svg解析页面 - - imageknifeTestCaseIndex.ets # 测试用例页面入口 - - index.ets # 程序入口页面 - - loadNetworkTestCasePage.ets # 网络加载测试 - - loadResourceTestCasePage.ets # 本地加载测试 - - showErrorholderTestCasePage.ets # 加载失败占位图测试 - - SignatureTestPage.ets # 自定义key测试 - - storageTestDiskLruCache.ets # 磁盘缓存测试 - - storageTestLruCache.ets # 内存缓存测试 - - testAllCacheInfoPage.ets # 所有缓存信息获取测试 - - testCustomDataFetchClientWithPage # 测试图片下载使用自定义的网络栈 - - testImageKnifeAutoHeightPage.ets # 图片高度自适应测试 - - testImageKnifeAutoWidthPage.ets # 图片宽度自适应测试 - - testImageKnifeAutoPage.ets # 图片宽高自适应测试 - - testImageKnifeOptionChangedPage.ets # 数据切换测试 - - testImageKnifeOptionChangedPage2.ets # 数据切换测试,部分变换 - - testImageKnifeOptionChangedPage3.ets # 数据切换测试,组件动画 - - testImageKnifeOptionChangedPage4.ets # 数据切换测试,内容动画 - - testImageKnifeOptionChangedPage5.ets # 数据切换测试,ImageKnifeDrawFactory封装圆角圆环边框等 - - testPreloadPage.ets # 预加载测试 - - transformPixelMapPage.ets # 所有类型变换测试 - - testSingleFrameGifPage.ets # 单帧gif加载测试 - - TestStopPlayingGifPage # gif播放暂停测试 - - TestImageKnifeNetPlaceholder # 缓存获取string类型占位图以及后备回调符测试 - - - OptionTestPage.ets # 图片缓存测试 - - testManyGifLoadWithPage # 测试gif加载页面 - - testImageKnifeCache # 测试图片是否在缓存或者磁盘中 - -workers - - upngWorkerTestCase.ets # png子线程解析 - - upngWorkerDepend.ts # png子线程解析具体执行 -``` +在下述版本验证通过: +DevEco Studio 5.0 Canary3(5.0.3.221)--SDK:API12 ## 贡献代码 @@ -640,47 +365,5 @@ HSP场景适配: ## 遗留问题 -1.目前svg和gif动图不支持变换效果。 - -## 补充说明 -### SVG标签说明 -从API version 10开始支持SVG标签, 使用版本为(SVG)1.1,SVG文件需添加xml声明,应以"(); + + public static getContext(): GlobalContext { + if (!GlobalContext.instance) { + GlobalContext.instance = new GlobalContext(); + } + return GlobalContext.instance; + } + + getObject(value: string): Object | undefined { + return this._objects.get(value); + } + + setObject(key: string, objectClass: Object): void { + this._objects.set(key, objectClass); + } +} \ No newline at end of file diff --git a/entry/src/main/ets/entryability/EntryAbility.ets b/entry/src/main/ets/entryability/EntryAbility.ets index c460bb3..488dcce 100644 --- a/entry/src/main/ets/entryability/EntryAbility.ets +++ b/entry/src/main/ets/entryability/EntryAbility.ets @@ -1,27 +1,37 @@ /* - * Copyright (C) 2023 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); + * 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, + * 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 UIAbility from '@ohos.app.ability.UIAbility'; +import AbilityConstant from '@ohos.app.ability.AbilityConstant'; import hilog from '@ohos.hilog'; +import UIAbility from '@ohos.app.ability.UIAbility'; +import Want from '@ohos.app.ability.Want'; import window from '@ohos.window'; -import { InitImageKnife, ImageKnifeGlobal, ImageKnife, ImageKnifeDrawFactory, LogUtil } from '@ohos/libraryimageknife' -import { CustomEngineKeyImpl } from './CustomEngineKeyImpl' +import { ImageKnife, InitImageKnife, LogUtil } from '@ohos/libraryimageknife'; +import { CustomEngineKeyImpl } from '../common/CustomEngineKeyImpl'; import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl'; import { BusinessError } from '@ohos.base' export default class EntryAbility extends UIAbility { - onWindowStageCreate(windowStage: window.WindowStage) { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); + } + + onDestroy(): void { + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy'); + } + + async onWindowStageCreate(windowStage: window.WindowStage): Promise { // Main window is created, set main page for this ability // let list: Array = ['ohos.permission.READ_IMAGEVIDEO', 'ohos.permission.WRITE_IMAGEVIDEO']; // let permissionRequestResult: Object; @@ -35,25 +45,37 @@ export default class EntryAbility extends UIAbility { // } // }) - windowStage.loadContent('pages/index', (err: BusinessError, data: void) => { - }); + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); - InitImageKnife.init(this.context); - - let imageKnife: ImageKnife | undefined = ImageKnifeGlobal.getInstance().getImageKnife() - if (imageKnife != undefined) { - // 全局配置网络加载进度条 - imageKnife - .setDefaultLifeCycle(ImageKnifeDrawFactory.createProgressLifeCycle("#10a5ff", 0.5)) - // 全局配置缓存key - imageKnife.setEngineKeyImpl(new CustomEngineKeyImpl()) - // 设置全局内存缓存大小张数 - imageKnife.setLruCacheSize(100, 100 * 1204 * 1024) - // 全局配置请求头 - imageKnife.addHeader('refer', "http://1.94.37.200:7070/AntiTheftChain/downloadImage"); - imageKnife.deleteHeader('refer'); - } - // 开启ImageKnife所有级别日志开关 LogUtil.mLogLevel = LogUtil.ALL + // 初始化ImageKnife的文件缓存 + await InitImageKnife.init(this.context) + ImageKnife.getInstance().setEngineKeyImpl(new CustomEngineKeyImpl()) + // 全局配置请求头 + ImageKnife.getInstance().addHeader('refer', "http://1.94.37.200:7070/AntiTheftChain/downloadImage"); + ImageKnife.getInstance().deleteHeader('refer'); + + windowStage.loadContent('pages/Index', (err, data) => { + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? ''); + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground'); } } diff --git a/entry/src/main/ets/pages/ImageAnimatorPage.ets b/entry/src/main/ets/pages/ImageAnimatorPage.ets new file mode 100644 index 0000000..bde0508 --- /dev/null +++ b/entry/src/main/ets/pages/ImageAnimatorPage.ets @@ -0,0 +1,41 @@ +import { AnimatorOption, ImageKnifeAnimatorComponent } from "@ohos/libraryimageknife" + +@Entry +@Component +struct ImageAnimatorPage { + @State animatorOption: AnimatorOption = { + state: AnimationStatus.Running, + iterations: -1 + } + build() { + Column(){ + Flex(){ + Button("播放").onClick(()=>{ + this.animatorOption.state = AnimationStatus.Running + }) + Button("暂停").onClick(()=>{ + this.animatorOption.state = AnimationStatus.Paused + }) + Button("停止").onClick(()=>{ + this.animatorOption.state = AnimationStatus.Stopped + }) + Button("无限循环").onClick(()=>{ + this.animatorOption.iterations = -1 + }) + Button("播放一次").onClick(()=>{ + this.animatorOption.iterations = 1 + }) + Button("播放两次").onClick(()=>{ + this.animatorOption.iterations = 2 + }) + } + ImageKnifeAnimatorComponent({ + imageKnifeOption:{ + loadSrc:"https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658", + placeholderSrc:$r('app.media.loading'), + errorholderSrc:$r('app.media.failed') + },animatorOption:this.animatorOption + }).width(300).height(300).backgroundColor(Color.Orange).margin({top:30}) + }.width("100%").height("100%") + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/ImageTransformation.ets b/entry/src/main/ets/pages/ImageTransformation.ets new file mode 100644 index 0000000..844ed62 --- /dev/null +++ b/entry/src/main/ets/pages/ImageTransformation.ets @@ -0,0 +1,432 @@ +/* + * 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 { + BlurTransformation, + BrightnessTransformation, + CropSquareTransformation, + CropTransformation, + GrayScaleTransformation, + ImageKnifeComponent, + ImageKnifeOption, + InvertTransformation, + KuwaharaTransformation, + MaskTransformation, + MultiTransTransformation, + PixelationTransformation, + PixelMapTransformation, + SepiaTransformation, + SketchTransformation, + SwirlTransformation, + ToonTransformation, + VignetterTransformation +} from '@ohos/libraryimageknife'; +import { collections } from '@kit.ArkTS' + +@Entry +@Component +struct ImageTransformation { + @State imageKnifeOption: ImageKnifeOption = { + loadSrc: $r('app.media.pngSample'), + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + objectFit: ImageFit.Contain + } + @State isRound: boolean = false; + @State isContrast: boolean = false; + @State isRotate: boolean = false; + isBlur: boolean = false + isBrightness: boolean = false + isGrayScale: boolean = false; + isInvert: boolean = false; + isToon: boolean = false; + isCropCircle: boolean = false; + isCropCircleWithBorder: boolean = false; + isKuwahara: boolean = false; + isPixelation: boolean = false; + isSketch: boolean = false; + isSwirl: boolean = false; + isVignetter: boolean = false; + isCropSquare: boolean = false; + isCropTop: boolean = false; + isCropCenter: boolean = false; + isCropBottom: boolean = false; + isMask: boolean = false; + isSepia: boolean = false; + + build() { + Scroll() { + 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.updateImageKnifeOption(); + }) + .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.updateImageKnifeOption(); + }) + .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) => { + this.isGrayScale = value; + this.updateImageKnifeOption(); + }) + .width(30) + .height(30) + Text('灰化效果').fontSize(20) + } + + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'checkbox4', group: 'checkboxGroup' }) + .selectedColor(0x39a2db) + .shape(CheckBoxShape.ROUNDED_SQUARE) + .onChange((value: boolean) => { + this.isInvert = value; + this.updateImageKnifeOption(); + }) + .width(30) + .height(30) + Text('反转效果').fontSize(20) + } + + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'checkbox5', group: 'checkboxGroup' }) + .selectedColor(0x39a2db) + .shape(CheckBoxShape.ROUNDED_SQUARE) + .onChange((value: boolean) => { + this.isToon = value; + this.updateImageKnifeOption(); + }) + .width(30) + .height(30) + Text('动画滤镜效果').fontSize(20) + } + + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'checkbox6', group: 'checkboxGroup' }) + .selectedColor(0x39a2db) + .shape(CheckBoxShape.ROUNDED_SQUARE) + .onChange((value: boolean) => { + this.isCropCircle = value; + this.updateImageKnifeOption(); + }) + .width(30) + .height(30) + Text('裁剪圆形效果').fontSize(20) + } + + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'checkbox7', group: 'checkboxGroup' }) + .selectedColor(0x39a2db) + .shape(CheckBoxShape.ROUNDED_SQUARE) + .onChange((value: boolean) => { + this.isCropCircleWithBorder = value; + this.updateImageKnifeOption(); + }) + .width(30) + .height(30) + Text('裁剪圆形带边框效果').fontSize(20) + } + + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'checkbox8', group: 'checkboxGroup' }) + .selectedColor(0x39a2db) + .shape(CheckBoxShape.ROUNDED_SQUARE) + .onChange((value: boolean) => { + this.isContrast = value; + }) + .width(30) + .height(30) + Text('对比度效果').fontSize(20) + } + + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'checkbox9', group: 'checkboxGroup' }) + .selectedColor(0x39a2db) + .shape(CheckBoxShape.ROUNDED_SQUARE) + .onChange((value: boolean) => { + this.isSepia = value; + this.updateImageKnifeOption(); + }) + .width(30) + .height(30) + Text('乌墨色滤波效果').fontSize(20) + } + + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'checkbox10', group: 'checkboxGroup' }) + .selectedColor(0x39a2db) + .shape(CheckBoxShape.ROUNDED_SQUARE) + .onChange((value: boolean) => { + this.isRotate = value; + }) + .width(30) + .height(30) + Text('旋转效果').fontSize(20) + } + + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'checkbox11', group: 'checkboxGroup' }) + .selectedColor(0x39a2db) + .shape(CheckBoxShape.ROUNDED_SQUARE) + .onChange((value: boolean) => { + this.isRound = value; + this.updateImageKnifeOption(); + }) + .width(30) + .height(30) + Text('圆角效果').fontSize(20) + } + + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'checkbox12', group: 'checkboxGroup' }) + .selectedColor(0x39a2db) + .shape(CheckBoxShape.ROUNDED_SQUARE) + .onChange((value: boolean) => { + this.isKuwahara = value; + this.updateImageKnifeOption(); + }) + .width(30) + .height(30) + Text('桑原滤波效果').fontSize(20) + } + + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'checkbox13', group: 'checkboxGroup' }) + .selectedColor(0x39a2db) + .shape(CheckBoxShape.ROUNDED_SQUARE) + .onChange((value: boolean) => { + this.isPixelation = value; + this.updateImageKnifeOption(); + }) + .width(30) + .height(30) + Text('像素化滤波效果').fontSize(20) + } + + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'checkbox14', group: 'checkboxGroup' }) + .selectedColor(0x39a2db) + .shape(CheckBoxShape.ROUNDED_SQUARE) + .onChange((value: boolean) => { + this.isSketch = value; + this.updateImageKnifeOption(); + }) + .width(30) + .height(30) + Text('素描滤波效果').fontSize(20) + } + + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'checkbox15', group: 'checkboxGroup' }) + .selectedColor(0x39a2db) + .shape(CheckBoxShape.ROUNDED_SQUARE) + .onChange((value: boolean) => { + this.isSwirl = value; + this.updateImageKnifeOption(); + }) + .width(30) + .height(30) + Text('扭曲滤波效果').fontSize(20) + } + + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'checkbox16', group: 'checkboxGroup' }) + .selectedColor(0x39a2db) + .shape(CheckBoxShape.ROUNDED_SQUARE) + .onChange((value: boolean) => { + this.isVignetter = value; + this.updateImageKnifeOption(); + }) + .width(30) + .height(30) + Text('装饰滤波效果').fontSize(20) + } + + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'checkbox17', group: 'checkboxGroup' }) + .selectedColor(0x39a2db) + .shape(CheckBoxShape.ROUNDED_SQUARE) + .onChange((value: boolean) => { + this.isCropSquare = value; + this.updateImageKnifeOption(); + }) + .width(30) + .height(30) + Text('正方形裁剪效果').fontSize(20) + } + + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'checkbox18', group: 'checkboxGroup' }) + .selectedColor(0x39a2db) + .shape(CheckBoxShape.ROUNDED_SQUARE) + .onChange((value: boolean) => { + this.isCropTop = value; + this.updateImageKnifeOption(); + }) + .width(30) + .height(30) + Text('上方裁剪效果').fontSize(20) + } + + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'checkbox19', group: 'checkboxGroup' }) + .selectedColor(0x39a2db) + .shape(CheckBoxShape.ROUNDED_SQUARE) + .onChange((value: boolean) => { + this.isCropCenter = value; + this.updateImageKnifeOption(); + }) + .width(30) + .height(30) + Text('中间裁剪效果').fontSize(20) + } + + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'checkbox20', group: 'checkboxGroup' }) + .selectedColor(0x39a2db) + .shape(CheckBoxShape.ROUNDED_SQUARE) + .onChange((value: boolean) => { + this.isCropBottom = value; + this.updateImageKnifeOption(); + }) + .width(30) + .height(30) + Text('底下裁剪效果').fontSize(20) + } + + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'checkbox21', group: 'checkboxGroup' }) + .selectedColor(0x39a2db) + .shape(CheckBoxShape.ROUNDED_SQUARE) + .onChange((value: boolean) => { + this.isMask = value; + this.updateImageKnifeOption(); + }) + .width(30) + .height(30) + Text('遮罩效果').fontSize(20) + } + + if (this.isContrast) { + ImageKnifeComponent({ + imageKnifeOption: this.imageKnifeOption + }) + .width(300) + .height(300) + .rotate({ angle: this.isRotate ? 90 : 0 }) + .contrast(12) + .backgroundColor(Color.Pink) + } else { + ImageKnifeComponent({ + imageKnifeOption: this.imageKnifeOption + }).width(300) + .height(300) + .rotate({ angle: this.isRotate ? 90 : 0 }) + .backgroundColor(Color.Pink) + } + } + } + .height('100%') + .width('100%') + } + + updateImageKnifeOption() { + let transformations: collections.Array = new collections.Array() + if (this.isBlur) { + transformations.push(new BlurTransformation(5)); + } + if (this.isBrightness) { + transformations.push(new BrightnessTransformation(0.2)); + } + if (this.isGrayScale) { + transformations.push(new GrayScaleTransformation()); + } + if (this.isInvert) { + transformations.push(new InvertTransformation()); + } + if (this.isToon) { + transformations.push(new ToonTransformation(0.3, 10.0)); + } + if (this.isKuwahara) { + transformations.push(new KuwaharaTransformation(10)); + } + if (this.isPixelation) { + transformations.push(new PixelationTransformation(5.0)); + } + if (this.isSketch) { + transformations.push(new SketchTransformation()); + } + if (this.isSwirl) { + transformations.push(new SwirlTransformation(200, 1.0, [0.5, 0.5])); + } + if (this.isVignetter) { + transformations.push(new VignetterTransformation([0.5, 0.5], [0.0, 0.0, 0.0], [0.3, 0.75])); + } + if (this.isCropSquare) { + transformations.push(new CropSquareTransformation()); + } + if (this.isCropTop) { + transformations.push(new CropTransformation(25, 25, 0)); + } + if (this.isCropCenter) { + transformations.push(new CropTransformation(25, 25, 1)); + } + if (this.isCropBottom) { + transformations.push(new CropTransformation(25, 25, 2)); + } + if (this.isSepia) { + transformations.push(new SepiaTransformation()); + } + if (this.isMask) { + transformations.push(new MaskTransformation($r('app.media.mask_starfish'))); + } + this.imageKnifeOption = { + loadSrc: $r('app.media.pngSample'), + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + objectFit: ImageFit.Contain, + border: { radius: this.isRound ? { topLeft: 50, bottomRight: 50 } : 0 }, + transformation: transformations.length > 0 ? new MultiTransTransformation(transformations) : undefined + } + if (this.isCropCircle) { + this.imageKnifeOption.objectFit = ImageFit.Cover; + this.imageKnifeOption.border = { radius: 150 }; + } + if (this.isCropCircleWithBorder) { + this.imageKnifeOption.objectFit = ImageFit.Cover; + this.imageKnifeOption.border = { radius: 150, color: Color.Red, width: 5 }; + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/ListPage.ets b/entry/src/main/ets/pages/ListPage.ets new file mode 100644 index 0000000..c604d97 --- /dev/null +++ b/entry/src/main/ets/pages/ListPage.ets @@ -0,0 +1,42 @@ +/* + * 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 { ImageKnifeComponent, ImageKnifeOption } from '@ohos/libraryimageknife'; + +@Entry +@Component +struct ListPage { + + private data: string[] = [] + @State ImageKnifeOption: ImageKnifeOption = { loadSrc: $r('app.media.startIcon')} + + + aboutToAppear(): void { + for (let i = 0; i < 1000; i++) { + this.data.push(i.toString()) + } + } + + build() { + Row() { + List({ space: 10 }) { + ForEach(this.data, (item: string) => { + ImageKnifeComponent({ imageKnifeOption: this.ImageKnifeOption }).height(200).width(200) + }, (item: string) => item) + } + .width('100%') + } + .height('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/LoadStatePage.ets b/entry/src/main/ets/pages/LoadStatePage.ets new file mode 100644 index 0000000..80c41a3 --- /dev/null +++ b/entry/src/main/ets/pages/LoadStatePage.ets @@ -0,0 +1,114 @@ +/* + * 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 { ImageKnifeComponent, ImageKnifeOption } from "@ohos/libraryimageknife" +import matrix4 from '@ohos.matrix4' + +@Entry +@Component +struct LoadStatePage { + + starTime:number = new Date().getTime() + + @State ImageKnifeOption: ImageKnifeOption = { + loadSrc: $r("app.media.rabbit"), + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + objectFit: ImageFit.Contain, + onLoadListener: { + onLoadFailed: (err) => { + console.error("Load Failed Reason: " + err); + }, + onLoadSuccess: (data) => { + return data; + }, + }, + border: { radius: 50 } + } + @State imageKnifeOption1: ImageKnifeOption = { + loadSrc: $r('app.media.startIcon') + } + @State message: string = "" + @State currentWidth: number = 200 + @State currentHeight: number = 200 + @State typeValue: string = "" + build() { + Column() { + Text('测试失败场景请先关闭网络,并保证本地没有此网络图片的缓存') + .margin({ top: 20 }) + Row() { + Button('测试失败/成功场景') + .onClick(() => { + this.ImageKnifeOption = { + loadSrc: "https://www.openharmony.cn/_nuxt/img/logo.dcf95b3.png", + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + objectFit: ImageFit.Contain, + onLoadListener: { + onLoadStart:()=>{ + this.starTime = new Date().getTime() + console.info("Load start: "); + }, + onLoadFailed: (err) => { + console.error("Load Failed Reason: " + err + " cost " + (new Date().getTime() - this.starTime) + " milliseconds"); + }, + onLoadSuccess: (data,imageData) => { + console.info("Load Successful: cost " + (new Date().getTime() - this.starTime) + " milliseconds"); + this.currentWidth = imageData.imageWidth! + this.currentHeight = imageData.imageHeight! + this.typeValue = imageData.type! + return data; + }, + }, + border: { radius: 50 }, + onComplete:(event)=>{ + console.error("Load onComplete width:"+event?.width , " height:"+event?.height , " componentWidth:"+event?.componentWidth," componentHeight:" + event?.componentHeight); + } + } + }) + } + .margin({ top: 20 }) + Text(this.typeValue) + ImageKnifeComponent({ imageKnifeOption: this.ImageKnifeOption }).height(this.currentHeight).width(this.currentWidth) + .margin({ top: 20 }) + Button("自定义下载失败").onClick(()=>{ + this.imageKnifeOption1 = { + loadSrc: "abc", + placeholderSrc:$r('app.media.loading'), + errorholderSrc:$r('app.media.failed'), + customGetImage:custom, + onLoadListener: { + onLoadFailed:(err)=>{ + this.message = "err:" + err + } + } + } + }).margin({ top: 20 }) + Text(this.message).fontSize(20).margin({ top: 20 }) + ImageKnifeComponent({ imageKnifeOption: this.imageKnifeOption1 }).height(this.currentHeight).width(this.currentWidth) + .margin({ top: 20 }) + } + + .width('100%') + .height('100%') + } + +} +// 自定义下载方法 +@Concurrent +async function custom(context: Context, src: string | PixelMap | Resource): Promise { + console.info("ImageKnife:: custom download:" + src) + // 举例写死从本地文件读取,也可以自己请求网络图片 + return undefined +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/LongImagePage.ets b/entry/src/main/ets/pages/LongImagePage.ets new file mode 100644 index 0000000..c8f6e1f --- /dev/null +++ b/entry/src/main/ets/pages/LongImagePage.ets @@ -0,0 +1,39 @@ +/* + * 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 { ImageKnifeComponent } from '@ohos/libraryimageknife' + +@Entry +@Component +struct LongImagePage { + + build() { + Scroll() { + + // Image("https://wx2.sinaimg.cn/mw690/006HyQKGgy1hnqp08dw09j30u04twu0x.jpg").objectFit(ImageFit.Auto).height(300) + + // Image($r("app.media.aaa")).objectFit(ImageFit.Auto).width(200) + ImageKnifeComponent({ + imageKnifeOption: { + loadSrc:"https://wx2.sinaimg.cn/mw690/006HyQKGgy1hnqp08dw09j30u04twu0x.jpg", + //src:$r("app.media.aaa"), + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.failed"), + objectFit: ImageFit.Auto + } + }) + } + .height('100%') .width('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/ObjectFitPage.ets b/entry/src/main/ets/pages/ObjectFitPage.ets new file mode 100644 index 0000000..c86415f --- /dev/null +++ b/entry/src/main/ets/pages/ObjectFitPage.ets @@ -0,0 +1,71 @@ +/* + * 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 { ImageKnife, ImageKnifeComponent, ImageKnifeOption } from '@ohos/libraryimageknife' + +@Entry +@Component +struct ObjectFitPage { + @State imageKnifeOption: ImageKnifeOption = { + loadSrc: $r("app.media.app_icon"), + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + objectFit: ImageFit.Fill + } + + build() { + Column() { + + Button("主图Fill拉伸填充").onClick(()=>{ + this.imageKnifeOption = { + loadSrc: $r("app.media.app_icon"), + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + objectFit: ImageFit.Fill + } + }) + + Button("占位图Contain保持比例填充").margin({top:10}).onClick(async () => { + ImageKnife.getInstance().removeAllMemoryCache() + await ImageKnife.getInstance().removeAllFileCache() + + this.imageKnifeOption = { + loadSrc: "https://wx2.sinaimg.cn/mw690/006HyQKGgy1hnqp08dw09j30u04twu0x.jpg", + placeholderSrc: $r("app.media.app_icon"), + errorholderSrc: $r("app.media.app_icon"), + objectFit: ImageFit.Fill, + placeholderObjectFit: ImageFit.Contain + } + }) + + + Button("错误图None不变化").margin({top:10}).onClick(() => { + this.imageKnifeOption = { + loadSrc: "http://xxxxx", + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + objectFit: ImageFit.Fill, + errorholderObjectFit: ImageFit.None + } + }) + + ImageKnifeComponent({ + imageKnifeOption: this.imageKnifeOption + }).width(300).height(200).border({width:1}).margin({top:50}) + + } + .height('100%') + .width('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/SignatureTestPage.ets b/entry/src/main/ets/pages/SignatureTestPage.ets index 5d26d49..42f49e4 100644 --- a/entry/src/main/ets/pages/SignatureTestPage.ets +++ b/entry/src/main/ets/pages/SignatureTestPage.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 @@ -12,9 +12,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { ImageKnifeComponent, ImageKnifeOption } from '@ohos/libraryimageknife'; -import { ImageKnifeComponent, ImageKnifeOption, NONE, DiskStrategy } from '@ohos/libraryimageknife' -import { ObjectKey } from '@ohos/libraryimageknife'; @Entry @Component @@ -22,43 +21,39 @@ struct SignatureTestPage { @State imageKnifeOption1: ImageKnifeOption = { loadSrc: $r('app.media.icon'), - placeholderSrc: $r('app.media.icon_loading'), - errorholderSrc: $r('app.media.icon_failed'), + placeholderSrc:$r("app.media.loading"), }; @State imageKnifeOption2: ImageKnifeOption = { loadSrc: $r('app.media.icon'), - placeholderSrc: $r('app.media.icon_loading'), - errorholderSrc: $r('app.media.icon_failed') + placeholderSrc:$r("app.media.loading"), }; build() { Scroll() { Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { - Text("Signature固定为 1").fontSize(15) + Text("key固定为 1").fontSize(15) Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { Button("加载") .onClick(() => { this.imageKnifeOption1 = { loadSrc: 'https://img-blog.csdn.net/20140514114029140', - placeholderSrc: $r('app.media.icon_loading'), - errorholderSrc: $r('app.media.icon_failed'), - signature: new ObjectKey("1") + placeholderSrc:$r("app.media.loading"), + signature: "1" } }).margin({ top: 5, left: 3 }) ImageKnifeComponent({ imageKnifeOption: this.imageKnifeOption1 }).width(300).height(300) }.width('100%').backgroundColor(Color.Pink) - Text("设置Signature,每次为时间戳").fontSize(15) + Text("key每次变化:时间戳").fontSize(15) Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { Button("加载") .onClick(() => { this.imageKnifeOption2 = { loadSrc: 'https://img-blog.csdn.net/20140514114029140', - placeholderSrc: $r('app.media.icon_loading'), - errorholderSrc: $r('app.media.icon_failed'), - signature: new ObjectKey(new Date().getTime().toString()) + placeholderSrc:$r("app.media.loading"), + signature: new Date().getTime().toString() } }).margin({ top: 5, left: 3 }) ImageKnifeComponent({ imageKnifeOption: this.imageKnifeOption2 }).width(300).height(300) @@ -71,7 +66,7 @@ struct SignatureTestPage { } aboutToAppear() { - console.log("唯一标识页面:" + new ObjectKey(new Date().getTime().toString()).getKey()) + console.log("唯一标识页面:" + new Date().getTime().toString()) } } \ No newline at end of file diff --git a/entry/src/main/ets/pages/SingleImage.ets b/entry/src/main/ets/pages/SingleImage.ets new file mode 100644 index 0000000..b7fc9b3 --- /dev/null +++ b/entry/src/main/ets/pages/SingleImage.ets @@ -0,0 +1,132 @@ +/* + * 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 { ImageKnifeComponent,BlurTransformation } from '@ohos/libraryimageknife'; +import fs from '@ohos.file.fs'; +import image from '@ohos.multimedia.image'; +import { common2D, drawing } from '@kit.ArkGraphics2D'; + +@Entry +@Component +struct SingleImage { + resource: string = "app.media.svgSample" + scroller: Scroller = new Scroller; + localFile: string = getContext(this).filesDir + "/icon.png" + @State pixelMap:PixelMap | undefined = undefined; + @State DrawingColorFilter: ColorFilter | undefined = undefined + private color: common2D.Color = { alpha: 255, red: 255, green: 0, blue: 0 }; + aboutToAppear(): void { + // 拷贝本地文件 + let icon: Uint8Array = getContext(this).resourceManager.getMediaContentSync($r("app.media.startIcon")); + let file = fs.openSync(this.localFile, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE); + fs.writeSync(file.fd, icon.buffer); + fs.fsyncSync(file.fd); + fs.closeSync(file); + this.changePic(getContext().resourceManager.getMediaContentSync( $r("app.media.aaa")) + .buffer as ArrayBuffer); + + + } + + build() { + Scroll(this.scroller) { + Column() { + Text("本地资源svg图片") + .fontSize(30) + .fontWeight(FontWeight.Bold) + ImageKnifeComponent({ + imageKnifeOption: { + loadSrc: $r("app.media.svgSample"), + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.failed"), + objectFit: ImageFit.Contain + } + }).width(100).height(100) + .onClick(()=>{ + this.DrawingColorFilter = drawing.ColorFilter.createBlendModeColorFilter(this.color, drawing.BlendMode.SRC_IN); + }) + Text("本地context files下文件") + .fontSize(30) + .fontWeight(FontWeight.Bold) + ImageKnifeComponent({ + imageKnifeOption: { + loadSrc: this.localFile, + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.failed"), + objectFit: ImageFit.Contain + } + }).width(100).height(100) + Text("网络图片") + .fontSize(30) + .fontWeight(FontWeight.Bold) + ImageKnifeComponent({ + imageKnifeOption: { + loadSrc:"https://www.openharmony.cn/_nuxt/img/logo.dcf95b3.png", + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.failed"), + objectFit: ImageFit.Contain, + progressListener:(progress:number)=>{console.info("ImageKnife:: call back progress = " + progress)} + } + }).width(100).height(100) + Text("自定义下载") + .fontSize(30) + .fontWeight(FontWeight.Bold) + ImageKnifeComponent({ + imageKnifeOption: { + loadSrc: "https://file.atomgit.com/uploads/user/1704857786989_8994.jpeg", + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.failed"), + objectFit: ImageFit.Contain, + customGetImage: custom, + transformation: new BlurTransformation(10) + } + }).width(100).height(100) + Text("pixelMap加载图片") + .fontSize(30) + .fontWeight(FontWeight.Bold) + ImageKnifeComponent({ + imageKnifeOption: { + loadSrc: this.pixelMap!, + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.failed"), + 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; + }) + } + } +} + +// 自定义下载方法 +@Concurrent +async function custom(context: Context, src: string | PixelMap | Resource): Promise { + console.info("ImageKnife:: custom download:" + src) + // 举例写死从本地文件读取,也可以自己请求网络图片 + return context.resourceManager.getMediaContentSync($r("app.media.startIcon").id).buffer as ArrayBuffer +} + diff --git a/entry/src/main/ets/pages/TestCommonImage.ets b/entry/src/main/ets/pages/TestCommonImage.ets new file mode 100644 index 0000000..669220c --- /dev/null +++ b/entry/src/main/ets/pages/TestCommonImage.ets @@ -0,0 +1,52 @@ +/* + * 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 { ImageKnifeComponent } from '@ohos/libraryimageknife'; + +@Entry +@Component +struct TestCommonImage { + private data: Array = [] + aboutToAppear(): void { + for (let index = 0; index < 30; index++) { + this.data.push(`https://img-blog.csdn.net/20140514114029140?${index}`) + } + } + build() { + Column() { + WaterFlow() { + ForEach(this.data,(item: string)=>{ + FlowItem() { + Column(){ + ImageKnifeComponent({ + imageKnifeOption: { + loadSrc: item, + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.failed"), + objectFit: ImageFit.Contain, + signature: "aaa" + } + }).width("50%").height(200) + } + }.height(200) + .backgroundColor("#95efd2") + },(item: string) => item) + }.columnsTemplate("1fr 1fr") + .columnsGap(10) + .rowsGap(5) + .backgroundColor(0xFAEEE0) + .width("100%").height("100%") + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/TestErrorHolderPage.ets b/entry/src/main/ets/pages/TestErrorHolderPage.ets new file mode 100644 index 0000000..f49ab6c --- /dev/null +++ b/entry/src/main/ets/pages/TestErrorHolderPage.ets @@ -0,0 +1,47 @@ +/* + * 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 { ImageKnifeComponent } from '@ohos/libraryimageknife' + +@Entry +@Component +struct TestErrorHolderPage { + + build() { + Column() { + Text("ImageKnifeComponent1").fontSize(20) + ImageKnifeComponent({ + imageKnifeOption: { + loadSrc: "abc", + errorholderSrc:$r('app.media.failed') + } + }).width(200).height(200) + Text("ImageKnifeComponent2").fontSize(20) + ImageKnifeComponent({ + imageKnifeOption: { + loadSrc: "abc", + errorholderSrc:$r('app.media.startIcon') + } + }).width(200).height(200) + Text("ImageKnifeComponent2").fontSize(20) + ImageKnifeComponent({ + imageKnifeOption: { + loadSrc: "abc", + errorholderSrc:$r('app.media.mask_starfish') + } + }).width(200).height(200) + } + .height('100%') .width('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/TestHeader.ets b/entry/src/main/ets/pages/TestHeader.ets new file mode 100644 index 0000000..9be198e --- /dev/null +++ b/entry/src/main/ets/pages/TestHeader.ets @@ -0,0 +1,39 @@ +/* + * 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 { ImageKnifeComponent,ImageKnifeOption } from '@ohos/libraryimageknife' + +@Entry +@Component +struct TestPrefetchToFileCachePage { + @State imageKnifeOption: ImageKnifeOption = { + loadSrc:"https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658", + placeholderSrc:$r('app.media.loading'), + headerOption:[ + { + key:"abc", + value:"单个" + } + ] + } + + build() { + Column() { + ImageKnifeComponent({ + imageKnifeOption: this.imageKnifeOption + }).width(300).height(300) + } + .height('100%') .width('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/TestHspPreLoadImage.ets b/entry/src/main/ets/pages/TestHspPreLoadImage.ets new file mode 100644 index 0000000..16ff464 --- /dev/null +++ b/entry/src/main/ets/pages/TestHspPreLoadImage.ets @@ -0,0 +1,25 @@ +/* + * 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 { IndexComponent } from "@ohos/libraryimageknife" + +@Entry +@Component +struct TestHspPreLoadImage { + build() { + Column() { + IndexComponent() + }.width("100%").height('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/TestImageFlash.ets b/entry/src/main/ets/pages/TestImageFlash.ets new file mode 100644 index 0000000..75144e0 --- /dev/null +++ b/entry/src/main/ets/pages/TestImageFlash.ets @@ -0,0 +1,137 @@ +/* + * 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 { ImageKnifeComponent } from '@ohos/libraryimageknife' + +@Observed +export class MsgModel { + id: string + cId: string + body: string + status: number + + constructor(id: string, body: string, cId?: string) { + this.id = id + this.body = body + this.status = -1 + this.cId = cId || '' + } +} + + +@Reusable +@Component +export struct MsgItem { + count: number = 0 + private data: Array = [ + "http://e.hiphotos.baidu.com/image/pic/item/a1ec08fa513d2697e542494057fbb2fb4316d81e.jpg", + "http://c.hiphotos.baidu.com/image/pic/item/30adcbef76094b36de8a2fe5a1cc7cd98d109d99.jpg", + "http://h.hiphotos.baidu.com/image/pic/item/7c1ed21b0ef41bd5f2c2a9e953da81cb39db3d1d.jpg", + "http://g.hiphotos.baidu.com/image/pic/item/55e736d12f2eb938d5277fd5d0628535e5dd6f4a.jpg", + "http://e.hiphotos.baidu.com/image/pic/item/4e4a20a4462309f7e41f5cfe760e0cf3d6cad6ee.jpg", + "http://b.hiphotos.baidu.com/image/pic/item/9d82d158ccbf6c81b94575cfb93eb13533fa40a2.jpg", + "http://e.hiphotos.baidu.com/image/pic/item/4bed2e738bd4b31c1badd5a685d6277f9e2ff81e.jpg", + "http://g.hiphotos.baidu.com/image/pic/item/0d338744ebf81a4c87a3add4d52a6059252da61e.jpg", + "http://a.hiphotos.baidu.com/image/pic/item/f2deb48f8c5494ee5080c8142ff5e0fe99257e19.jpg", + "http://f.hiphotos.baidu.com/image/pic/item/4034970a304e251f503521f5a586c9177e3e53f9.jpg", + "http://b.hiphotos.baidu.com/image/pic/item/279759ee3d6d55fbb3586c0168224f4a20a4dd7e.jpg", + "http://img2.xkhouse.com/bbs/hfhouse/data/attachment/forum/corebbs/2009-11/2009113011534566298.jpg", + "http://a.hiphotos.baidu.com/image/pic/item/e824b899a9014c087eb617650e7b02087af4f464.jpg", + "http://c.hiphotos.baidu.com/image/pic/item/9c16fdfaaf51f3de1e296fa390eef01f3b29795a.jpg", + "http://d.hiphotos.baidu.com/image/pic/item/b58f8c5494eef01f119945cbe2fe9925bc317d2a.jpg", + "http://h.hiphotos.baidu.com/image/pic/item/902397dda144ad340668b847d4a20cf430ad851e.jpg", + "http://b.hiphotos.baidu.com/image/pic/item/359b033b5bb5c9ea5c0e3c23d139b6003bf3b374.jpg", + "http://a.hiphotos.baidu.com/image/pic/item/8d5494eef01f3a292d2472199d25bc315d607c7c.jpg", + "http://b.hiphotos.baidu.com/image/pic/item/e824b899a9014c08878b2c4c0e7b02087af4f4a3.jpg", + "http://g.hiphotos.baidu.com/image/pic/item/6d81800a19d8bc3e770bd00d868ba61ea9d345f2.jpg", + ] + build(){ + if (this.count % 2 == 0 && this.count <6){ + ImageKnifeComponent({ + imageKnifeOption:{ + loadSrc:$r("app.media.startIcon"), + placeholderSrc:$r("app.media.loading") + },syncLoad:true + }) + }else if (this.count > 6 && this.count - 6 < this.data.length){ + ImageKnifeComponent({ + imageKnifeOption:{ + loadSrc:this.data[this.count - 6], + placeholderSrc:$r("app.media.loading") + },syncLoad:true + }) + }else { + ImageKnifeComponent({ + imageKnifeOption:{ + loadSrc:$r("app.media.pngSample"), + placeholderSrc:$r("app.media.loading") + },syncLoad:true + }) + } + } +} + +@Entry +@Component +struct ImageTestPage { + count : number = 0 + rCount: number = 0 + scroller: Scroller = new Scroller() + @State list: MsgModel[] = [] + @State imageSize: number =100 + handAdd(){ + this.count++ + const msgItem = new MsgModel('add_id'+this.count, 'addBody'+this.count,'cId'+ this.count) + this.list.push(msgItem) + setTimeout(()=> { + msgItem.status = 1 + },3000) + this.scroller.scrollEdge(Edge.Bottom) + } + + build(){ + Column(){ + Row(){ + Button("addItem").onClick(()=> { + this.handAdd() + }) + Button("remove").onClick(()=> { + this.list.splice(0,1) + }) + } + Row(){ + Text("点击尺寸加50") + .onClick(()=> { + this.imageSize = this.imageSize + 50 + }) + .width('50%').backgroundColor(0x88ff0000).textAlign(TextAlign.Center).height(50) + Text("点击尺寸减50") + .onClick(()=> { + this.imageSize = Math.max(this.imageSize - 50, 0) + }) + .width('50%').backgroundColor(0x88ff0000).textAlign(TextAlign.Center).height(50) + }.height(50).width('100%') + + List({space: 20, scroller: this.scroller }) { + ForEach(this.list, (item: MsgModel)=> { + ListItem(){ + MsgItem({count : this.count}).width(this.imageSize).height(this.imageSize); + } + },(item:MsgModel)=> item.id) + }.width('100%').height('auto').layoutWeight(1) + } + .width('100%') + .height('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/TestIsUrlExist.ets b/entry/src/main/ets/pages/TestIsUrlExist.ets new file mode 100644 index 0000000..1225edc --- /dev/null +++ b/entry/src/main/ets/pages/TestIsUrlExist.ets @@ -0,0 +1,86 @@ +/* + * 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 { ImageKnifeComponent, ImageKnife, ImageKnifeOption, CacheStrategy } from '@ohos/libraryimageknife' + +@Entry +@Component +struct TestIsUrlExist { + @State imageKnifeOption: ImageKnifeOption = { + loadSrc: $r('app.media.startIcon'), + placeholderSrc: $r('app.media.loading'), + errorholderSrc:$r('app.media.failed') + } + @State source: PixelMap | string | Resource = $r("app.media.startIcon") + @State source1: PixelMap | string | Resource = $r("app.media.startIcon") + + build() { + Column() { + Flex() { + Button("预加载gif图").onClick(() => { + this.imageKnifeOption.loadSrc = + "https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658" + }) + Button("内存缓存获取gif").onClick(() => { + ImageKnife.getInstance() + .getCacheImage("https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658", + CacheStrategy.Memory) + .then((data) => { + this.source = data !== undefined ? data.source : $r("app.media.startIcon") + }) + }) + Button("文件缓存获取gif").onClick(() => { + ImageKnife.getInstance() + .getCacheImage("https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658", + CacheStrategy.File) + .then((data) => { + this.source1 = data !== undefined ? data.source : $r("app.media.startIcon") + }) + }) + } + + Flex() { + Button("预加载静态图").onClick(() => { + this.imageKnifeOption.loadSrc = + 'https://hbimg.huabanimg.com/95a6d37a39aa0b70d48fa18dc7df8309e2e0e8e85571e-x4hhks_fw658/format/webp' + }) + Button("内存缓存获取").onClick(() => { + ImageKnife.getInstance() + .getCacheImage('https://hbimg.huabanimg.com/95a6d37a39aa0b70d48fa18dc7df8309e2e0e8e85571e-x4hhks_fw658/format/webp', + CacheStrategy.Memory) + .then((data) => { + this.source = data!.source + }) + }) + Button("文件缓存获取").onClick(() => { + ImageKnife.getInstance() + .getCacheImage('https://hbimg.huabanimg.com/95a6d37a39aa0b70d48fa18dc7df8309e2e0e8e85571e-x4hhks_fw658/format/webp', + CacheStrategy.File) + .then((data) => { + this.source1 = data!.source + }) + }) + }.margin({ top: 10 }) + + ImageKnifeComponent({ + imageKnifeOption: this.imageKnifeOption + }).width(200).height(200).margin({ top: 10 }) + Image(this.source).margin({ top: 10 }) + .width(200).height(200) + Image(this.source1).margin({ top: 10 }) + .width(200).height(200) + } + .height('100%').width('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/TestPrefetchToFileCache.ets b/entry/src/main/ets/pages/TestPrefetchToFileCache.ets new file mode 100644 index 0000000..eb735a7 --- /dev/null +++ b/entry/src/main/ets/pages/TestPrefetchToFileCache.ets @@ -0,0 +1,50 @@ +/* + * 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 { ImageKnifeComponent,ImageKnife,ImageKnifeOption } from '@ohos/libraryimageknife' + +@Entry +@Component +struct TestPrefetchToFileCachePage { + @State imageKnifeOption: ImageKnifeOption = { + loadSrc:$r('app.media.startIcon'), + placeholderSrc:$r('app.media.loading'), + errorholderSrc:$r('app.media.failed') + } + async preload(url:string) { + let fileCachePath = await ImageKnife.getInstance().preLoadCache(url) + console.log("preload-fileCachePath=="+ fileCachePath) + } + async preload1(url:string) { + let fileCachePath = await ImageKnife.getInstance().preLoadCache({ loadSrc: url }) + console.log("preload-fileCachePath1=="+ fileCachePath) + } + build() { + Column() { + Button("url预加载图片到文件缓存").margin({top:10}).onClick(async ()=>{ + await this.preload("https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658") + }) + Button("option预加载图片到文件缓存").margin({top:10}).onClick(async ()=>{ + await this.preload1("https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658") + }) + Button("加载图片(预加载后可断网加载)").margin({top:10}).onClick(()=>{ + this.imageKnifeOption.loadSrc = "https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658" + }) + ImageKnifeComponent({ + imageKnifeOption: this.imageKnifeOption + }).width(300).height(300).margin({top:30}) + } + .height('100%') .width('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/TestRemoveCache.ets b/entry/src/main/ets/pages/TestRemoveCache.ets new file mode 100644 index 0000000..13e7def --- /dev/null +++ b/entry/src/main/ets/pages/TestRemoveCache.ets @@ -0,0 +1,126 @@ +/* + * 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 { ImageKnifeComponent, ImageKnife, ImageKnifeOption, CacheStrategy } from '@ohos/libraryimageknife' + +@Entry +@Component +struct TestRemoveCache { + @State imageKnifeOption: ImageKnifeOption = { + loadSrc: $r('app.media.startIcon'), + placeholderSrc: $r('app.media.loading'), + errorholderSrc:$r('app.media.failed') + } + @State source: PixelMap | string | Resource = $r("app.media.startIcon"); + @State source1: PixelMap | string | Resource = $r("app.media.startIcon"); + @State url: string = ''; + + build() { + Column() { + Flex() { + Button("预加载gif图").onClick(() => { + this.imageKnifeOption.loadSrc = "https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658"; + this.url = "https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658"; + }) + .margin({left:10}) + Button("内存缓存获取gif").onClick(() => { + ImageKnife.getInstance() + .getCacheImage("https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658", + CacheStrategy.Memory) + .then((data) => { + this.source = data !== undefined ? data.source : $r("app.media.startIcon"); + }) + }) + .margin({left:10}) + Button("文件缓存获取gif").onClick(() => { + ImageKnife.getInstance() + .getCacheImage("https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658", + CacheStrategy.File) + .then((data) => { + this.source1 = data !== undefined ? data.source : $r("app.media.startIcon"); + }) + }) + .margin({left:10}) + } + + Flex() { + Button("预加载静态图").onClick(() => { + this.imageKnifeOption.loadSrc = 'https://hbimg.huabanimg.com/95a6d37a39aa0b70d48fa18dc7df8309e2e0e8e85571e-x4hhks_fw658/format/webp'; + this.url = 'https://hbimg.huabanimg.com/95a6d37a39aa0b70d48fa18dc7df8309e2e0e8e85571e-x4hhks_fw658/format/webp'; + }) + .margin({left:10}) + Button("内存缓存获取").onClick(() => { + ImageKnife.getInstance() + .getCacheImage('https://hbimg.huabanimg.com/95a6d37a39aa0b70d48fa18dc7df8309e2e0e8e85571e-x4hhks_fw658/format/webp', + CacheStrategy.Memory) + .then((data) => { + this.source = data!.source; + }) + }) + .margin({left:10}) + Button("文件缓存获取").onClick(() => { + ImageKnife.getInstance() + .getCacheImage('https://hbimg.huabanimg.com/95a6d37a39aa0b70d48fa18dc7df8309e2e0e8e85571e-x4hhks_fw658/format/webp', + CacheStrategy.File) + .then((data) => { + this.source1 = data!.source; + }) + }) + .margin({left:10}) + }.margin({ top: 10 }) + + Flex() { + Button("删除全部缓存").onClick(() => { + ImageKnife.getInstance() + .removeAllMemoryCache() + ImageKnife.getInstance() + .removeAllFileCache() + }) + .margin({left:5}) + Button("删除全部内存缓存").onClick(() => { + ImageKnife.getInstance() + .removeAllMemoryCache() + }) + .margin({left:5}) + Button("删除全部文件缓存").onClick(() => { + ImageKnife.getInstance() + .removeAllFileCache() + }) + .margin({left:5}) + }.margin({ top: 10 }) + + Flex() { + Button("删除自定义内存缓存").onClick(() => { + ImageKnife.getInstance() + .removeMemoryCache(this.url) + }) + .margin({left:20}) + Button("删除自定义文件缓存").onClick(() => { + ImageKnife.getInstance() + .removeFileCache(this.url) + }) + .margin({left:20}) + }.margin({ top: 10 }) + + ImageKnifeComponent({ + imageKnifeOption: this.imageKnifeOption + }).width(200).height(200).margin({ top: 10 }) + Image(this.source).margin({ top: 10 }) + .width(200).height(200) + Image(this.source1).margin({ top: 10 }) + .width(200).height(200) + } + .height('100%').width('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/TestSetCustomImagePage.ets b/entry/src/main/ets/pages/TestSetCustomImagePage.ets new file mode 100644 index 0000000..809769f --- /dev/null +++ b/entry/src/main/ets/pages/TestSetCustomImagePage.ets @@ -0,0 +1,64 @@ +/* + * 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 { ImageKnifeComponent, ImageKnife, ImageKnifeOption } from '@ohos/libraryimageknife' + +@Entry +@Component +struct TestSetCustomImagePage { + @State imageKnifeOption: ImageKnifeOption = { + loadSrc: $r('app.media.startIcon'), + placeholderSrc: $r('app.media.loading') + } + aboutToAppear(): void { + ImageKnife.getInstance().setCustomGetImage(custom) + } + aboutToDisappear(): void { + ImageKnife.getInstance().setCustomGetImage() + } + build() { + Column() { + Button("自定义下载a").onClick(()=>{ + this.imageKnifeOption = { + loadSrc: "aaa", + placeholderSrc: $r('app.media.loading') + } + }) + Button("自定义下载b").onClick(()=>{ + this.imageKnifeOption = { + loadSrc: "bbb", + placeholderSrc: $r('app.media.loading') + } + }) + Button("自定义下载c").onClick(()=>{ + this.imageKnifeOption = { + loadSrc: "ccc", + placeholderSrc: $r('app.media.loading') + } + }) + ImageKnifeComponent({ + imageKnifeOption: this.imageKnifeOption + }).width(300) + .height(300) + } + .width("100%") + .height("100%") + } +} +@Concurrent +async function custom(context: Context, src: string | PixelMap | Resource): Promise { + console.info("ImageKnife:: custom download:" + src) + // 举例写死从本地文件读取,也可以自己请求网络图片 + return context.resourceManager.getMediaContentSync($r("app.media.pngSample").id).buffer as ArrayBuffer +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/TestWriteCacheStage.ets b/entry/src/main/ets/pages/TestWriteCacheStage.ets new file mode 100644 index 0000000..6b0b203 --- /dev/null +++ b/entry/src/main/ets/pages/TestWriteCacheStage.ets @@ -0,0 +1,74 @@ +/* + * 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 { ImageKnifeComponent,CacheStrategy,ImageKnifeOption } from '@ohos/libraryimageknife' + +@Entry +@Component +struct TestWriteCacheStage { + @State imageKnifeOption1: ImageKnifeOption = { + loadSrc:$r('app.media.startIcon'), + placeholderSrc:$r('app.media.loading'), + errorholderSrc:$r('app.media.failed') + } + @State imageKnifeOption2: ImageKnifeOption = { + loadSrc:$r('app.media.startIcon'), + placeholderSrc:$r('app.media.loading'), + errorholderSrc:$r('app.media.failed') + } + @State imageKnifeOption3: ImageKnifeOption = { + loadSrc:$r('app.media.startIcon'), + placeholderSrc:$r('app.media.loading'), + errorholderSrc:$r('app.media.failed') + } + + build() { + Column() { + Button("写入内存文件缓存").margin({top:10}).onClick(async ()=>{ + this.imageKnifeOption1 = { + loadSrc:'https://hbimg.huabanimg.com/95a6d37a39aa0b70d48fa18dc7df8309e2e0e8e85571e-x4hhks_fw658/format/webp', + placeholderSrc:$r('app.media.loading'), + errorholderSrc:$r('app.media.failed'), + writeCacheStrategy:CacheStrategy.Default + } + }) + ImageKnifeComponent({ + imageKnifeOption: this.imageKnifeOption1 + }).width(200).height(200).margin({top:10}) + Button("写入内存缓存").margin({top:10}).onClick(async ()=>{ + this.imageKnifeOption2 = { + loadSrc:"https://hbimg.huabanimg.com/cc6af25f8d782d3cf3122bef4e61571378271145735e9-vEVggB", + placeholderSrc:$r('app.media.loading'), + errorholderSrc:$r('app.media.failed'), + writeCacheStrategy:CacheStrategy.Memory + } + }) + ImageKnifeComponent({ + imageKnifeOption: this.imageKnifeOption2 + }).width(200).height(200).margin({top:10}) + Button("写入文件缓存").margin({top:10}).onClick(async ()=>{ + this.imageKnifeOption3 = { + loadSrc:'https://img-blog.csdn.net/20140514114029140', + placeholderSrc:$r('app.media.loading'), + errorholderSrc:$r('app.media.failed'), + writeCacheStrategy:CacheStrategy.File + } + }) + ImageKnifeComponent({ + imageKnifeOption: this.imageKnifeOption3 + }).width(200).height(200).margin({top:10}) + } + .height('100%') .width('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/TransformPage.ets b/entry/src/main/ets/pages/TransformPage.ets new file mode 100644 index 0000000..7674156 --- /dev/null +++ b/entry/src/main/ets/pages/TransformPage.ets @@ -0,0 +1,51 @@ +/* + * 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 { ImageKnifeComponent, ImageKnifeOption } from "@ohos/libraryimageknife" +import matrix4 from '@ohos.matrix4' + + +@Entry +@Component +struct TransformPage { + private custom_scale:number = 1 + @State matrix1:object = matrix4.identity().scale({ x: 1, y: 1 }) + @State ImageKnifeOption: ImageKnifeOption = { + loadSrc: $r("app.media.rabbit"), + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + objectFit: ImageFit.Contain, + border: { radius: 50 } + } + + build() { + Column() { + 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(()=>{ + this.custom_scale = this.custom_scale * 2 + this.matrix1 = matrix4.identity().scale({ x: this.custom_scale, y: this.custom_scale }) + }) + + Button("缩小").onClick(()=>{ + this.custom_scale = this.custom_scale / 2 + this.matrix1 = matrix4.identity().scale({ x: this.custom_scale, y: this.custom_scale }) + }) + } + .width('100%') + + .height('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/User.ets b/entry/src/main/ets/pages/User.ets new file mode 100644 index 0000000..1e3e582 --- /dev/null +++ b/entry/src/main/ets/pages/User.ets @@ -0,0 +1,98 @@ +/* + * 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 { ImageKnifeComponent, ImageKnifeOption } from '@ohos/libraryimageknife' + +// const logger = new imUtils.logger.IMLogger('Avatar') + +class MyImageOption extends ImageKnifeOption { + account?: string +} + +@Component +export struct UserAvatar { + @Prop @Watch('userInfoUpdate') userInfo: string = "" + // @Prop userInfo: string = "" + imgSize: number = 100 + radius: number = 12 + borderSize: number = 0 + imgSizes: number = 1 + @State ImageKnifeOption: ImageKnifeOption = new ImageKnifeOption() + @StorageProp('WeLink_Mob_fontSize_multiple') @Watch('updateImgSize') WeLink_Mob_fontSize_multiple: number = 0 + scalable: boolean = true; + @State calcImgSize: number = 100 + + aboutToAppear(): void { + this.userInfoUpdate() + this.setImageSize() + } + + setImageSize() { + if (!this.scalable) { + this.calcImgSize = this.imgSize + } else if (this.WeLink_Mob_fontSize_multiple < 0.9) { + this.calcImgSize = this.imgSize * 0.9 + } else if (this.WeLink_Mob_fontSize_multiple > 1.6) { + this.calcImgSize = this.imgSize * 1.6 + } else { + this.calcImgSize = this.imgSize * this.WeLink_Mob_fontSize_multiple + } + } + + updateImgSize() { + this.setImageSize() + } + + aboutToReuse(param: ESObject) { + this.userInfoUpdate() + } + + userInfoUpdate() { + // if (uri === 'userInfo' && this.imageKnifeOption.account !== this.userInfo.contactId) return; + // // logger.info(`userInfoUpdate uri=${uri} oldAcc=${this.imageKnifeOption.loadSrc} nowAcc=${this.userInfo.externalHeadUrl}`) + // if (this.userInfo.externalHeadUrl === this.imageKnifeOption.loadSrc && this.userInfo.infoUpdateTime.getTime() + // .toString() === this.imageKnifeOption?.signature?.getKey()) return; + this.ImageKnifeOption = { + //TODO:写死loadSRC,场景:变更组件大小,所有图片不显示 + loadSrc: this.userInfo, + placeholderSrc: $r('app.media.loading'), + errorholderSrc: $r('app.media.failed'), + border: { radius:20,width:5,color:$r('app.color.start_window_background') }, + objectFit:ImageFit.Contain + // signature: new ObjectKey(this.userInfo.infoUpdateTime.getTime().toString()) + } + } + + build() { + Row() { + // Image(this.imageKnifeOption.loadSrc) + + ImageKnifeComponent({ imageKnifeOption: this.ImageKnifeOption }) + .borderRadius(this.radius) + .clip(true) + .width(this.calcImgSize) + .height(this.calcImgSize) + .backgroundColor(Color.Pink) + + + + // Image(this.userInfo) + // Text((this.imageKnifeOption.loadSrc as string).split('/')[8]) + // .position({ x: 0, y: 0 }) + // .zIndex(9) + // .fontSize(12) + // .fontColor('#ff0000') + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/UserPage.ets b/entry/src/main/ets/pages/UserPage.ets new file mode 100644 index 0000000..ee210bc --- /dev/null +++ b/entry/src/main/ets/pages/UserPage.ets @@ -0,0 +1,133 @@ +/* + * 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 { UserAvatar } from './User' + +class CommonDataSource implements IDataSource { + private dataArray: T[] = [] + private listeners: DataChangeListener[] = [] + + constructor(element: []) { + this.dataArray = element + } + + public getData(index: number) { + return this.dataArray[index] + } + + public totalCount(): number { + return this.dataArray.length + } + + public addData(index: number, data: T[]): void { + this.dataArray = this.dataArray.concat(data) + this.notifyDataAdd(index) + } + + unregisterDataChangeListener(listener: DataChangeListener): void { + const pos = this.listeners.indexOf(listener); + if (pos >= 0) { + this.listeners.splice(pos, 1); + } + } + + registerDataChangeListener(listener: DataChangeListener): void { + if (this.listeners.indexOf(listener) < 0) { + this.listeners.push(listener) + } + } + + notifyDataAdd(index: number): void { + this.listeners.forEach((listener: DataChangeListener) => { + listener.onDataAdd(index) + }) + } +} +@Entry +@Component +struct Index { + @State hotCommendList:CommonDataSource = new CommonDataSource([]) + private data:string[] = [ + "http://e.hiphotos.baidu.com/image/pic/item/a1ec08fa513d2697e542494057fbb2fb4316d81e.jpg", + "http://c.hiphotos.baidu.com/image/pic/item/30adcbef76094b36de8a2fe5a1cc7cd98d109d99.jpg", + "http://h.hiphotos.baidu.com/image/pic/item/7c1ed21b0ef41bd5f2c2a9e953da81cb39db3d1d.jpg", + "http://g.hiphotos.baidu.com/image/pic/item/55e736d12f2eb938d5277fd5d0628535e5dd6f4a.jpg", + "http://e.hiphotos.baidu.com/image/pic/item/4e4a20a4462309f7e41f5cfe760e0cf3d6cad6ee.jpg", + "http://b.hiphotos.baidu.com/image/pic/item/9d82d158ccbf6c81b94575cfb93eb13533fa40a2.jpg", + "http://e.hiphotos.baidu.com/image/pic/item/4bed2e738bd4b31c1badd5a685d6277f9e2ff81e.jpg", + "http://g.hiphotos.baidu.com/image/pic/item/0d338744ebf81a4c87a3add4d52a6059252da61e.jpg", + "http://a.hiphotos.baidu.com/image/pic/item/f2deb48f8c5494ee5080c8142ff5e0fe99257e19.jpg", + "http://f.hiphotos.baidu.com/image/pic/item/4034970a304e251f503521f5a586c9177e3e53f9.jpg", + "http://b.hiphotos.baidu.com/image/pic/item/279759ee3d6d55fbb3586c0168224f4a20a4dd7e.jpg", + "http://img2.xkhouse.com/bbs/hfhouse/data/attachment/forum/corebbs/2009-11/2009113011534566298.jpg", + "http://a.hiphotos.baidu.com/image/pic/item/e824b899a9014c087eb617650e7b02087af4f464.jpg", + "http://c.hiphotos.baidu.com/image/pic/item/9c16fdfaaf51f3de1e296fa390eef01f3b29795a.jpg", + "http://d.hiphotos.baidu.com/image/pic/item/b58f8c5494eef01f119945cbe2fe9925bc317d2a.jpg", + "http://h.hiphotos.baidu.com/image/pic/item/902397dda144ad340668b847d4a20cf430ad851e.jpg", + "http://b.hiphotos.baidu.com/image/pic/item/359b033b5bb5c9ea5c0e3c23d139b6003bf3b374.jpg", + "http://a.hiphotos.baidu.com/image/pic/item/8d5494eef01f3a292d2472199d25bc315d607c7c.jpg", + "http://b.hiphotos.baidu.com/image/pic/item/e824b899a9014c08878b2c4c0e7b02087af4f4a3.jpg", + "http://g.hiphotos.baidu.com/image/pic/item/6d81800a19d8bc3e770bd00d868ba61ea9d345f2.jpg", + "https://hbimg.huabanimg.com/cc6af25f8d782d3cf3122bef4e61571378271145735e9-vEVggB", + 'https://img-blog.csdnimg.cn/20191215043500229.png', + 'https://img-blog.csdn.net/20140514114029140', + 'https://hbimg.huabanimg.com/95a6d37a39aa0b70d48fa18dc7df8309e2e0e8e85571e-x4hhks_fw658/format/webp', + ] + aboutToAppear(): void { + this.hotCommendList.addData(this.hotCommendList.totalCount(),this.data) + AppStorage.set("WeLink_Mob_fontSize_multiple",1) + } + + build() { + Column() { + Button("bigger").onClick(()=>{ + AppStorage.set("WeLink_Mob_fontSize_multiple",1.6) + }) + Button("small").onClick(()=>{ + AppStorage.set("WeLink_Mob_fontSize_multiple",0.8) + }) + List(){ + LazyForEach(this.hotCommendList,(item:string)=>{ + ListItem(){ + ReuseImage({ + userInfo:item + }).width("100%").height("100%").backgroundColor(Color.Yellow) + }.width(200).height(200).margin({bottom:5}) + }) + } + // .cachedCount(20) + .width("100%") + .height("100%") + .backgroundColor(0xFAEEE0) + }.width('100%').height("100%") + } +} + + +@Reusable +@Component +struct ReuseImage { + @State userInfo:string = "" + aboutToReuse(params: ESObject): void { + this.userInfo = params.userInfo + } + + build() { + Column(){ + UserAvatar({ + userInfo:this.userInfo + }) + }.width("100%").height("100%") + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/dataShareUriLoadPage.ets b/entry/src/main/ets/pages/dataShareUriLoadPage.ets index b3fe696..bb71ac2 100644 --- a/entry/src/main/ets/pages/dataShareUriLoadPage.ets +++ b/entry/src/main/ets/pages/dataShareUriLoadPage.ets @@ -24,8 +24,8 @@ struct DataShareUriLoadPage { { loadSrc: $r('app.media.icon'), - placeholderSrc: $r('app.media.icon_loading'), - errorholderSrc: $r('app.media.icon_failed') + placeholderSrc: $r('app.media.loading'), + errorholderSrc: $r('app.media.failed') }; @@ -45,7 +45,7 @@ struct DataShareUriLoadPage { uris = photoSelectResult.photoUris; this.imageKnifeOption1 = { loadSrc: uris[0], - placeholderSrc:$r('app.media.icon_loading') + placeholderSrc:$r('app.media.loading') } }).margin({ top: 5, left: 3 }) ImageKnifeComponent({ imageKnifeOption: this.imageKnifeOption1 }).width(300).height(300) diff --git a/entry/src/main/ets/pages/index.ets b/entry/src/main/ets/pages/index.ets deleted file mode 100644 index 63f783a..0000000 --- a/entry/src/main/ets/pages/index.ets +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2022 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import router from '@ohos.router'; -import { - ImageKnifeComponent, - ImageKnifeOption, - ImageKnifeGlobal, - ImageKnife, - HeaderOptions -} from '@ohos/libraryimageknife' - -import { ObjectKey } from '@ohos/libraryimageknife'; - -@Entry -@Component -struct IndexFunctionDemo { - @State headerOptions1: HeaderOptions = { - key: "refer", - value: "http://1.94.37.200:7070/AntiTheftChain/downloadImage" - }; - @State imageKnifeOption1: ImageKnifeOption = - { - loadSrc: $r('app.media.icon'), - placeholderSrc: $r('app.media.icon_loading'), - errorholderSrc: $r('app.media.icon_failed'), - headerOption: [this.headerOptions1] - }; - @State imageKnifeOption2: ImageKnifeOption = - { - loadSrc: $r('app.media.icon'), - placeholderSrc: $r('app.media.icon_loading'), - errorholderSrc: $r('app.media.icon_failed'), - headerOption: [this.headerOptions1] - }; - build() { - Scroll() { - Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { - Text("简单示例1:加载一张本地png图片").fontSize(15) - Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { - Button("加载PNG") - .onClick(() => { - this.imageKnifeOption1 = { - loadSrc: $r('app.media.pngSample'), - placeholderSrc: $r('app.media.icon_loading'), - errorholderSrc: $r('app.media.icon_failed'), - signature: new ObjectKey('ccccccc') - } - }).margin({ top: 5, left: 3 }) - ImageKnifeComponent({ imageKnifeOption: this.imageKnifeOption1 }).width(300).height(300) - }.width('100%').backgroundColor(Color.Pink) - - Text("简单示例2:加载一张网络gif图片").fontSize(15) - Text("gif解析在子线程,请在页面构建后创建worker,注入imageknife").fontSize(15) - Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { - Button("加载GIF") - .onClick(() => { - this.imageKnifeOption2 = { - loadSrc: 'https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658', - placeholderSrc: $r('app.media.icon_loading'), - errorholderSrc: $r('app.media.icon_failed'), - displayProgress:true, - } - }).margin({ top: 5, left: 3 }) - ImageKnifeComponent({ imageKnifeOption: this.imageKnifeOption2 }).width(300).height(300) - }.width('100%').backgroundColor(Color.Pink) - - Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { - Button("ImageKnife测试目录页面") - .onClick(() => { - console.log("pages/imageknifeTestCaseIndex 页面跳转") - router.pushUrl({ url: "pages/imageknifeTestCaseIndex" }); - }).margin({ top: 15 }) - }.width('100%').height(60).backgroundColor(Color.Pink) - } - } - .width('100%') - .height('100%') - } - - aboutToAppear() { - - } - aboutToDisappear(){ - - } - - onBackPress() { - - } -} \ No newline at end of file diff --git a/entry/src/main/ets/pages/manyPhotoShowPage.ets b/entry/src/main/ets/pages/manyPhotoShowPage.ets deleted file mode 100644 index dffbb0a..0000000 --- a/entry/src/main/ets/pages/manyPhotoShowPage.ets +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2021 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 {ImageKnifeComponent, ScaleType} from '@ohos/libraryimageknife' -import {ImageKnifeOption} from '@ohos/libraryimageknife' -import {ImageKnifeGlobal} from '@ohos/libraryimageknife' -import {RotateImageTransformation} from '@ohos/libraryimageknife' -import {Material} from './model/Material' -import {TestDataSource} from './model/TestDataSource' -import {DiskLruCache} from '@ohos/disklrucache' -import ArkWorker from '@ohos.worker' -import Prompt from '@system.prompt' -@Entry -@Component -struct ManyPhotoShowPage { - private data: TestDataSource = new TestDataSource(); - - private elementScroller: Scroller = new Scroller() - - build() { - Column() { - - List({ space: 20, scroller: this.elementScroller }) { - LazyForEach(this.data, (item: Material, index) => { - ListItem() { - Column() { - Stack({ alignContent: Alignment.BottomEnd }) { - // 滤镜图片 - ImageKnifeComponent({ imageKnifeOption: { - loadSrc: item.thumbnail, - mainScaleType: ScaleType.FIT_XY, - } }) - } - .width(56).height(56) - //滤镜标题 - Text(item.name) - .fontSize(10) - .maxLines(1) - .fontColor(Color.White) - .textAlign(TextAlign.Center) - .layoutWeight(1) - .width('100%') - .backgroundColor(Color.Orange) - } - .width(56) - .height(72) - .clip(true) - .borderRadius(4) - } - }, (item: Material) => item.material_id) - } - .listDirection(Axis.Horizontal) - .width('100%') - .height(72) - - } - } - - - -} - - diff --git a/entry/src/main/module.json5 b/entry/src/main/module.json5 index c83bbef..9036eb0 100644 --- a/entry/src/main/module.json5 +++ b/entry/src/main/module.json5 @@ -2,33 +2,26 @@ "module": { "name": "entry", "type": "entry", - - "description": "$string:entry_desc", - "mainElement": "MainAbility", + "description": "$string:module_desc", + "mainElement": "EntryAbility", "deviceTypes": [ - "default", - "tablet" + "phone", + "tablet", + "2in1" ], "deliveryWithInstall": true, "installationFree": false, "pages": "$profile:main_pages", - "metadata": [ - { - "name": "ArkTSPartialUpdate", - "value": "true" - } - ], - "uiSyntax": "ets", "abilities": [ { - "name": "MainAbility", + "name": "EntryAbility", "srcEntry": "./ets/entryability/EntryAbility.ets", - "description": "$string:MainAbility_desc", + "description": "$string:EntryAbility_desc", "icon": "$media:icon", - "label": "$string:MainAbility_label", - "startWindowIcon": "$media:icon", - "startWindowBackground": "$color:test_color", - "visible": true, + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:startIcon", + "startWindowBackground": "$color:start_window_background", + "exported": true, "skills": [ { "entities": [ diff --git a/entry/src/main/resources/base/element/color.json b/entry/src/main/resources/base/element/color.json index 6146fec..3c71296 100644 --- a/entry/src/main/resources/base/element/color.json +++ b/entry/src/main/resources/base/element/color.json @@ -1,8 +1,8 @@ { "color": [ { - "name": "test_color", - "value": "#3d2564" + "name": "start_window_background", + "value": "#FFFFFF" } ] } \ No newline at end of file diff --git a/entry/src/main/resources/base/element/string.json b/entry/src/main/resources/base/element/string.json index 8d7b7cf..13c1fec 100644 --- a/entry/src/main/resources/base/element/string.json +++ b/entry/src/main/resources/base/element/string.json @@ -1,125 +1,24 @@ { "string": [ { - "name": "entry_desc", + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", "value": "description" }, { - "name": "MainAbility_desc", - "value": "description" - }, - { - "name": "MainAbility_label", - "value": "ImageKnife" - }, - - { - "name": "ImageKnife_OHOS", - "value": "ImageKnife_OHOS" - }, - { - "name": "mainability_description", - "value": "ETS_Empty Ability" - }, - { - "name": "left_top_corner", - "value": "左上角" - }, - { - "name": "r_top_corner", - "value": "右上角" - }, - { - "name": "left_bottom_corner", - "value": "左下角" - }, - { - "name": "r_bottom_corner", - "value": "右下角" - }, - { - "name": "trans_circle", - "value": "裁剪-圆" - }, - { - "name": "trans_circle_border", - "value": "裁剪-圆环" - }, - { - "name": "trans_rotate", - "value": "旋转" - }, - { - "name": "trans_square", - "value": "正方形裁剪" - }, - { - "name": "trans_clip_top", - "value": "上方裁剪" - }, - { - "name": "trans_clip_center", - "value": "中间裁剪" - }, - { - "name": "trans_clip_bottom", - "value": "底下裁剪" - }, - { - "name": "resource_image_compress", - "value": "资源图片压缩" - }, - { - "name": "file_image_compress", - "value": "本地文件图片压缩" - }, - { - "name": "image_transform", - "value": "图片变换" - }, - { - "name": "image_compress", - "value": "图片压缩" - }, - { - "name": "image_grayscale", - "value": "灰度处理" - }, - { - "name": "image_Brightness", - "value": "亮度处理" - }, - { - "name": "image_Contrast", - "value": "对比度处理" - }, - { - "name": "image_Invert", - "value": "反转处理" - }, - { - "name": "image_Sepia", - "value": "黑褐色处理" - }, - { - "name": "image_Sketch", - "value": "素描处理" - }, - { - "name": "image_blur", - "value": "模糊处理" - }, - { - "name": "image_pixel", - "value": "马赛克处理" - }, - { - "name": "app_permission_READ_IMAGEVIDEO", - "value": "获取读媒体资源权限" + "name": "EntryAbility_label", + "value": "label" }, { "name": "app_permission_WRITE_IMAGEVIDEO", "value": "获取写入媒体资源权限" + }, + { + "name": "app_permission_READ_IMAGEVIDEO", + "value": "获取读媒体资源权限" } ] } \ No newline at end of file diff --git a/entry/src/main/resources/base/media/aaa.jpg b/entry/src/main/resources/base/media/aaa.jpg new file mode 100644 index 0000000..b4212c2 Binary files /dev/null and b/entry/src/main/resources/base/media/aaa.jpg differ diff --git a/entry/src/main/resources/base/media/failed.png b/entry/src/main/resources/base/media/failed.png new file mode 100644 index 0000000..94c63eb Binary files /dev/null and b/entry/src/main/resources/base/media/failed.png differ diff --git a/entry/src/main/resources/base/media/icon.png b/entry/src/main/resources/base/media/icon.png index ce307a8..cd45acc 100644 Binary files a/entry/src/main/resources/base/media/icon.png and b/entry/src/main/resources/base/media/icon.png differ diff --git a/entry/src/main/resources/base/media/loading.png b/entry/src/main/resources/base/media/loading.png new file mode 100644 index 0000000..e7ddd2e Binary files /dev/null and b/entry/src/main/resources/base/media/loading.png differ diff --git a/entry/src/main/resources/base/media/pngSample.png b/entry/src/main/resources/base/media/pngSample.png index ebe1711..df4a140 100644 Binary files a/entry/src/main/resources/base/media/pngSample.png and b/entry/src/main/resources/base/media/pngSample.png differ diff --git a/entry/src/main/resources/base/media/rabbit.gif b/entry/src/main/resources/base/media/rabbit.gif new file mode 100644 index 0000000..c2c1402 Binary files /dev/null and b/entry/src/main/resources/base/media/rabbit.gif differ diff --git a/entry/src/main/resources/base/media/startIcon.png b/entry/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000..366f764 Binary files /dev/null and b/entry/src/main/resources/base/media/startIcon.png differ diff --git a/entry/src/main/resources/base/profile/main_pages.json b/entry/src/main/resources/base/profile/main_pages.json index ae140a8..42b571c 100644 --- a/entry/src/main/resources/base/profile/main_pages.json +++ b/entry/src/main/resources/base/profile/main_pages.json @@ -1,67 +1,27 @@ { "src": [ - "pages/index", - "pages/CacheRuleChangedPage", - "pages/frescoRetryTestCasePage", - "pages/basicTestFeatureAbilityPage", - "pages/basicTestFileIOPage", - "pages/basicTestMediaImage", - "pages/basicTestResourceManagerPage", - "pages/storageTestLruCache", - "pages/pngjTestCasePage", - "pages/showErrorholderTestCasePage", - "pages/transformPixelMapPage", - "pages/transformsPage", - "pages/testPreloadPage", - "pages/testDiskPreLoadPage", - "pages/testImageKnifeOptionChangedPage", - "pages/testImageKnifeOptionChangedPage2", - "pages/testImageKnifeOptionChangedPage3", - "pages/testImageKnifeOptionChangedPage4", - "pages/testImageKnifeOptionChangedPage5", - "pages/testGifPlayTimesPage", - "pages/compressPage", - "pages/testAllCacheInfoPage", - "pages/cropImagePage2", - "pages/svgTestCasePage", - "pages/imageknifeTestCaseIndex", - "pages/dataShareUriLoadPage", - "pages/manyPhotoShowPage", - "pages/photosPausedResumedPage", - "pages/photosPausedResumedPage2", - "pages/tempUrlTestPage", - "pages/drawFactoryTestPage", - "pages/testSingleFrameGifPage", - "pages/OptionTestPage", + "pages/Index", + "pages/ListPage", + "pages/SingleImage", + "pages/ManyPhotoShowPage", + "pages/LongImagePage", + "pages/TransformPage", + "pages/UserPage", + "pages/TestImageFlash", "pages/SignatureTestPage", - "pages/hspCacheTestPage", - "pages/multiHspTestPage", - "pages/testManyNetImageLoadWithPage", - "pages/testManyNetImageLoadWithPage2", - "pages/testManyGifLoadWithPage", - "pages/testImageAntiAliasingWithPage", - "pages/testImageKnifeRouter1", - "pages/testImageKnifeRouter2", - "pages/RequestOptionLoadImage", - "pages/testImageKnifeHttpRequestHeader", - "pages/testImageKnifeHttpRequestHeader1", - "pages/testImageKnifeAutoPage", - "pages/testImageKnifeAutoWidthPage", - "pages/testImageKnifeAutoHeightPage", - "pages/testPriorityComponent", - "pages/testVisiblePage", - "pages/testReusePhotoPage", - "pages/testImageKnifeCache", - "pages/webpImageTestPage", - "pages/testStopPlayingGifPage", - "pages/testImageKnifeDataFetch", - "pages/testImageKnifeHeic", - "pages/testImageKnifeNetPlaceholder", - "pages/testCustomDataFetchClientWithPage", - "pages/testReuseAblePages", - "pages/downsamplingPage", - "pages/testImageKnifeLoadState", - "pages/testImageKnifeRemoveCache", - "pages/TestDurationAndPlayTimesPage" + "pages/TestPrefetchToFileCache", + "pages/TestIsUrlExist", + "pages/TestHeader", + "pages/ImageTransformation", + "pages/ObjectFitPage", + "pages/TestWriteCacheStage", + "pages/LoadStatePage", + "pages/TestHspPreLoadImage", + "pages/TestRemoveCache", + "pages/dataShareUriLoadPage", + "pages/TestCommonImage", + "pages/ImageAnimatorPage", + "pages/TestSetCustomImagePage", + "pages/TestErrorHolderPage" ] } \ No newline at end of file diff --git a/entry/src/main/resources/en_US/element/string.json b/entry/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000..13c1fec --- /dev/null +++ b/entry/src/main/resources/en_US/element/string.json @@ -0,0 +1,24 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + }, + { + "name": "app_permission_WRITE_IMAGEVIDEO", + "value": "获取写入媒体资源权限" + }, + { + "name": "app_permission_READ_IMAGEVIDEO", + "value": "获取读媒体资源权限" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/zh_CN/element/string.json b/entry/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000..6250115 --- /dev/null +++ b/entry/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,24 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "模块描述" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + }, + { + "name": "app_permission_WRITE_IMAGEVIDEO", + "value": "获取写入媒体资源权限" + }, + { + "name": "app_permission_READ_IMAGEVIDEO", + "value": "获取读媒体资源权限" + } + ] +} \ No newline at end of file diff --git a/entry/src/ohosTest/ets/test/DefaultJobQueueTest.test.ets b/entry/src/ohosTest/ets/test/DefaultJobQueueTest.test.ets index 3212de0..eb13a71 100644 --- a/entry/src/ohosTest/ets/test/DefaultJobQueueTest.test.ets +++ b/entry/src/ohosTest/ets/test/DefaultJobQueueTest.test.ets @@ -12,60 +12,76 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { describe, it, expect } from '@ohos/hypium'; -import { ImageKnife, ImageKnifeGlobal, RequestOption } from '@ohos/imageknife/Index'; -import { DefaultJobQueue } from '@ohos/imageknife/src/main/ets/components/imageknife/utils/DefaultJobQueue'; -import { IJobQueue } from '@ohos/imageknife/src/main/ets/components/imageknife/utils/IJobQueue'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; +import { ImageKnifeOption, ImageKnifeRequest } from '@ohos/imageknife/Index'; +import { DefaultJobQueue } from '@ohos/imageknife/src/main/ets/utils/DefaultJobQueue'; +import { IJobQueue } from '@ohos/imageknife/src/main/ets/utils/IJobQueue'; import taskpool from '@ohos.taskpool'; import common from '@ohos.app.ability.common'; -import { fitter} from '@ohos/imageknife'; + + export default function DefaultJobQueueTest() { describe('DefaultJobQueueTest', () => { - it('testJob', 0, () => { - let job: IJobQueue = new DefaultJobQueue(); - job.add(makeRequest("medium1", getContext() as common.UIAbilityContext)); - job.add(makeRequest("medium2", getContext() as common.UIAbilityContext, taskpool.Priority.MEDIUM)); - job.add(makeRequest("medium3", getContext() as common.UIAbilityContext)); - job.add(makeRequest("low1", getContext() as common.UIAbilityContext, taskpool.Priority.LOW)); - job.add(makeRequest("high1", getContext() as common.UIAbilityContext, taskpool.Priority.HIGH)); - job.add(makeRequest("low2", getContext() as common.UIAbilityContext, taskpool.Priority.LOW)); - job.add(makeRequest("medium4", getContext() as common.UIAbilityContext)); - - expect(job.getQueueLength()).assertEqual(7); - expect(job.pop()!.loadSrc).assertEqual("high1"); - expect(job.pop()!.loadSrc).assertEqual("medium1"); - expect(job.pop()!.loadSrc).assertEqual("medium2"); - expect(job.pop()!.loadSrc).assertEqual("medium3"); - expect(job.pop()!.loadSrc).assertEqual("medium4"); - expect(job.pop()!.loadSrc).assertEqual("low1"); - expect(job.pop()!.loadSrc).assertEqual("low2"); - expect(job.pop()).assertEqual(undefined); - expect(job.getQueueLength()).assertEqual(0); + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + + it('testJob', 0, async () => { + let job: IJobQueue = new DefaultJobQueue() + job.add(makeRequest("medium1", getContext() as common.UIAbilityContext)) + job.add(makeRequest("medium2", getContext() as common.UIAbilityContext, taskpool.Priority.MEDIUM)) + job.add(makeRequest("medium3", getContext() as common.UIAbilityContext)) + job.add(makeRequest("low1", getContext() as common.UIAbilityContext, taskpool.Priority.LOW)) + job.add(makeRequest("high1", getContext() as common.UIAbilityContext, taskpool.Priority.HIGH)) + job.add(makeRequest("low2", getContext() as common.UIAbilityContext, taskpool.Priority.LOW)) + job.add(makeRequest("medium4", getContext() as common.UIAbilityContext)) + + expect(job.getQueueLength()).assertEqual(7) + expect(job.pop()!.imageKnifeOption.loadSrc).assertEqual("high1") + expect(job.pop()!.imageKnifeOption.loadSrc).assertEqual("medium4") + expect(job.pop()!.imageKnifeOption.loadSrc).assertEqual("medium3") + expect(job.pop()!.imageKnifeOption.loadSrc).assertEqual("medium2") + expect(job.pop()!.imageKnifeOption.loadSrc).assertEqual("medium1") + expect(job.pop()!.imageKnifeOption.loadSrc).assertEqual("low2") + expect(job.pop()!.imageKnifeOption.loadSrc).assertEqual("low1") + expect(job.pop()).assertEqual(undefined) + expect(job.getQueueLength()).assertEqual(0) + }); - it("testMaxRequests", 1, () => { - let imageKnife: ImageKnife | undefined = ImageKnifeGlobal.getInstance().getImageKnife(); - if (imageKnife) { - expect(imageKnife.maxRequests).assertEqual(64); - imageKnife.setMaxRequests(10); - expect(imageKnife.maxRequests).assertEqual(10); - }; - }) - it("downsampleOf", 1, () => { - let request: RequestOption = new RequestOption() - if (request) { - let requests = request.downsampleOf(new fitter()) - expect(requests.downsampType.getName()=='FitCenter').assertTrue() - }; - }) }); } -function makeRequest(src: string, context: common.UIAbilityContext, priority: taskpool.Priority = taskpool.Priority.MEDIUM): RequestOption { - let request: RequestOption = new RequestOption() - request.loadSrc = src; - request.priority = priority; - request.moduleContext = context; - return request; +function makeRequest(src: string, context: common.UIAbilityContext, priority?: taskpool.Priority): ImageKnifeRequest { + let option: ImageKnifeOption = { + loadSrc: src, + priority: priority + } + return new ImageKnifeRequest( + option, + context, + 0, + 0, + 0, + { + showPixelMap: async (version: number,pixelMap: PixelMap | string) => { + } + }) } diff --git a/entry/src/ohosTest/ets/test/FileLruCache.test.ets b/entry/src/ohosTest/ets/test/FileLruCache.test.ets new file mode 100644 index 0000000..2dd76f3 --- /dev/null +++ b/entry/src/ohosTest/ets/test/FileLruCache.test.ets @@ -0,0 +1,183 @@ +/* + * 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +import Constants from '../../../main/ets/common/Constants'; +import taskpool from '@ohos.taskpool'; +import { GlobalContext } from '../../../main/ets/common/GlobalContext'; +import { FileCache } from '@ohos/imageknife/src/main/ets/utils/FileCache'; +import { IEngineKey, ImageKnifeOption } from '@ohos/imageknife'; +import { DefaultEngineKey } from '@ohos/imageknife/src/main/ets/key/DefaultEngineKey'; + + +export default function FileLruCacheTest() { + + describe('FileLruCacheTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + + // 测试基础put,get以及size功能 + it('assertFileSizeAllInMainThread', 0, async () => { + const buf: ArrayBuffer = new ArrayBuffer(1024 * 1024); + console.info(Constants.TAG + buf.byteLength) + console.info(Constants.TAG + GlobalContext.getContext().getObject("cacheDir")) + + let fileCache: FileCache = new FileCache(GlobalContext.getContext() + .getObject("context") as Context, 5, 3 * 1024 * 1024) + await fileCache.initFileCache() + + fileCache.put("aaa", buf) + await sleep(1000) + fileCache.put("bbb", buf) + await sleep(1000) + fileCache.put("ccc", buf) + expect(fileCache.size()).assertEqual(3) + console.info(Constants.TAG + fileCache.currentMemory + "") + fileCache.get("aaa") + await sleep(1000) + fileCache.put("ddd", buf) + await sleep(1000) + console.info(Constants.TAG + fileCache.currentMemory + "") + expect(fileCache.size()).assertEqual(3) + console.info(Constants.TAG + fileCache.get("aaa")?.byteLength) + expect(fileCache.get("ddd")?.byteLength).assertEqual(buf.byteLength) + await sleep(1000) + expect(fileCache.get("aaa")?.byteLength).assertEqual(buf.byteLength) + await sleep(1000) + expect(fileCache.get("ccc")?.byteLength).assertEqual(buf.byteLength) + await sleep(1000) + expect(fileCache.get("bbb")).assertUndefined() + + + // 模拟第二次启动后重新加载缓存 + let fileCache2: FileCache = new FileCache(GlobalContext.getContext() + .getObject("context") as Context, 5, 3 * 1024 * 1024) + await fileCache2.initFileCache() + expect(fileCache2.size()).assertEqual(3) + expect(fileCache2.get("ddd")?.byteLength).assertEqual(buf.byteLength) + expect(fileCache2.get("aaa")?.byteLength).assertEqual(buf.byteLength) + expect(fileCache2.get("ccc")?.byteLength).assertEqual(buf.byteLength) + + }); + + it('assertFileSizeAllInSubThread', 0, async () => { + const buf: ArrayBuffer = new ArrayBuffer(1024 * 1024); + console.info(Constants.TAG + buf.byteLength) + console.info(Constants.TAG + GlobalContext.getContext().getObject("cacheDir")) + + let fileCache: FileCache = new FileCache(GlobalContext.getContext() + .getObject("context") as Context, 5, 3 * 1024 * 1024) + await fileCache.initFileCache() + + fileCache.put("aaa", buf) + + let task: taskpool.Task = new taskpool.Task(getFile, "bbb", GlobalContext.getContext() + .getObject("context") as Context); + let res: ArrayBuffer | undefined = await taskpool.execute(task) as (ArrayBuffer | undefined) + if (res !== undefined) { + fileCache.putWithoutWriteFile("bbb", res) + } + + task = new taskpool.Task(getFile, "aaa", GlobalContext.getContext() + .getObject("context") as Context); + res = await taskpool.execute(task) as (ArrayBuffer | undefined) + if (res !== undefined) { + fileCache.putWithoutWriteFile("aaa", res) + } + + fileCache.put("ddd", buf) + expect(fileCache.size()).assertEqual(3) + expect(fileCache.currentMemory).assertEqual(3 * 1024 * 1024) + expect(fileCache.get("bbb")?.byteLength).assertEqual(1024 * 1024) + expect(fileCache.get("aaa")?.byteLength).assertEqual(1024 * 1024) + expect(fileCache.get("ddd")?.byteLength).assertEqual(1024 * 1024) + + + task = new taskpool.Task(getFile, "eee", GlobalContext.getContext() + .getObject("context") as Context); + + res = await taskpool.execute(task) as (ArrayBuffer | undefined) + if (res !== undefined) { + fileCache.putWithoutWriteFile("eee", res) + } + expect(fileCache.size()).assertEqual(3) + expect(fileCache.currentMemory).assertEqual(3 * 1024 * 1024) + expect(fileCache.get("bbb")).assertEqual(undefined) + expect(fileCache.get("aaa")?.byteLength).assertEqual(1024 * 1024) + expect(fileCache.get("ddd")?.byteLength).assertEqual(1024 * 1024) + + }); + + + it('test', 0, async () => { + const buf: ArrayBuffer = new ArrayBuffer(1024 * 1024); + console.info(Constants.TAG + buf.byteLength) + console.info(Constants.TAG + GlobalContext.getContext().getObject("cacheDir")) + + let fileCache: FileCache = new FileCache(GlobalContext.getContext() + .getObject("context") as Context, 5, 3 * 1024 * 1024) + await fileCache.initFileCache() + + console.info(Constants.TAG + JSON.stringify("xxxxx")) + fileCache.put(JSON.stringify("xxxxx"),buf) + expect(fileCache.get(JSON.stringify("xxxxx"))?.byteLength).assertEqual(1024 * 1024) + }); + it('fileCacheEngineKey', 0, () => { + let engineKey: IEngineKey = new DefaultEngineKey() + let imageKnifeOption: ImageKnifeOption = { + loadSrc:"abc" + } + let imageKey = engineKey.generateFileKey(imageKnifeOption.loadSrc,"") + let imageAnimatorKey = engineKey.generateFileKey(imageKnifeOption.loadSrc,"",true) + expect(imageKey == imageAnimatorKey).assertFalse() + }); + }); +} + +function sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +@Concurrent +async function getFile(key: string, context: Context): Promise { + // 读取文件缓存 + let buf = FileCache.getFileCacheByFile(context, key) + + if (buf !== undefined) { + return buf + } + + buf = new ArrayBuffer(1024 * 1024) + // 写文件缓存 + FileCache.saveFileCacheOnlyFile(context, key, buf) + return buf; +} \ No newline at end of file diff --git a/entry/src/ohosTest/ets/test/List.test.ets b/entry/src/ohosTest/ets/test/List.test.ets index ad551ac..8c04309 100644 --- a/entry/src/ohosTest/ets/test/List.test.ets +++ b/entry/src/ohosTest/ets/test/List.test.ets @@ -1,39 +1,29 @@ /* - * Copyright (C) 2023 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); + * 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, + * 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 abilityTest from './Ability.test' -import lruCacheTest from './lrucache.test' -import CustomDataFetchClientTest from './customdatafetchclient.test' -import LogUtilTest from './logutil.test' -import Transfrom from './transfrom.test' -import RequestOptionTest from './requestoption.test' -import ImageKnifeTest from './imageknife.test' -import DiskLruCacheTest from './diskLruCache.test' -import SendableDataTest from './SendableData.test' import DefaultJobQueueTest from './DefaultJobQueueTest.test'; -import ImageKnifeOptionTest from './imageknifeOption.test'; +import FileLruCacheTest from './FileLruCache.test'; +import ImageKnifeOptionTest from './ImageKnifeOption.test'; +import MemoryLruCacheTest from './MemoryLruCache.test'; +import ImageKnifeTest from './ImageKnife.test'; +import Transform from './transform.test'; export default function testsuite() { - abilityTest() - lruCacheTest() - DiskLruCacheTest() - LogUtilTest() - Transfrom() - RequestOptionTest() - ImageKnifeTest(); - SendableDataTest(); + MemoryLruCacheTest(); + FileLruCacheTest(); DefaultJobQueueTest(); - CustomDataFetchClientTest(); ImageKnifeOptionTest(); + ImageKnifeTest(); + Transform(); } \ No newline at end of file diff --git a/entry/src/ohosTest/ets/test/MemoryLruCache.test.ets b/entry/src/ohosTest/ets/test/MemoryLruCache.test.ets new file mode 100644 index 0000000..98f2514 --- /dev/null +++ b/entry/src/ohosTest/ets/test/MemoryLruCache.test.ets @@ -0,0 +1,151 @@ +/* + * 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; +import image from '@ohos.multimedia.image'; + + +import Constants from '../../../main/ets/common/Constants'; +import { MemoryLruCache } from '@ohos/imageknife/src/main/ets/utils/MemoryLruCache'; +import { ImageKnifeData } from '@ohos/imageknife/src/main/ets/model/ImageKnifeData'; +import { IEngineKey, ImageKnifeOption,ImageKnifeRequestSource } from '@ohos/imageknife'; +import { DefaultEngineKey } from '@ohos/imageknife/src/main/ets/key/DefaultEngineKey'; + + +export default function MemoryLruCacheTest() { + + describe('MemoryLruCacheTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + + // 测试基础put,get以及size功能 + it('assertBasicFunction', 0, async () => { + let memoryCache: MemoryLruCache = new MemoryLruCache(3, 3 * 1024 * 1024); + + let data: ImageKnifeData = await getNewImageKnifeData(96) + memoryCache.put("aaa", data) + memoryCache.put("bbb", data) + memoryCache.put("ccc", data) + expect(memoryCache.size()).assertEqual(3) + expect(memoryCache.get("aaa")).assertEqual(data) + expect(memoryCache.get("bbb")).assertEqual(data) + expect(memoryCache.get("ccc")).assertEqual(data) + expect(memoryCache.size()).assertEqual(3) + + memoryCache.remove("ccc") + memoryCache.remove("ddd") + expect(memoryCache.size()).assertEqual(2) + + memoryCache.removeAll() + expect(memoryCache.size()).assertEqual(0) + }); + + + // 测试内存缓存size的lru功能 + it('assertSizeLruFuction', 0, async () => { + let memoryCache: MemoryLruCache = new MemoryLruCache(3, 3 * 1024 * 1024); + + let data1: ImageKnifeData = await getNewImageKnifeData(96) + let data2: ImageKnifeData = await getNewImageKnifeData(106) + let data3: ImageKnifeData = await getNewImageKnifeData(116) + let data4: ImageKnifeData = await getNewImageKnifeData(126) + let data5: ImageKnifeData = await getNewImageKnifeData(136) + memoryCache.put("aaa", data1) + memoryCache.put("bbb", data2) + memoryCache.put("ccc", data3) + memoryCache.put("ddd", data4) + + expect(memoryCache.get("aaa")).assertUndefined() + expect(memoryCache.get("bbb")).assertEqual(data2) + memoryCache.put("eee", data5) + expect(memoryCache.get("ccc")).assertUndefined() + expect(memoryCache.get("bbb")).assertEqual(data2) + expect(memoryCache.get("ddd")).assertEqual(data4) + expect(memoryCache.get("eee")).assertEqual(data5) + }); + + // 测试内存缓存memorySize的lru功能 + it('assertMemorySizeLruFuction', 0, async () => { + let memoryCache: MemoryLruCache = new MemoryLruCache(3, 2 * 1024 * 1024); + + const color: ArrayBuffer = new ArrayBuffer(1024 * 1024); //96为需要创建的像素buffer大小,取值为:height * width *4 + let opts: image.InitializationOptions = { + editable: true, pixelFormat: 3, size: { + height: 512, width: 512 + } + } + let pixelmap: PixelMap = await image.createPixelMap(color, opts) + console.info(Constants.TAG + pixelmap.getPixelBytesNumber()) + let data: ImageKnifeData = { + source: pixelmap, + imageWidth: 5, + imageHeight: 5 + } + + memoryCache.put("aaa", data) + memoryCache.put("bbb", data) + memoryCache.get("aaa") + memoryCache.put("ccc", data) + expect(memoryCache.size()).assertEqual(2) + expect(memoryCache.get("bbb")).assertUndefined() + expect(memoryCache.get("aaa")).assertEqual(data) + expect(memoryCache.get("ccc")).assertEqual(data) + }); + + it('memoryCacheEngineKey', 0, () => { + let engineKey: IEngineKey = new DefaultEngineKey() + let imageKnifeOption: ImageKnifeOption = { + loadSrc:"abc" + } + let imageKey = engineKey.generateMemoryKey(imageKnifeOption.loadSrc,ImageKnifeRequestSource.SRC,imageKnifeOption) + let imageAnimatorKey = engineKey.generateMemoryKey(imageKnifeOption.loadSrc,ImageKnifeRequestSource.SRC,imageKnifeOption,true) + expect(imageKey == imageAnimatorKey).assertFalse() + }); + + }); +} + +async function getNewImageKnifeData(num:number): Promise { + const color: ArrayBuffer = new ArrayBuffer(num); //96为需要创建的像素buffer大小,取值为:height * width *4 + let opts: image.InitializationOptions = { + editable: true, pixelFormat: 3, size: { + height: 4, width: 6 + } + } + let pixelmap: PixelMap = await image.createPixelMap(color, opts) + + let data: ImageKnifeData = { + source: pixelmap, + imageWidth: 5, + imageHeight: 5 + } + return data +} \ No newline at end of file diff --git a/entry/src/ohosTest/ets/test/imageknife.test.ets b/entry/src/ohosTest/ets/test/imageknife.test.ets deleted file mode 100644 index 5f3b429..0000000 --- a/entry/src/ohosTest/ets/test/imageknife.test.ets +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2023 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 hilog from '@ohos.hilog'; -import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium' -import { DiskLruCache, ImageKnife,ImageKnifeDrawFactory,ImageKnifeGlobal,LruCache} from '@ohos/imageknife'; -import { common } from '@kit.AbilityKit'; -import { GlobalContext } from '../testability/GlobalContext'; - -const BASE_COUNT: number = 2000; - -export default function ImageKnifeTest() { - describe('ImageKnifeTest', ()=> { - // Defines a test suite. Two parameters are supported: test suite name and test suite function. - beforeAll( ()=> { - // Presets an action, which is performed only once before all test cases of the test suite start. - // This API supports only one parameter: preset action function. - }) - beforeEach( ()=> { - // Presets an action, which is performed before each unit test case starts. - // The number of execution times is the same as the number of test cases defined by **it**. - // This API supports only one parameter: preset action function. - }) - afterEach( ()=> { - // Presets a clear action, which is performed after each unit test case ends. - // The number of execution times is the same as the number of test cases defined by **it**. - // This API supports only one parameter: clear action function. - }) - afterAll( ()=> { - // Presets a clear action, which is performed after all test cases of the test suite end. - // This API supports only one parameter: clear action function. - }) - - - it('TestGlobalImageKnife',0, ()=> { - let context:Object|undefined = ImageKnifeGlobal.getInstance().getHapContext(); - if(context != undefined) { - let startTime = new Date().getTime(); - for (let index = 0; index < BASE_COUNT; index++) { - ImageKnife.with(context); - } - endTime(startTime, 'TestGlobalImageKnife'); - let global: ImageKnifeGlobal = ImageKnife.with(context) - expect(global.getImageKnife()).not().assertUndefined() - } - }) - - - it('TestGlobalDefaultLifeCycle',1, ()=> { - let imageKnife:ImageKnife|undefined = ImageKnifeGlobal.getInstance().getImageKnife() - if(imageKnife != undefined){ - let startTime = new Date().getTime(); - for (let index = 0; index < BASE_COUNT; index++) { - imageKnife.setDefaultLifeCycle(ImageKnifeDrawFactory.createProgressLifeCycle("#10a5ff", 0.5)) - } - endTime(startTime, 'setDefaultLifeCycle'); - let startTime1 = new Date().getTime(); - for (let index = 0; index < BASE_COUNT; index++) { - imageKnife.getDefaultLifeCycle(); - } - endTime(startTime1, 'getDefaultLifeCycle'); - imageKnife.setDefaultLifeCycle(ImageKnifeDrawFactory.createProgressLifeCycle("#10a5ff", 0.5)) - let globalLifeCycle = imageKnife.getDefaultLifeCycle(); - expect(globalLifeCycle).not().assertUndefined() - } - - }) - it('TestRemoveAllMemoryCache',2,()=>{ - let imageKnife:ImageKnife|undefined = ImageKnifeGlobal.getInstance().getImageKnife(); - imageKnife?.removeAllMemoryCache(); - let a = imageKnife?.getMemoryCache(); - expect(a).assertEqual(undefined); - }) - it('TestRemoveAllFileCache',3,()=>{ - let imageKnife:ImageKnife|undefined = ImageKnifeGlobal.getInstance().getImageKnife(); - imageKnife?.removeAllFileCache(); - let a = imageKnife?.getDiskMemoryCache(); - expect(a).assertEqual(undefined); - }) - it('TestRemoveMemoryCache',4,()=>{ - let memoryCache = new LruCache(5); - memoryCache.put("1","1"); - memoryCache.remove("1"); - let result = memoryCache.get("1"); - expect(result).assertEqual(undefined); - }) - it('TestRemoveFileCache',5,()=>{ - let context: object | undefined = GlobalContext.getInstance().getObject("hapContext"); - let disLruCache: DiskLruCache = DiskLruCache.create(context as common.UIAbilityContext, 1024); - disLruCache.set('test', "Hello World Simple Example."); - disLruCache.deleteCacheDataByKey('test'); - let a = disLruCache.get('test'); - expect(a).assertEqual(undefined); - }) - }) -} - -function endTime(startTime: number, tag: string) { - let endTime: number = new Date().getTime(); - let averageTime = ((endTime - startTime) * 1000 / BASE_COUNT) - console.info(tag + " startTime: " + endTime) - console.info(tag + " endTime: " + endTime) - console.log(tag + " averageTime: " + averageTime + "μs"); -} \ No newline at end of file diff --git a/entry/src/ohosTest/ets/test/transform.test.ets b/entry/src/ohosTest/ets/test/transform.test.ets new file mode 100644 index 0000000..cec2fe7 --- /dev/null +++ b/entry/src/ohosTest/ets/test/transform.test.ets @@ -0,0 +1,226 @@ +/* + * 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium' +import { + BlurTransformation, + BrightnessTransformation, + CropCircleTransformation, + CropCircleWithBorderTransformation, + CropSquareTransformation, + CropTransformation, + GrayScaleTransformation, + InvertTransformation, + KuwaharaTransformation, + MaskTransformation, + PixelationTransformation, + SepiaTransformation, + SketchTransformation, + SwirlTransformation, + ToonTransformation, + VignetterTransformation +} from '@ohos/imageknife' + +const BASE_COUNT: number = 1000; + +export default function Transform() { + describe('Transform', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('TestBlurTransformation', 0, () => { + let startTime = new Date().getTime(); + for (let index = 0; index < BASE_COUNT; index++) { + new BlurTransformation(15); + } + endTime(startTime, 'TestBlurTransformation'); + let blur = new BlurTransformation(15); + expect(blur.getName()).assertEqual('BlurTransformation;radius:15'); + }) + it('TestBrightnessTransformation', 1, () => { + let startTime = new Date().getTime(); + for (let index = 0; index < BASE_COUNT; index++) { + new BrightnessTransformation(20); + } + endTime(startTime, 'BrightnessTransformation'); + let bright = new BrightnessTransformation(20); + expect(bright.getName()).assertEqual("BrightnessTransformation;bright:20"); + }) + it('TestCropCircleTransformation', 3, () => { + let startTime = new Date().getTime(); + for (let index = 0; index < BASE_COUNT; index++) { + new CropCircleTransformation(); + } + endTime(startTime, 'TestCropCircleTransformation'); + let cropCircle = new CropCircleTransformation(); + expect(cropCircle.getName()).assertContain("CropCircleTransformation"); + expect(cropCircle.getName()).assertContain(";mCenterX:"); + expect(cropCircle.getName()).assertContain(";mCenterY:"); + expect(cropCircle.getName()).assertContain(";mRadius:"); + }) + it('TestCropCircleWithBorderTransformation', 4, () => { + let startTime = new Date().getTime(); + for (let index = 0; index < BASE_COUNT; index++) { + new CropCircleWithBorderTransformation(10, { + r_color: 100, g_color: 100, b_color: 100 + }); + } + endTime(startTime, 'TestCropCircleWithBorderTransformation'); + let CropCircleWithBorder = new CropCircleWithBorderTransformation(10, { + r_color: 100, g_color: 100, b_color: 100 + }); + expect(CropCircleWithBorder.getName()).assertContain("CropCircleWithBorderTransformation") + expect(CropCircleWithBorder.getName()).assertContain(";mCenterX:"); + expect(CropCircleWithBorder.getName()).assertContain(";mCenterY:"); + expect(CropCircleWithBorder.getName()).assertContain(";mRadius:"); + expect(CropCircleWithBorder.getName()).assertContain(";mBorderSize:"); + expect(CropCircleWithBorder.getName()).assertContain(";mRColor:"); + expect(CropCircleWithBorder.getName()).assertContain(";mGColor:"); + expect(CropCircleWithBorder.getName()).assertContain(";mBColor:"); + }) + it('TestCropSquareTransformation', 5, () => { + let startTime = new Date().getTime(); + for (let index = 0; index < BASE_COUNT; index++) { + new CropSquareTransformation(); + } + endTime(startTime, 'TestCropSquareTransformation'); + let CropSquare = new CropSquareTransformation(); + expect(CropSquare.getName()).assertContain("CropSquareTransformation"); + }) + it('TestCropTransformation', 6, () => { + let startTime = new Date().getTime(); + for (let index = 0; index < BASE_COUNT; index++) { + new CropTransformation(10, 10, 1); + } + endTime(startTime, 'TestCropTransformation'); + let crop = new CropTransformation(10, 10, 1); + expect(crop.getName()).assertContain("CropTransformation" + ";mWidth:10" + ";mHeight:10" + ";mCropType:1") + }) + it('TestGrayScaleTransformation', 7, () => { + let startTime = new Date().getTime(); + for (let index = 0; index < BASE_COUNT; index++) { + new GrayScaleTransformation(); + } + endTime(startTime, 'GrayScaleTransformation'); + let grayscale = new GrayScaleTransformation(); + expect(grayscale.getName()).assertContain("GrayScaleTransformation") + }) + it('TestInvertTransformation', 8, () => { + let startTime = new Date().getTime(); + for (let index = 0; index < BASE_COUNT; index++) { + new InvertTransformation(); + } + endTime(startTime, 'TestInvertFilterTransformation'); + let invert = new InvertTransformation(); + expect(invert.getName()).assertContain("InvertTransformation"); + }) + it('TestPixelationTransformation', 9, () => { + let startTime = new Date().getTime(); + for (let index = 0; index < BASE_COUNT; index++) { + new PixelationTransformation(); + } + endTime(startTime, 'TestPixelationTransformation'); + let pixelation = new PixelationTransformation(); + expect(pixelation.getName()).assertContain("PixelationTransformation"); + }) + it('TestSepiaTransformation', 12, () => { + let startTime = new Date().getTime(); + for (let index = 0; index < BASE_COUNT; index++) { + new SepiaTransformation(); + } + endTime(startTime, 'SepiaTransformation'); + let speia = new SepiaTransformation(); + expect(speia.getName()).assertContain("SepiaTransformation"); + }) + it('TestSketchTransformation', 13, () => { + let startTime = new Date().getTime(); + for (let index = 0; index < BASE_COUNT; index++) { + new SketchTransformation(); + } + endTime(startTime, 'TestSketchTransformation'); + let Sketch = new SketchTransformation(); + expect(Sketch.getName()).assertContain("SketchTransformation"); + }) + it('TestMaskTransformation', 14, () => { + let startTime = new Date().getTime(); + for (let index = 0; index < BASE_COUNT; index++) { + new MaskTransformation($r('app.media.icon')); + } + endTime(startTime, 'TestMaskTransformation'); + let mask = new MaskTransformation($r('app.media.icon')); + expect(mask.getName()).assertContain("MaskTransformation"); + }) + it('TestSwirlTransformation', 15, () => { + let startTime = new Date().getTime(); + for (let index = 0; index < BASE_COUNT; index++) { + new SwirlTransformation(10, 180, [10, 10]); + } + endTime(startTime, 'TestSwirlTransformation'); + let swirl = new SwirlTransformation(10, 180, [10, 10]); + expect(swirl.getName()).assertContain("SwirlTransformation"); + }) + it('TestKuwaharaTransformation', 16, () => { + let startTime = new Date().getTime(); + for (let index = 0; index < BASE_COUNT; index++) { + new KuwaharaTransformation(10); + } + endTime(startTime, 'TestKuwaharaTransformation'); + let kuwahara = new KuwaharaTransformation(10); + expect(kuwahara.getName()).assertContain("KuwaharaTransformation;radius:10"); + }) + it('TestToonTransformation', 17, () => { + let startTime = new Date().getTime(); + for (let index = 0; index < BASE_COUNT; index++) { + new ToonTransformation(10); + } + endTime(startTime, 'TestToonTransformation'); + let toon = new ToonTransformation(10); + expect(toon.getName()).assertContain("ToonTransformation;threshold:10"); + }) + it('TestVignetterTransformation', 18, () => { + let startTime = new Date().getTime(); + for (let index = 0; index < BASE_COUNT; index++) { + new VignetterTransformation([0.5, 0.5], [0.0, 0.0, 0.0], [0.3, 0.75]); + } + endTime(startTime, 'TestVignetterTransformation'); + let vignette = new VignetterTransformation([0.5, 0.5], [0.0, 0.0, 0.0], [0.3, 0.75]); + expect(vignette.getName()).assertContain("VignetterTransformation"); + }) + }) +} + +function endTime(startTime: number, tag: string) { + let endTime: number = new Date().getTime(); + let averageTime = ((endTime - startTime) * 1000 / BASE_COUNT); + console.info(tag + " startTime: " + endTime); + console.info(tag + " endTime: " + endTime); + console.log(tag + " averageTime: " + averageTime + "μs"); +} \ No newline at end of file diff --git a/entry/src/ohosTest/ets/testability/TestAbility.ets b/entry/src/ohosTest/ets/testability/TestAbility.ets index 1860105..628af01 100644 --- a/entry/src/ohosTest/ets/testability/TestAbility.ets +++ b/entry/src/ohosTest/ets/testability/TestAbility.ets @@ -1,13 +1,13 @@ /* - * Copyright (C) 2023 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); + * 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, + * 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. @@ -18,46 +18,50 @@ import hilog from '@ohos.hilog'; import { Hypium } from '@ohos/hypium'; import testsuite from '../test/List.test'; import window from '@ohos.window'; -import {ImageKnife,ImageKnifeDrawFactory,ImageKnifeGlobal} from '@ohos/libraryimageknife' -import AbilityConstant from '@ohos.app.ability.AbilityConstant'; import Want from '@ohos.app.ability.Want'; -import { BusinessError } from '@ohos.base' -import {GlobalContext } from './GlobalContext' +import AbilityConstant from '@ohos.app.ability.AbilityConstant'; +import { GlobalContext } from '../../../main/ets/common/GlobalContext'; + export default class TestAbility extends UIAbility { - onCreate(want: Want, param: AbilityConstant.LaunchParam) { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onCreate'); + hilog.info(0x0000, 'testTag', '%{public}s', 'want param:' + JSON.stringify(want) ?? ''); + hilog.info(0x0000, 'testTag', '%{public}s', 'launchParam:' + JSON.stringify(launchParam) ?? ''); + let abilityDelegator: AbilityDelegatorRegistry.AbilityDelegator; + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator(); + let abilityDelegatorArguments: AbilityDelegatorRegistry.AbilityDelegatorArgs; + abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments(); + hilog.info(0x0000, 'testTag', '%{public}s', 'start run testcase!!!'); + Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite); + } - let abilityDelegator: AbilityDelegatorRegistry.AbilityDelegator - abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() - let abilityDelegatorArguments: AbilityDelegatorRegistry.AbilityDelegatorArgs - abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() + onDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onDestroy'); + } - Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite) - // 初始化xts的ImageKnife - ImageKnife.with(this.context.createModuleContext("entry_test")); - GlobalContext.getInstance().setObject("hapContext",this.context.createModuleContext("entry_test")); - GlobalContext.getInstance().setObject("filesDir",this.context.createModuleContext("entry_test").filesDir); - } + onWindowStageCreate(windowStage: window.WindowStage) { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageCreate'); + GlobalContext.getContext().setObject("context", this.context); + GlobalContext.getContext().setObject("cacheDir", this.context.cacheDir); + windowStage.loadContent('testability/pages/Index', (err, data) => { + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', + JSON.stringify(data) ?? ''); + }); + } - onDestroy() { + onWindowStageDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy'); + } - } + onForeground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground'); + } - onWindowStageCreate(windowStage: window.WindowStage) { - - windowStage.loadContent('testability/pages/Index', (err:BusinessError, data:void) => { - - }); - } - - onWindowStageDestroy() { - hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy'); - } - - onForeground() { - hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground'); - } - - onBackground() { - hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground'); - } + onBackground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground'); + } } \ No newline at end of file diff --git a/entry/src/ohosTest/ets/testability/pages/Index.ets b/entry/src/ohosTest/ets/testability/pages/Index.ets index 19ddc97..98be4ce 100644 --- a/entry/src/ohosTest/ets/testability/pages/Index.ets +++ b/entry/src/ohosTest/ets/testability/pages/Index.ets @@ -1,50 +1,31 @@ /* - * Copyright (C) 2023 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); + * 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, + * 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 hilog from '@ohos.hilog'; - @Entry @Component struct Index { + @State message: string = 'Hello World'; - aboutToAppear() { - hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility index aboutToAppear'); + build() { + Row() { + Column() { + Text(this.message) + .fontSize(50) + .fontWeight(FontWeight.Bold) + } + .width('100%') + } + .height('100%') } - - @State message: string = 'Hello World' - build() { - Row() { - Column() { - Text(this.message) - .fontSize(50) - .fontWeight(FontWeight.Bold) - Button() { - Text('next page') - .fontSize(20) - .fontWeight(FontWeight.Bold) - }.type(ButtonType.Capsule) - .margin({ - top: 20 - }) - .backgroundColor('#0D9FFB') - .width('35%') - .height('5%') - .onClick(()=>{ - }) - } - .width('100%') - } - .height('100%') - } - } \ No newline at end of file +} \ No newline at end of file diff --git a/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ets b/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ets new file mode 100644 index 0000000..9d981ea --- /dev/null +++ b/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ets @@ -0,0 +1,64 @@ +/* + * 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 hilog from '@ohos.hilog'; +import TestRunner from '@ohos.application.testRunner'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; +import Want from '@ohos.app.ability.Want'; + +let abilityDelegator: AbilityDelegatorRegistry.AbilityDelegator | undefined = undefined +let abilityDelegatorArguments: AbilityDelegatorRegistry.AbilityDelegatorArgs | undefined = undefined + +async function onAbilityCreateCallback() { + hilog.info(0x0000, 'testTag', '%{public}s', 'onAbilityCreateCallback'); +} + +async function addAbilityMonitorCallback(err : Error) { + hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? ''); +} + +export default class OpenHarmonyTestRunner implements TestRunner { + constructor() { + } + + onPrepare() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare '); + } + + async onRun() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun run'); + abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() + const bundleName = abilityDelegatorArguments.bundleName; + const testAbilityName = 'TestAbility'; + const moduleName = abilityDelegatorArguments.parameters['-m']; + let lMonitor: AbilityDelegatorRegistry.AbilityMonitor = { + abilityName: testAbilityName, + onAbilityCreate: onAbilityCreateCallback, + moduleName: moduleName + }; + abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback) + const want: Want = { + bundleName: bundleName, + abilityName: testAbilityName, + moduleName: moduleName + }; + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator(); + abilityDelegator.startAbility(want, (err, data) => { + hilog.info(0x0000, 'testTag', 'startAbility : err : %{public}s', JSON.stringify(err) ?? ''); + hilog.info(0x0000, 'testTag', 'startAbility : data : %{public}s',JSON.stringify(data) ?? ''); + }) + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun end'); + } +} \ No newline at end of file diff --git a/entry/src/ohosTest/module.json5 b/entry/src/ohosTest/module.json5 index 3054622..d0dc5d5 100644 --- a/entry/src/ohosTest/module.json5 +++ b/entry/src/ohosTest/module.json5 @@ -2,19 +2,16 @@ "module": { "name": "entry_test", "type": "feature", - "srcEntry": "./ets/testability/TestAbility.ets", "description": "$string:module_test_desc", "mainElement": "TestAbility", - "deviceTypes": ["default"], + "deviceTypes": [ + "phone", + "tablet", + "2in1" + ], "deliveryWithInstall": true, "installationFree": false, "pages": "$profile:test_pages", - "metadata": [ - { - "name": "ArkTSPartialUpdate", - "value": "true" - } - ], "abilities": [ { "name": "TestAbility", diff --git a/entry/src/ohosTest/resources/base/media/icon.png b/entry/src/ohosTest/resources/base/media/icon.png index ce307a8..cd45acc 100644 Binary files a/entry/src/ohosTest/resources/base/media/icon.png and b/entry/src/ohosTest/resources/base/media/icon.png differ diff --git a/entry/src/ohosTest/resources/base/profile/test_pages.json b/entry/src/ohosTest/resources/base/profile/test_pages.json index 2d90c65..b7e7343 100644 --- a/entry/src/ohosTest/resources/base/profile/test_pages.json +++ b/entry/src/ohosTest/resources/base/profile/test_pages.json @@ -1,5 +1,5 @@ { "src": [ - "testability/pages/index" + "testability/pages/Index" ] } diff --git a/library/.gitignore b/library/.gitignore index f8fba9f..e2713a2 100644 --- a/library/.gitignore +++ b/library/.gitignore @@ -1,5 +1,6 @@ /node_modules +/oh_modules /.preview /build -/oh_modules/ -/oh-package-lock.json5 +/.cxx +/.test \ No newline at end of file diff --git a/library/BuildProfile.ets b/library/BuildProfile.ets new file mode 100644 index 0000000..774bfdf --- /dev/null +++ b/library/BuildProfile.ets @@ -0,0 +1,19 @@ +/* + * 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 default class BuildProfile { + static readonly HAR_VERSION = '3.0.0-rc.0'; + static readonly BUILD_MODE_NAME = 'debug'; + static readonly DEBUG = true; +} \ No newline at end of file diff --git a/library/build-profile.json5 b/library/build-profile.json5 index 35dff6d..312d38e 100644 --- a/library/build-profile.json5 +++ b/library/build-profile.json5 @@ -1,5 +1,28 @@ { "apiType": "stageMode", "buildOption": { - } + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": true, + "files": [ + "./obfuscation-rules.txt" + ] + }, + "consumerFiles": [ + "./consumer-rules.txt" + ] + } + }, + }, + ], + "targets": [ + { + "name": "default" + } + ] } diff --git a/library/consumer-rules.txt b/library/consumer-rules.txt new file mode 100644 index 0000000..e69de29 diff --git a/library/hvigorfile.ts b/library/hvigorfile.ts index 42ed4b4..4218707 100644 --- a/library/hvigorfile.ts +++ b/library/hvigorfile.ts @@ -1,3 +1,6 @@ -// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. -module.exports = require('@ohos/hvigor-ohos-plugin').harTasks +import { harTasks } from '@ohos/hvigor-ohos-plugin'; +export default { + system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/library/index.ets b/library/index.ets index ca65555..4576c08 100644 --- a/library/index.ets +++ b/library/index.ets @@ -1,10 +1,10 @@ /* - * 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 * - * http:// www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -12,127 +12,58 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +export { ImageKnifeComponent } from './src/main/ets/components/ImageKnifeComponent' +export { ImageKnifeAnimatorComponent } from './src/main/ets/components/ImageKnifeAnimatorComponent' -/** - * cache - */ +export { ImageKnife } from './src/main/ets/ImageKnife' -export { FileUtils } from './src/main/ets/components/cache/FileUtils' -export { Base64 } from './src/main/ets/components/cache/Base64' -export { LruCache } from './src/main/ets/components/cache/LruCache' -export { DiskLruCache } from './src/main/ets/components/cache/DiskLruCache' -export { DiskCacheEntry } from './src/main/ets/components/cache/DiskCacheEntry' -export { DiskStrategy } from './src/main/ets/components/cache/diskstrategy/DiskStrategy' -export { ALL } from './src/main/ets/components/cache/diskstrategy/enum/ALL' -export { AUTOMATIC } from './src/main/ets/components/cache/diskstrategy/enum/AUTOMATIC' -export { DATA } from './src/main/ets/components/cache/diskstrategy/enum/DATA' -export { NONE } from './src/main/ets/components/cache/diskstrategy/enum/NONE' -export { RESOURCE } from './src/main/ets/components/cache/diskstrategy/enum/RESOURCE' -export { EngineKeyInterface } from './src/main/ets/components/cache/key/EngineKeyInterface' -export { EngineKeyFactories } from './src/main/ets/components/cache/key/EngineKeyFactories' -export { DataFetchResult } from './src/main/ets/components/imageknife/networkmanage/DataFetchResult' +export { ImageKnifeOption , AnimatorOption } from './src/main/ets/ImageKnifeOption' -/** - * compress - */ -export { CompressBuilder } from './src/main/ets/components/imageknife/compress/CompressBuilder' -export { OnCompressListener } from './src/main/ets/components/imageknife/compress/listener/OnCompressListener' -export { OnRenameListener } from './src/main/ets/components/imageknife/compress/listener/OnRenameListener' -export { CompressDataListener } from './src/main/ets/components/imageknife/compress/listener/CompressDataListener' -export { CompressionPredicate } from './src/main/ets/components/imageknife/compress/listener/CompressionPredicate' -export { CompressAdapter } from './src/main/ets/components/imageknife/compress/provider/CompressAdapter' -export { CompressProvider } from './src/main/ets/components/imageknife/compress/provider/CompressProvider' -export { DataStringPathProvider } from './src/main/ets/components/imageknife/compress/provider/DataStringPathProvider' -export { RecourseProvider } from './src/main/ets/components/imageknife/compress/provider/RecourseProvider' +export { ImageKnifeRequest } from './src/main/ets/ImageKnifeRequest' -/** - * crop - */ +export { FileUtils } from './src/main/ets/utils/FileUtils' -export { CropImage } from './src/main/ets/components/imageknife/crop/CropImage' -export { CropOptions } from './src/main/ets/components/imageknife/crop/CropOptions' -export { PixelMapCrop,Options } from './src/main/ets/components/imageknife/crop/PixelMapCrop' -export { CropCallback } from './src/main/ets/components/imageknife/crop/CropCallback' +export { LogUtil } from './src/main/ets/utils/LogUtil' -/** - * transform - */ -export { BaseTransform } from './src/main/ets/components/imageknife/transform/BaseTransform' -export { BlurTransformation } from './src/main/ets/components/imageknife/transform/BlurTransformation' -export { BrightnessFilterTransformation } from './src/main/ets/components/imageknife/transform/BrightnessFilterTransformation' -export { ContrastFilterTransformation } from './src/main/ets/components/imageknife/transform/ContrastFilterTransformation' -export { CropCircleTransformation } from './src/main/ets/components/imageknife/transform/CropCircleTransformation' -export { CropCircleWithBorderTransformation,rgbColor } from './src/main/ets/components/imageknife/transform/CropCircleWithBorderTransformation' -export { CropSquareTransformation } from './src/main/ets/components/imageknife/transform/CropSquareTransformation' -export { CropTransformation,CropType } from './src/main/ets/components/imageknife/transform/CropTransformation' -export { GrayscaleTransformation } from './src/main/ets/components/imageknife/transform/GrayscaleTransformation' -export { InvertFilterTransformation } from './src/main/ets/components/imageknife/transform/InvertFilterTransformation' -export { PixelationFilterTransformation } from './src/main/ets/components/imageknife/transform/PixelationFilterTransformation' -export { RotateImageTransformation } from './src/main/ets/components/imageknife/transform/RotateImageTransformation' -export { RoundedCornersTransformation,RoundCorner } from './src/main/ets/components/imageknife/transform/RoundedCornersTransformation' -export { SepiaFilterTransformation } from './src/main/ets/components/imageknife/transform/SepiaFilterTransformation' -export { SketchFilterTransformation } from './src/main/ets/components/imageknife/transform/SketchFilterTransformation' -export { MaskTransformation } from './src/main/ets/components/imageknife/transform/MaskTransformation' -export { SwirlFilterTransformation } from './src/main/ets/components/imageknife/transform/SwirlFilterTransformation' -export { KuwaharaFilterTransform } from './src/main/ets/components/imageknife/transform/KuwaharaFilterTransform' -export { ToonFilterTransform } from './src/main/ets/components/imageknife/transform/ToonFilterTransform' -export { VignetteFilterTransform } from './src/main/ets/components/imageknife/transform/VignetteFilterTransform' -export { TransformUtils } from './src/main/ets/components/imageknife/transform/TransformUtils' -export { TransformType } from './src/main/ets/components/imageknife/transform/TransformType' -export { CenterCrop } from './src/main/ets/components/imageknife/transform/pixelmap/CenterCrop' -export { CenterInside } from './src/main/ets/components/imageknife/transform/pixelmap/CenterInside' -export { FitCenter } from './src/main/ets/components/imageknife/transform/pixelmap/FitCenter' +export { IEngineKey } from './src/main/ets/key/IEngineKey' -/** - * pngj - */ -export { Pngj } from './src/main/ets/components/imageknife/pngj/Pngj' -export {handler} from './PngWork' -export { UPNG } from './src/main/ets/components/3rd_party/upng/UPNG' +export { ImageKnifeData , CacheStrategy , ImageKnifeRequestSource} from "./src/main/ets/model/ImageKnifeData" +export { PixelMapTransformation } from './src/main/ets/transform/PixelMapTransformation' +export { MultiTransTransformation } from './src/main/ets/transform/MultiTransTransformation' -/** - * ImageKnife - */ -export { ImageKnife } from './src/main/ets/components/imageknife/ImageKnife' -export { ImageKnifeGlobal } from './src/main/ets/components/imageknife/ImageKnifeGlobal' -export { ObjectKey } from './src/main/ets/components/imageknife/ObjectKey' -export {RequestOption,Size,DetachFromLayout,CacheType} from './src/main/ets/components/imageknife/RequestOption' -export { ImageKnifeComponent, ScaleType, ScaleTypeHelper, AntiAliasing} from './src/main/ets/components/imageknife/ImageKnifeComponent' -export { ImageKnifeDrawFactory } from './src/main/ets/components/imageknife/ImageKnifeDrawFactory' -export {ImageKnifeOption,CropCircleWithBorder,Crop,GifOptions,TransformOptions,HeaderOptions} from './src/main/ets/components/imageknife/ImageKnifeOption' -export { ImageKnifeData } from './src/main/ets/components/imageknife/ImageKnifeData' -export {IAllCacheInfoCallback,AllCacheInfo,ResourceCacheInfo,MemoryCacheInfo,DataCacheInfo} from './src/main/ets/components/imageknife/interface/IAllCacheInfoCallback' -export {IParseImage} from './src/main/ets/components/imageknife/interface/IParseImage' -export {IDataFetch} from './src/main/ets/components/imageknife/networkmanage/IDataFetch' -export {ICache} from './src/main/ets/components/imageknife/requestmanage/ICache' -export { FileTypeUtil } from './src/main/ets/components/imageknife/utils/FileTypeUtil' -export { ParseImageUtil } from './src/main/ets/components/imageknife/utils/ParseImageUtil' -export { DownloadClient } from './src/main/ets/components/imageknife/networkmanage/DownloadClient'; -export { CustomDataFetchClient } from './src/main/ets/components/imageknife/networkmanage/CustomDataFetchClient'; +export { BrightnessTransformation } from './src/main/ets/transform/BrightnessTransformation' -/** - * svg parse - */ -export { SVGParseImpl } from './src/main/ets/components/imageknife/utils/svg/SVGParseImpl' +export { BlurTransformation } from './src/main/ets/transform/BlurTransformation' -/** - * gif parse - */ -export { GIFParseImpl } from './src/main/ets/components/imageknife/utils/gif/GIFParseImpl' -export { GIFFrame } from './src/main/ets/components/imageknife/utils/gif/GIFFrame' +export { SparkMD5 } from "./src/main/ets/3rd_party/sparkmd5/spark-md5" +export { GrayScaleTransformation } from './src/main/ets/transform/GrayScaleTransformation' -// 自定义组件新增 -// 自定义组件绘制生命周期 -export { IDrawLifeCycle } from './src/main/ets/components/imageknife/interface/IDrawLifeCycle' +export { InvertTransformation } from './src/main/ets/transform/InvertTransformation' -// 日志管理 -export { LogUtil } from './src/main/ets/components/imageknife/utils/LogUtil' +export { ToonTransformation } from './src/main/ets/transform/ToonTransformation' -/*下采样*/ -export {Downsampler} from './src/main/ets/components/imageknife/downsampling/Downsampler' +export { CropCircleTransformation } from './src/main/ets/transform/CropCircleTransformation' -export {DownsampleNone as sampleNone, FitCenter as fitter} from './src/main/ets/components/imageknife/downsampling/DownsampleStartegy' \ No newline at end of file +export { CropCircleWithBorderTransformation } from './src/main/ets/transform/CropCircleWithBorderTransformation' + +export { KuwaharaTransformation } from './src/main/ets/transform/KuwaharaTransformation' + +export { PixelationTransformation } from './src/main/ets/transform/PixelationTransformation' + +export { SketchTransformation } from './src/main/ets/transform/SketchTransformation' + +export { SwirlTransformation } from './src/main/ets/transform/SwirlTransformation' + +export { VignetterTransformation } from './src/main/ets/transform/VignetterTransformation' + +export { CropSquareTransformation } from './src/main/ets/transform/CropSquareTransformation' + +export { CropTransformation } from './src/main/ets/transform/CropTransformation' + +export { MaskTransformation } from './src/main/ets/transform/MaskTransformation' + +export { SepiaTransformation } from './src/main/ets/transform/SepiaTransformation' \ No newline at end of file diff --git a/library/obfuscation-rules.txt b/library/obfuscation-rules.txt new file mode 100644 index 0000000..985b2ae --- /dev/null +++ b/library/obfuscation-rules.txt @@ -0,0 +1,18 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://gitee.com/openharmony/arkcompiler_ets_frontend/blob/master/arkguard/README.md + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope \ No newline at end of file diff --git a/library/oh-package.json5 b/library/oh-package.json5 index dce5ea3..67af1d6 100644 --- a/library/oh-package.json5 +++ b/library/oh-package.json5 @@ -14,10 +14,9 @@ "main": "index.ets", "repository": "https://gitee.com/openharmony-tpc/ImageKnife", "type": "module", - "version": "2.3.0-rc.2", + "version": "3.0.1-rc.2", "dependencies": { - "pako": "^2.1.0", - "@ohos/gpu_transform": "^1.0.0" + "@ohos/gpu_transform": "^1.0.2" }, "tags": [ "ImageCache", diff --git a/library/src/main/ets/3rd_party/sparkmd5/spark-md5.js b/library/src/main/ets/3rd_party/sparkmd5/spark-md5.js new file mode 100644 index 0000000..9a3e5d0 --- /dev/null +++ b/library/src/main/ets/3rd_party/sparkmd5/spark-md5.js @@ -0,0 +1,741 @@ +import buffer from '@ohos.buffer'; + + /* + * Fastest md5 implementation around (JKM md5). + * Credits: Joseph Myers + * + * @see http://www.myersdaily.org/joseph/javascript/md5-text.html + * @see http://jsperf.com/md5-shootout/7 + */ + + /* this function is much faster, + so if possible we use it. Some IEs + are the only ones I know of that + need the idiotic second function, + generated by an if clause. */ + var add32 = function (a, b) { + return (a + b) & 0xFFFFFFFF; + }, + hex_chr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']; + + + function cmn(q, a, b, x, s, t) { + a = add32(add32(a, q), add32(x, t)); + return add32((a << s) | (a >>> (32 - s)), b); + } + + function md5cycle(x, k) { + var a = x[0], + b = x[1], + c = x[2], + d = x[3]; + + a += (b & c | ~b & d) + k[0] - 680876936 | 0; + a = (a << 7 | a >>> 25) + b | 0; + d += (a & b | ~a & c) + k[1] - 389564586 | 0; + d = (d << 12 | d >>> 20) + a | 0; + c += (d & a | ~d & b) + k[2] + 606105819 | 0; + c = (c << 17 | c >>> 15) + d | 0; + b += (c & d | ~c & a) + k[3] - 1044525330 | 0; + b = (b << 22 | b >>> 10) + c | 0; + a += (b & c | ~b & d) + k[4] - 176418897 | 0; + a = (a << 7 | a >>> 25) + b | 0; + d += (a & b | ~a & c) + k[5] + 1200080426 | 0; + d = (d << 12 | d >>> 20) + a | 0; + c += (d & a | ~d & b) + k[6] - 1473231341 | 0; + c = (c << 17 | c >>> 15) + d | 0; + b += (c & d | ~c & a) + k[7] - 45705983 | 0; + b = (b << 22 | b >>> 10) + c | 0; + a += (b & c | ~b & d) + k[8] + 1770035416 | 0; + a = (a << 7 | a >>> 25) + b | 0; + d += (a & b | ~a & c) + k[9] - 1958414417 | 0; + d = (d << 12 | d >>> 20) + a | 0; + c += (d & a | ~d & b) + k[10] - 42063 | 0; + c = (c << 17 | c >>> 15) + d | 0; + b += (c & d | ~c & a) + k[11] - 1990404162 | 0; + b = (b << 22 | b >>> 10) + c | 0; + a += (b & c | ~b & d) + k[12] + 1804603682 | 0; + a = (a << 7 | a >>> 25) + b | 0; + d += (a & b | ~a & c) + k[13] - 40341101 | 0; + d = (d << 12 | d >>> 20) + a | 0; + c += (d & a | ~d & b) + k[14] - 1502002290 | 0; + c = (c << 17 | c >>> 15) + d | 0; + b += (c & d | ~c & a) + k[15] + 1236535329 | 0; + b = (b << 22 | b >>> 10) + c | 0; + + a += (b & d | c & ~d) + k[1] - 165796510 | 0; + a = (a << 5 | a >>> 27) + b | 0; + d += (a & c | b & ~c) + k[6] - 1069501632 | 0; + d = (d << 9 | d >>> 23) + a | 0; + c += (d & b | a & ~b) + k[11] + 643717713 | 0; + c = (c << 14 | c >>> 18) + d | 0; + b += (c & a | d & ~a) + k[0] - 373897302 | 0; + b = (b << 20 | b >>> 12) + c | 0; + a += (b & d | c & ~d) + k[5] - 701558691 | 0; + a = (a << 5 | a >>> 27) + b | 0; + d += (a & c | b & ~c) + k[10] + 38016083 | 0; + d = (d << 9 | d >>> 23) + a | 0; + c += (d & b | a & ~b) + k[15] - 660478335 | 0; + c = (c << 14 | c >>> 18) + d | 0; + b += (c & a | d & ~a) + k[4] - 405537848 | 0; + b = (b << 20 | b >>> 12) + c | 0; + a += (b & d | c & ~d) + k[9] + 568446438 | 0; + a = (a << 5 | a >>> 27) + b | 0; + d += (a & c | b & ~c) + k[14] - 1019803690 | 0; + d = (d << 9 | d >>> 23) + a | 0; + c += (d & b | a & ~b) + k[3] - 187363961 | 0; + c = (c << 14 | c >>> 18) + d | 0; + b += (c & a | d & ~a) + k[8] + 1163531501 | 0; + b = (b << 20 | b >>> 12) + c | 0; + a += (b & d | c & ~d) + k[13] - 1444681467 | 0; + a = (a << 5 | a >>> 27) + b | 0; + d += (a & c | b & ~c) + k[2] - 51403784 | 0; + d = (d << 9 | d >>> 23) + a | 0; + c += (d & b | a & ~b) + k[7] + 1735328473 | 0; + c = (c << 14 | c >>> 18) + d | 0; + b += (c & a | d & ~a) + k[12] - 1926607734 | 0; + b = (b << 20 | b >>> 12) + c | 0; + + a += (b ^ c ^ d) + k[5] - 378558 | 0; + a = (a << 4 | a >>> 28) + b | 0; + d += (a ^ b ^ c) + k[8] - 2022574463 | 0; + d = (d << 11 | d >>> 21) + a | 0; + c += (d ^ a ^ b) + k[11] + 1839030562 | 0; + c = (c << 16 | c >>> 16) + d | 0; + b += (c ^ d ^ a) + k[14] - 35309556 | 0; + b = (b << 23 | b >>> 9) + c | 0; + a += (b ^ c ^ d) + k[1] - 1530992060 | 0; + a = (a << 4 | a >>> 28) + b | 0; + d += (a ^ b ^ c) + k[4] + 1272893353 | 0; + d = (d << 11 | d >>> 21) + a | 0; + c += (d ^ a ^ b) + k[7] - 155497632 | 0; + c = (c << 16 | c >>> 16) + d | 0; + b += (c ^ d ^ a) + k[10] - 1094730640 | 0; + b = (b << 23 | b >>> 9) + c | 0; + a += (b ^ c ^ d) + k[13] + 681279174 | 0; + a = (a << 4 | a >>> 28) + b | 0; + d += (a ^ b ^ c) + k[0] - 358537222 | 0; + d = (d << 11 | d >>> 21) + a | 0; + c += (d ^ a ^ b) + k[3] - 722521979 | 0; + c = (c << 16 | c >>> 16) + d | 0; + b += (c ^ d ^ a) + k[6] + 76029189 | 0; + b = (b << 23 | b >>> 9) + c | 0; + a += (b ^ c ^ d) + k[9] - 640364487 | 0; + a = (a << 4 | a >>> 28) + b | 0; + d += (a ^ b ^ c) + k[12] - 421815835 | 0; + d = (d << 11 | d >>> 21) + a | 0; + c += (d ^ a ^ b) + k[15] + 530742520 | 0; + c = (c << 16 | c >>> 16) + d | 0; + b += (c ^ d ^ a) + k[2] - 995338651 | 0; + b = (b << 23 | b >>> 9) + c | 0; + + a += (c ^ (b | ~d)) + k[0] - 198630844 | 0; + a = (a << 6 | a >>> 26) + b | 0; + d += (b ^ (a | ~c)) + k[7] + 1126891415 | 0; + d = (d << 10 | d >>> 22) + a | 0; + c += (a ^ (d | ~b)) + k[14] - 1416354905 | 0; + c = (c << 15 | c >>> 17) + d | 0; + b += (d ^ (c | ~a)) + k[5] - 57434055 | 0; + b = (b << 21 | b >>> 11) + c | 0; + a += (c ^ (b | ~d)) + k[12] + 1700485571 | 0; + a = (a << 6 | a >>> 26) + b | 0; + d += (b ^ (a | ~c)) + k[3] - 1894986606 | 0; + d = (d << 10 | d >>> 22) + a | 0; + c += (a ^ (d | ~b)) + k[10] - 1051523 | 0; + c = (c << 15 | c >>> 17) + d | 0; + b += (d ^ (c | ~a)) + k[1] - 2054922799 | 0; + b = (b << 21 | b >>> 11) + c | 0; + a += (c ^ (b | ~d)) + k[8] + 1873313359 | 0; + a = (a << 6 | a >>> 26) + b | 0; + d += (b ^ (a | ~c)) + k[15] - 30611744 | 0; + d = (d << 10 | d >>> 22) + a | 0; + c += (a ^ (d | ~b)) + k[6] - 1560198380 | 0; + c = (c << 15 | c >>> 17) + d | 0; + b += (d ^ (c | ~a)) + k[13] + 1309151649 | 0; + b = (b << 21 | b >>> 11) + c | 0; + a += (c ^ (b | ~d)) + k[4] - 145523070 | 0; + a = (a << 6 | a >>> 26) + b | 0; + d += (b ^ (a | ~c)) + k[11] - 1120210379 | 0; + d = (d << 10 | d >>> 22) + a | 0; + c += (a ^ (d | ~b)) + k[2] + 718787259 | 0; + c = (c << 15 | c >>> 17) + d | 0; + b += (d ^ (c | ~a)) + k[9] - 343485551 | 0; + b = (b << 21 | b >>> 11) + c | 0; + + x[0] = a + x[0] | 0; + x[1] = b + x[1] | 0; + x[2] = c + x[2] | 0; + x[3] = d + x[3] | 0; + } + + function md5blk(s) { + var md5blks = [], + i; /* Andy King said do it this way. */ + + for (i = 0; i < 64; i += 4) { + md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24); + } + return md5blks; + } + + function md5blk_array(a) { + var md5blks = [], + i; /* Andy King said do it this way. */ + + for (i = 0; i < 64; i += 4) { + md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24); + } + return md5blks; + } + + function md51(s) { + var n = s.length, + state = [1732584193, -271733879, -1732584194, 271733878], + i, + length, + tail, + tmp, + lo, + hi; + + for (i = 64; i <= n; i += 64) { + md5cycle(state, md5blk(s.substring(i - 64, i))); + } + s = s.substring(i - 64); + length = s.length; + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3); + } + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { + md5cycle(state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + + // Beware that the final length might not fit in 32 bits so we take care of that + tmp = n * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + + tail[14] = lo; + tail[15] = hi; + + md5cycle(state, tail); + return state; + } + + function md51_array(a) { + var n = a.length, + state = [1732584193, -271733879, -1732584194, 271733878], + i, + length, + tail, + tmp, + lo, + hi; + + for (i = 64; i <= n; i += 64) { + md5cycle(state, md5blk_array(a.subarray(i - 64, i))); + } + + // Not sure if it is a bug, however IE10 will always produce a sub array of length 1 + // containing the last element of the parent array if the sub array specified starts + // beyond the length of the parent array - weird. + // https://connect.microsoft.com/IE/feedback/details/771452/typed-array-subarray-issue + a = (i - 64) < n ? a.subarray(i - 64) : new Uint8Array(0); + + length = a.length; + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= a[i] << ((i % 4) << 3); + } + + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { + md5cycle(state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + + // Beware that the final length might not fit in 32 bits so we take care of that + tmp = n * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + + tail[14] = lo; + tail[15] = hi; + + md5cycle(state, tail); + + return state; + } + + function rhex(n) { + var s = '', + j; + for (j = 0; j < 4; j += 1) { + s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F]; + } + return s; + } + + function hex(x) { + var i; + for (i = 0; i < x.length; i += 1) { + x[i] = rhex(x[i]); + } + return x.join(''); + } + + // In some cases the fast add32 function cannot be used.. + if (hex(md51('hello')) !== '5d41402abc4b2a76b9719d911017c592') { + add32 = function (x, y) { + var lsw = (x & 0xFFFF) + (y & 0xFFFF), + msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); + }; + } + + // --------------------------------------------------- + + /** + * ArrayBuffer slice polyfill. + * + * @see https://github.com/ttaubert/node-arraybuffer-slice + */ + + if (typeof ArrayBuffer !== 'undefined' && !ArrayBuffer.prototype.slice) { + (function () { + + function clamp(val, length) { + val = (val | 0) || 0; + + if (val < 0) { + return Math.max(val + length, 0); + } + + return Math.min(val, length); + } + + ArrayBuffer.prototype.slice = function (from, to) { + var length = this.byteLength, + begin = clamp(from, length), + end = length, + num, + target, + targetArray, + sourceArray; + + if (to !== undefined) { + end = clamp(to, length); + } + + if (begin > end) { + return new ArrayBuffer(0); + } + + num = end - begin; + target = new ArrayBuffer(num); + targetArray = new Uint8Array(target); + + sourceArray = new Uint8Array(this, begin, num); + targetArray.set(sourceArray); + + return target; + }; + })(); + } + + // --------------------------------------------------- + + /** + * Helpers. + */ + + function toUtf8(str) { + if (/[\u0080-\uFFFF]/.test(str)) { + // 源码是str = unescape(encodeURIComponent(str));这里的API并不对等 + buffer.from(str).toString("utf-8") + } + return str; + } + + function utf8Str2ArrayBuffer(str, returnUInt8Array) { + var length = str.length, + buff = new ArrayBuffer(length), + arr = new Uint8Array(buff), + i; + + for (i = 0; i < length; i += 1) { + arr[i] = str.charCodeAt(i); + } + + return returnUInt8Array ? arr : buff; + } + + function arrayBuffer2Utf8Str(buff) { + return String.fromCharCode.apply(null, new Uint8Array(buff)); + } + + function concatenateArrayBuffers(first, second, returnUInt8Array) { + var result = new Uint8Array(first.byteLength + second.byteLength); + + result.set(new Uint8Array(first)); + result.set(new Uint8Array(second), first.byteLength); + + return returnUInt8Array ? result : result.buffer; + } + + function hexToBinaryString(hex) { + var bytes = [], + length = hex.length, + x; + + for (x = 0; x < length - 1; x += 2) { + bytes.push(parseInt(hex.substr(x, 2), 16)); + } + + return String.fromCharCode.apply(String, bytes); + } + + // --------------------------------------------------- + + /** + * SparkMD5 OOP implementation. + * + * Use this class to perform an incremental md5, otherwise use the + * static methods instead. + */ + + function SparkMD5() { + // call reset to init the instance + this.reset(); + } + + /** + * Appends a string. + * A conversion will be applied if an utf8 string is detected. + * + * @param {String} str The string to be appended + * + * @return {SparkMD5} The instance itself + */ + SparkMD5.prototype.append = function (str) { + // Converts the string to utf8 bytes if necessary + // Then append as binary + this.appendBinary(toUtf8(str)); + + return this; + }; + + /** + * Appends a binary string. + * + * @param {String} contents The binary string to be appended + * + * @return {SparkMD5} The instance itself + */ + SparkMD5.prototype.appendBinary = function (contents) { + this._buff += contents; + this._length += contents.length; + + var length = this._buff.length, + i; + + for (i = 64; i <= length; i += 64) { + md5cycle(this._hash, md5blk(this._buff.substring(i - 64, i))); + } + + this._buff = this._buff.substring(i - 64); + + return this; + }; + + /** + * Finishes the incremental computation, reseting the internal state and + * returning the result. + * + * @param {Boolean} raw True to get the raw string, false to get the hex string + * + * @return {String} The result + */ + SparkMD5.prototype.end = function (raw) { + var buff = this._buff, + length = buff.length, + i, + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ret; + + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= buff.charCodeAt(i) << ((i % 4) << 3); + } + + this._finish(tail, length); + ret = hex(this._hash); + + if (raw) { + ret = hexToBinaryString(ret); + } + + this.reset(); + + return ret; + }; + + /** + * Resets the internal state of the computation. + * + * @return {SparkMD5} The instance itself + */ + SparkMD5.prototype.reset = function () { + this._buff = ''; + this._length = 0; + this._hash = [1732584193, -271733879, -1732584194, 271733878]; + + return this; + }; + + /** + * Gets the internal state of the computation. + * + * @return {Object} The state + */ + SparkMD5.prototype.getState = function () { + return { + buff: this._buff, + length: this._length, + hash: this._hash.slice() + }; + }; + + /** + * Gets the internal state of the computation. + * + * @param {Object} state The state + * + * @return {SparkMD5} The instance itself + */ + SparkMD5.prototype.setState = function (state) { + this._buff = state.buff; + this._length = state.length; + this._hash = state.hash; + + return this; + }; + + /** + * Releases memory used by the incremental buffer and other additional + * resources. If you plan to use the instance again, use reset instead. + */ + SparkMD5.prototype.destroy = function () { + delete this._hash; + delete this._buff; + delete this._length; + }; + + /** + * Finish the final calculation based on the tail. + * + * @param {Array} tail The tail (will be modified) + * @param {Number} length The length of the remaining buffer + */ + SparkMD5.prototype._finish = function (tail, length) { + var i = length, + tmp, + lo, + hi; + + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { + md5cycle(this._hash, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + + // Do the final computation based on the tail and length + // Beware that the final length may not fit in 32 bits so we take care of that + tmp = this._length * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + + tail[14] = lo; + tail[15] = hi; + md5cycle(this._hash, tail); + }; + + /** + * Performs the md5 hash on a string. + * A conversion will be applied if utf8 string is detected. + * + * @param {String} str The string + * @param {Boolean} [raw] True to get the raw string, false to get the hex string + * + * @return {String} The result + */ + SparkMD5.hash = function (str, raw) { + // Converts the string to utf8 bytes if necessary + // Then compute it using the binary function + return SparkMD5.hashBinary(toUtf8(str), raw); + }; + + /** + * Performs the md5 hash on a binary string. + * + * @param {String} content The binary string + * @param {Boolean} [raw] True to get the raw string, false to get the hex string + * + * @return {String} The result + */ + SparkMD5.hashBinary = function (content, raw) { + var hash = md51(content), + ret = hex(hash); + + return raw ? hexToBinaryString(ret) : ret; + }; + + // --------------------------------------------------- + + /** + * SparkMD5 OOP implementation for array buffers. + * + * Use this class to perform an incremental md5 ONLY for array buffers. + */ + SparkMD5.ArrayBuffer = function () { + // call reset to init the instance + this.reset(); + }; + + /** + * Appends an array buffer. + * + * @param {ArrayBuffer} arr The array to be appended + * + * @return {SparkMD5.ArrayBuffer} The instance itself + */ + SparkMD5.ArrayBuffer.prototype.append = function (arr) { + // @ts-ignore + var buff = concatenateArrayBuffers(this._buff.buffer, arr, true), + // @ts-ignore + length = buff.length, + i; + + // @ts-ignore + this._length += arr.byteLength; + + for (i = 64; i <= length; i += 64) { + // @ts-ignore + md5cycle(this._hash, md5blk_array(buff.subarray(i - 64, i))); + } + + // @ts-ignore + this._buff = (i - 64) < length ? new Uint8Array(buff.buffer.slice(i - 64)) : new Uint8Array(0); + + return this; + }; + + /** + * Finishes the incremental computation, reseting the internal state and + * returning the result. + * + * @param {Boolean} raw True to get the raw string, false to get the hex string + * + * @return {String} The result + */ + SparkMD5.ArrayBuffer.prototype.end = function (raw) { + // @ts-ignore + var buff = this._buff, + length = buff.length, + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + i, + ret; + + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= buff[i] << ((i % 4) << 3); + } + + this._finish(tail, length); + // @ts-ignore + ret = hex(this._hash); + + if (raw) { + ret = hexToBinaryString(ret); + } + + this.reset(); + + return ret; + }; + + /** + * Resets the internal state of the computation. + * + * @return {SparkMD5.ArrayBuffer} The instance itself + */ + SparkMD5.ArrayBuffer.prototype.reset = function () { + // @ts-ignore + this._buff = new Uint8Array(0); + // @ts-ignore + this._length = 0; + // @ts-ignore + this._hash = [1732584193, -271733879, -1732584194, 271733878]; + + return this; + }; + + /** + * Gets the internal state of the computation. + * + * @return {Object} The state + */ + SparkMD5.ArrayBuffer.prototype.getState = function () { + var state = SparkMD5.prototype.getState.call(this); + + // Convert buffer to a string + state.buff = arrayBuffer2Utf8Str(state.buff); + + return state; + }; + + /** + * Gets the internal state of the computation. + * + * @param {Object} state The state + * + * @return {SparkMD5.ArrayBuffer} The instance itself + */ + SparkMD5.ArrayBuffer.prototype.setState = function (state) { + // Convert string to buffer + state.buff = utf8Str2ArrayBuffer(state.buff, true); + + return SparkMD5.prototype.setState.call(this, state); + }; + + SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy; + + SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish; + + /** + * Performs the md5 hash on an array buffer. + * + * @param {ArrayBuffer} arr The array buffer + * @param {Boolean} [raw] True to get the raw string, false to get the hex one + * + * @return {String} The result + */ + SparkMD5.ArrayBuffer.hash = function (arr, raw) { + var hash = md51_array(new Uint8Array(arr)), + ret = hex(hash); + + return raw ? hexToBinaryString(ret) : ret; + }; + +export { SparkMD5 } + diff --git a/library/src/main/ets/ImageKnife.ets b/library/src/main/ets/ImageKnife.ets new file mode 100644 index 0000000..d168038 --- /dev/null +++ b/library/src/main/ets/ImageKnife.ets @@ -0,0 +1,393 @@ +/* + * 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 { ImageKnifeRequest } from './ImageKnifeRequest'; +import { CacheStrategy, ImageKnifeData, ImageKnifeRequestSource } from './model/ImageKnifeData'; +import { MemoryLruCache } from './utils/MemoryLruCache'; +import { IMemoryCache } from './utils/IMemoryCache' +import { FileCache } from './utils/FileCache'; +import { ImageKnifeDispatcher } from './ImageKnifeDispatcher'; +import { IEngineKey } from './key/IEngineKey'; +import { HeaderOptions, ImageKnifeOption } from './ImageKnifeOption'; +import { FileTypeUtil } from './utils/FileTypeUtil'; +import { util } from '@kit.ArkTS'; +import { image } from '@kit.ImageKit'; +import { common } from '@kit.AbilityKit'; +import { LogUtil } from './utils/LogUtil'; + + +export class ImageKnife { + private static instance: ImageKnife; + // 内存缓存 + private memoryCache: IMemoryCache = new MemoryLruCache(256, 128 * 1024 * 1024); + // 文件缓存 + private fileCache?: FileCache + private dispatcher: ImageKnifeDispatcher = new ImageKnifeDispatcher() + // 配置全局是否在子线程加载图片请求 + private _isRequestInSubThread: boolean = true; + //定义全局网络请求header map + headerMap: Map = new Map(); + customGetImage: ((context: Context, src: string | PixelMap | Resource) => Promise) | undefined = undefined + public static getInstance(): ImageKnife { + if (!ImageKnife.instance) { + ImageKnife.instance = new ImageKnife(); + } + return ImageKnife.instance; + } + + private constructor() { + } + + public set isRequestInSubThread(value: boolean) { + this._isRequestInSubThread = value; + } + + public get isRequestInSubThread(): boolean { + return this._isRequestInSubThread; + } + + /** + * 初始化文件缓存个数,大小,以及路径 + * @param context 上下文 + * @param size 缓存数量 + * @param memory 内存大小 + * @param path 文件目录 + */ + async initFileCache(context: Context, size: number = 256, memory: number = 256 * 1024 * 1024,path?: string) { + this.fileCache = new FileCache(context, size, memory) + if ( path != undefined ) { + await this.fileCache.initFileCache(path) + } else { + await this.fileCache.initFileCache() + } + } + + /** + * 判断文件缓存是否已完成初始化 + * @returns 是否初始化 + */ + public isFileCacheInit(): boolean { + return this.fileCache === undefined ? false : this.fileCache.isFileCacheInit() + } + + /** + * 全局添加单个请求头header + * @param key 请求头属性 + * @param value 请求头值 + */ + addHeader(key: string, value: Object) { + this.headerMap.set(key, value) + } + + /** + * 全局设置请求头header + * @param options 请求头数组 + */ + serHeaderOptions(options: Array) { + options.forEach((value) => { + this.headerMap.set(value.key, value.value) + }) + } + + /** + * 删除单个请求头header + * @param key 请求头属性 + */ + deleteHeader(key: string) { + this.headerMap.delete(key) + } + + /** + * 设置自定义的内存缓存 + * @param newMemoryCache 自定义内存缓存 + */ + initMemoryCache(newMemoryCache: IMemoryCache): void { + this.memoryCache = newMemoryCache + } + + /** + * 清除所有内存缓存 + */ + removeAllMemoryCache(): void { + this.memoryCache.removeAll() + } + + /** + * 清除指定内存缓存 + * @param url 待清除的url路径或ImageKnifeOption + */ + removeMemoryCache(url: string | ImageKnifeOption) { + let imageKnifeOption = new ImageKnifeOption(); + if (typeof url == 'string') { + imageKnifeOption.loadSrc = url; + } else { + imageKnifeOption = url; + } + let key = this.getEngineKeyImpl().generateMemoryKey(imageKnifeOption.loadSrc, ImageKnifeRequestSource.SRC, imageKnifeOption); + this.memoryCache.remove(key); + } + + + /** + * 预加载图片到文件缓存 + * @param loadSrc 图片地址url + * @returns 返回文件缓存路径 + */ + preLoadCache(loadSrc: string | ImageKnifeOption): Promise { + return new Promise((resolve, reject) => { + let imageKnifeOption = new ImageKnifeOption() + if (typeof loadSrc == "string") { + imageKnifeOption.loadSrc = loadSrc + } else { + imageKnifeOption = loadSrc; + } + LogUtil.log("ImageKnife_DataTime_preLoadCache-imageKnifeOption:"+loadSrc) + let fileKey = this.getEngineKeyImpl().generateFileKey(imageKnifeOption.loadSrc, imageKnifeOption.signature) + let cachePath = ImageKnife.getInstance().getFileCache().getFileToPath(fileKey) + if (cachePath == null || cachePath == "" || cachePath == undefined) { + imageKnifeOption.onLoadListener = { + onLoadSuccess(){ + resolve(ImageKnife.getInstance().getFileCache().getFileToPath(fileKey)) + }, + onLoadFailed(err) { + reject(err) + } + } + let request = new ImageKnifeRequest( + imageKnifeOption, + imageKnifeOption.context !== undefined ? imageKnifeOption.context : getContext(this) as common.UIAbilityContext, + 0, + 0, + 0, + { + showPixelMap(version: number, pixelMap: PixelMap | string) { + } + } + ) + this.execute(request) + } else { + resolve(cachePath) + } + }) + } + + /** + * 从内存或文件缓存中获取图片数据 + * @param url 图片地址url + * @param cacheType 缓存策略 + * @returns 图片数据 + * @param signature key自定义信息 + */ + getCacheImage(loadSrc: string, + cacheType: CacheStrategy = CacheStrategy.Default, signature?: string): Promise { + let option: ImageKnifeOption = { + loadSrc: loadSrc, + signature:signature + } + let engineKeyImpl: IEngineKey = this.getEngineKeyImpl(); + + return new Promise((resolve, reject) => { + if (cacheType == CacheStrategy.Memory) { + resolve(this.readMemoryCache(loadSrc, option, engineKeyImpl)) + } else if (cacheType == CacheStrategy.File) { + this.readFileCache(loadSrc, engineKeyImpl, resolve) + } else { + let data = this.readMemoryCache(loadSrc, option, engineKeyImpl) + data == undefined ? this.readFileCache(loadSrc, engineKeyImpl, resolve) : resolve(data) + } + }) + } + + /** + * 预加载缓存(用于外部已获取pixelmap,需要加入imageknife缓存的场景) + * @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 + } + } + + /** + * 清除所有文件缓存 + * @returns + */ + async removeAllFileCache(): Promise { + if (this.fileCache !== undefined) { + await this.fileCache.removeAll() + } + } + /* + * 清除指定文件缓存 + * */ + removeFileCache(url: string | ImageKnifeOption) { + let imageKnifeOption: ImageKnifeOption; + if (url instanceof ImageKnifeOption) { + imageKnifeOption = url; + } else { + imageKnifeOption = { + loadSrc: url + }; + } + let key = this.getEngineKeyImpl().generateFileKey(imageKnifeOption.loadSrc, imageKnifeOption.signature); + if (this.fileCache !== undefined) { + this.fileCache.remove(key); + } + } + + /** + * 设置taskpool默认并发数量 + * @param concurrency 默认并发数量,默认为8 + */ + setMaxRequests(concurrency: number): void { + this.dispatcher.setMaxRequests(concurrency) + } + + getFileCacheByFile(context: Context, key: string): ArrayBuffer | undefined { + if (this.fileCache !== undefined) { + return FileCache.getFileCacheByFile(context, key) + } + return undefined + } + + loadFromMemoryCache(key: string): ImageKnifeData | undefined { + if (key !== "") { + return this.memoryCache.get(key) + } + return undefined + } + + saveMemoryCache(key: string, data: ImageKnifeData): void { + if (key !== "") { + this.memoryCache.put(key, data) + } + } + + loadFromFileCache(key: string): ArrayBuffer | undefined { + return this.fileCache?.get(key) + } + + saveFileCache(key: string, data: ArrayBuffer): void { + this.fileCache?.put(key, data) + } + + getFileCache(): FileCache { + return this.fileCache as FileCache + } + + + 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) { + let keys = engineKey.generateFileKey(loadSrc) + let buffer = ImageKnife.getInstance().loadFromFileCache(keys) + 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)) + onComplete({ + source: base64str, + imageWidth: 0, + imageHeight: 0 + }) + } + + let imageSource: image.ImageSource = image.createImageSource(buffer); + let decodingOptions: image.DecodingOptions = { + editable: true, + } + + imageSource.createPixelMap(decodingOptions) + .then((pixelmap: PixelMap) => { + onComplete({ + source: pixelmap, + imageWidth: 0, + imageHeight: 0 + }) + imageSource.release() + }) + } else { + onComplete(undefined) + } + } + + saveWithoutWriteFile(key: string, bufferSize: number): void { + this.fileCache?.putWithoutWriteFile(key, bufferSize) + } + + saveFileCacheOnlyFile(context: Context, key: string, value: ArrayBuffer): boolean { + if (this.fileCache !== undefined) { + return FileCache.saveFileCacheOnlyFile(context, key, value) + } + return false + } + + async execute(request: ImageKnifeRequest,isAnimator?: boolean): Promise { + LogUtil.log("ImageKnife_DataTime_execute.start:"+request.imageKnifeOption.loadSrc) + if (this.headerMap.size > 0) { + request.addHeaderMap(this.headerMap) + } + this.dispatcher.enqueue(request,isAnimator) + LogUtil.log("ImageKnife_DataTime_execute.end:"+request.imageKnifeOption.loadSrc) + } + + setEngineKeyImpl(impl: IEngineKey): void { + this.dispatcher.setEngineKeyImpl(impl); + } + + getEngineKeyImpl(): IEngineKey { + return this.dispatcher.getEngineKeyImpl(); + } + /** + * 全局设置自定义下载 + * @param customGetImage 自定义请求函数 + */ + setCustomGetImage(customGetImage?: (context: Context, src: string | PixelMap | Resource) => Promise) { + this.customGetImage = customGetImage + } + getCustomGetImage(): undefined | ((context: Context, src: string | PixelMap | Resource) => Promise){ + return this.customGetImage + } +} \ No newline at end of file diff --git a/library/src/main/ets/ImageKnifeDispatcher.ets b/library/src/main/ets/ImageKnifeDispatcher.ets new file mode 100644 index 0000000..795d709 --- /dev/null +++ b/library/src/main/ets/ImageKnifeDispatcher.ets @@ -0,0 +1,668 @@ +/* + * 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 { ImageKnifeRequest, ImageKnifeRequestState } from './ImageKnifeRequest' +import { DefaultJobQueue } from './utils/DefaultJobQueue' +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 { + ImageKnifeRequestWithSource, + ImageKnifeRequestSource, + RequestJobResult, + RequestJobRequest +} from './model/ImageKnifeData' +import { combineArrayBuffers } from './model/utils'; +import { BusinessError } from '@kit.BasicServicesKit'; + +export class ImageKnifeDispatcher { + // 最大并发 + private maxRequests: number = 8 + // 排队队列 + private jobQueue: IJobQueue = new DefaultJobQueue() + // 执行中的请求 + executingJobMap: LightWeightMap> = new LightWeightMap(); + // 开发者可配置全局缓存 + 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) + 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,isAnimator)); + } + + + if (memoryCache !== undefined) { + // 画主图 + if (request.requestState === ImageKnifeRequestState.PROGRESS) { + // 回调请求开始 + if (requestSource === ImageKnifeRequestSource.SRC && request.imageKnifeOption.onLoadListener?.onLoadStart !== undefined) { + request.imageKnifeOption.onLoadListener?.onLoadStart() + LogUtil.log("ImageKnife_DataTime_MemoryCache_onLoadStart:" + request.imageKnifeOption.loadSrc) + } + LogUtil.log("ImageKnife_DataTime_MemoryCache_showPixelMap.start:" + request.imageKnifeOption.loadSrc) + request.ImageKnifeRequestCallback?.showPixelMap(request.componentVersion, memoryCache.source, requestSource,memoryCache.imageAnimator) + LogUtil.log("ImageKnife_DataTime_MemoryCache_showPixelMap.end:" + request.imageKnifeOption.loadSrc) + + if (requestSource == ImageKnifeRequestSource.SRC) { + request.requestState = ImageKnifeRequestState.COMPLETE + // 回调请求开结束 + if (request.imageKnifeOption.onLoadListener?.onLoadSuccess !== undefined) { + 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_true:" + request.imageKnifeOption.loadSrc) + return true + } + LogUtil.log("ImageKnife_DataTime_showFromMemomry.end_false:" + request.imageKnifeOption.loadSrc) + return false + } + + + enqueue(request: ImageKnifeRequest,isAnimator?: boolean): void { + + //1.内存有的话直接渲染 + if (this.showFromMemomry(request, request.imageKnifeOption.loadSrc, ImageKnifeRequestSource.SRC,isAnimator)) { + return + } + // 2.内存获取占位图 + if (request.imageKnifeOption.placeholderSrc !== undefined) { + if (this.showFromMemomry(request, request.imageKnifeOption.placeholderSrc, ImageKnifeRequestSource.PLACE_HOLDER)) { + request.drawPlayHolderSuccess = true + } + } + //3.判断是否要排队 + if (this.executingJobMap.length > this.maxRequests) { + this.jobQueue.add(request) + return + } + this.executeJob(request,isAnimator) + } + + executeJob(request: ImageKnifeRequest,isAnimator?: boolean): void { + LogUtil.log("ImageKnife_DataTime_executeJob.start:" + request.imageKnifeOption.loadSrc) + // 加载占位符 + if (request.imageKnifeOption.placeholderSrc !== undefined && request.drawPlayHolderSuccess == false) { + this.getAndShowImage(request, request.imageKnifeOption.placeholderSrc, ImageKnifeRequestSource.PLACE_HOLDER) + } + + // 加载主图 + this.getAndShowImage(request, request.imageKnifeOption.loadSrc, ImageKnifeRequestSource.SRC,isAnimator) + LogUtil.log("ImageKnife_DataTime_executeJob.end:" + request.imageKnifeOption.loadSrc) + } + + /** + * 获取和显示图片 + */ + getAndShowImage(currentRequest: ImageKnifeRequest, imageSrc: string | PixelMap | Resource, requestSource: ImageKnifeRequestSource,isAnimator?: boolean): void { + LogUtil.log("ImageKnife_DataTime_getAndShowImage.start:" + currentRequest.imageKnifeOption.loadSrc) + let memoryKey: string = this.engineKey.generateMemoryKey(imageSrc, requestSource, currentRequest.imageKnifeOption,isAnimator) + let requestList: List | undefined = this.executingJobMap.get(memoryKey) + if (requestList == undefined) { + requestList = new List() + requestList.add({ request: currentRequest, source: requestSource }) + this.executingJobMap.set(memoryKey, requestList) + } else { + requestList.add({ request: currentRequest, source: requestSource }) + return + } + + let isWatchProgress : boolean = false + + // 回调请求开始 + requestList.forEach((requestWithSource: ImageKnifeRequestWithSource) => { + if (requestWithSource.source === ImageKnifeRequestSource.SRC && requestWithSource.request.imageKnifeOption.onLoadListener?.onLoadStart !== undefined) { + requestWithSource.request.imageKnifeOption.onLoadListener?.onLoadStart() + LogUtil.log("ImageKnife_DataTime_getAndShowImage_onLoadStart:" + currentRequest.imageKnifeOption.loadSrc) + } + if (requestWithSource.request.imageKnifeOption.progressListener !== undefined && requestWithSource.source === ImageKnifeRequestSource.SRC) { + isWatchProgress = true + } + }); + + let request: RequestJobRequest = { + context: currentRequest.context, + src: imageSrc, + headers: currentRequest.imageKnifeOption.headerOption, + allHeaders: currentRequest.headers, + componentWidth:currentRequest.componentWidth, + componentHeight:currentRequest.componentHeight, + customGetImage: currentRequest.imageKnifeOption.customGetImage, + onlyRetrieveFromCache: currentRequest.imageKnifeOption.onlyRetrieveFromCache, + transformation: currentRequest.imageKnifeOption.transformation, + writeCacheStrategy: ImageKnife.getInstance() + .isFileCacheInit() ? currentRequest.imageKnifeOption.writeCacheStrategy : CacheStrategy.Memory, // 未初始化文件缓存时,不写文件缓存 + engineKey: this.engineKey, + signature: currentRequest.imageKnifeOption.signature, + requestSource: requestSource, + isWatchProgress: isWatchProgress, + memoryKey: memoryKey, + fileCacheFolder: ImageKnife.getInstance().getFileCache().getCacheFolder(), + isAnimator:isAnimator + } + + if(request.customGetImage == undefined) { + request.customGetImage = ImageKnife.getInstance().getCustomGetImage() + } + if (ImageKnife.getInstance().isRequestInSubThread){ + // 启动线程下载和解码主图 + LogUtil.log("ImageKnife_DataTime_getAndShowImage_Task.start:" + currentRequest.imageKnifeOption.loadSrc) + let task = new taskpool.Task(requestJob, request) + LogUtil.log("ImageKnife_DataTime_getAndShowImage_Task.end:" + currentRequest.imageKnifeOption.loadSrc) + if (isWatchProgress){ + 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) + taskpool.execute(task).then((res: Object) => { + this.doTaskCallback(res as RequestJobResult | undefined, requestList!, currentRequest, memoryKey, imageSrc, requestSource,isAnimator); + if (isWatchProgress){ + emitter.off(Constants.PROGRESS_EMITTER + memoryKey) + } + 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) + if (isWatchProgress){ + emitter.off(Constants.PROGRESS_EMITTER + memoryKey) + } + this.executingJobMap.remove(memoryKey); + this.dispatchNextJob(); + }) + } else { //主线程请求 + 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) + this.executingJobMap.remove(memoryKey); + this.dispatchNextJob(); + }) + } + } + + /** + * 回调下载进度 + * @param requestList 请求列表 + * @param data 进度 + */ + private progressCallBack(requestList:List, data: number) { + for (let i = 0; i < requestList.length; i++) { + let requestWithSource:ImageKnifeRequestWithSource = requestList[i] + if (requestWithSource.request.imageKnifeOption.progressListener !== undefined && requestWithSource.source === ImageKnifeRequestSource.SRC) { + requestWithSource.request.imageKnifeOption.progressListener(data) + } + } + } + + private doTaskCallback(requestJobResult: RequestJobResult | undefined, requestList: List , + currentRequest: ImageKnifeRequest, memoryKey: string, imageSrc: string | PixelMap | Resource, requestSource: ImageKnifeRequestSource,isAnimator?: boolean):void { + LogUtil.log("ImageKnife_DataTime_getAndShowImage_CallBack.start:"+currentRequest.imageKnifeOption.loadSrc) + if (requestJobResult === undefined){ + return + } + let pixelmap = requestJobResult.pixelMap; + if (pixelmap === undefined) { + requestList.forEach((requestWithSource: ImageKnifeRequestWithSource) => { + // 回调请求失败 + if (requestWithSource.source === ImageKnifeRequestSource.SRC && + requestWithSource.request.imageKnifeOption.onLoadListener?.onLoadFailed !== undefined && + requestJobResult.loadFail) { + requestWithSource.request.imageKnifeOption.onLoadListener.onLoadFailed(requestJobResult.loadFail); + LogUtil.log("ImageKnife_DataTime_getAndShowImage_onLoadFailed:"+currentRequest.imageKnifeOption.loadSrc) + } + if (requestWithSource.source === ImageKnifeRequestSource.SRC && + requestWithSource.request.imageKnifeOption.errorholderSrc !== undefined) { + + if (this.showFromMemomry(requestWithSource.request, requestWithSource.request.imageKnifeOption.errorholderSrc, + ImageKnifeRequestSource.ERROR_HOLDER) === false) { + this.getAndShowImage(requestWithSource.request, requestWithSource.request.imageKnifeOption.errorholderSrc, + ImageKnifeRequestSource.ERROR_HOLDER); + } + } + }); + this.executingJobMap.remove(memoryKey); + return; + } + // 保存文件缓存 + if (requestJobResult.bufferSize > 0 && currentRequest.imageKnifeOption.writeCacheStrategy !== CacheStrategy.Memory) { + LogUtil.log("ImageKnife_DataTime_getAndShowImage_saveWithoutWriteFile.start:"+currentRequest.imageKnifeOption.loadSrc) + ImageKnife.getInstance().saveWithoutWriteFile(requestJobResult.fileKey, requestJobResult.bufferSize); + LogUtil.log("ImageKnife_DataTime_getAndShowImage_saveWithoutWriteFile.end:"+currentRequest.imageKnifeOption.loadSrc) + } + + let ImageKnifeData: ImageKnifeData = { + source: pixelmap!, + imageWidth: requestJobResult.size == undefined ? 0 : requestJobResult.size.width, + imageHeight: requestJobResult.size == undefined ? 0 : requestJobResult.size.height, + type:requestJobResult.type + }; + if(requestJobResult.pixelMapList != undefined) { + let imageAnimator: Array = [] + requestJobResult.pixelMapList.forEach((item,index)=>{ + imageAnimator.push({ + src:requestJobResult.pixelMapList![index], + duration:requestJobResult.delayList![index] + }) + }) + ImageKnifeData.imageAnimator = imageAnimator + } + // 保存内存缓存 + if (currentRequest.imageKnifeOption.writeCacheStrategy !== CacheStrategy.File) { + LogUtil.log("ImageKnife_DataTime_getAndShowImage_saveMemoryCache.start:"+currentRequest.imageKnifeOption.loadSrc) + ImageKnife.getInstance() + .saveMemoryCache(this.engineKey.generateMemoryKey(imageSrc, requestSource, currentRequest.imageKnifeOption,isAnimator), + ImageKnifeData); + 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) { + // 画主图 + if (requestWithSource.source === ImageKnifeRequestSource.SRC || + requestWithSource.source === ImageKnifeRequestSource.ERROR_HOLDER + || (requestWithSource.source === ImageKnifeRequestSource.PLACE_HOLDER && + requestWithSource.request.requestState === ImageKnifeRequestState.PROGRESS)) { + LogUtil.log("ImageKnife_DataTime_getAndShowImage_showPixelMap.start:"+currentRequest.imageKnifeOption.loadSrc) + requestWithSource.request.ImageKnifeRequestCallback.showPixelMap(requestWithSource.request.componentVersion, + ImageKnifeData.source, requestWithSource.source,ImageKnifeData.imageAnimator); + LogUtil.log("ImageKnife_DataTime_getAndShowImage_showPixelMap.end:"+currentRequest.imageKnifeOption.loadSrc) + } + + if (requestWithSource.source == ImageKnifeRequestSource.SRC) { + requestWithSource.request.requestState = ImageKnifeRequestState.COMPLETE; + if (requestWithSource.request.imageKnifeOption.onLoadListener && + requestWithSource.request.imageKnifeOption.onLoadListener.onLoadSuccess) { + // 回调请求成功 + requestWithSource.request.imageKnifeOption.onLoadListener.onLoadSuccess(ImageKnifeData.source,ImageKnifeData); + LogUtil.log("ImageKnife_DataTime_getAndShowImage_onLoadSuccess:"+currentRequest.imageKnifeOption.loadSrc) + } + } else if (requestWithSource.source == ImageKnifeRequestSource.ERROR_HOLDER) { + requestWithSource.request.requestState = ImageKnifeRequestState.ERROR; + } + } else { + if (requestWithSource.source == ImageKnifeRequestSource.SRC && requestWithSource.request.imageKnifeOption.onLoadListener?.onLoadCancel) { + // 回调请求成功 + requestWithSource.request.imageKnifeOption.onLoadListener.onLoadCancel("component has destroyed") + } + } + }); + + this.executingJobMap.remove(memoryKey); + this.dispatchNextJob(); + } else { + LogUtil.log("error: no requestlist need to draw for key = " + memoryKey); + } + LogUtil.log("ImageKnife_DataTime_getAndShowImage_CallBack.end:"+currentRequest.imageKnifeOption.loadSrc) + } + + + dispatchNextJob() { + LogUtil.log("ImageKnife_DataTime_dispatchNextJob.start") + while (true) { + let request = this.jobQueue.pop() + if (request === undefined) { + break // 队列已无任务 + } + else if (request.requestState === ImageKnifeRequestState.PROGRESS) { + this.executeJob(request) + LogUtil.log("ImageKnife_DataTime_dispatchNextJob.end:" + request.imageKnifeOption.loadSrc) + break + }else if (request.requestState == ImageKnifeRequestState.DESTROY && request.imageKnifeOption.onLoadListener?.onLoadCancel) { + request.imageKnifeOption.onLoadListener.onLoadCancel("component has destroyed") + } + } + } + + setMaxRequests(concurrency: number): void { + if (concurrency > 0) { + this.maxRequests = concurrency + } + } + + setEngineKeyImpl(impl: IEngineKey): void { + this.engineKey = impl; + } + + getEngineKeyImpl(): IEngineKey { + return this.engineKey; + } +} + +/** + * 通过taskpool 二级缓存,下载/读取本地文件,编解码 + * @param context + * @param src + * @returns + */ +@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 + } + + // 生成文件key + let fileKey = request.engineKey.generateFileKey(request.src, request.signature,request.isAnimator) + + // 判断自定义下载 + if (request.customGetImage !== undefined && request.requestSource == ImageKnifeRequestSource.SRC) { + // 先从文件缓存获取 + 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: 6000, + readTimeout: 6000, + // usingProtocol:http.HttpProtocol.HTTP1_1 + // header: new Header('application/json') + }); + + await promise.then((data: number) => { + if (data == 200) { + 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 ((request.src as Resource).id !== undefined) { //从资源文件获取 + let res = request.src as Resource; + let manager = request.context.createModuleContext(res.moduleName).resourceManager + if (resBuf == undefined && request.onlyRetrieveFromCache != true && request.requestSource == ImageKnifeRequestSource.SRC) { + if(res.id == -1) { + let resName = (res.params![0] as string) + resBuf = (await manager.getMediaByName(resName.substring(10))).buffer as ArrayBuffer + } else { + resBuf = manager.getMediaContentSync(res.id).buffer as ArrayBuffer + } + } else if (resBuf == undefined && request.requestSource != ImageKnifeRequestSource.SRC) { + if(res.id == -1) { + let resName = (res.params![0] as string) + resBuf = (await manager.getMediaByName(resName.substring(10))).buffer as ArrayBuffer + } else { + resBuf = manager.getMediaContentSync(res.id).buffer as ArrayBuffer + } + } + } + } + + + 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); + if(typeValue == null) { + return { + pixelMap: undefined, + bufferSize: 0, + fileKey: '', + loadFail: "request is not a valid image source", + } + } + let imageSource: image.ImageSource = image.createImageSource(resBuf); + let decodingOptions: image.DecodingOptions = { + editable: true, + } + 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 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 hValue = Math.round(request.componentHeight); + let wValue = Math.round(request.componentWidth); + let defaultSize: image.Size = { + height: vp2px(hValue), + 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() + }) + + // 图形变化 + if (request.requestSource === ImageKnifeRequestSource.SRC && request.transformation !== undefined) { + resPixelmap = await request.transformation?.transform(request.context, resPixelmap!, request.componentWidth, request.componentHeight); + } + 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 + }; +} + diff --git a/library/src/main/ets/ImageKnifeOption.ets b/library/src/main/ets/ImageKnifeOption.ets new file mode 100644 index 0000000..438849e --- /dev/null +++ b/library/src/main/ets/ImageKnifeOption.ets @@ -0,0 +1,85 @@ +/* + * 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 taskpool from '@ohos.taskpool'; +import common from '@ohos.app.ability.common' +import { CacheStrategy, ImageKnifeData,EventImage } from './model/ImageKnifeData'; +import { PixelMapTransformation } from './transform/PixelMapTransformation'; +import { drawing } from '@kit.ArkGraphics2D'; + +export interface HeaderOptions { + key: string; + value: Object; +} + +@Observed +export class AnimatorOption { + @Track + state?: AnimationStatus = AnimationStatus.Running + @Track + iterations?: number = -1 + @Track + reverse?: boolean = false +} + +@Observed +export class ImageKnifeOption { + // 主图资源 + loadSrc: string | PixelMap | Resource = ""; + // 占位图 + placeholderSrc?: string | PixelMap | Resource; + // 失败占位图 + errorholderSrc?: string | PixelMap | Resource; + headerOption?: Array; + // 自定义缓存关键字 + signature?: string; + // 主图填充效果 + objectFit?: ImageFit + // 占位图填充效果 + placeholderObjectFit?: ImageFit + // 错误图填充效果 + errorholderObjectFit?: ImageFit + customGetImage?: (context: Context, src: string | PixelMap | Resource) => Promise + border?: BorderOptions + // 缓存策略 + writeCacheStrategy?: CacheStrategy + // 仅使用缓存加载数据 + onlyRetrieveFromCache?: boolean = false; + priority?: taskpool.Priority = taskpool.Priority.LOW + context?: common.UIAbilityContext; + progressListener?: (progress: number) => void; + transformation?: PixelMapTransformation + onLoadListener?: OnLoadCallBack | undefined; + onComplete?:(event:EventImage | undefined) => void + drawingColorFilter?: ColorFilter | drawing.ColorFilter + constructor() { + + } +} + +/** + * 请求回调 + */ +export interface OnLoadCallBack { + // 请求开始 + onLoadStart?: () => void; + + // 请求成功 + onLoadSuccess?: (data: string | PixelMap | undefined, imageKnifeData: ImageKnifeData) => void; + + // 请求结束 + onLoadFailed?: (err: string) => void; + // 请求取消 + onLoadCancel?: (reason: string) => void; +} \ No newline at end of file diff --git a/library/src/main/ets/ImageKnifeRequest.ets b/library/src/main/ets/ImageKnifeRequest.ets new file mode 100644 index 0000000..cb2115b --- /dev/null +++ b/library/src/main/ets/ImageKnifeRequest.ets @@ -0,0 +1,68 @@ +/* + * 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 { ImageKnifeOption } from './ImageKnifeOption'; +import common from '@ohos.app.ability.common'; +import { ImageKnifeRequestSource } from './model/ImageKnifeData'; + + +export class ImageKnifeRequest { + requestState: ImageKnifeRequestState = ImageKnifeRequestState.PROGRESS + componentWidth: number = 0 + componentHeight: number = 0 + drawPlayHolderSuccess: boolean = false + imageKnifeOption: ImageKnifeOption + context: common.UIAbilityContext + ImageKnifeRequestCallback: ImageKnifeRequestCallback + componentVersion: number = 0 + headers: Map = new Map() + constructor(option: ImageKnifeOption, + uIAbilityContext: common.UIAbilityContext, + width: number, + height: number, + version: number, + ImageKnifeRequestCallback: ImageKnifeRequestCallback) { + this.imageKnifeOption = option + this.context = uIAbilityContext + this.componentWidth = width + this.componentHeight = height + this.componentVersion = version + this.ImageKnifeRequestCallback = ImageKnifeRequestCallback + } + // RequestOption调用header对于的方法 + addHeader(key: string, value: Object) { + this.headers.set(key, value); + } + + // 全局调用header对应的方法,包含RequestOption的形式 + addHeaderMap(map: Map) { + map.forEach((value, key) => { + if (!this.headers.has(key)) { + this.addHeader(key, value); + } + }) + } +} + +export enum ImageKnifeRequestState { + PROGRESS, + COMPLETE, + ERROR, + DESTROY +} + + +export interface ImageKnifeRequestCallback { + showPixelMap: (version: number, pixelMap: PixelMap | string , requestSource: ImageKnifeRequestSource,imageAnimator?: Array) => void; +} diff --git a/library/src/main/ets/components/ImageKnifeAnimatorComponent.ets b/library/src/main/ets/components/ImageKnifeAnimatorComponent.ets new file mode 100644 index 0000000..2e1671f --- /dev/null +++ b/library/src/main/ets/components/ImageKnifeAnimatorComponent.ets @@ -0,0 +1,154 @@ +/* + * 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 { AnimatorOption, ImageKnifeOption } from '../ImageKnifeOption'; +import { ImageKnifeRequest, ImageKnifeRequestState } from '../ImageKnifeRequest'; +import common from '@ohos.app.ability.common'; +import { ImageKnife } from '../ImageKnife'; +import { LogUtil } from '../utils/LogUtil'; +import { ImageKnifeRequestSource } from '../model/ImageKnifeData'; + +@Component +export struct ImageKnifeAnimatorComponent { + @Watch('watchImageKnifeOption') @ObjectLink imageKnifeOption: ImageKnifeOption; + @Watch('watchAnimatorOption') @State animatorOption: AnimatorOption = new AnimatorOption(); + @State pixelMap: PixelMap | string | undefined = undefined + @State imageAnimator: Array | undefined = undefined + @State state: AnimationStatus = AnimationStatus.Running + @State iterations: number = -1 + @State reverse: boolean = false + @State adaptiveWidth: Length = '100%' + @State adaptiveHeight: Length = '100%' + @State objectFit: ImageFit = ImageFit.Contain + private request: ImageKnifeRequest | undefined + private lastWidth: number = 0 + private lastHeight: number = 0 + private currentWidth: number = 0 + private currentHeight: number = 0 + private componentVersion: number = 0 + private currentContext: common.UIAbilityContext | undefined = undefined + + aboutToAppear(): void { + this.objectFit = this.imageKnifeOption.objectFit === undefined ? ImageFit.Contain : this.imageKnifeOption.objectFit + } + + aboutToDisappear(): void { + if (this.request !== undefined) { + this.request.requestState = ImageKnifeRequestState.DESTROY + this.request = undefined + } + } + + aboutToRecycle() { + if (this.request !== undefined) { + this.request.requestState = ImageKnifeRequestState.DESTROY + this.request = undefined + } + this.objectFit = this.imageKnifeOption.objectFit === undefined ? ImageFit.Contain : this.imageKnifeOption.objectFit + } + + build() { + ImageAnimator() + .images(this.imageAnimator) + .width(this.adaptiveWidth) + .height(this.adaptiveHeight) + .border(this.imageKnifeOption.border) + .state(this.state) + .iterations(this.iterations) + .reverse(this.reverse) + .onSizeChange((oldValue:SizeOptions, newValue:SizeOptions) => { + this.currentWidth = newValue.width as number + this.currentHeight = newValue.height as number + this.lastWidth = oldValue.width as number + this.lastHeight = oldValue.height as number + if (this.currentWidth <= 0 || this.currentHeight <= 0) { + // 存在宽或者高为0,此次重回无意义,无需进行request请求 + } else { + // 前提:宽高值均有效,值>0. 条件1:当前宽高与上一次宽高不同 条件2:当前是第一次绘制 + if (this.currentHeight != this.lastHeight || this.currentWidth != this.lastWidth) { + LogUtil.log("execute request:width=" + this.currentWidth + " height= " + this.currentHeight) + ImageKnife.getInstance().execute(this.getRequest(this.currentWidth, this.currentHeight),true) + } + } + }) + } + + watchAnimatorOption(){ + if(this.animatorOption.state != undefined) { + this.state = this.animatorOption.state + } + if(this.animatorOption.iterations != undefined) { + this.iterations = this.animatorOption.iterations + } + if(this.animatorOption.reverse != undefined) { + this.reverse = this.animatorOption.reverse + } + } + + watchImageKnifeOption() { + if (this.request !== undefined) { + this.request.requestState = ImageKnifeRequestState.DESTROY + } + this.request = undefined + this.componentVersion++ + ImageKnife.getInstance().execute(this.getRequest(this.currentWidth, this.currentHeight),true) + } + + getCurrentContext(): common.UIAbilityContext { + if (this.currentContext == undefined) { + this.currentContext = getContext(this) as common.UIAbilityContext + } + return this.currentContext + } + + 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(), + width, + height, + this.componentVersion, + { + showPixelMap: async (version: number, pixelMap: PixelMap | string, requestSource: ImageKnifeRequestSource,imageAnimator?: Array) => { + if (version !== this.componentVersion) { + return //针对reuse场景,不显示历史图片 + } + if (imageAnimator != undefined) { + this.imageAnimator = imageAnimator + } else { + this.imageAnimator = [ + { + src: pixelMap + } + ] + } + + if (requestSource == ImageKnifeRequestSource.SRC) { + this.objectFit = + this.imageKnifeOption.objectFit === undefined ? ImageFit.Contain : this.imageKnifeOption.objectFit + } else if (requestSource == ImageKnifeRequestSource.PLACE_HOLDER) { + this.objectFit = + this.imageKnifeOption.placeholderObjectFit === undefined ? (this.imageKnifeOption.objectFit === undefined ? ImageFit.Contain : this.imageKnifeOption.objectFit) : this.imageKnifeOption.placeholderObjectFit + } else { + this.objectFit = + this.imageKnifeOption.errorholderObjectFit === undefined ? (this.imageKnifeOption.objectFit === undefined ? ImageFit.Contain : this.imageKnifeOption.objectFit) : this.imageKnifeOption.errorholderObjectFit + } + } + }) + } + + return this.request + } +} \ No newline at end of file diff --git a/library/src/main/ets/components/ImageKnifeComponent.ets b/library/src/main/ets/components/ImageKnifeComponent.ets new file mode 100644 index 0000000..57e7432 --- /dev/null +++ b/library/src/main/ets/components/ImageKnifeComponent.ets @@ -0,0 +1,175 @@ +/* + * 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 { ImageKnifeOption } from '../ImageKnifeOption'; +import { ImageKnifeRequest, ImageKnifeRequestState } from '../ImageKnifeRequest'; +import common from '@ohos.app.ability.common'; +import { ImageKnife } from '../ImageKnife'; +import { LogUtil } from '../utils/LogUtil'; +import { ImageKnifeData, ImageKnifeRequestSource } from '../model/ImageKnifeData'; +import { IEngineKey } from '../key/IEngineKey'; +import { DefaultEngineKey } from '../key/DefaultEngineKey'; + +@Component +export struct ImageKnifeComponent { + @Watch('watchImageKnifeOption') @ObjectLink imageKnifeOption: ImageKnifeOption; + @State pixelMap: PixelMap | string | undefined = undefined + @State syncLoad: boolean = false + @State adaptiveWidth: Length = '100%' + @State adaptiveHeight: Length = '100%' + @State objectFit: ImageFit = ImageFit.Contain + private request: ImageKnifeRequest | undefined + private lastWidth: number = 0 + private lastHeight: number = 0 + private currentWidth: number = 0 + private currentHeight: number = 0 + private componentVersion: number = 0 + private currentContext: common.UIAbilityContext | undefined = undefined + + aboutToAppear(): void { + //闪动问题失效,注释相应代码后续修复 + if(this.syncLoad) { + let engineKey: IEngineKey = new DefaultEngineKey(); + let memoryCacheSrc: ImageKnifeData | undefined = ImageKnife.getInstance() + .loadFromMemoryCache(engineKey.generateMemoryKey(this.imageKnifeOption.loadSrc,ImageKnifeRequestSource.SRC,this.imageKnifeOption)) + if (memoryCacheSrc !== undefined){ + LogUtil.log("aboutToAppear load from memory cache for key = "+ engineKey.generateMemoryKey(this.imageKnifeOption.loadSrc,ImageKnifeRequestSource.SRC,this.imageKnifeOption)) + //画主图 + this.pixelMap = memoryCacheSrc.source; + }else { + let memoryCachePlace: ImageKnifeData | undefined = ImageKnife.getInstance() + .loadFromMemoryCache(engineKey.generateMemoryKey(this.imageKnifeOption.placeholderSrc!,ImageKnifeRequestSource.PLACE_HOLDER,this.imageKnifeOption)) + if (memoryCachePlace !== undefined){ + LogUtil.log("aboutToAppear load from memory cache for key = "+ engineKey.generateMemoryKey(this.imageKnifeOption.loadSrc,ImageKnifeRequestSource.SRC,this.imageKnifeOption)) + //画主图 + this.pixelMap = memoryCachePlace.source; + } + } + } + this.objectFit = this.imageKnifeOption.objectFit === undefined ? ImageFit.Contain : this.imageKnifeOption.objectFit + } + + aboutToDisappear(): void { + if (this.request !== undefined) { + this.request.requestState = ImageKnifeRequestState.DESTROY + this.request = undefined + } + } + + aboutToRecycle() { + if (this.request !== undefined) { + this.request.requestState = ImageKnifeRequestState.DESTROY + this.request = undefined + } + this.objectFit = this.imageKnifeOption.objectFit === undefined ? ImageFit.Contain : this.imageKnifeOption.objectFit + } + + build() { + Image(this.pixelMap) + .colorFilter(this.imageKnifeOption.drawingColorFilter) + .objectFit(this.objectFit) + .width(this.adaptiveWidth) + .height(this.adaptiveHeight) + .border(this.imageKnifeOption.border) + .syncLoad(this.syncLoad) + .draggable(false) + .onComplete(this.imageKnifeOption.onComplete) + .onSizeChange((oldValue:SizeOptions, newValue:SizeOptions) => { + this.currentWidth = newValue.width as number + this.currentHeight = newValue.height as number + this.lastWidth = oldValue.width as number + this.lastHeight = oldValue.height as number + if (this.currentWidth <= 0 || this.currentHeight <= 0) { + // 存在宽或者高为0,此次重回无意义,无需进行request请求 + } else { + // 前提:宽高值均有效,值>0. 条件1:当前宽高与上一次宽高不同 条件2:当前是第一次绘制 + if (this.currentHeight != this.lastHeight || this.currentWidth != this.lastWidth) { + LogUtil.log("execute request:width=" + this.currentWidth + " height= " + this.currentHeight) + ImageKnife.getInstance().execute(this.getRequest(this.currentWidth, this.currentHeight)) + } + } + }) + } + + watchImageKnifeOption() { + if (this.request !== undefined) { + this.request.requestState = ImageKnifeRequestState.DESTROY + } + this.request = undefined + this.componentVersion++ + ImageKnife.getInstance().execute(this.getRequest(this.currentWidth, this.currentHeight)) + } + + getCurrentContext(): common.UIAbilityContext { + if (this.currentContext == undefined) { + this.currentContext = getContext(this) as common.UIAbilityContext + } + return this.currentContext + } + + 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(), + width, + height, + this.componentVersion, + { + showPixelMap: async (version: number, pixelMap: PixelMap | string, requestSource: ImageKnifeRequestSource) => { + if (version !== this.componentVersion) { + return //针对reuse场景,不显示历史图片 + } + this.pixelMap = pixelMap + if (typeof this.pixelMap !== 'string') { + if (this.imageKnifeOption.objectFit === ImageFit.Auto) { + let info = await this.pixelMap.getImageInfo() + + this.adaptiveWidth = this.currentWidth + this.adaptiveHeight = info.size.height * this.currentWidth / info.size.width + + // if (this.currentWidth / this.currentHeight > info.size.width / info.size.height) { + // this.adaptiveWidth = this.currentWidth + // this.adaptiveHeight = info.size.height * this.currentWidth / this.currentHeight + // } + // else { + // this.adaptiveWidth = info.size.width * this.currentWidth / this.currentHeight + // this.adaptiveHeight = this.currentHeight + // } + } + } else { + //console.info("KKKKKKKKKKK:" + pixelMap) + } + + if (requestSource == ImageKnifeRequestSource.SRC) { + this.objectFit = + this.imageKnifeOption.objectFit === undefined ? ImageFit.Contain : this.imageKnifeOption.objectFit + } else if (requestSource == ImageKnifeRequestSource.PLACE_HOLDER) { + this.objectFit = + this.imageKnifeOption.placeholderObjectFit === undefined ? (this.imageKnifeOption.objectFit === undefined ? ImageFit.Contain : this.imageKnifeOption.objectFit) : this.imageKnifeOption.placeholderObjectFit + } else { + this.objectFit = + this.imageKnifeOption.errorholderObjectFit === undefined ? (this.imageKnifeOption.objectFit === undefined ? ImageFit.Contain : this.imageKnifeOption.objectFit) : this.imageKnifeOption.errorholderObjectFit + } + } + }) + } + + return this.request + } +} + +interface KeyCanvas { + keyId: string +} \ No newline at end of file diff --git a/library/src/main/ets/key/DefaultEngineKey.ets b/library/src/main/ets/key/DefaultEngineKey.ets new file mode 100644 index 0000000..6d43be1 --- /dev/null +++ b/library/src/main/ets/key/DefaultEngineKey.ets @@ -0,0 +1,53 @@ +/* + * 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 { ImageKnifeOption } from '../ImageKnifeOption'; +import { IEngineKey } from './IEngineKey'; +import { PixelMapTransformation } from '../transform/PixelMapTransformation'; +import { ImageKnifeRequestSource } from '../model/ImageKnifeData'; + +@Sendable +export class DefaultEngineKey implements IEngineKey { + // 生成内存缓存key + generateMemoryKey(loadSrc: string | PixelMap | Resource, requestSource: ImageKnifeRequestSource, + imageKnifeOption: ImageKnifeOption,isAnimator?: boolean, width?: number, height?: number): string { + let key = (isAnimator == true ? "Animator=" : "loadSrc==") + (typeof loadSrc == "string" ? loadSrc : JSON.stringify(loadSrc)) + ";" + if (requestSource === ImageKnifeRequestSource.SRC) { + if (imageKnifeOption.signature !== undefined && imageKnifeOption.signature !== "") { + key += "signature=" + imageKnifeOption.signature + ";" + } + if (imageKnifeOption.transformation) { + key += "transformation=" + this.getTransformation(imageKnifeOption.transformation) + ";" + } + } + return key + } + + // 生成文件缓存key + generateFileKey(loadSrc: string | PixelMap | Resource, signature?: string,isAnimator?: boolean): string { + let src = (isAnimator == true ? "Animator=" : "loadSrc==") + (typeof loadSrc == "string" ? loadSrc : JSON.stringify(loadSrc)) + ";" + if (signature !== undefined && signature !== "") { + src += "signature=" + signature + ";" + } + return SparkMD5.hashBinary(src) + } + + 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 new file mode 100644 index 0000000..54be5bb --- /dev/null +++ b/library/src/main/ets/key/IEngineKey.ets @@ -0,0 +1,28 @@ +/* + * 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 { ImageKnifeOption } from '../ImageKnifeOption' +import { ImageKnifeRequestSource } from '../model/ImageKnifeData' + +export interface IEngineKey { + // 生成内存缓存key + generateMemoryKey(loadSrc: string | PixelMap | Resource, requestSource: ImageKnifeRequestSource, + imageKnifeOption: ImageKnifeOption,isAnimator?: boolean, width?: number, height?: number): string + + // 生成文件缓存key + generateFileKey(loadSrc: string | PixelMap | Resource, signature?: string,isAnimator?: boolean): string +} + + + diff --git a/library/src/main/ets/model/ImageKnifeData.ets b/library/src/main/ets/model/ImageKnifeData.ets new file mode 100644 index 0000000..79fe5d0 --- /dev/null +++ b/library/src/main/ets/model/ImageKnifeData.ets @@ -0,0 +1,106 @@ +/* + * 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 { HeaderOptions } from '../ImageKnifeOption' +import { ImageKnifeRequest } from '../ImageKnifeRequest' +import { IEngineKey } from '../key/IEngineKey' +import { PixelMapTransformation } from '../transform/PixelMapTransformation' +import common from '@ohos.app.ability.common'; +import { Size } from '@kit.ArkUI' + +export interface ImageKnifeData { + source: PixelMap | string, + imageWidth: number, + imageHeight: number, + type?:string, + imageAnimator?: Array +} +/** + * onComplete成功回调 + */ +export interface EventImage { + width: number; + height: number; + componentWidth: number; + componentHeight: number; + loadingStatus: number; + contentWidth: number; + contentHeight: number; + contentOffsetX: number; + contentOffsetY: number; +} +/** + * 缓存策略 + */ +export enum CacheStrategy { + // 默认-写入/读取内存和文件缓存 + Default = 0, + // 只写入/读取内存缓存 + Memory = 1, + // 只写入/读取文件缓存 + File = 2 +} + +/** + * 区分是src,placehodler,还是error_holder + */ +export enum ImageKnifeRequestSource { + SRC, + PLACE_HOLDER, + ERROR_HOLDER +} + + +export interface ImageKnifeRequestWithSource { + request: ImageKnifeRequest + source: ImageKnifeRequestSource +} + +/** + * request子线程处理时的返回 + */ +export interface RequestJobResult { + pixelMap: PixelMap | string | undefined + bufferSize: number + fileKey: string + loadFail?: string, + size?:Size, + type?: string, + pixelMapList?:Array, + delayList?: Array +} + +/** + * request子线程处理时的请求参数 + */ +export interface RequestJobRequest { + context: common.UIAbilityContext, + src: string | PixelMap | Resource, + headers?: Array, + allHeaders: Map, + componentWidth: number, + componentHeight: number, + customGetImage?: (context: Context, src: string | PixelMap | Resource) => Promise, + onlyRetrieveFromCache?: boolean + requestSource: ImageKnifeRequestSource + transformation?: PixelMapTransformation + writeCacheStrategy?: CacheStrategy + signature?: string + engineKey: IEngineKey + isWatchProgress: boolean + memoryKey: string + fileCacheFolder: string, + isAnimator?: boolean +} + diff --git a/library/src/main/ets/model/utils.ets b/library/src/main/ets/model/utils.ets new file mode 100644 index 0000000..d5d4269 --- /dev/null +++ b/library/src/main/ets/model/utils.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. + */ + +export function combineArrayBuffers(arrayBuffers: ArrayBuffer[]): ArrayBuffer { + // 计算多个ArrayBuffer的总字节大小 + let totalByteLength = 0; + for (const arrayBuffer of arrayBuffers) { + totalByteLength += arrayBuffer.byteLength; + } + + // 创建一个新的ArrayBuffer + const combinedArrayBuffer = new ArrayBuffer(totalByteLength); + + // 创建一个Uint8Array来操作新的ArrayBuffer + const combinedUint8Array = new Uint8Array(combinedArrayBuffer); + + // 依次复制每个ArrayBuffer的内容到新的ArrayBuffer中 + let offset = 0; + for (const arrayBuffer of arrayBuffers) { + const sourceUint8Array = new Uint8Array(arrayBuffer); + combinedUint8Array.set(sourceUint8Array, offset); + offset += sourceUint8Array.length; + } + + return combinedArrayBuffer; +} \ No newline at end of file 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..10af8cd --- /dev/null +++ b/library/src/main/ets/transform/BlurTransformation.ets @@ -0,0 +1,42 @@ +/* + * 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 + } + + getName(): string { + return this.constructor.name + ';radius:' + this.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..a263ec9 --- /dev/null +++ b/library/src/main/ets/transform/BrightnessTransformation.ets @@ -0,0 +1,42 @@ +/* + * 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 + } + + getName(): string { + return this.constructor.name + ';bright:' + this.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/CropCircleTransformation.ets b/library/src/main/ets/transform/CropCircleTransformation.ets new file mode 100644 index 0000000..707ecbf --- /dev/null +++ b/library/src/main/ets/transform/CropCircleTransformation.ets @@ -0,0 +1,89 @@ +/* + * 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 { Size } from '@kit.ArkUI'; +import { image } from '@kit.ImageKit'; + +/** + * 图片变换:圆形裁剪效果 + */ +@Sendable +export class CropCircleTransformation extends PixelMapTransformation { + private mCenterX: number = 0; + private mCenterY: number = 0; + private mRadius: number = 0; + + constructor() { + super(); + } + + getName(): string { + return this.constructor.name + ';mCenterX:' + this.mCenterX + ';mCenterY:' + this.mCenterY + ';mRadius:' + this.mRadius; + } + + async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise { + return await this.transformCircle(toTransform); + } + + private async transformCircle(data: PixelMap): Promise { + let imageInfo: image.ImageInfo = await data.getImageInfo(); + let size: Size = { + width: imageInfo.size.width, + height: imageInfo.size.height + }; + if (!size) { + console.error("CropCircleTransformation The image size does not exist."); + return data; + } + let height: number = size.height; + let width: number = size.width; + this.mRadius = 0; + if (width > height) { + this.mRadius = height / 2; + } else { + this.mRadius = width / 2; + } + this.mCenterX = width / 2; + this.mCenterY = height / 2; + + let bufferData: ArrayBuffer = new ArrayBuffer(data.getPixelBytesNumber()); + await data.readPixelsToBuffer(bufferData); + + let dataArray = new Uint8Array(bufferData); + + for (let h = 0; h <= height; h++) { + for (let w = 0; w <= width; w++) { + if (this.isContainsCircle(w, h)) { + continue; + } + // 针对的点 + let index = (h * width + w) * 4; + dataArray[index] = 0; + dataArray[index+1] = 0; + dataArray[index+2] = 0; + dataArray[index+3] = 0; + } + } + await data.writeBufferToPixels(bufferData); + return data; + } + + isContainsCircle(x: number, y: number): boolean { + let a = Math.pow((this.mCenterX - x), 2); + let b = Math.pow((this.mCenterY - y), 2); + let c = Math.sqrt((a + b)); + return c <= this.mRadius; + } +} \ No newline at end of file diff --git a/library/src/main/ets/transform/CropCircleWithBorderTransformation.ets b/library/src/main/ets/transform/CropCircleWithBorderTransformation.ets new file mode 100644 index 0000000..bad7336 --- /dev/null +++ b/library/src/main/ets/transform/CropCircleWithBorderTransformation.ets @@ -0,0 +1,133 @@ +/* + * 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 { Size } from '@kit.ArkUI'; +import { image } from '@kit.ImageKit'; + +export interface rgbColor { + r_color: number, + g_color: number, + b_color: number, +} + +/** + * 图片变换:圆环裁剪效果 + */ +@Sendable +export class CropCircleWithBorderTransformation extends PixelMapTransformation { + private mBorderSize: number = 5; + private mCenterX: number = 0; + private mCenterY: number = 0; + private mRadius: number = 0; + private mRColor: number = 0; + private mGColor: number = 0; + private mBColor: number = 0; + + constructor(borderSize: number, value: rgbColor) { + super(); + this.mRColor = value.g_color; + this.mGColor = value.g_color; + this.mBColor = value.b_color; + this.mBorderSize = borderSize; + } + + getConstructorParams() { + return JSON.stringify([this.mBorderSize, { + r_color: this.mRColor, + g_color: this.mGColor, + b_color: this.mBColor + }]); + } + + getName(): string { + return this.constructor.name + ';mBorderSize:' + this.mBorderSize + ';mCenterX:' + this.mCenterX + ';mCenterY:' + + this.mCenterY + ';mRadius:' + this.mRadius + ';mRColor:' + this.mRColor + ';mGColor:' + this.mGColor + ';mBColor:' + this.mBColor; + } + + async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise { + return await this.transformPixelMap(toTransform); + } + + private async transformPixelMap(pixelMap: PixelMap): Promise { + let imageInfo: image.ImageInfo = await pixelMap.getImageInfo(); + let size: Size = { + width: imageInfo.size.width, + height: imageInfo.size.height + }; + if (!size) { + console.error("CropCircleWithBorderTransformation The image size does not exist."); + return pixelMap; + } + let height: number = size.height; + let width: number = size.width; + this.mRadius = 0; + if (width > height) { + this.mRadius = height / 2; + } else { + this.mRadius = width / 2; + } + this.mCenterX = width / 2; + this.mCenterY = height / 2; + + + let bufferData = new ArrayBuffer(pixelMap.getPixelBytesNumber()); + await pixelMap.readPixelsToBuffer(bufferData); + + let dataArray = new Uint8Array(bufferData); + + for (let h = 0; h <= height; h++) { + for (let w = 0; w <= width; w++) { + // 不在大圆之内的设置透明 + // 在大圆与小圆之间的 设置rgb值 + // 小圆之内的不变 + let isSmallCircle: boolean = this.isContainsSmallCircle(w, h); + let isBigCircle: boolean = this.isContainsCircle(w, h); + if (isSmallCircle) { + continue; + } + + let index = (h * width + w) * 4; + if (!isBigCircle) { + // 设置透明 + dataArray[index] = 0; + dataArray[index+1] = 0; + dataArray[index+2] = 0; + dataArray[index+3] = 0; + } else { + // 设置broke + dataArray[index] = this.mRColor; + dataArray[index+1] = this.mGColor; + dataArray[index+2] = this.mBColor; + } + } + } + await pixelMap.writeBufferToPixels(bufferData); + return pixelMap; + } + + isContainsCircle(x: number, y: number): boolean { + let a: number = Math.pow((this.mCenterX - x), 2); + let b: number = Math.pow((this.mCenterY - y), 2); + let c: number = Math.sqrt((a + b)); + return c <= this.mRadius; + } + + isContainsSmallCircle(x: number, y: number): boolean { + let a: number = Math.pow((this.mCenterX - x), 2); + let b: number = Math.pow((this.mCenterY - y), 2); + let c: number = Math.sqrt((a + b)); + return c <= (this.mRadius - this.mBorderSize); + } +} \ No newline at end of file diff --git a/library/src/main/ets/transform/CropSquareTransformation.ets b/library/src/main/ets/transform/CropSquareTransformation.ets new file mode 100644 index 0000000..f939845 --- /dev/null +++ b/library/src/main/ets/transform/CropSquareTransformation.ets @@ -0,0 +1,49 @@ +/* + * 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 { Size } from '@kit.ArkUI'; +import { image } from '@kit.ImageKit'; + +/** + * 图片变换:正方形裁剪效果 + */ +@Sendable +export class CropSquareTransformation extends PixelMapTransformation { + constructor() { + super(); + } + + async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise { + let imageInfo: image.ImageInfo = await toTransform.getImageInfo(); + let size: Size = { + width: imageInfo.size.width, + height: imageInfo.size.height + }; + if (!size) { + console.error("CropSquareTransformation The image size does not exist."); + return toTransform; + } + let pixelMapWidth: number = size.width; + let pixelMapHeight: number = size.height; + let targetSize: number = pixelMapWidth > pixelMapHeight ? pixelMapHeight : pixelMapWidth; + let region: image.Region = { + size: { width: targetSize, height: targetSize }, + x: pixelMapWidth / 2 - targetSize / 2, + y: pixelMapHeight / 2 - targetSize / 2 + }; + await toTransform.crop(region); + return toTransform; + } +} \ No newline at end of file diff --git a/library/src/main/ets/transform/CropTransformation.ets b/library/src/main/ets/transform/CropTransformation.ets new file mode 100644 index 0000000..1d63651 --- /dev/null +++ b/library/src/main/ets/transform/CropTransformation.ets @@ -0,0 +1,84 @@ +/* + * 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 { Size } from '@kit.ArkUI'; +import { image } from '@kit.ImageKit'; + +/** + * 图片变换:自定义裁剪效果 + */ +@Sendable +export class CropTransformation extends PixelMapTransformation { + private mWidth: number = 0; + private mHeight: number = 0; + private mCropType: number = 0; + + constructor(width: number, height: number, cropType: number) { + super(); + this.mWidth = width; + this.mHeight = height; + this.mCropType = cropType; + } + + getName(): string { + return this.constructor.name + ";mWidth:" + this.mWidth + ";mHeight:" + this.mHeight + ";mCropType:" + this.mCropType; + } + + async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise { + let imageInfo: image.ImageInfo = await toTransform.getImageInfo(); + let size: Size = { + width: imageInfo.size.width, + height: imageInfo.size.height + }; + if (!size) { + console.error("CropTransformation The image size does not exist."); + return toTransform; + } + let pixelMapWidth: number = size.width; + let pixelMapHeight: number = size.height; + this.mWidth = this.mWidth == 0 ? pixelMapWidth : this.mWidth; + this.mHeight = this.mHeight == 0 ? pixelMapHeight : this.mHeight; + let scaleX: number = this.mWidth / pixelMapWidth; + let scaleY: number = this.mHeight / pixelMapHeight; + let scale: number = Math.max(scaleX, scaleY); + let scaledWidth: number = scale * pixelMapWidth; + let scaledHeight: number = scale * pixelMapHeight; + let left: number = (this.mWidth - scaledWidth) / 2; + let top: number = Math.abs(this.getTop(pixelMapHeight)); + let region: image.Region = { + size: { + width: scaledWidth > pixelMapWidth ? pixelMapWidth : scaledWidth, + height: scaledHeight > pixelMapHeight ? pixelMapHeight : scaledHeight + }, + x: left < 0 ? 0 : left, + y: top < 0 ? 0 : top + }; + toTransform.cropSync(region); + return toTransform; + } + + private getTop(scaledHeight: number): number { + switch (this.mCropType) { + case 0: + return 0; + case 1: + return (this.mHeight - scaledHeight) / 2; + case 2: + return this.mHeight - scaledHeight; + default: + return 0; + } + } +} \ No newline at end of file diff --git a/library/src/main/ets/transform/GrayScaleTransformation.ets b/library/src/main/ets/transform/GrayScaleTransformation.ets new file mode 100644 index 0000000..eb13e48 --- /dev/null +++ b/library/src/main/ets/transform/GrayScaleTransformation.ets @@ -0,0 +1,34 @@ +/* + * 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 '@kit.ArkGraphics2D'; + +/** + * 图片变换:灰化效果 + */ +@Sendable +export class GrayScaleTransformation extends PixelMapTransformation { + constructor() { + super(); + } + + async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise { + let headFilter = effectKit.createEffect(toTransform); + if (headFilter != null) { + return await headFilter.grayscale().getEffectPixelMap(); + } + return toTransform; + } +} \ No newline at end of file diff --git a/library/src/main/ets/transform/InvertTransformation.ets b/library/src/main/ets/transform/InvertTransformation.ets new file mode 100644 index 0000000..40e5e32 --- /dev/null +++ b/library/src/main/ets/transform/InvertTransformation.ets @@ -0,0 +1,34 @@ +/* + * 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 '@kit.ArkGraphics2D'; + +/** + * 图片变换:反转效果 + */ +@Sendable +export class InvertTransformation extends PixelMapTransformation { + constructor() { + super(); + } + + async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise { + let headFilter = effectKit.createEffect(toTransform); + if (headFilter != null) { + return await headFilter.invert().getEffectPixelMap(); + } + return toTransform; + } +} \ No newline at end of file diff --git a/library/src/main/ets/transform/KuwaharaTransformation.ets b/library/src/main/ets/transform/KuwaharaTransformation.ets new file mode 100644 index 0000000..4baf79b --- /dev/null +++ b/library/src/main/ets/transform/KuwaharaTransformation.ets @@ -0,0 +1,54 @@ +/* + * 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 { GPUImageKuwaharaFilter } from '@ohos/gpu_transform'; +import { PixelMapTransformation } from './PixelMapTransformation'; +import { image } from '@kit.ImageKit'; + +/** + * 图片变换:桑原滤波效果 + */ +@Sendable +export class KuwaharaTransformation extends PixelMapTransformation { + private radius: number; + + constructor(radius: number) { + super(); + this.radius = radius; + } + + getName(): string { + return this.constructor.name + ';radius:' + this.radius; + } + + async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise { + let imageInfo: image.ImageInfo = await toTransform.getImageInfo(); + if (!imageInfo.size) { + console.error("KuwaharaTransformation The image size does not exist."); + return toTransform; + } + return await this.kuwaharaGpu(toTransform, imageInfo.size.width, imageInfo.size.height); + } + + private async kuwaharaGpu(bitmap: PixelMap, targetWidth: number, targetHeight: number): Promise { + let bufferData = new ArrayBuffer(bitmap.getPixelBytesNumber()); + await bitmap.readPixelsToBuffer(bufferData); + let filter = new GPUImageKuwaharaFilter(); + filter.setImageData(bufferData, targetWidth, targetHeight); + filter.setRadius(this.radius); + let buf = await filter.getPixelMapBuf(0, 0, targetWidth, targetHeight); + await bitmap.writeBufferToPixels(buf); + return bitmap; + } +} \ No newline at end of file diff --git a/library/src/main/ets/transform/MaskTransformation.ets b/library/src/main/ets/transform/MaskTransformation.ets new file mode 100644 index 0000000..a715cf1 --- /dev/null +++ b/library/src/main/ets/transform/MaskTransformation.ets @@ -0,0 +1,198 @@ +/* + * 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 { Size } from '@kit.ArkUI'; +import { image } from '@kit.ImageKit'; +import { resourceManager } from '@kit.LocalizationKit'; +import { CalculatePixelUtils } from '../utils/CalculatePixelUtils'; +import { ColorUtils } from '../utils/ColorUtils'; +import { PixelEntry } from './entry/PixelEntry'; + +/** + * 图片变换:遮罩效果 + */ +@Sendable +export class MaskTransformation extends PixelMapTransformation { + private mResourceId: number; + private mResourceModuleName: string; + + constructor(resource: Resource) { + super(); + this.mResourceId = resource.id; + this.mResourceModuleName = resource.moduleName; + } + + getName(): string { + return this.constructor.name + ';resourceId:' + this.mResourceId + ';resourceModuleName:' + this.mResourceModuleName; + } + + async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise { + let imageInfo = await toTransform.getImageInfo(); + let size: Size = { + width: imageInfo.size.width, + height: imageInfo.size.height + }; + if (!size) { + console.error("MaskTransformation The image size does not exist."); + return toTransform; + } + let pixelMapWidth: number = size.width; + let pixelMapHeight: number = size.height; + let targetWidth: number = width; + let targetHeight: number = height; + if (pixelMapWidth > targetWidth && pixelMapHeight > targetHeight) { + let scale = Math.max(targetWidth / pixelMapWidth, targetHeight / pixelMapHeight); + await toTransform.scale(scale, scale); + return await this.openInternal(context, toTransform, scale * pixelMapWidth, scale * pixelMapHeight); + } + return await this.openInternal(context, toTransform, size.width, size.height); + } + + private async openInternal(context: Context, bitmap: PixelMap, width: number, height: number): Promise { + if (context == undefined) { + console.error("MaskTransformation openInternal the context is undefined."); + return bitmap; + } + let moduleContext = context.createModuleContext(this.mResourceModuleName); + if (moduleContext == undefined) { + console.error("MaskTransformation openInternal the moduleContext is undefined."); + return bitmap; + } + let resourceManager = moduleContext.resourceManager as resourceManager.ResourceManager; + if (resourceManager == undefined) { + console.error("MaskTransformation openInternal the resourceManager is undefined."); + return bitmap; + } + let array: Uint8Array = await resourceManager.getMediaContent(this.mResourceId); + let buffer = array.buffer.slice(array.byteOffset, array.byteLength + array.byteOffset); + let imageSource: image.ImageSource = image.createImageSource(buffer); + let options: image.DecodingOptions = { + editable: true, + desiredSize: { + width: width, + height: height + } + }; + let maskBitmap: PixelMap = await imageSource.createPixelMap(options); + return await this.mask(bitmap, maskBitmap); + } + + async mask(bitmap: PixelMap, maskBitmap: PixelMap): Promise { + let imageInfo = await bitmap.getImageInfo(); + let size: Size = { + width: imageInfo.size.width, + height: imageInfo.size.height + }; + if (!size) { + console.error("MaskTransformation mask the image size does not exist."); + return bitmap; + } + let width = size.width; + let height = size.height; + let rgbData = CalculatePixelUtils.createInt2DArray(height, width); + let bufferData = new ArrayBuffer(bitmap.getPixelBytesNumber()); + await bitmap.readPixelsToBuffer(bufferData); + let dataArray = new Uint8Array(bufferData); + let ph = 0; + let pw = 0; + for (let index = 0; index < dataArray.length; index += 4) { + const r = dataArray[index]; + const g = dataArray[index+1]; + const b = dataArray[index+2]; + const f = dataArray[index+3]; + let entry = new PixelEntry(); + entry.a = 0; + entry.b = b; + entry.g = g; + entry.r = r; + entry.f = f; + entry.pixel = ColorUtils.rgb(entry.r, entry.g, entry.b); + rgbData[ph][pw] = ColorUtils.rgb(entry.r, entry.g, entry.b); + if (pw == width - 1) { + pw = 0; + ph++; + } else { + pw++; + } + } + let imageInfoMask = await maskBitmap.getImageInfo(); + let sizeMask: Size = { + width: imageInfoMask.size.width, + height: imageInfoMask.size.height + }; + if (!sizeMask) { + console.error("MaskTransformation mask the sizeMask size does not exist."); + return bitmap; + } + let widthMask = sizeMask.width; + let heightMask = sizeMask.height; + let rgbDataMask = CalculatePixelUtils.createInt2DArray(heightMask, widthMask); + let pixEntry: Array = new Array(); + let bufferDataM = new ArrayBuffer(maskBitmap.getPixelBytesNumber()); + await maskBitmap.readPixelsToBuffer(bufferDataM); + let dataArrayM = new Uint8Array(bufferDataM); + let phM = 0; + let pwM = 0; + for (let index = 0; index < dataArrayM.length; index += 4) { + const r = dataArrayM[index]; + const g = dataArrayM[index+1]; + const b = dataArrayM[index+2]; + const f = dataArrayM[index+3]; + let entry = new PixelEntry(); + entry.a = 0; + entry.b = b; + entry.g = g; + entry.r = r; + entry.f = f; + entry.pixel = ColorUtils.rgb(entry.r, entry.g, entry.b); + pixEntry.push(entry); + if (entry.r == 0 && entry.g == 0 && entry.b == 0) { + rgbDataMask[phM][pwM] = rgbData[phM][pwM]; + } else { + rgbDataMask[phM][pwM] = ColorUtils.rgb(entry.r, entry.g, entry.b); + } + if (pwM == widthMask - 1) { + pwM = 0; + phM++; + } else { + pwM++; + } + } + let bufferNewData = new ArrayBuffer(maskBitmap.getPixelBytesNumber()); + let dataNewArray = new Uint8Array(bufferNewData); + let index = 0; + let mh = 0; + let nw = 0; + for (let i = 0; i < dataNewArray.length; i += 4) { + let pixel1 = rgbDataMask[mh][nw]; + if (nw == widthMask - 1) { + nw = 0; + mh++; + } else { + nw++; + } + let pR = ColorUtils.red(pixel1); + let pG = ColorUtils.green(pixel1); + let pB = ColorUtils.blue(pixel1); + dataNewArray[i] = pR; + dataNewArray[i+1] = pG; + dataNewArray[i+2] = pB; + dataNewArray[i+3] = pixEntry[index].f; + index++; + } + await maskBitmap.writeBufferToPixels(bufferNewData); + return maskBitmap; + } +} \ No newline at end of file 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 diff --git a/library/src/main/ets/transform/PixelationTransformation.ets b/library/src/main/ets/transform/PixelationTransformation.ets new file mode 100644 index 0000000..2a32d78 --- /dev/null +++ b/library/src/main/ets/transform/PixelationTransformation.ets @@ -0,0 +1,58 @@ +/* + * 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 { GPUImagePixelationFilter } from '@ohos/gpu_transform'; +import { PixelMapTransformation } from './PixelMapTransformation'; +import { image } from '@kit.ImageKit'; + +/** + * Applies a Pixelation effect to the image. + * The pixel with a default of 10.0. + */ +@Sendable +export class PixelationTransformation extends PixelMapTransformation { + private mPixel: number = 10.0; + + constructor(pixel?: number) { + super(); + if (pixel) { + this.mPixel = pixel; + } + } + + getName(): string { + return this.constructor.name + ';pixel:' + this.mPixel; + } + + + async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise { + let imageInfo: image.ImageInfo = await toTransform.getImageInfo(); + if (!imageInfo.size) { + console.error("PixelationTransformation The image size does not exist."); + return toTransform; + } + return await this.pixelGPU(toTransform, imageInfo.size.width, imageInfo.size.height); + } + + private async pixelGPU(bitmap: PixelMap, targetWidth: number, targetHeight: number): Promise { + let bufferData = new ArrayBuffer(bitmap.getPixelBytesNumber()); + await bitmap.readPixelsToBuffer(bufferData); + let filter = new GPUImagePixelationFilter(); + filter.setImageData(bufferData, targetWidth, targetHeight); + filter.setPixel(this.mPixel); + let buf = await filter.getPixelMapBuf(0, 0, targetWidth, targetHeight); + await bitmap.writeBufferToPixels(buf); + return bitmap; + } +} \ No newline at end of file diff --git a/library/src/main/ets/transform/SepiaTransformation.ets b/library/src/main/ets/transform/SepiaTransformation.ets new file mode 100644 index 0000000..153d69d --- /dev/null +++ b/library/src/main/ets/transform/SepiaTransformation.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 { GPUImageSepiaToneFilter } from '@ohos/gpu_transform'; +import { PixelMapTransformation } from './PixelMapTransformation'; +import { image } from '@kit.ImageKit'; + +/** + * 图片变换:乌墨色滤波效果 + */ +@Sendable +export class SepiaTransformation extends PixelMapTransformation { + constructor() { + super(); + } + + async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise { + let imageInfo: image.ImageInfo = await toTransform.getImageInfo(); + if (!imageInfo.size) { + console.error("SepiaTransformation The image size does not exist."); + return toTransform; + } + return await this.sepiaGPU(toTransform, imageInfo.size.width, imageInfo.size.height); + } + + private async sepiaGPU(bitmap: PixelMap, targetWidth: number, targetHeight: number): Promise { + let bufferData = new ArrayBuffer(bitmap.getPixelBytesNumber()); + await bitmap.readPixelsToBuffer(bufferData); + let filter = new GPUImageSepiaToneFilter(); + filter.setImageData(bufferData, targetWidth, targetHeight); + let buf = await filter.getPixelMapBuf(0, 0, targetWidth, targetHeight); + await bitmap.writeBufferToPixels(buf); + return bitmap; + } +} \ No newline at end of file diff --git a/library/src/main/ets/transform/SketchTransformation.ets b/library/src/main/ets/transform/SketchTransformation.ets new file mode 100644 index 0000000..2f8933d --- /dev/null +++ b/library/src/main/ets/transform/SketchTransformation.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 { GPUImageSketchFilter } from '@ohos/gpu_transform'; +import { PixelMapTransformation } from './PixelMapTransformation'; +import { image } from '@kit.ImageKit'; + +/** + * 图片变换:素描效果 + */ +@Sendable +export class SketchTransformation extends PixelMapTransformation { + constructor() { + super(); + } + + async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise { + let imageInfo: image.ImageInfo = await toTransform.getImageInfo(); + if (!imageInfo.size) { + console.error("SketchTransformation The image size does not exist."); + return toTransform; + } + return await this.sketchGPU(toTransform, imageInfo.size.width, imageInfo.size.height); + } + + private async sketchGPU(bitmap: PixelMap, targetWidth: number, targetHeight: number): Promise { + let bufferData = new ArrayBuffer(bitmap.getPixelBytesNumber()); + await bitmap.readPixelsToBuffer(bufferData); + let filter = new GPUImageSketchFilter(); + filter.setImageData(bufferData, targetWidth, targetHeight); + let buf = await filter.getPixelMapBuf(0, 0, targetWidth, targetHeight); + await bitmap.writeBufferToPixels(buf); + return bitmap; + } +} \ No newline at end of file diff --git a/library/src/main/ets/transform/SwirlTransformation.ets b/library/src/main/ets/transform/SwirlTransformation.ets new file mode 100644 index 0000000..1cebc74 --- /dev/null +++ b/library/src/main/ets/transform/SwirlTransformation.ets @@ -0,0 +1,71 @@ +/* + * 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 { GPUImageSwirlFilter } from '@ohos/gpu_transform'; +import { PixelMapTransformation } from './PixelMapTransformation'; +import { image } from '@kit.ImageKit'; + +/** + * 图片变换:扭曲效果 + */ +@Sendable +export class SwirlTransformation extends PixelMapTransformation { + // degree:值越大范围越大 + private mDegree: number = 0; + // 取值范围 0.0 ~ 1.0 + private mAngle: number = 0.9; + // 在图中的位置 取值范围 0.0 ~ 1.0 + private mXCenter: number = 0.5; + // 在图中的位置 取值范围 0.0 ~ 1.0 + private mYCenter: number = 0.5; + + constructor(degree: number, angle?: number, centerPoint?: Array) { + super(); + this.mDegree = degree; + if (angle) { + this.mAngle = angle; + } + if (centerPoint && centerPoint.length === 2) { + this.mXCenter = centerPoint[0]; + this.mYCenter = centerPoint[1]; + } + } + + getName(): string { + return this.constructor.name + ';degree:' + this.mDegree + ';angle:' + this.mAngle + ';XCenter:' + this.mXCenter + + ';YCenter:' + this.mYCenter; + } + + async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise { + let imageInfo: image.ImageInfo = await toTransform.getImageInfo(); + if (!imageInfo.size) { + console.error("SwirlTransformation The image size does not exist."); + return toTransform; + } + return await this.swirlGPU(toTransform, imageInfo.size.width, imageInfo.size.height); + } + + private async swirlGPU(bitmap: image.PixelMap, targetWidth: number, targetHeight: number): Promise { + let bufferData = new ArrayBuffer(bitmap.getPixelBytesNumber()); + await bitmap.readPixelsToBuffer(bufferData); + let filter = new GPUImageSwirlFilter(); + filter.setImageData(bufferData, targetWidth, targetHeight); + filter.setRadius(this.mDegree); + filter.setAngle(this.mAngle); + filter.setCenter(this.mXCenter, this.mYCenter); + let buf = await filter.getPixelMapBuf(0, 0, targetWidth, targetHeight); + await bitmap.writeBufferToPixels(buf); + return bitmap; + } +} \ No newline at end of file diff --git a/library/src/main/ets/transform/ToonTransformation.ets b/library/src/main/ets/transform/ToonTransformation.ets new file mode 100644 index 0000000..8b73343 --- /dev/null +++ b/library/src/main/ets/transform/ToonTransformation.ets @@ -0,0 +1,61 @@ +/* + * 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 { GPUImageToonFilter } from '@ohos/gpu_transform'; +import { PixelMapTransformation } from './PixelMapTransformation'; +import { image } from '@kit.ImageKit'; + +/** + * 图片变换:动画滤镜效果 + */ +@Sendable +export class ToonTransformation extends PixelMapTransformation { + private threshold: number = 0.2; + private quantizationLevels: number = 10.0; + + constructor(threshold?: number, quantizationLevels?: number) { + super(); + if (threshold) { + this.threshold = threshold; + } + if (quantizationLevels) { + this.quantizationLevels = quantizationLevels; + } + } + + getName(): string { + return this.constructor.name + ';threshold:' + this.threshold + ';quantizationLevels:' + this.quantizationLevels; + } + + async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise { + let imageInfo: image.ImageInfo = await toTransform.getImageInfo(); + if (!imageInfo.size) { + console.error("ToonTransformation The image size does not exist."); + return toTransform; + } + return await this.toonGPU(toTransform, imageInfo.size.width, imageInfo.size.height); + } + + private async toonGPU(bitmap: image.PixelMap, targetWidth: number, targetHeight: number) { + let bufferData = new ArrayBuffer(bitmap.getPixelBytesNumber()); + await bitmap.readPixelsToBuffer(bufferData); + let filter = new GPUImageToonFilter(); + filter.setImageData(bufferData, targetWidth, targetHeight); + filter.setThreshold(this.threshold); + filter.setQuantizationLevels(this.quantizationLevels); + let buf = await filter.getPixelMapBuf(0, 0, targetWidth, targetHeight); + await bitmap.writeBufferToPixels(buf); + return bitmap; + } +} \ No newline at end of file diff --git a/library/src/main/ets/transform/VignetterTransformation.ets b/library/src/main/ets/transform/VignetterTransformation.ets new file mode 100644 index 0000000..a8c7691 --- /dev/null +++ b/library/src/main/ets/transform/VignetterTransformation.ets @@ -0,0 +1,76 @@ +/* + * 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 { GPUImageVignetterFilter } from '@ohos/gpu_transform'; +import { PixelMapTransformation } from './PixelMapTransformation'; +import { image } from '@kit.ImageKit'; + +/** + * 图片变换:装饰效果 + */ +@Sendable +export class VignetterTransformation extends PixelMapTransformation { + private mXCenter: number = 0.5; + private mYCenter: number = 0.5; + private mRed: number = 0.0; + private mGreen: number = 0.0; + private mBlue: number = 0.0; + private mStart: number = 0.3; + private mEnd: number = 0.75; + + constructor(centerPoint: Array, vignetteColor: Array, vignetteSpace: Array) { + super(); + if (centerPoint.length === 2) { + this.mXCenter = centerPoint[0]; + this.mYCenter = centerPoint[1]; + } + if (vignetteColor.length === 3) { + this.mRed = vignetteColor[0]; + this.mGreen = vignetteColor[1]; + this.mBlue = vignetteColor[2]; + } + if (vignetteSpace.length === 2) { + this.mStart = vignetteSpace[0]; + this.mEnd = vignetteSpace[1]; + } + } + + getName(): string { + return this.constructor.name + ';XCenter:' + this.mXCenter + ';YCenter:' + this.mYCenter + ';Red:' + + this.mRed + ';Green:' + this.mGreen + ';Blue:' + this.mBlue + ';Start:' + this.mStart + ';End:' + this.mEnd; + } + + async transform(context: Context, toTransform: PixelMap, width: number, height: number): Promise { + let imageInfo: image.ImageInfo = await toTransform.getImageInfo(); + if (!imageInfo.size) { + console.error("VignetterTransformation The image size does not exist."); + return toTransform; + } + return await this.swirlGPU(toTransform, imageInfo.size.width, imageInfo.size.height); + } + + private async swirlGPU(bitmap: image.PixelMap, targetWidth: number, targetHeight: number) { + let bufferData = new ArrayBuffer(bitmap.getPixelBytesNumber()); + await bitmap.readPixelsToBuffer(bufferData); + let filter = new GPUImageVignetterFilter(); + filter.setImageData(bufferData, targetWidth, targetHeight); + filter.setVignetteCenter([this.mXCenter, this.mYCenter]); + filter.setVignetteColor([this.mRed, this.mGreen, this.mBlue]); + filter.setVignetteStart(this.mStart); + filter.setVignetteEnd(this.mEnd); + let buf = await filter.getPixelMapBuf(0, 0, targetWidth, targetHeight); + await bitmap.writeBufferToPixels(buf); + return bitmap; + } +} \ No newline at end of file diff --git a/sharedlibrary2/src/main/ets/pages/GetRes2.ets b/library/src/main/ets/transform/entry/PixelEntry.ets similarity index 64% rename from sharedlibrary2/src/main/ets/pages/GetRes2.ets rename to library/src/main/ets/transform/entry/PixelEntry.ets index 36bfdef..4a0205a 100644 --- a/sharedlibrary2/src/main/ets/pages/GetRes2.ets +++ b/library/src/main/ets/transform/entry/PixelEntry.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,10 +12,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import common from '@ohos.app.ability.common' +export class PixelEntry { + a: number = 0; + b: number = 0; + r: number = 0; + g: number = 0; + f: number = 0; + pixel: number = 0; -export class GetRes2{ - static getSample2():Resource { - return $r('app.media.icon_loading'); + public toString(): string { + return "PixelEntry a:" + this.a + ";b:" + this.b + ";r:" + this.r + ";g:" + this.g + ";f:" + this.f; } } \ No newline at end of file diff --git a/library/src/main/ets/utils/CalculatePixelUtils.ets b/library/src/main/ets/utils/CalculatePixelUtils.ets new file mode 100644 index 0000000..3bf1568 --- /dev/null +++ b/library/src/main/ets/utils/CalculatePixelUtils.ets @@ -0,0 +1,30 @@ +/* + * 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 namespace CalculatePixelUtils { + + export function createInt2DArray(first_len: number, second_len: number): Array> { + let array = new Array>(); + for (let f = 0; f < first_len; f++) { + let s1 = new Array(); + for (let s = 0; s < second_len; s++) { + s1.push(0); + } + array.push(s1); + } + return array; + } +} \ No newline at end of file diff --git a/library/src/main/ets/utils/ColorUtils.ets b/library/src/main/ets/utils/ColorUtils.ets new file mode 100644 index 0000000..5c1ee2e --- /dev/null +++ b/library/src/main/ets/utils/ColorUtils.ets @@ -0,0 +1,36 @@ +/* + * 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 namespace ColorUtils { + + export function red(color: number): number { + return (color >> 16) & 0xFF; + } + + export function green(color: number): number { + return (color >> 8) & 0xFF; + } + + export function blue(color: number): number { + return color & 0xFF; + } + + export function alpha(color: number): number { + return color >>> 24; + } + + export function rgb(red: number, green: number, blue: number): number { + return 0xff000000 | (red << 16) | (green << 8) | blue; + } +} \ No newline at end of file diff --git a/library/src/main/ets/utils/Constants.ets b/library/src/main/ets/utils/Constants.ets new file mode 100644 index 0000000..d187451 --- /dev/null +++ b/library/src/main/ets/utils/Constants.ets @@ -0,0 +1,17 @@ +/* + * 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 class Constants { + public static PROGRESS_EMITTER: string = "progressEmitter" +} \ No newline at end of file diff --git a/library/src/main/ets/utils/DefaultJobQueue.ets b/library/src/main/ets/utils/DefaultJobQueue.ets new file mode 100644 index 0000000..ac31956 --- /dev/null +++ b/library/src/main/ets/utils/DefaultJobQueue.ets @@ -0,0 +1,48 @@ +/* + * 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 { ImageKnifeRequest } from '../ImageKnifeRequest'; +import { IJobQueue } from './IJobQueue' +import Queue from '@ohos.util.Queue'; +import { taskpool,Stack } from '@kit.ArkTS'; + +export class DefaultJobQueue implements IJobQueue { + highQueue: Stack = new Stack(); + normalQueue: Stack = new Stack(); + lowQueue: Stack = new Stack(); + + getQueueLength(): number { + return this.highQueue.length + this.normalQueue.length + this.lowQueue.length + } + + add(request: ImageKnifeRequest): void { + if (request.imageKnifeOption.priority === undefined || request.imageKnifeOption.priority === taskpool.Priority.MEDIUM) { + this.normalQueue.push(request) + } else if (request.imageKnifeOption.priority === taskpool.Priority.HIGH) { + this.highQueue.push(request) + } else { + this.lowQueue.push(request) + } + } + + pop(): ImageKnifeRequest | undefined { + if (this.highQueue.length > 0) { + return this.highQueue.pop() + } else if (this.normalQueue.length > 0) { + return this.normalQueue.pop() + } else { + return this.lowQueue.pop() + } + } +} \ No newline at end of file diff --git a/library/src/main/ets/utils/FileCache.ets b/library/src/main/ets/utils/FileCache.ets new file mode 100644 index 0000000..8c47562 --- /dev/null +++ b/library/src/main/ets/utils/FileCache.ets @@ -0,0 +1,291 @@ +/* + * 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 util from '@ohos.util'; +import { FileUtils } from './FileUtils'; +import fs from '@ohos.file.fs'; +import { LogUtil } from './LogUtil'; +import { SparkMD5 } from '../3rd_party/sparkmd5/spark-md5'; + + +/** + * 二级文件缓存 + * 主线程通过lruCache管理缓存大小 + * 子线程直接读写文件 + */ +export class FileCache { + static CACHE_FOLDER: string = "ImageKnife" // context cacheDir 的缓存文件目录 + maxMemory: number = 0 + currentMemory: number = 0 + maxSize: number = 0 + path: string = "" + private lruCache: util.LRUCache + private isInited: boolean = false + private context?: Context + readonly defaultMaxSize: number = 512; + readonly defaultSize: number = 128; + readonly defaultMaxMemorySize: number = 512 * 1024 * 1024; + readonly defaultMemorySize: number = 128 * 1024 * 1024; + + constructor(context: Context, size: number, memory: number) { + if (size <= 0) { + size = this.defaultSize + } + if (memory <= 0 || memory > this.defaultMaxMemorySize) { + memory = this.defaultMemorySize + } + + this.lruCache = new util.LRUCache(size); + this.maxMemory = memory + this.currentMemory = 0; + this.maxSize = size + this.context = context + } + + /** + * 遍历缓存文件目录,初始化缓存 + */ + public async initFileCache(path: string = FileCache.CACHE_FOLDER) { + if (this.isInited) { + return + } + if (this.context && path.startsWith(this.context.cacheDir) === true) { + this.path = path + } else { + FileCache.CACHE_FOLDER = path + this.path = this.context?.cacheDir + FileUtils.SEPARATOR + FileCache.CACHE_FOLDER + FileUtils.SEPARATOR + } + await FileUtils.getInstance().createFolder(this.path) + // 遍历缓存目录下的文件,按照时间顺序加入缓存 + let filenames: string[] = await FileUtils.getInstance().ListFile(this.path) + + interface CacheFileInfo { + file: string; + ctime: number; + } + + // 按照上次访问该文件的时间排序 + let cachefiles: CacheFileInfo[] = [] + for (let i = 0; i < filenames.length; i++) { + let stat: fs.Stat | undefined = await FileUtils.getInstance().Stat(this.path + filenames[i]) + cachefiles.push({ + file: filenames[i], + ctime: stat === undefined ? 0 : stat.ctime + }) + } + let sortedCachefiles: CacheFileInfo[] = cachefiles.sort((a, b) => a.ctime - b.ctime) + + for (let i = 0; i < sortedCachefiles.length; i++) { + let buf: ArrayBuffer | undefined = await FileUtils.getInstance().readFile(this.path + sortedCachefiles[i].file) + if (buf !== undefined) { + // 处理数量超过size的场景,移除即将排除的文件 + if (this.lruCache.length == this.maxSize && !this.lruCache.contains(sortedCachefiles[i].file)) { + let remove: number | undefined = this.lruCache.remove(this.lruCache.keys()[0]) + if (remove !== undefined) { + FileUtils.getInstance().deleteFile(this.path + this.lruCache.keys()[0]) + this.removeMemorySize(buf) + } + } + + this.lruCache.put(sortedCachefiles[i].file, buf.byteLength) + this.addMemorySize(buf) + } + } + + this.trimToSize(); + this.isInited = true + } + + public isFileCacheInit():boolean { + return this.isInited + } + + public getCacheFolder(): string { + return FileCache.CACHE_FOLDER + } + + // 添加缓存键值对,同时写文件 + put(key: string, value: ArrayBuffer): void { + if (key == null || value == null) { + throw new Error('key or value is invalid '); + } + if (!this.isInited) { + return + } + + // 如果size满了的话,需要按照LRU的方式删除第一个 + if (this.lruCache.length == this.maxSize && !this.lruCache.contains(key)) { + this.remove(this.lruCache.keys()[0]) + } else if (this.lruCache.contains(key)) { + this.remove(key) + } + + let pre = this.lruCache.put(key, value.byteLength) + FileUtils.getInstance().writeDataSync(this.path + key, value) + if (pre !== undefined) { + this.addMemorySize(value) + } + this.trimToSize() + } + + // 添加缓存键值对,但不写文件(用于子线程已经写文件的场景) + putWithoutWriteFile(key: string, value: ArrayBuffer | number): void { + if (key == null || value == null) { + throw new Error('key or value is invalid '); + } + if (!this.isInited) { + return + } + + // 如果size满了的话,需要按照LRU的方式删除第一个 + if (this.lruCache.length == this.maxSize && !this.lruCache.contains(key)) { + this.remove(this.lruCache.keys()[0]) + } else if (this.lruCache.contains(key)) { + this.lruCache.remove(key) + this.lruCache.put(key, typeof value == "number" ? value : value.byteLength) + return + } + + this.lruCache.put(key, typeof value == "number" ? value : value.byteLength) + this.addMemorySize(value) + this.trimToSize() + } + + get(key: string): ArrayBuffer | undefined { + if (!this.isInited) { + return + } + + if (this.lruCache.get(key) !== undefined) { + // TODO 如何才能修改文件访问时间呢 + return FileUtils.getInstance().readFileSync(this.path + key) + } + return undefined + } + + // 移除键为key的缓存 + remove(key: string): void { + if (key == null) { + throw new Error('key is null,checking the parameter'); + } + if (!this.isInited) { + return + } + + let remove: number | undefined = this.lruCache.remove(key) + if (remove !== undefined) { + FileUtils.getInstance().deleteFile(this.path + key) + this.removeMemorySize(remove) + } + } + + async removeAll(): Promise { + if (!this.isInited) { + return + } + this.isInited = false + this.lruCache.clear() + this.currentMemory = 0; + + let filenames: string[] = await FileUtils.getInstance().ListFile(this.path) + for (let i = 0; i < filenames.length; i++) { + await FileUtils.getInstance().deleteFile(this.path + filenames[i]) + } + + this.isInited = true + } + + size(): number { + return this.lruCache.length + } + + // 移除较少使用的缓存数据 + private trimToSize(): void { + while (true) { + if (this.currentMemory <= this.maxMemory || this.lruCache.isEmpty()) { + break + } + let delkey = this.lruCache.keys()[0] + let remove: number | undefined = this.lruCache.remove(delkey) + if (remove !== undefined) { + FileUtils.getInstance().deleteFile(this.path + delkey) + this.removeMemorySize(remove) + } + this.lruCache.remove(delkey) + } + } + + private removeMemorySize(value: ArrayBuffer | number): void { + if (typeof value == "number") { + this.currentMemory -= value + } + else if (value != undefined) { + this.currentMemory -= value.byteLength + LogUtil.info("FileCache removeMemorySize: " + value.byteLength + " currentMemory:" + this.currentMemory) + } + } + + private addMemorySize(value: ArrayBuffer | number): void { + if (typeof value == "number") { + this.currentMemory += value + LogUtil.info("FileCache addMemorySize: " + value + " currentMemory:" + this.currentMemory) + } + else if (value != undefined) { + this.currentMemory += value.byteLength + LogUtil.info("FileCache addMemorySize: " + value.byteLength + " currentMemory:" + this.currentMemory) + } + } + + /** + * 子线程里只写入缓存文件 + * @param context + * @param key + * @param value + */ + static saveFileCacheOnlyFile(context: Context, key: string, value: ArrayBuffer, folder: string = FileCache.CACHE_FOLDER): boolean { + // 写文件 + FileUtils.getInstance() + .writeFileSync(context.cacheDir + FileUtils.SEPARATOR + folder + FileUtils.SEPARATOR + key, value) + return true + } + + /** + * 子线程中,通过文件名,直接查找是否有文件缓存 + * @param context + * @param key + * @returns + */ + static getFileCacheByFile(context: Context, key: string, folder: string = FileCache.CACHE_FOLDER): ArrayBuffer | undefined { + // 从文件获取查看是否有缓存 + return FileUtils.getInstance() + .readFileSync(context.cacheDir + FileUtils.SEPARATOR + folder + FileUtils.SEPARATOR + key) + } + + /** + * 获取key缓存数据绝对路径 + * + * @params key 数值 + */ + getFileToPath(key: string): string { + if(!!!key) { + throw new Error("key is null,checking the parameter") + } + let path = this.path + key + if(FileUtils.getInstance().exist(path)) { + return path + } else { + return "" + } + } +} \ No newline at end of file diff --git a/library/src/main/ets/utils/FileTypeUtil.ets b/library/src/main/ets/utils/FileTypeUtil.ets new file mode 100644 index 0000000..7627162 --- /dev/null +++ b/library/src/main/ets/utils/FileTypeUtil.ets @@ -0,0 +1,112 @@ +/* + * 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 class FileTypeUtil { + private fileSignatureMap: Record> = { + // 添加文件类型和对应的文件头部特征 + 'jpg': [new Uint8Array([0xFF, 0xD8])], + 'png': [new Uint8Array([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])], + 'gif': [new Uint8Array([0x47, 0x49, 0x46, 0x38, 0x39, 0x61]),new Uint8Array([0x47, 0x49, 0x46, 0x38])], + 'bmp': [new Uint8Array([0x42, 0x4D])], + 'svg': [new Uint8Array([0x3C, 0x3F, 0x78, 0x6D, 0x6C]),new Uint8Array([0x3C, 0x73, 0x76, 0x67, 0x20])], + 'webp': [new Uint8Array([0x52, 0x49, 0x46, 0x46])], + 'tiff': [new Uint8Array([0x49, 0x20, 0x49]), new Uint8Array([0x49, 0x49, 0x2A, 0x00]), new Uint8Array([0x4D, 0x4D, 0x00, 0x2A]), new Uint8Array([0x4D, 0x4D, 0x00, 0x2B])], + // 添加更多的文件类型和特征 + 'heic': [new Uint8Array([0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x68, 0x65, 0x69, 0x63, 0x00, 0x00, 0x00, 0x00]),new Uint8Array([0x00, 0x00, 0x00, 0x1C, 0x66, 0x74, 0x79, 0x70, 0x68, 0x65, 0x69, 0x63, 0x00, 0x00, 0x00, 0x00])], + }; + + + constructor() { + } + + isImage(arraybuffer: ArrayBuffer) { + let value = this.getFileType(arraybuffer); + if ( + value == SupportFormat.jpg || + value == SupportFormat.png || + value == SupportFormat.tiff || + value == SupportFormat.webp || + value == SupportFormat.bmp || + value == SupportFormat.gif || + value == SupportFormat.svg || + value == SupportFormat.heic + ) { + return true; + } + return false; + } + + getFileType(file: ArrayBuffer): string | null { + const fileData = new Uint8Array(file); + let hasMatched = false; + let matchedFileType = '' + Object.keys(this.fileSignatureMap).map((fileType)=>{ + if(!hasMatched) { + const bufferList = this.fileSignatureMap[fileType]; + for (let i = 0; i < bufferList.length; i++) { + let signature = bufferList[i]; + if (this.matchesSignature(fileData, signature,fileType)) { + hasMatched = true; + matchedFileType = fileType; + break + } + } + }else{ + // 由于map函数会输出所有的keys, 所以匹配之后不再需要执行任何逻辑 + } + }) + if(hasMatched){ + return matchedFileType; + } + return null; // 若无法识别文件类型,返回null + } + + + matchesSignature(fileData: Uint8Array, signature: Uint8Array,fileType:string): boolean { + if (fileData.length < signature.length) { + return false; // 文件长度不足,无法匹配魔数 + } + + for (let i = fileType == "heic" ? 4 : 0; i < signature.length; i++) { + if (fileData[i] !== signature[i]) { + return false; // 魔数不匹配 + } + } + return true; // 文件头部魔数匹配 + } +} + + +export enum PhotoFormat { + jpg = 'jpg,0,FFD8', + png = 'png,0,89504E470D0A1A0A', + bmp = 'bmp,0,424D', + gif = 'gif,0,474946383961', + svg = 'svg,0,3C3F786D6C', + webp = 'webp,0,52494646', + tiff = 'tiff,0,492049|49492A00|4D4D002A|4D4D002B' +} + +export enum SupportFormat { + jpg = 'jpg', + png = 'png', + webp = 'webp', + bmp = 'bmp', + gif = 'gif', + svg = 'svg', + tiff = 'tiff', + heic = 'heic' +} diff --git a/library/src/main/ets/utils/FileUtils.ets b/library/src/main/ets/utils/FileUtils.ets new file mode 100644 index 0000000..5f41dd1 --- /dev/null +++ b/library/src/main/ets/utils/FileUtils.ets @@ -0,0 +1,301 @@ +/* + * 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 fs from '@ohos.file.fs'; +import { BusinessError } from '@ohos.base'; +import { LogUtil } from './LogUtil' + + +export class FileUtils { + static readonly SEPARATOR: string = '/' + private static sInstance: FileUtils + + private constructor() { + } + + /** + * 单例实现FileUtils类 + */ + public static getInstance(): FileUtils { + if (!FileUtils.sInstance) { + FileUtils.sInstance = new FileUtils(); + } + return FileUtils.sInstance; + } + + /** + * 删除文件 + * + * @param path 文件绝对路径及文件名 + */ + deleteFileSync(path: string): boolean { + try { + let fileExist = fs.accessSync(path); + if (fileExist) { + fs.unlinkSync(path); + } + return true + } catch (err) { + LogUtil.error("FileUtils deleteFileSync failed: err msg=" + err.message + " err code=" + err.code); + return false + } + } + + /** + * 异步删除文件 + * @param path + * @returns + */ + async deleteFile(path: string): Promise { + // const isExist: boolean = await fs.access(path) + // if (isExist) { + try { + await fs.unlink(path) + } catch (err) { + LogUtil.error("FileUtils deleteFile failed: err msg=" + err.message + " err code=" + err.code); + } + } + + /** + * 向path写入数据 + * + * @param path 文件绝对路径 + * @param content 文件内容 + */ + writeDataSync(path: string, content: ArrayBuffer | string): boolean { + try { + let fd = fs.openSync(path, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE | fs.OpenMode.TRUNC).fd + let stat = fs.statSync(path) + fs.writeSync(fd, content, { offset: stat.size }) + fs.closeSync(fd) + return true + } + catch (err) { + LogUtil.error("FileUtils writeDataSync failed: err msg=" + err.message + " err code=" + err.code); + return false + } + } + + /** + * 异步向path写入数据 + * + * @param path 文件绝对路径 + * @param content 文件内容 + */ + async writeData(path: string, content: ArrayBuffer | string): Promise { + try { + let fd = (await fs.open(path, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE)).fd + let stat = await fs.stat(path) + await fs.write(fd, content, { offset: stat.size }) + await fs.close(fd) + return true + } + catch (err) { + LogUtil.error("FileUtils writeData failed: err msg=" + err.message + " err code=" + err.code); + return false + } + } + + /** + * 判断path文件是否存在 + * + * @param path 文件绝对路径 + */ + exist(path: string): boolean { + try { + if (fs.accessSync(path)) { + let stat = fs.statSync(path) + return stat.isFile() + } else { + return false + } + } catch (error) { + let err: BusinessError = error as BusinessError; + LogUtil.error("FileUtils exist failed with error message: " + err.message + ", error code: " + err.code); + } + return false + } + + /** + * 获取path的文件大小 + * + * @param path 文件绝对路径 + */ + getFileSizeSync(path: string): number { + try { + let stat = fs.statSync(path) + return stat.size + } catch (e) { + LogUtil.error("FileUtils getFileSize e " + e) + return -1 + } + } + + /** + * 读取路径path的文件 + * + * @param path 文件绝对路径 + */ + readFileSync(path: string): ArrayBuffer | undefined { + try { + if (fs.accessSync(path)) { + let fd = fs.openSync(path, fs.OpenMode.READ_ONLY).fd; + let length = fs.statSync(path).size + let buf = new ArrayBuffer(length); + fs.readSync(fd, buf) + fs.closeSync(fd) + return buf + } + } catch (error) { + let err: BusinessError = error as BusinessError; + LogUtil.error("FileUtils readFileSync failed with error message: " + err.message + ", error code: " + err.code); + } + return undefined + } + + /** + * 读取路径path的文件 + * + * @param path 文件绝对路径 + */ + async readFile(path: string): Promise { + try { + let stat = await fs.stat(path); + let fd = (await fs.open(path, fs.OpenMode.READ_ONLY)).fd; + let length = stat.size; + let buf = new ArrayBuffer(length); + await fs.read(fd, buf); + return buf + } catch (error) { + let err: BusinessError = error as BusinessError; + LogUtil.error("FileUtils readFile failed with error message: " + err.message + ", error code: " + err.code); + } + return undefined + } + + /** + * 创建文件夹 + * + * @param path 文件夹绝对路径,只有是权限范围内的路径,可以生成 + * @param recursive + */ + createFolderSync(path: string, recursive?: boolean) { + try { + if (recursive) { + if (!this.existFolder(path)) { + let lastInterval = path.lastIndexOf(FileUtils.SEPARATOR) + if (lastInterval == 0) { + return + } + let newPath = path.substring(0, lastInterval) + this.createFolderSync(newPath, true) + if (!this.existFolder(path)) { + fs.mkdirSync(path) + } + } + } else { + if (!this.existFolder(path)) { + fs.mkdirSync(path) + } + } + } catch (e) { + LogUtil.log("FileUtils createFolder err : " + e) + } + } + + async createFolder(path: string): Promise { + try { + let isExist: boolean = await fs.access(path) + if (!isExist) { + await fs.mkdir(path) + + } + return true + } catch (error) { + let err: BusinessError = error as BusinessError; + LogUtil.error("FileUtils createFolder failed with error message: " + err.message + ", error code: " + err.code); + } + return false + } + + /** + * 判断文件夹是否存在 + * + * @param path 文件夹绝对路径 + */ + existFolder(path: string): boolean { + try { + if (fs.accessSync(path)) { + let stat = fs.statSync(path) + return stat.isDirectory() + } else { + return false + } + } + catch (error) { + let err: BusinessError = error as BusinessError; + LogUtil.error("FileUtils existFolder failed with error message: " + err.message + ", error code: " + err.code); + } + return false + } + + writeFileSync(path: string, content: ArrayBuffer | string): boolean { + try { + let fd = fs.openSync(path, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE).fd + fs.truncateSync(fd) + fs.writeSync(fd, content) + fs.closeSync(fd) + return true + } catch (err) { + LogUtil.error("FileUtils writeFileSync failed with error message: " + err.message + ", error code: " + err.code); + } + return false + } + + ListFileSync(path: string): string[] { + try { + return fs.listFileSync(path) + } catch (err) { + LogUtil.error("FileUtils ListFileSync failed with error message: " + err.message + ", error code: " + err.code); + } + return [] + } + + async ListFile(path: string): Promise { + try { + return fs.listFile(path) + } catch (err) { + LogUtil.error("FileUtils ListFile failed with error message: " + err.message + ", error code: " + err.code); + } + return [] + } + + StatSync(path: string): fs.Stat | undefined { + try { + return fs.statSync(path) + } catch (err) { + LogUtil.error("FileUtils StatSync failed with error message: " + err.message + ", error code: " + err.code); + } + return undefined + } + + async Stat(path: string): Promise { + try { + return fs.stat(path) + } catch (err) { + LogUtil.error("FileUtils Stat failed with error message: " + err.message + ", error code: " + err.code); + } + return undefined + } +} \ No newline at end of file diff --git a/library/src/main/ets/utils/IJobQueue.ets b/library/src/main/ets/utils/IJobQueue.ets new file mode 100644 index 0000000..0a51ff0 --- /dev/null +++ b/library/src/main/ets/utils/IJobQueue.ets @@ -0,0 +1,37 @@ +/* + * 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 { ImageKnifeRequest } from '../ImageKnifeRequest' + +export interface IJobQueue { + + /** + * 获取队列长度 + * @returns + */ + getQueueLength(): number + + /** + * 往队列中添加元素 + * @param request + */ + add(request: ImageKnifeRequest): void + + + /** + * 移除并返回队列头部的元素 + * @returns + */ + pop(): ImageKnifeRequest | undefined +} \ No newline at end of file diff --git a/library/src/main/ets/utils/IMemoryCache.ets b/library/src/main/ets/utils/IMemoryCache.ets new file mode 100644 index 0000000..8e35c62 --- /dev/null +++ b/library/src/main/ets/utils/IMemoryCache.ets @@ -0,0 +1,28 @@ +/* + * 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 { ImageKnifeData } from '../model/ImageKnifeData' + +export interface IMemoryCache { + + put(key: string, value: ImageKnifeData): void + + get(key: string): ImageKnifeData | undefined + + remove(key: string): ImageKnifeData | undefined + + removeAll(): void + + size(): number +} \ No newline at end of file diff --git a/library/src/main/ets/utils/LogUtil.ets b/library/src/main/ets/utils/LogUtil.ets new file mode 100644 index 0000000..02c2d39 --- /dev/null +++ b/library/src/main/ets/utils/LogUtil.ets @@ -0,0 +1,55 @@ +/* + * 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 class LogUtil { + public static OFF: number = 1 + public static LOG: number = 2 + public static DEBUG: number = 3 + public static INFO: number = 4 + public static WARN: number = 5 + public static ERROR: number = 6 + public static ALL: number = 7 + public static mLogLevel: number = LogUtil.OFF; + public static TAG: string = "ImageKnife:: "; + + public static debug(message: string, ...args: Object[]) { + if (LogUtil.mLogLevel >= LogUtil.DEBUG) { + console.debug(LogUtil.TAG + message, args) + } + } + + public static info(message: string, ...args: Object[]) { + if (LogUtil.mLogLevel >= LogUtil.INFO) { + console.info(LogUtil.TAG + message, args) + } + } + + public static log(message: string, ...args: Object[]) { + if (LogUtil.mLogLevel >= LogUtil.LOG) { + console.log(LogUtil.TAG + message, args) + } + } + + public static warn(message: string, ...args: Object[]) { + if (LogUtil.mLogLevel >= LogUtil.WARN) { + console.warn(LogUtil.TAG + message, args) + } + } + + public static error(message: string, ...args: Object[]) { + if (LogUtil.mLogLevel >= LogUtil.ERROR) { + console.error(LogUtil.TAG + message, args) + } + } +} \ No newline at end of file diff --git a/library/src/main/ets/utils/MemoryLruCache.ets b/library/src/main/ets/utils/MemoryLruCache.ets new file mode 100644 index 0000000..493fc11 --- /dev/null +++ b/library/src/main/ets/utils/MemoryLruCache.ets @@ -0,0 +1,137 @@ +/* + * 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 { ImageKnifeData } from '../model/ImageKnifeData'; +import util from '@ohos.util'; +import { IMemoryCache } from './IMemoryCache'; + +export class MemoryLruCache implements IMemoryCache { + maxMemory: number = 0 + currentMemory: number = 0 + maxSize: number = 0 + private lruCache: util.LRUCache + readonly defaultMaxSize: number = 4096 + readonly defaultSize: number = 512 + readonly defaultMaxMemorySize: number = 1024 * 1024 * 1024 + readonly defaultMemorySize: number = 128 * 1024 * 1024 + + constructor(size: number, memory: number) { + if (size <= 0 || size > this.defaultMaxSize) { + size = this.defaultSize + } + if (memory <= 0 || memory > this.defaultMaxMemorySize) { + memory = this.defaultMemorySize + } + + this.lruCache = new util.LRUCache(size); + this.maxMemory = memory + this.maxSize = size + this.currentMemory = 0 + } + + // 添加缓存键值对 + put(key: string, value: ImageKnifeData): void { + if (key == null || value == null) { + throw new Error('key or value is invalid '); + } + + // 如果size满了的话,需要按照LRU的方式删除第一个 + if (this.lruCache.length == this.maxSize && !this.lruCache.contains(key)) { + this.remove(this.lruCache.keys()[0]) + } else if (this.lruCache.contains(key)) { + this.remove(key) + } + + let pre: ImageKnifeData = this.lruCache.put(key, value) + this.addMemorySize(value) + // if (pre !== undefined) { // 当前返回不是key的之前value + // this.removeMemorySize(pre) + // } + this.trimToSize(); + } + + get(key: string): ImageKnifeData | undefined { + return this.lruCache.get(key) + } + + // 移除键为key的缓存 + remove(key: string): ImageKnifeData | undefined { + if (key == null) { + throw new Error('key is null,checking the parameter'); + } + + let remove: ImageKnifeData | undefined = this.lruCache.remove(key) + if (remove !== undefined) { + this.removeMemorySize(remove) + } + return remove + } + + removeAll(): void { + this.lruCache.clear() + this.currentMemory = 0; + } + + size(): number { + return this.lruCache.length + } + + // 移除较少使用的缓存数据 + trimToSize(): void { + while (true) { + if (this.currentMemory <= this.maxMemory || this.lruCache.isEmpty()) { + break + } + let delkey = this.lruCache.keys()[0] + let data: ImageKnifeData | undefined = this.lruCache.get(delkey) + if (data != undefined) { + this.removeMemorySize(data) + } + this.lruCache.remove(delkey) + } + } + + private removeMemorySize(value: ImageKnifeData): void { + if (value.source != undefined) { + if (typeof value.source === 'string' && value.source != "") { + this.currentMemory -= value.source.length + } else if (value.source == "") { + for (let index = 0;index < value.imageAnimator!.length;index++) { + let pixelMap = value.imageAnimator![index].src as PixelMap + this.currentMemory -= pixelMap.getPixelBytesNumber() + } + } else { + this.currentMemory -= value.source.getPixelBytesNumber(); + value.source.release() + } + // LogUtil.info("MemoryCache removeMemorySize: " + value.source.getPixelBytesNumber() + " currentMemory:" + this.currentMemory) + } + } + + private addMemorySize(value: ImageKnifeData): void { + if (value.source != undefined) { + if (typeof value.source === 'string' && value.source != "") { + this.currentMemory += value.source.length + } else if (value.source == "") { + for (let index = 0;index < value.imageAnimator!.length;index++) { + let pixelMap = value.imageAnimator![index].src as PixelMap + this.currentMemory += pixelMap.getPixelBytesNumber() + } + } else { + this.currentMemory += value.source.getPixelBytesNumber(); + } + //LogUtil.log("MemoryCache addMemorySize: " + value.source.getPixelBytesNumber() + " currentMemory:" + this.currentMemory) + } + } +} \ No newline at end of file diff --git a/library/src/main/ets/utils/Tools.ets b/library/src/main/ets/utils/Tools.ets new file mode 100644 index 0000000..a51faa8 --- /dev/null +++ b/library/src/main/ets/utils/Tools.ets @@ -0,0 +1,35 @@ +/* + * 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 diff --git a/library/src/main/module.json5 b/library/src/main/module.json5 index 7274415..dd30f8e 100644 --- a/library/src/main/module.json5 +++ b/library/src/main/module.json5 @@ -4,8 +4,8 @@ "type": "har", "deviceTypes": [ "default", - "tablet" - ], - "uiSyntax": "ets" + "tablet", + "2in1" + ] } -} \ No newline at end of file +} diff --git a/library/src/main/resources/base/element/string.json b/library/src/main/resources/base/element/string.json index 1e76de0..f51a9c8 100644 --- a/library/src/main/resources/base/element/string.json +++ b/library/src/main/resources/base/element/string.json @@ -2,7 +2,7 @@ "string": [ { "name": "page_show", - "value": "page from npm package" + "value": "page from package" } ] } diff --git a/library/src/main/resources/base/media/rabbit.gif b/library/src/main/resources/base/media/rabbit.gif new file mode 100644 index 0000000..c2c1402 Binary files /dev/null and b/library/src/main/resources/base/media/rabbit.gif differ diff --git a/library/src/main/resources/base/media/startIcon.png b/library/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000..366f764 Binary files /dev/null and b/library/src/main/resources/base/media/startIcon.png differ diff --git a/library/src/main/resources/en_US/element/string.json b/library/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000..f51a9c8 --- /dev/null +++ b/library/src/main/resources/en_US/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from package" + } + ] +} diff --git a/library/src/main/resources/zh_CN/element/string.json b/library/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000..f51a9c8 --- /dev/null +++ b/library/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from package" + } + ] +} diff --git a/library/src/test/List.test.ets b/library/src/test/List.test.ets new file mode 100644 index 0000000..f43d80b --- /dev/null +++ b/library/src/test/List.test.ets @@ -0,0 +1,19 @@ +/* + * 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 localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/library/src/test/LocalUnit.test.ets b/library/src/test/LocalUnit.test.ets new file mode 100644 index 0000000..dacc818 --- /dev/null +++ b/library/src/test/LocalUnit.test.ets @@ -0,0 +1,47 @@ +/* + * 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { + describe('localUnitTest',() => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file diff --git a/sharedlibrary/Index.ets b/sharedlibrary/Index.ets new file mode 100644 index 0000000..5d74d4b --- /dev/null +++ b/sharedlibrary/Index.ets @@ -0,0 +1,75 @@ +/* + * 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 { add } from './src/main/ets/utils/Calc' + +export { InitImageKnife } from "./src/main/ets/pages/InitImageKnife" + +export { IndexComponent } from "./src/main/ets/pages/Index" + +export { ImageKnifeComponent,ImageKnifeAnimatorComponent } from '@ohos/imageknife' + +export { ImageKnife } from '@ohos/imageknife' + +export { ImageKnifeOption,AnimatorOption } from '@ohos/imageknife' + +export { ImageKnifeRequest } from '@ohos/imageknife' + +export { FileUtils } from '@ohos/imageknife' + +export { LogUtil } from '@ohos/imageknife' + +export { IEngineKey } from '@ohos/imageknife' + +export { ImageKnifeData , CacheStrategy} from "@ohos/imageknife" + +export { PixelMapTransformation } from '@ohos/imageknife' + +export { MultiTransTransformation } from '@ohos/imageknife' + +export { BrightnessTransformation } from '@ohos/imageknife' + +export { BlurTransformation } from '@ohos/imageknife' + +export { SparkMD5 } from "@ohos/imageknife" + +export { ImageKnifeRequestSource } from "@ohos/imageknife" + +export { CropCircleTransformation } from '@ohos/imageknife' + +export { CropCircleWithBorderTransformation } from '@ohos/imageknife' + +export { CropSquareTransformation } from '@ohos/imageknife' + +export { CropTransformation } from '@ohos/imageknife' + +export { GrayScaleTransformation } from '@ohos/imageknife' + +export { InvertTransformation } from '@ohos/imageknife' + +export { KuwaharaTransformation } from '@ohos/imageknife' + +export { MaskTransformation } from '@ohos/imageknife' + +export { PixelationTransformation } from '@ohos/imageknife' + +export { SepiaTransformation } from '@ohos/imageknife' + +export { SketchTransformation } from '@ohos/imageknife' + +export { SwirlTransformation } from '@ohos/imageknife' + +export { ToonTransformation } from '@ohos/imageknife' + +export { VignetterTransformation } from '@ohos/imageknife' \ No newline at end of file diff --git a/sharedlibrary/build-profile.json5 b/sharedlibrary/build-profile.json5 index befa101..170d980 100644 --- a/sharedlibrary/build-profile.json5 +++ b/sharedlibrary/build-profile.json5 @@ -1,10 +1,28 @@ { - "apiType": 'stageMode', + "apiType": "stageMode", "buildOption": { }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": true, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + }, + }, + ], "targets": [ { "name": "default" + }, + { + "name": "ohosTest" } ] } \ No newline at end of file diff --git a/sharedlibrary/hvigorfile.ts b/sharedlibrary/hvigorfile.ts index 0e65ea8..d993120 100644 --- a/sharedlibrary/hvigorfile.ts +++ b/sharedlibrary/hvigorfile.ts @@ -1,2 +1,6 @@ -// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. -module.exports = require('@ohos/hvigor-ohos-plugin').hspTasks +import { hspTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: hspTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/sharedlibrary/obfuscation-rules.txt b/sharedlibrary/obfuscation-rules.txt new file mode 100644 index 0000000..985b2ae --- /dev/null +++ b/sharedlibrary/obfuscation-rules.txt @@ -0,0 +1,18 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://gitee.com/openharmony/arkcompiler_ets_frontend/blob/master/arkguard/README.md + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope \ No newline at end of file diff --git a/sharedlibrary/oh-package.json5 b/sharedlibrary/oh-package.json5 index 1d879aa..65115c7 100644 --- a/sharedlibrary/oh-package.json5 +++ b/sharedlibrary/oh-package.json5 @@ -1,12 +1,12 @@ { - "license": "Apache-2.0", - "devDependencies": {}, - "author": "", "name": "sharedlibrary", - "description": "Please describe the basic information.", - "main": "./src/main/ets/Index.ets", "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "Index.ets", + "author": "", + "license": "Apache-2.0", + "packageType": "InterfaceHar", "dependencies": { "@ohos/imageknife": "file:../library" } -} +} \ No newline at end of file diff --git a/sharedlibrary/src/main/ets/pages/Index.ets b/sharedlibrary/src/main/ets/pages/Index.ets index d2b98af..ad35007 100644 --- a/sharedlibrary/src/main/ets/pages/Index.ets +++ b/sharedlibrary/src/main/ets/pages/Index.ets @@ -1,77 +1,37 @@ /* - * Copyright (C) 2023 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); + * 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, + * 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 {ImageKnifeGlobal, ImageKnifeComponent, ImageKnifeOption, FileUtils } from '@ohos/imageknife' -import common from '@ohos.app.ability.common' +import { ImageKnife , ImageKnifeComponent ,ImageKnifeOption } from "@ohos/imageknife" -@Entry @Component -struct Index { - - - @State imageOption1:ImageKnifeOption = { - loadSrc: $r('app.media.icon'), - context: getContext(this).createModuleContext('sharedlibrary') as common.UIAbilityContext +export struct IndexComponent { + @State imageKnifeOption: ImageKnifeOption = { + loadSrc: $r('app.media.startIcon') } - @State imageOption2:ImageKnifeOption = { - loadSrc: $r('app.media.icon'), - context: getContext(this).createModuleContext('sharedlibrary') as common.UIAbilityContext - } - @State imageOption3:ImageKnifeOption = { - loadSrc: $r('app.media.icon'), - context: getContext(this).createModuleContext('sharedlibrary') as common.UIAbilityContext - } - build() { - Scroll() { - Column() { - Button('点击加载Resource').onClick(()=>{ - this.imageOption1 = { - loadSrc: $r('app.media.setting'), - // 只要涉及resource加载 在HSP中都要带上context属性 - context: getContext(this).createModuleContext('sharedlibrary') as common.UIAbilityContext - } - }) - ImageKnifeComponent({imageKnifeOption:this.imageOption1}).width(300).height(300).backgroundColor(Color.Pink) - Button('点击加载网络图片').onClick(()=>{ - this.imageOption2 = { - loadSrc: 'https://hbimg.huabanimg.com/cc6af25f8d782d3cf3122bef4e61571378271145735e9-vEVggB', - context: getContext(this).createModuleContext('sharedlibrary') as common.UIAbilityContext - } - }) - ImageKnifeComponent({imageKnifeOption:this.imageOption2}).width(300).height(300).backgroundColor(Color.Pink) - - Button('点击加载本地文件').onClick(()=>{ - getContext(this).createModuleContext('sharedlibrary').resourceManager.getMediaContent($r('app.media.setting').id).then((data:Uint8Array)=>{ - let ctx = getContext(this).createModuleContext('sharedlibrary') as common.UIAbilityContext; - let path = ctx.filesDir+"/set.jpeg"; - FileUtils.getInstance().writeFile(path,data.buffer) - FileUtils.getInstance().readFilePicAsync(path).then(buffer=>{ - this.imageOption3 = { - loadSrc: path, - context: getContext(this).createModuleContext('sharedlibrary') as common.UIAbilityContext - } - }) + Column() { + Button("预加载").onClick((event: ClickEvent) => { + ImageKnife.getInstance() + .preLoadCache('https://hbimg.huabanimg.com/95a6d37a39aa0b70d48fa18dc7df8309e2e0e8e85571e-x4hhks_fw658/format/webp') + .then((data) => { + console.log("preLoadImage_FileCache:" + data) + this.imageKnifeOption.loadSrc = data }) - - }) - ImageKnifeComponent({imageKnifeOption:this.imageOption3}).width(300).height(300).backgroundColor(Color.Pink) - - - - }.width('100%') - + }) + ImageKnifeComponent({ + imageKnifeOption:this.imageKnifeOption + }).width(300).height(300) } .width('100%') .height('100%') diff --git a/sharedlibrary2/src/main/ets/pages/Index.ets b/sharedlibrary/src/main/ets/pages/IndexPage.ets similarity index 66% rename from sharedlibrary2/src/main/ets/pages/Index.ets rename to sharedlibrary/src/main/ets/pages/IndexPage.ets index f2894ad..86424e3 100644 --- a/sharedlibrary2/src/main/ets/pages/Index.ets +++ b/sharedlibrary/src/main/ets/pages/IndexPage.ets @@ -1,27 +1,21 @@ /* - * Copyright (C) 2023 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); + * 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, + * 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. */ - @Entry @Component -struct Index { - - +export struct IndexPage { build() { - Column(){ - Text('Hello world') - } } } \ No newline at end of file diff --git a/sharedlibrary/src/main/ets/pages/InitImageKnife.ets b/sharedlibrary/src/main/ets/pages/InitImageKnife.ets index 05fc782..7bf492c 100644 --- a/sharedlibrary/src/main/ets/pages/InitImageKnife.ets +++ b/sharedlibrary/src/main/ets/pages/InitImageKnife.ets @@ -1,22 +1,21 @@ /* - * Copyright (C) 2023 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); + * 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, + * 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 { ImageKnife } from '@ohos/imageknife' import common from '@ohos.app.ability.common' -import { ImageKnifeComponent, ImageKnifeOption, FileUtils } from '@ohos/imageknife' -import { ImageKnifeGlobal,ImageKnife,ImageKnifeDrawFactory,LogUtil } from '@ohos/imageknife' export class InitImageKnife{ - static init(entryContext:common.UIAbilityContext){ - ImageKnife.with(entryContext); + static async init(entryContext:common.UIAbilityContext){ + await ImageKnife.getInstance().initFileCache(entryContext, 256, 256 * 1024 * 1024,"ImageKnifeCache") } } \ No newline at end of file diff --git a/sharedlibrary2/src/main/ets/Index.ets b/sharedlibrary/src/main/ets/utils/Calc.ets similarity index 64% rename from sharedlibrary2/src/main/ets/Index.ets rename to sharedlibrary/src/main/ets/utils/Calc.ets index aeba7a3..b2ed92e 100644 --- a/sharedlibrary2/src/main/ets/Index.ets +++ b/sharedlibrary/src/main/ets/utils/Calc.ets @@ -1,16 +1,17 @@ /* - * Copyright (C) 2023 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); + * 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, + * 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 {GetRes2} from '../ets/pages/GetRes2' \ No newline at end of file +export function add(a:number, b:number) { + return a + b; +} \ No newline at end of file diff --git a/sharedlibrary/src/main/module.json5 b/sharedlibrary/src/main/module.json5 index 7e37699..5ff0ab4 100644 --- a/sharedlibrary/src/main/module.json5 +++ b/sharedlibrary/src/main/module.json5 @@ -4,8 +4,9 @@ "type": "shared", "description": "$string:shared_desc", "deviceTypes": [ - "default", - "tablet" + "phone", + "tablet", + "2in1" ], "deliveryWithInstall": true, "pages": "$profile:main_pages" diff --git a/sharedlibrary/src/main/resources/base/profile/main_pages.json b/sharedlibrary/src/main/resources/base/profile/main_pages.json index 1898d94..3187f74 100644 --- a/sharedlibrary/src/main/resources/base/profile/main_pages.json +++ b/sharedlibrary/src/main/resources/base/profile/main_pages.json @@ -1,5 +1,5 @@ { "src": [ - "pages/Index" + "pages/IndexPage" ] -} +} \ No newline at end of file diff --git a/sharedlibrary/src/mock/mock-config.json5 b/sharedlibrary/src/mock/mock-config.json5 new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/sharedlibrary/src/mock/mock-config.json5 @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/entry/src/ohosTest/ets/test/imageknifeOption.test.ets b/sharedlibrary/src/ohosTest/ets/test/Ability.test.ets similarity index 56% rename from entry/src/ohosTest/ets/test/imageknifeOption.test.ets rename to sharedlibrary/src/ohosTest/ets/test/Ability.test.ets index 21a0f19..3417975 100644 --- a/entry/src/ohosTest/ets/test/imageknifeOption.test.ets +++ b/sharedlibrary/src/ohosTest/ets/test/Ability.test.ets @@ -1,64 +1,49 @@ /* * Copyright (C) 2024 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the 'License'); * you may not use this file except in compliance with the License. * You may 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, + * 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium' -import { ImageKnifeOption } from '@ohos/imageknife' +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; -export default function ImageKnifeOptionTest() { - describe('ImageKnifeOptionTest', ()=> { +export default function abilityTest() { + describe('ActsAbilityTest', () => { // Defines a test suite. Two parameters are supported: test suite name and test suite function. - beforeAll( ()=> { + beforeAll(() => { // Presets an action, which is performed only once before all test cases of the test suite start. // This API supports only one parameter: preset action function. }) - beforeEach( ()=> { + beforeEach(() => { // Presets an action, which is performed before each unit test case starts. // The number of execution times is the same as the number of test cases defined by **it**. // This API supports only one parameter: preset action function. }) - afterEach( ()=> { + afterEach(() => { // Presets a clear action, which is performed after each unit test case ends. // The number of execution times is the same as the number of test cases defined by **it**. // This API supports only one parameter: clear action function. }) - afterAll( ()=> { + afterAll(() => { // Presets a clear action, which is performed after all test cases of the test suite end. // This API supports only one parameter: clear action function. }) - - it('onLoadListener',0, ()=> { + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); let a = 'abc'; - let b: string = ''; - let imageKnifeOption: ImageKnifeOption = { - loadSrc: 'https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658', - onLoadListener: { - onLoadSuccess: (data) => { - if (typeof data == 'string') { - b = data; - } - return data; - }, - onLoadFailed: (err) => { - console.log('Load Failed Reason: '+err); - } - } - } - if(imageKnifeOption.onLoadListener && imageKnifeOption.onLoadListener.onLoadSuccess && imageKnifeOption.onLoadListener.onLoadFailed) { - imageKnifeOption.onLoadListener.onLoadSuccess(a); - imageKnifeOption.onLoadListener.onLoadFailed(a); - } - expect(a).assertEqual(b); + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); }) }) } \ No newline at end of file diff --git a/sharedlibrary/src/ohosTest/ets/test/List.test.ets b/sharedlibrary/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000..b80e94c --- /dev/null +++ b/sharedlibrary/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,19 @@ +/* + * 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 abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/sharedlibrary/src/ohosTest/ets/testability/TestAbility.ets b/sharedlibrary/src/ohosTest/ets/testability/TestAbility.ets new file mode 100644 index 0000000..64fb765 --- /dev/null +++ b/sharedlibrary/src/ohosTest/ets/testability/TestAbility.ets @@ -0,0 +1,61 @@ +/* + * 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 { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { abilityDelegatorRegistry } from '@kit.TestKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { window } from '@kit.ArkUI'; +import { Hypium } from '@ohos/hypium'; +import testsuite from '../test/List.test'; + +export default class TestAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onCreate'); + hilog.info(0x0000, 'testTag', '%{public}s', 'want param:' + JSON.stringify(want) ?? ''); + hilog.info(0x0000, 'testTag', '%{public}s', 'launchParam:' + JSON.stringify(launchParam) ?? ''); + let abilityDelegator: abilityDelegatorRegistry.AbilityDelegator; + abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator(); + let abilityDelegatorArguments: abilityDelegatorRegistry.AbilityDelegatorArgs; + abilityDelegatorArguments = abilityDelegatorRegistry.getArguments(); + hilog.info(0x0000, 'testTag', '%{public}s', 'start run testcase!!!'); + Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite); + } + + onDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage) { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageCreate'); + windowStage.loadContent('testability/pages/Index', (err) => { + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.'); + }); + } + + onWindowStageDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy'); + } + + onForeground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground'); + } + + onBackground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground'); + } +} \ No newline at end of file diff --git a/sharedlibrary/src/ohosTest/ets/testability/pages/Index.ets b/sharedlibrary/src/ohosTest/ets/testability/pages/Index.ets new file mode 100644 index 0000000..98be4ce --- /dev/null +++ b/sharedlibrary/src/ohosTest/ets/testability/pages/Index.ets @@ -0,0 +1,31 @@ +/* + * 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. + */ +@Entry +@Component +struct Index { + @State message: string = 'Hello World'; + + build() { + Row() { + Column() { + Text(this.message) + .fontSize(50) + .fontWeight(FontWeight.Bold) + } + .width('100%') + } + .height('100%') + } +} \ No newline at end of file diff --git a/sharedlibrary/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ets b/sharedlibrary/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ets new file mode 100644 index 0000000..a5c1586 --- /dev/null +++ b/sharedlibrary/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ets @@ -0,0 +1,104 @@ +/* + * 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 { abilityDelegatorRegistry, TestRunner } from '@kit.TestKit'; +import { UIAbility, Want } from '@kit.AbilityKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { resourceManager } from '@kit.LocalizationKit'; +import { util } from '@kit.ArkTS'; + +let abilityDelegator: abilityDelegatorRegistry.AbilityDelegator; +let abilityDelegatorArguments: abilityDelegatorRegistry.AbilityDelegatorArgs; +let jsonPath: string = 'mock/mock-config.json'; +let tag: string = 'testTag'; + +async function onAbilityCreateCallback(data: UIAbility) { + hilog.info(0x0000, 'testTag', 'onAbilityCreateCallback, data: ${}', JSON.stringify(data)); +} + +async function addAbilityMonitorCallback(err: BusinessError) { + hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? ''); +} + +export default class OpenHarmonyTestRunner implements TestRunner { + constructor() { + } + + onPrepare() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare'); + } + + async onRun() { + let tag = 'testTag'; + hilog.info(0x0000, tag, '%{public}s', 'OpenHarmonyTestRunner onRun run'); + abilityDelegatorArguments = abilityDelegatorRegistry.getArguments() + abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator() + let moduleName = abilityDelegatorArguments.parameters['-m']; + let context = abilityDelegator.getAppContext().getApplicationContext().createModuleContext(moduleName); + let mResourceManager = context.resourceManager; + await checkMock(abilityDelegator, mResourceManager); + const bundleName = abilityDelegatorArguments.bundleName; + const testAbilityName: string = 'TestAbility'; + let lMonitor: abilityDelegatorRegistry.AbilityMonitor = { + abilityName: testAbilityName, + onAbilityCreate: onAbilityCreateCallback, + moduleName: moduleName + }; + abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback) + const want: Want = { + bundleName: bundleName, + abilityName: testAbilityName, + moduleName: moduleName + }; + abilityDelegator.startAbility(want, (err: BusinessError, data: void) => { + hilog.info(0x0000, tag, 'startAbility : err : %{public}s', JSON.stringify(err) ?? ''); + hilog.info(0x0000, tag, 'startAbility : data : %{public}s', JSON.stringify(data) ?? ''); + }) + hilog.info(0x0000, tag, '%{public}s', 'OpenHarmonyTestRunner onRun end'); + } +} + +async function checkMock(abilityDelegator: abilityDelegatorRegistry.AbilityDelegator, resourceManager: resourceManager.ResourceManager) { + let rawFile: Uint8Array; + try { + rawFile = resourceManager.getRawFileContentSync(jsonPath); + hilog.info(0x0000, tag, 'MockList file exists'); + let mockStr: string = util.TextDecoder.create('utf-8', { ignoreBOM: true }).decodeWithStream(rawFile); + let mockMap: Record = getMockList(mockStr); + try { + abilityDelegator.setMockList(mockMap) + } catch (error) { + let code = (error as BusinessError).code; + let message = (error as BusinessError).message; + hilog.error(0x0000, tag, `abilityDelegator.setMockList failed, error code: ${code}, message: ${message}.`); + } + } catch (error) { + let code = (error as BusinessError).code; + let message = (error as BusinessError).message; + hilog.error(0x0000, tag, `ResourceManager:callback getRawFileContent failed, error code: ${code}, message: ${message}.`); + } +} + +function getMockList(jsonStr: string) { + let jsonObj: Record = JSON.parse(jsonStr); + let map: Map = new Map(Object.entries(jsonObj)); + let mockList: Record = {}; + map.forEach((value: object, key: string) => { + let realValue: string = value['source'].toString(); + mockList[key] = realValue; + }); + hilog.info(0x0000, tag, '%{public}s', 'mock-json value:' + JSON.stringify(mockList) ?? ''); + return mockList; +} \ No newline at end of file diff --git a/sharedlibrary/src/ohosTest/module.json5 b/sharedlibrary/src/ohosTest/module.json5 new file mode 100644 index 0000000..4cca4a0 --- /dev/null +++ b/sharedlibrary/src/ohosTest/module.json5 @@ -0,0 +1,38 @@ +{ + "module": { + "name": "sharedlibrary_test", + "type": "feature", + "description": "$string:module_test_desc", + "mainElement": "TestAbility", + "deviceTypes": [ + "phone", + "tablet", + "2in1" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:test_pages", + "abilities": [ + { + "name": "TestAbility", + "srcEntry": "./ets/testability/TestAbility.ets", + "description": "$string:TestAbility_desc", + "icon": "$media:icon", + "label": "$string:TestAbility_label", + "exported": true, + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:start_window_background", + "skills": [ + { + "actions": [ + "action.system.home" + ], + "entities": [ + "entity.system.home" + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/sharedlibrary2/src/main/resources/base/element/color.json b/sharedlibrary/src/ohosTest/resources/base/element/color.json similarity index 58% rename from sharedlibrary2/src/main/resources/base/element/color.json rename to sharedlibrary/src/ohosTest/resources/base/element/color.json index 1bbc9aa..3c71296 100644 --- a/sharedlibrary2/src/main/resources/base/element/color.json +++ b/sharedlibrary/src/ohosTest/resources/base/element/color.json @@ -1,7 +1,7 @@ { "color": [ { - "name": "white", + "name": "start_window_background", "value": "#FFFFFF" } ] diff --git a/sharedlibrary/src/ohosTest/resources/base/element/string.json b/sharedlibrary/src/ohosTest/resources/base/element/string.json new file mode 100644 index 0000000..65d8fa5 --- /dev/null +++ b/sharedlibrary/src/ohosTest/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_test_desc", + "value": "test ability description" + }, + { + "name": "TestAbility_desc", + "value": "the test ability" + }, + { + "name": "TestAbility_label", + "value": "test label" + } + ] +} \ No newline at end of file diff --git a/sharedlibrary/src/ohosTest/resources/base/media/icon.png b/sharedlibrary/src/ohosTest/resources/base/media/icon.png new file mode 100644 index 0000000..a39445d Binary files /dev/null and b/sharedlibrary/src/ohosTest/resources/base/media/icon.png differ diff --git a/sharedlibrary/src/ohosTest/resources/base/profile/test_pages.json b/sharedlibrary/src/ohosTest/resources/base/profile/test_pages.json new file mode 100644 index 0000000..b7e7343 --- /dev/null +++ b/sharedlibrary/src/ohosTest/resources/base/profile/test_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "testability/pages/Index" + ] +} diff --git a/sharedlibrary/src/test/List.test.ets b/sharedlibrary/src/test/List.test.ets new file mode 100644 index 0000000..f43d80b --- /dev/null +++ b/sharedlibrary/src/test/List.test.ets @@ -0,0 +1,19 @@ +/* + * 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 localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/sharedlibrary/src/test/LocalUnit.test.ets b/sharedlibrary/src/test/LocalUnit.test.ets new file mode 100644 index 0000000..9ec8a77 --- /dev/null +++ b/sharedlibrary/src/test/LocalUnit.test.ets @@ -0,0 +1,47 @@ +/* + * 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { + describe('localUnitTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file diff --git a/sharedlibrary2/.gitignore b/sharedlibrary2/.gitignore deleted file mode 100644 index e2713a2..0000000 --- a/sharedlibrary2/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -/node_modules -/oh_modules -/.preview -/build -/.cxx -/.test \ No newline at end of file diff --git a/sharedlibrary2/build-profile.json5 b/sharedlibrary2/build-profile.json5 deleted file mode 100644 index befa101..0000000 --- a/sharedlibrary2/build-profile.json5 +++ /dev/null @@ -1,10 +0,0 @@ -{ - "apiType": 'stageMode', - "buildOption": { - }, - "targets": [ - { - "name": "default" - } - ] -} \ No newline at end of file diff --git a/sharedlibrary2/hvigorfile.ts b/sharedlibrary2/hvigorfile.ts deleted file mode 100644 index 0e65ea8..0000000 --- a/sharedlibrary2/hvigorfile.ts +++ /dev/null @@ -1,2 +0,0 @@ -// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. -module.exports = require('@ohos/hvigor-ohos-plugin').hspTasks diff --git a/sharedlibrary2/oh-package.json5 b/sharedlibrary2/oh-package.json5 deleted file mode 100644 index f6d8e3a..0000000 --- a/sharedlibrary2/oh-package.json5 +++ /dev/null @@ -1,11 +0,0 @@ -{ - "license": "Apache-2.0", - "devDependencies": {}, - "author": "", - "name": "sharedlibrary2", - "description": "Please describe the basic information.", - "main": "./src/main/ets/Index.ets", - "version": "1.0.0", - "dependencies": { - } -} diff --git a/sharedlibrary2/src/main/module.json5 b/sharedlibrary2/src/main/module.json5 deleted file mode 100644 index ff37b1a..0000000 --- a/sharedlibrary2/src/main/module.json5 +++ /dev/null @@ -1,13 +0,0 @@ -{ - "module": { - "name": "sharedlibrary2", - "type": "shared", - "description": "$string:shared_desc", - "deviceTypes": [ - "default", - "tablet" - ], - "deliveryWithInstall": true, - "pages": "$profile:main_pages" - } -} \ No newline at end of file diff --git a/sharedlibrary2/src/main/resources/base/element/string.json b/sharedlibrary2/src/main/resources/base/element/string.json deleted file mode 100644 index 98e1d8a..0000000 --- a/sharedlibrary2/src/main/resources/base/element/string.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "string": [ - { - "name": "shared_desc", - "value": "description" - } - ] -} \ No newline at end of file diff --git a/sharedlibrary2/src/main/resources/base/media/icon_loading.png b/sharedlibrary2/src/main/resources/base/media/icon_loading.png deleted file mode 100644 index 592d50b..0000000 Binary files a/sharedlibrary2/src/main/resources/base/media/icon_loading.png and /dev/null differ diff --git a/sharedlibrary2/src/main/resources/base/profile/main_pages.json b/sharedlibrary2/src/main/resources/base/profile/main_pages.json deleted file mode 100644 index 1898d94..0000000 --- a/sharedlibrary2/src/main/resources/base/profile/main_pages.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "src": [ - "pages/Index" - ] -}