3.x分支更新3.1.0版本代码,并且删除适配componentV2装饰器提交

Signed-off-by: zgf <zenggaofeng2@h-partners.com>
This commit is contained in:
zgf 2024-09-30 15:28:02 +08:00
parent ccd4455b83
commit a3ed45a468
41 changed files with 1989 additions and 670 deletions

View File

@ -1,3 +1,15 @@
## 3.0.2
- ImageKnifeAnimatorComponent新增开始、结束、暂停的回调事件
- 文件缓存数量负数和超过INT最大值时默认为INT最大值
- 修复宽高不等svg图片显示有毛边
- 部分静态webp图片有delay属性导致识别成动图,改用getFrameCount识别
- 修复加载错误图后未去请求排队队列中的请求
- 子线程本地Resource参数类型转换成number
- 修改使用hilog记录日志默认打开debug级别的日志
- 解码pixelMap默认不可编辑图形变化可编辑
- 修改网络请求超时设置
- 重构代码抽取ImageKnifeDispatcher子线程requestJob相关代码到ImageKnifeLoader中降低函数复杂度
## 3.0.2-rc.1
- release打包关闭混淆

344
README.md
View File

@ -1,229 +1,228 @@
# ImageKnife
**专门为OpenHarmony打造的一款图像加载缓存库致力于更高效、更轻便、更简单。**
ImageKnife is a specially crafted image loading and caching library for OpenHarmony, optimized for efficiency, lightness, and simplicity.
## 简介
## Introduction
本项目参考开源库 [Glide](https://github.com/bumptech/glide) 进行OpenHarmony的自研版本
This project is a self-developed version for OpenHarmony, inspired by the open-source [Glide](https://github.com/bumptech/glide) library. It sports the following features:
- 支持自定义内存缓存策略,支持设置内存缓存的大小(默认LRU策略)。
- 支持磁盘二级缓存,对于下载图片会保存一份至磁盘当中。
- 支持自定义实现图片获取/网络下载
- 支持监听网络下载回调进度
- 继承Image的能力支持option传入border设置边框圆角
- 继承Image的能力支持option传入objectFit设置图片缩放包括objectFit为auto时根据图片自适应高度
- 支持通过设置transform缩放图片
- 并发请求数量,支持请求排队队列的优先级
- 支持生命周期已销毁的图片,不再发起请求
- 自定义缓存key
- 自定义http网络请求头
- 支持writeCacheStrategy控制缓存的存入策略(只存入内存或文件缓存)
- 支持preLoadCache预加载图片
- 支持onlyRetrieveFromCache仅用缓存加载
- 支持使用一个或多个图片变换,如模糊,高亮等
- Customizable memory cache strategy with adjustable cache size (default LRU)
- Disk L2 cache for downloaded images
- Custom implementation for image acquisition and network downloading
- Listening for progress of network downloads through callbacks
- Image options for borders and rounded corners
- Image scaling with **objectFit**, including auto-adapting height
- Image scaling through transformation
- Concurrent request management with priority queuing
- No requests made for images whose lifecycle has been destroyed
- Custom cache keys
- Custom HTTP request headers
- **writeCacheStrategy** for controlling cache storage (memory or file)
- Preloading images with **preLoadCache**
- Loading images exclusively from cache with **onlyRetrieveFromCache**
- Support for image transformations such as blurring and highlighting
待实现特性
Planned features:
- gif/webp动图显示与控制
- 内存降采样优化,节约内存的占用
- 支持自定义图片解码
- Memory downsampling optimization to save memory usage
- Support for custom image decoding
注意3.x版本相对2.x版本做了重大的重构主要体现在
Note: The 3.x version has been significantly restructured from the 2.x version, mainly in the following aspects:
- 使用Image组件代替Canvas组件渲染
- 重构Dispatch分发逻辑支持控制并发请求数支持请求排队队列的优先级
- 支持通过initMemoryCache自定义策略内存缓存策略和大小
- 支持option自定义实现图片获取/网络下载
- Use of the **Image** component instead of the **Canvas** component for rendering
- Refactored dispatch logic to control the number of concurrent requests and support priority in request queuing
- Support for custom memory cache strategies and sizes through **initMemoryCache**
- Support for custom implementation of image acquisition/network downloading through options
因此API及能力上目前有部分差异主要体现在
Therefore, there are some differences in APIs and capabilities, which mainly include the following:
- 不支持drawLifeCycle接口通过canvas自会图片
- mainScaleTypeborder等参数新版本与系统Image保持一致
- gif/webp动图播放与控制
- 抗锯齿相关参数
- The **drawLifeCycle** API is not supported; images are drawn manually through the canvas.
- In the new version, parameters such as **mainScaleType** and **border** are consistent with the system **Image** component.
- GIF/WebP animation playback and control (implemented by **ImageAnimator**).
- Anti-aliasing related parameters.
## 下载安装
## How to Install
```
ohpm install @ohos/imageknife
// 如果需要用文件缓存,需要提前初始化文件缓存
// If file caching is required, initialize the file cache in advance.
await ImageKnife.getInstance().initFileCache(context, 256, 256 * 1024 * 1024)
```
## 使用说明
## How to Use
#### 1.显示本地资源图片
#### 1. Displaying a Local Resource Image
```
ImageKnifeComponent({
ImageKnifeOption: {
ImageKnifeOption: new 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.显示本地context files下文件
#### 2. Displaying a File from Local Context Files
```
ImageKnifeComponent({
ImageKnifeOption: {
ImageKnifeOption: new ImageKnifeOption({
loadSrc: this.localFile,
placeholderSrc: $r("app.media.loading"),
errorholderSrc: $r("app.media.app_icon"),
objectFit: ImageFit.Auto
}
})
}).width(100).height(100)
```
#### 3.显示网络图片
#### 3. Displaying a Network Image
```
ImageKnifeComponent({
ImageKnifeOption: {
ImageKnifeOption: new 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)
```
#### 4.自定义下载图片
#### 4. Downloading an Image with Custom Options
```
ImageKnifeComponent({
ImageKnifeOption: {
ImageKnifeOption: new ImageKnifeOption({
loadSrc: "https://file.atomgit.com/uploads/user/1704857786989_8994.jpeg",
placeholderSrc: $r("app.media.loading"),
errorholderSrc: $r("app.media.app_icon"),
objectFit: ImageFit.Auto,
customGetImage: custom
}
})
}).width(100).height(100)
// 自定义实现图片获取方法,如自定义网络下载
// Custom implementation of the image acquisition method, such as custom network download。
@Concurrent
async function custom(context: Context, src: string | PixelMap | Resource): Promise<ArrayBuffer | undefined> {
console.info("ImageKnife:: custom download" + src)
// 举例写死从本地文件读取,也可以自己请求网络图片
console.info("ImageKnife:: custom download: " + src)
// Example of hardcoding to read from a local file. You can also request a network image.
return context.resourceManager.getMediaContentSync($r("app.media.bb").id).buffer as ArrayBuffer
}
```
#### 5.监听网络下载进度
#### 5. Listening for Network Download Progress
```
ImageKnifeComponent({
ImageKnifeOption: {
ImageKnifeOption: new 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设置边框圆角
#### 6. Setting Border Options
```
ImageKnifeComponent({ ImageKnifeOption:
ImageKnifeComponent({ ImageKnifeOption: new ImageKnifeOption(
{
loadSrc: $r("app.media.rabbit"),
border: {radius:50}
}
})
}).width(100).height(100)
```
#### 7.支持option图片变换
#### 7. Setting Image Transformation Options
```
ImageKnifeComponent({ ImageKnifeOption:
ImageKnifeComponent({ ImageKnifeOption: new ImageKnifeOption(
{
loadSrc: $r("app.media.rabbit"),
border: {radius:50},
transformation: new BlurTransformation(3)
}
})
}).width(100).height(100)
```
多种组合变换用法
Multiple combined transformation usages:
```
let transformations: collections.Array<PixelMapTransformation> = new collections.Array<PixelMapTransformation>();
transformations.push(new BlurTransformation(5));
transformations.push(new BrightnessTransformation(0.2));
ImageKnifeComponent({
{
imageKnifeOption: new ImageKnifeOption({
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 // 图形变换组
}
border: { radius: { topLeft: 50, bottomRight: 50 } }, // Rounded corner settings
transformation: transformations.length > 0 ? new MultiTransTransformation(transformations) : undefined // Graphic transformation group
})
}).width(300)
.height(300)
.rotate({ angle: 90 }) // 旋转90度
.contrast(12) // 对比度滤波器
.rotate ({angle: 90}) // Rotate by 90 degrees.
.contrast(12) // Contrast filter
```
其他变换相关属性,可叠加实现组合变换效果
Other transformation-related properties can be stacked to achieve combined transformation effects.
圆形裁剪变换示例
Example of circular cropping transformation:
```
ImageKnifeComponent({ ImageKnifeOption:
ImageKnifeComponent({ ImageKnifeOption:new ImageKnifeOption(
{
loadSrc: $r('app.media.pngSample'),
objectFit: ImageFit.Cover,
border: { radius: 150 }
}
})
}).width(300)
.height(300)
```
圆形裁剪带边框变换示例
Example of Circular cropping with border transformation:
```
ImageKnifeComponent({ ImageKnifeOption:
ImageKnifeComponent({ ImageKnifeOption:new ImageKnifeOption(
{
loadSrc: $r('app.media.pngSample'),
objectFit: ImageFit.Cover,
border: { radius: 150, color: Color.Red, width: 5 }
}
})
}).width(300)
.height(300)
```
对比度滤波变换示例
Example of contrast filtering transformation:
```
ImageKnifeComponent({
imageKnifeOption: {
imageKnifeOption: new ImageKnifeOption({
loadSrc: $r('app.media.pngSample')
}
})
}).width(300)
.height(300)
.contrast(12)
```
旋转变换示例
Example of rotation transformation:
```
ImageKnifeComponent({
imageKnifeOption: {
imageKnifeOption: new ImageKnifeOption({
loadSrc: $r('app.media.pngSample')
}
})
}).width(300)
.height(300)
.rotate({angle:90})
.backgroundColor(Color.Pink)
```
#### 8.监听图片加载成功与失败
#### 8. Listening for Image Loading Success and Failure
```
ImageKnifeComponent({ ImageKnifeOption:
ImageKnifeComponent({ ImageKnifeOption: new ImageKnifeOption(
{
loadSrc: $r("app.media.rabbit"),
onLoadListener:{
@ -242,128 +241,135 @@ ImageKnifeComponent({ ImageKnifeOption:
console.info(err)
}
}
}
})
}).width(100).height(100)
```
#### 9.ImageKnifeComponent - syncLoad
设置是否同步加载图片默认是异步加载。建议加载尺寸较小的本地图片时将syncLoad设为true因为耗时较短在主线程上执行即可
#### 9. Use of syncLoad
**syncLoad** sets whether to load the image synchronously. By default, the image is loaded asynchronously. When loading a small image, you are advised to set **syncLoad** to **true** so that the image loading can be quickly completed on the main thread.
```
ImageKnifeComponent({
imageKnifeOption:{
imageKnifeOption:new ImageKnifeOption({
loadSrc:$r("app.media.pngSample"),
placeholderSrc:$r("app.media.loading")
},syncLoad:true
}),syncLoad:true
})
```
#### 10.ImageKnifeAnimatorComponent 示例
#### 10. Use of ImageKnifeAnimatorComponent
```
ImageKnifeAnimatorComponent({
imageKnifeOption:{
imageKnifeOption:new ImageKnifeOption({
loadSrc:"https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658",
placeholderSrc:$r('app.media.loading'),
errorholderSrc:$r('app.media.failed')
},animatorOption:this.animatorOption
}),animatorOption:this.animatorOption
}).width(300).height(300).backgroundColor(Color.Orange).margin({top:30})
```
## 接口说明
### ImageKnife组件
| 组件名称 | 入参内容 | 功能简介 |
|-----------------------------|---------------------------------|--------|
| ImageKnifeComponent | ImageKnifeOption | 图片显示组件 |
| ImageKnifeAnimatorComponent | ImageKnifeOption、AnimatorOption | 动图控制组件 |
#### Reuse Scenario
Clear the component content in the **aboutToRecycle** lifecycle and trigger image loading through watch observeration.
## Available APIs
### ImageKnife
| Component | Parameter | Description |
| --------------------------- | -------------------------------- | ------------ |
| ImageKnifeComponent | ImageKnifeOption | Image display component.|
| ImageKnifeAnimatorComponent | ImageKnifeOption, AnimatorOption| Animated image control component.|
### AnimatorOption参数列表
| 参数名称 | 入参内容 | 功能简介 |
|-----------------------|-------------------------------------------------------|----------|
| state | AnimationStatus | 播放状态(可选) |
| iterations | number | 播放次数(可选) |
| reverse | boolean | 播放顺序(可选) |
### AnimatorOption
| Parameter | Type | Description |
| ---------- | --------------- | ---------------------------------------- |
| state | AnimationStatus | Playback status. Optional. |
| iterations | number | Number of playback times. Optional. |
| reverse | boolean | Playback order. Optional. |
| onStart | ()=>void | Triggered when the animation starts. Optional. |
| onFinish | ()=>void | Triggered when the animation finishes or stops. Optional.|
| onPause | ()=>void | Triggered when the animation pauses. Optional. |
| onCancel | ()=>void | Triggered when the animation is canceled, that is, when it is reset to its initial state. Optional. |
| onRepeat | ()=>void | Triggered when the animation repeats. Optional. |
### ImageKnifeOption参数列表
### ImageKnifeOption
| 参数名称 | 入参内容 | 功能简介 |
|-----------------------|-------------------------------------------------------|-----------------|
| 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<HeaderOptions> | 设置请求头(可选) |
| transformation | PixelMapTransformation | 图片变换(可选) |
| drawingColorFilter | ColorFilter | drawing.ColorFilter | 图片变换(可选) |
| onComplete | (event:EventImage | undefined) => voi | 颜色滤镜效果(可选) |
| onLoadListener | onLoadStart: () => void、onLoadSuccess: (data: string | PixelMap | undefined) => void、onLoadFailed: (err: string) => void| 监听图片加载成功与失败 |
| Parameter | Type | Description |
| --------------------- | ----------------------------------------------------- | ------------------------------ |
| loadSrc | string, PixelMap, Resource | Main image. |
| placeholderSrc | PixelMap, Resource | Placeholder image. Optional. |
| errorholderSrc | PixelMap, Resource | Error image. Optional. |
| objectFit | ImageFit | How the main image is resized to fit its container. Optional. |
| placeholderObjectFit | ImageFit | How the placeholder image is resized to fit its container. Optional. |
| errorholderObjectFit | ImageFit | How the error image is resized to fit its container. Optional. |
| writeCacheStrategy | CacheStrategyType | Cache writing strategy. Optional. |
| onlyRetrieveFromCache | boolean | Whether to skip network and local requests. Optional.|
| customGetImage | (context: Context, src: string | Custom image download. Optional. |
| border | BorderOptions | Border corner. Optional. |
| priority | taskpool.Priority | Load priority. Optional. |
| context | common.UIAbilityContext | Context. Optional. |
| progressListener | (progress: number)=>void | Progress. Optional. |
| signature | String | Custom cache signature. Optional. |
| headerOption | Array\<HeaderOptions> | Request headers. Optional. |
| transformation | PixelMapTransformation | Image transformation. Optional. |
| drawingColorFilter | ColorFilter | Drawing color filter. Optional. |
| onComplete | (event:EventImage \| undefined)=>void | Callback for image loading completion. Optional. |
| onLoadListener | onLoadStart:()=>void,onLoadSuccess:(data:string\|Pixelmap)=>void | Callback for image loading events. Optional. |
### ImageKnife接口
### ImageKnife
| 参数名称 | 入参内容 | 功能简介 |
|------------------|-------------------------------------------------------------------------------------------------------|---------------|
| 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<HeaderOptions> | 全局设置http请求头 |
| deleteHeader | key: string | 全局删除http请求头 |
| setCustomGetImage | customGetImage?: (context: Context, src: string | PixelMap | Resource) => Promise<ArrayBuffer | undefined> | 全局设置自定义下载 |
| setEngineKeyImpl | IEngineKey | 全局配置缓存key生成策略 |
| putCacheImage | url: string, pixelMap: PixelMap, cacheType: CacheStrategy = CacheStrategy.Default, signature?: string | 写入内存磁盘缓存 |
| removeMemoryCache| url: string | ImageKnifeOption | 清理指定内存缓存 |
| removeFileCache | url: string | ImageKnifeOption | 清理指定磁盘缓存 |
### 图形变换类型需要为GPUImage添加依赖项
| Parameter | Type | Description |
| ----------------- | ------------------------------------------------------------ | -------------------------- |
| initMemoryCache | newMemoryCache: IMemoryCache | Initializes a custom memory cache strategy. |
| initFileCache | context: Context, size: number, memory: number | Initializes the file cache size and quantity |
| preLoadCache | loadSrc: string I ImageKnifeOption | Preloads and returns the file cache path. |
| getCacheImage | loadSrc: string, cacheType: CacheStrategy = CacheStrategy.Default, signature?: string) | Obtains resources from memory or file cache.|
| addHeader | key: string, value: Object | Adds a global HTTP request header. |
| setHeaderOptions | Array<HeaderOptions> | Sets global HTTP request headers. |
| deleteHeader | key: string | Deletes a global HTTP request header. |
| setCustomGetImage | customGetImage?: (context: Context, src: string | PixelMap |
| setEngineKeyImpl | IEngineKey | Sets a global cache key generation strategy. |
| putCacheImage | url: string, pixelMap: PixelMap, cacheType: CacheStrategy = CacheStrategy.Default, signature?: string | Writes to the memory disk cache. |
| removeMemoryCache | url: string | Removes an entry from the memory cache. |
| removeFileCache | url: string | Removes an entry from the file cache. |
### Graphics tRansformation Types (GPUImage Dependency Required)
| 类型 | 相关描述 |
| ---------------------------------- | ----------------------------- |
| BlurTransformation | 模糊处理 |
| BrightnessTransformation | 亮度滤波器 |
| CropSquareTransformation | 正方形剪裁 |
| CropTransformation | 自定义矩形剪裁 |
| GrayScaleTransformation | 灰度级滤波器 |
| InvertTransformation | 反转滤波器 |
| KuwaharaTransformation | 桑原滤波器使用GPUIImage |
| MaskTransformation | 遮罩 |
| PixelationTransformation | 像素化滤波器使用GPUIImage |
| SepiaTransformation | 乌墨色滤波器使用GPUIImage |
| SketchTransformation | 素描滤波器使用GPUIImage |
| SwirlTransformation | 扭曲滤波器使用GPUIImage |
| ToonTransformation | 动画滤波器使用GPUIImage |
| VignetterTransformation | 装饰滤波器使用GPUIImage |
| Type | Description |
| ------------------------ | ----------------------------- |
| BlurTransformation | Blurs the image. |
| BrightnessTransformation | Applies a brightness filter. |
| CropSquareTransformation | Crops the image to a square. |
| CropTransformation | Crops the image to a custom rectangle. |
| GrayScaleTransformation | Applies a grayscale filter. |
| InvertTransformation | Applies an inversion filter. |
| KuwaharaTransformation | Applies a Kuwahara filter (requires **GPUImage**). |
| MaskTransformation | Applies a mask. |
| PixelationTransformation | Applies a pixelation filter (requires **GPUImage**).|
| SepiaTransformation | Applies a sepia filter (requires **GPUImage**).|
| SketchTransformation | Applies a sketch filter (requires **GPUIImage**). |
| SwirlTransformation | Applies a swirl filter (requires **GPUImage**). |
| ToonTransformation | Applies a cartoon filter (requires **GPUImage**). |
| VignetterTransformation | Applies a vignette filter (requires **GPUImage**). |
## 下载安装GPUImage依赖
方法一在Terminal窗口中执行如下命令安装三方包DevEco Studio会自动在工程的oh-package.json5中自动添加三方包依赖。
## Downloading and Installing the GPUImage Dependency
Method 1: In the **Terminal** window, run the following command to install the third-party HAR. DevEco Studio will automatically add the HAR as a dependency to the **oh-package.json5** file of the project.
```
ohpm install @ohos/gpu_transform
```
方法二: 在工程的oh-package.json5中设置三方包依赖配置示例如下
Method 2: Set the third-party HAR as a dependency in the **oh-package.json5** file of the project. The following is a configuration example:
```
"dependencies": {
"@ohos/gpu_transform": "^1.0.2"
}
```
## 约束与限制
## Constraints
在下述版本验证通过:
DevEco Studio 5.0 Canary35.0.3.221--SDK:API12
This project has been verified in the following version:
## 贡献代码
DevEco Studio: 5.0 Canary3 (5.0.3.502), SDK: API 12 (5.0.0.31)
使用过程中发现任何问题都可以提 [issue](https://gitee.com/openharmony-tpc/ImageKnife/issues)
给我们,当然,我们也非常欢迎你给我们发 [PR](https://gitee.com/openharmony-tpc/ImageKnife/issues) 。
## How to Contribute
## 开源协议
If you find any problem during the use, submit an [Issue](https://gitee.com/openharmony-tpc/ImageKnife/issues) or a [PR](https://gitee.com/openharmony-tpc/ImageKnife/issues) to us.
本项目基于 [Apache License 2.0](https://gitee.com/openharmony-tpc/ImageKnife/blob/master/LICENSE) ,请自由的享受和参与开源。
## License
## 遗留问题
This project is licensed under [Apache License 2.0](https://gitee.com/openharmony-tpc/ImageKnife/blob/master/LICENSE).
- ImageKnifeAnimator组件无法设置ImageFit属性
- ImageKnifeAnimator组件设置border属性无法将图片变为圆角
## Known Issues
- The **ImageFit** attribute cannot be set for the **ImageKnifeAnimator** component.
- The **border** attribute of the **ImageKnifeAnimator** component cannot make the image rounded corners.

375
README_zh.md Normal file
View File

@ -0,0 +1,375 @@
# ImageKnife
**专门为OpenHarmony打造的一款图像加载缓存库致力于更高效、更轻便、更简单。**
## 简介
本项目参考开源库 [Glide](https://github.com/bumptech/glide) 进行OpenHarmony的自研版本
- 支持自定义内存缓存策略,支持设置内存缓存的大小(默认LRU策略)。
- 支持磁盘二级缓存,对于下载图片会保存一份至磁盘当中。
- 支持自定义实现图片获取/网络下载
- 支持监听网络下载回调进度
- 继承Image的能力支持option传入border设置边框圆角
- 继承Image的能力支持option传入objectFit设置图片缩放包括objectFit为auto时根据图片自适应高度
- 支持通过设置transform缩放图片
- 并发请求数量,支持请求排队队列的优先级
- 支持生命周期已销毁的图片,不再发起请求
- 自定义缓存key
- 自定义http网络请求头
- 支持writeCacheStrategy控制缓存的存入策略(只存入内存或文件缓存)
- 支持preLoadCache预加载图片
- 支持onlyRetrieveFromCache仅用缓存加载
- 支持使用一个或多个图片变换,如模糊,高亮等
待实现特性
- 内存降采样优化,节约内存的占用
- 支持自定义图片解码
注意3.x版本相对2.x版本做了重大的重构主要体现在
- 使用Image组件代替Canvas组件渲染
- 重构Dispatch分发逻辑支持控制并发请求数支持请求排队队列的优先级
- 支持通过initMemoryCache自定义策略内存缓存策略和大小
- 支持option自定义实现图片获取/网络下载
因此API及能力上目前有部分差异主要体现在
- 不支持drawLifeCycle接口通过canvas自会图片
- mainScaleTypeborder等参数新版本与系统Image保持一致
- gif/webp动图播放与控制(ImageAnimator实现)
- 抗锯齿相关参数
## 下载安装
```
ohpm install @ohos/imageknife
// 如果需要用文件缓存,需要提前初始化文件缓存
await ImageKnife.getInstance().initFileCache(context, 256, 256 * 1024 * 1024)
```
## 使用说明
#### 1.显示本地资源图片
```
ImageKnifeComponent({
ImageKnifeOption: new 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.显示本地context files下文件
```
ImageKnifeComponent({
ImageKnifeOption: new ImageKnifeOption({
loadSrc: this.localFile,
placeholderSrc: $r("app.media.loading"),
errorholderSrc: $r("app.media.app_icon"),
objectFit: ImageFit.Auto
})
}).width(100).height(100)
```
#### 3.显示网络图片
```
ImageKnifeComponent({
ImageKnifeOption: new 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)
```
#### 4.自定义下载图片
```
ImageKnifeComponent({
ImageKnifeOption: new ImageKnifeOption({
loadSrc: "https://file.atomgit.com/uploads/user/1704857786989_8994.jpeg",
placeholderSrc: $r("app.media.loading"),
errorholderSrc: $r("app.media.app_icon"),
objectFit: ImageFit.Auto,
customGetImage: custom
})
}).width(100).height(100)
// 自定义实现图片获取方法,如自定义网络下载
@Concurrent
async function custom(context: Context, src: string | PixelMap | Resource): Promise<ArrayBuffer | undefined> {
console.info("ImageKnife:: custom download" + src)
// 举例写死从本地文件读取,也可以自己请求网络图片
return context.resourceManager.getMediaContentSync($r("app.media.bb").id).buffer as ArrayBuffer
}
```
#### 5.监听网络下载进度
```
ImageKnifeComponent({
ImageKnifeOption: new 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设置边框圆角
```
ImageKnifeComponent({ ImageKnifeOption: new ImageKnifeOption(
{
loadSrc: $r("app.media.rabbit"),
border: {radius:50}
})
}).width(100).height(100)
```
#### 7.支持option图片变换
```
ImageKnifeComponent({ ImageKnifeOption: new ImageKnifeOption(
{
loadSrc: $r("app.media.rabbit"),
border: {radius:50},
transformation: new BlurTransformation(3)
})
}).width(100).height(100)
```
多种组合变换用法
```
let transformations: collections.Array<PixelMapTransformation> = new collections.Array<PixelMapTransformation>();
transformations.push(new BlurTransformation(5));
transformations.push(new BrightnessTransformation(0.2));
ImageKnifeComponent({
imageKnifeOption: new ImageKnifeOption({
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:new ImageKnifeOption(
{
loadSrc: $r('app.media.pngSample'),
objectFit: ImageFit.Cover,
border: { radius: 150 }
})
}).width(300)
.height(300)
```
圆形裁剪带边框变换示例
```
ImageKnifeComponent({ ImageKnifeOption:new ImageKnifeOption(
{
loadSrc: $r('app.media.pngSample'),
objectFit: ImageFit.Cover,
border: { radius: 150, color: Color.Red, width: 5 }
})
}).width(300)
.height(300)
```
对比度滤波变换示例
```
ImageKnifeComponent({
imageKnifeOption: new ImageKnifeOption({
loadSrc: $r('app.media.pngSample')
})
}).width(300)
.height(300)
.contrast(12)
```
旋转变换示例
```
ImageKnifeComponent({
imageKnifeOption: new ImageKnifeOption({
loadSrc: $r('app.media.pngSample')
})
}).width(300)
.height(300)
.rotate({angle:90})
.backgroundColor(Color.Pink)
```
#### 8.监听图片加载成功与失败
```
ImageKnifeComponent({ ImageKnifeOption: new 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
设置是否同步加载图片默认是异步加载。建议加载尺寸较小的Resource图片时将syncLoad设为true因为耗时较短在主线程上执行即可
```
ImageKnifeComponent({
imageKnifeOption:new ImageKnifeOption({
loadSrc:$r("app.media.pngSample"),
placeholderSrc:$r("app.media.loading")
}),syncLoad:true
})
```
#### 10.ImageKnifeAnimatorComponent 示例
```
ImageKnifeAnimatorComponent({
imageKnifeOption:new 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})
```
#### 复用场景
在aboutToRecycle生命周期清空组件内容通过watch监听触发图片的加载。
## 接口说明
### ImageKnife组件
| 组件名称 | 入参内容 | 功能简介 |
|-----------------------------|---------------------------------|--------|
| ImageKnifeComponent | ImageKnifeOption | 图片显示组件 |
| ImageKnifeAnimatorComponent | ImageKnifeOption、AnimatorOption | 动图控制组件 |
### AnimatorOption参数列表
| 参数名称 | 入参内容 | 功能简介 |
|------------|-----------------|----------|
| state | AnimationStatus | 播放状态(可选) |
| iterations | number | 播放次数(可选) |
| reverse | boolean | 播放顺序(可选) |
| onStart | ()=>void | 动画开始播放时触发(可选) |
| onFinish | ()=>void | 动画播放完成时或者停止播放时触发(可选) |
| onPause | ()=>void | 动画暂停播放时触发(可选) |
| onCancel | ()=>void | 动画返回最初状态时触发(可选) |
| onRepeat | ()=>void | 动画重复播放时触发(可选) |
### ImageKnifeOption参数列表
| 参数名称 | 入参内容 | 功能简介 |
|-----------------------|-------------------------------------------------------|-----------------|
| 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<HeaderOptions> | 设置请求头(可选) |
| transformation | PixelMapTransformation | 图片变换(可选) |
| drawingColorFilter | ColorFilter | drawing.ColorFilter | 图片变换(可选) |
| onComplete | (event:EventImage | undefined) => voi | 颜色滤镜效果(可选) |
| onLoadListener | onLoadStart: () => void、onLoadSuccess: (data: string | PixelMap | undefined) => void、onLoadFailed: (err: string) => void| 监听图片加载成功与失败 |
### ImageKnife接口
| 参数名称 | 入参内容 | 功能简介 |
|------------------|-------------------------------------------------------------------------------------------------------|---------------|
| 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<HeaderOptions> | 全局设置http请求头 |
| deleteHeader | key: string | 全局删除http请求头 |
| setCustomGetImage | customGetImage?: (context: Context, src: string | PixelMap | Resource) => Promise<ArrayBuffer | undefined> | 全局设置自定义下载 |
| 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 |
## 下载安装GPUImage依赖
方法一在Terminal窗口中执行如下命令安装三方包DevEco Studio会自动在工程的oh-package.json5中自动添加三方包依赖。
```
ohpm install @ohos/gpu_transform
```
方法二: 在工程的oh-package.json5中设置三方包依赖配置示例如下
```
"dependencies": {
"@ohos/gpu_transform": "^1.0.2"
}
```
## 约束与限制
在下述版本验证通过:
DevEco Studio 5.0 Canary35.0.3.502--SDK:API12 (5.0.0.31)
## 贡献代码
使用过程中发现任何问题都可以提 [issue](https://gitee.com/openharmony-tpc/ImageKnife/issues)
,当然,也非常欢迎发 [PR](https://gitee.com/openharmony-tpc/ImageKnife/pulls) 共建。
## 开源协议
本项目基于 [Apache License 2.0](https://gitee.com/openharmony-tpc/ImageKnife/blob/master/LICENSE) ,请自由的享受和参与开源。
## 遗留问题
- ImageKnifeAnimator组件无法设置ImageFit属性
- ImageKnifeAnimator组件设置border属性无法将图片变为圆角

View File

@ -17,7 +17,7 @@ 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 { ImageKnife, InitImageKnife, LogUtil } from '@ohos/libraryimageknife';
import { ImageKnife, InitImageKnife } from '@ohos/libraryimageknife';
import { CustomEngineKeyImpl } from '../common/CustomEngineKeyImpl';
import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
import { BusinessError } from '@ohos.base'
@ -47,7 +47,6 @@ export default class EntryAbility extends UIAbility {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
LogUtil.mLogLevel = LogUtil.ALL
// 初始化ImageKnife的文件缓存
await InitImageKnife.init(this.context)
ImageKnife.getInstance().setEngineKeyImpl(new CustomEngineKeyImpl())

View File

@ -1,3 +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.
*/
import { AnimatorOption, ImageKnifeAnimatorComponent } from "@ohos/libraryimageknife"
@Entry
@ -5,7 +19,29 @@ import { AnimatorOption, ImageKnifeAnimatorComponent } from "@ohos/libraryimagek
struct ImageAnimatorPage {
@State animatorOption: AnimatorOption = {
state: AnimationStatus.Running,
iterations: -1
iterations: 1,
onFinish:()=>{
console.log("ImageKnifeAnimatorComponent animatorOption onFinish")
},
onStart:()=>{
console.log("ImageKnifeAnimatorComponent animatorOption onStart")
},
onPause:()=>{
console.log("ImageKnifeAnimatorComponent animatorOption onPause")
},
onCancel:()=>{
console.log("ImageKnifeAnimatorComponent animatorOption onCancel")
},
onRepeat:()=>{
console.log("ImageKnifeAnimatorComponent animatorOption onRepeat")
}
}
@State animatorOption1: AnimatorOption = {
state: AnimationStatus.Initial
}
@State animatorOption2: AnimatorOption = {
state: AnimationStatus.Initial,
reverse: true
}
build() {
Column(){
@ -36,6 +72,22 @@ struct ImageAnimatorPage {
errorholderSrc:$r('app.media.failed')
},animatorOption:this.animatorOption
}).width(300).height(300).backgroundColor(Color.Orange).margin({top:30})
Text($r('app.string.Display_the_first_frame')).fontSize(20)
ImageKnifeAnimatorComponent({
imageKnifeOption:{
loadSrc:"https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658",
placeholderSrc:$r('app.media.loading'),
errorholderSrc:$r('app.media.failed')
},animatorOption:this.animatorOption1
}).width(200).height(200).backgroundColor(Color.Orange).margin({top:30})
Text($r('app.string.Display_the_last_frame')).fontSize(20)
ImageKnifeAnimatorComponent({
imageKnifeOption:{
loadSrc:"https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658",
placeholderSrc:$r('app.media.loading'),
errorholderSrc:$r('app.media.failed')
},animatorOption:this.animatorOption2
}).width(200).height(200).backgroundColor(Color.Orange).margin({top:30})
}.width("100%").height("100%")
}
}

View File

@ -78,7 +78,7 @@ struct ImageTransformation {
})
.width(30)
.height(30)
Text('模糊效果').fontSize(20)
Text($r('app.string.Blur_effect')).fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
@ -91,7 +91,7 @@ struct ImageTransformation {
})
.width(30)
.height(30)
Text('高亮效果').fontSize(20)
Text($r('app.string.Highlighting_effect')).fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
@ -104,7 +104,7 @@ struct ImageTransformation {
})
.width(30)
.height(30)
Text('灰化效果').fontSize(20)
Text($r('app.string.Ashing_effect')).fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
@ -117,7 +117,7 @@ struct ImageTransformation {
})
.width(30)
.height(30)
Text('反转效果').fontSize(20)
Text($r('app.string.Inverse_effect')).fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
@ -130,7 +130,7 @@ struct ImageTransformation {
})
.width(30)
.height(30)
Text('动画滤镜效果').fontSize(20)
Text($r('app.string.Animation_filter_effect')).fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
@ -143,7 +143,7 @@ struct ImageTransformation {
})
.width(30)
.height(30)
Text('裁剪圆形效果').fontSize(20)
Text($r('app.string.Crop_circular_effect')).fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
@ -156,7 +156,7 @@ struct ImageTransformation {
})
.width(30)
.height(30)
Text('裁剪圆形带边框效果').fontSize(20)
Text($r('app.string.Crop_circular_with_border_effect')).fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
@ -168,7 +168,7 @@ struct ImageTransformation {
})
.width(30)
.height(30)
Text('对比度效果').fontSize(20)
Text($r('app.string.Contrast_effect')).fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
@ -181,7 +181,7 @@ struct ImageTransformation {
})
.width(30)
.height(30)
Text('乌墨色滤波效果').fontSize(20)
Text($r('app.string.Black_ink_filtering_effect')).fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
@ -193,7 +193,7 @@ struct ImageTransformation {
})
.width(30)
.height(30)
Text('旋转效果').fontSize(20)
Text($r('app.string.Rotate')).fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
@ -206,7 +206,7 @@ struct ImageTransformation {
})
.width(30)
.height(30)
Text('圆角效果').fontSize(20)
Text($r('app.string.Corners')).fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
@ -219,7 +219,7 @@ struct ImageTransformation {
})
.width(30)
.height(30)
Text('桑原滤波效果').fontSize(20)
Text($r('app.string.Kuwahara_Filter_effect')).fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
@ -232,7 +232,7 @@ struct ImageTransformation {
})
.width(30)
.height(30)
Text('像素化滤波效果').fontSize(20)
Text($r('app.string.Pixelated_Filter_effect')).fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
@ -245,7 +245,7 @@ struct ImageTransformation {
})
.width(30)
.height(30)
Text('素描滤波效果').fontSize(20)
Text($r('app.string.Sketch_Filter_effect')).fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
@ -258,7 +258,7 @@ struct ImageTransformation {
})
.width(30)
.height(30)
Text('扭曲滤波效果').fontSize(20)
Text($r('app.string.Distortion_Filter_effect')).fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
@ -271,7 +271,7 @@ struct ImageTransformation {
})
.width(30)
.height(30)
Text('装饰滤波效果').fontSize(20)
Text($r('app.string.Decorative_Filter_effect')).fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
@ -284,7 +284,7 @@ struct ImageTransformation {
})
.width(30)
.height(30)
Text('正方形裁剪效果').fontSize(20)
Text($r('app.string.Square_cutting_effect')).fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
@ -297,7 +297,7 @@ struct ImageTransformation {
})
.width(30)
.height(30)
Text('上方裁剪效果').fontSize(20)
Text($r('app.string.Top_cutting_effect')).fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
@ -310,7 +310,7 @@ struct ImageTransformation {
})
.width(30)
.height(30)
Text('中间裁剪效果').fontSize(20)
Text($r('app.string.Middle_cutting_effect')).fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
@ -323,7 +323,7 @@ struct ImageTransformation {
})
.width(30)
.height(30)
Text('底下裁剪效果').fontSize(20)
Text($r('app.string.Bottom_cutting_effect')).fontSize(20)
}
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
@ -336,7 +336,7 @@ struct ImageTransformation {
})
.width(30)
.height(30)
Text('遮罩效果').fontSize(20)
Text($r('app.string.Mask_effect')).fontSize(20)
}
if (this.isContrast) {

View File

@ -18,6 +18,10 @@ import router from '@system.router';
@Component
struct Index {
getResourceString(res:Resource){
return getContext().resourceManager.getStringSync(res.id)
}
aboutToAppear(): void {
@ -26,95 +30,100 @@ struct Index {
build() {
Scroll(){
Column() {
Button("测试ImageAnimator组件").onClick(()=>{
Button($r('app.string.Test_ImageAnimator')).onClick(()=>{
router.push({
uri: 'pages/ImageAnimatorPage',
});
})
Button("测试加载多张相同图片").margin({top:10}).onClick(()=>{
Button($r('app.string.Test_multiple_images')).margin({top:10}).onClick(()=>{
router.push({
uri: 'pages/TestCommonImage',
});
})
Button("测试HSP场景预加载").margin({top:10}).onClick(()=>{
Button($r('app.string.Test_Task_error')).margin({top:10}).onClick(()=>{
router.push({
uri: 'pages/TestTaskResourcePage',
});
})
Button($r('app.string.Test_HSP')).margin({top:10}).onClick(()=>{
router.push({
uri: 'pages/TestHspPreLoadImage',
});
})
Button("单个图片使用").margin({top:10}).onClick(()=>{
Button($r('app.string.Test_SingleImage')).margin({top:10}).onClick(()=>{
router.push({
uri: 'pages/SingleImage',
});
})
Button("全局自定义下载").margin({top:10}).onClick(()=>{
Button($r('app.string.Test_custom_download')).margin({top:10}).onClick(()=>{
router.push({
uri: 'pages/TestSetCustomImagePage',
});
})
Button("多图 + LazyForEach").margin({top:10}).onClick(()=>{
Button(this.getResourceString($r('app.string.Multiple_images')) + " + LazyForEach").margin({top:10}).onClick(()=>{
router.push({
uri: 'pages/ManyPhotoShowPage',
});
})
Button("多图 + reuse + LazyForeach").margin({top:10}).onClick(()=>{
Button(this.getResourceString($r('app.string.Multiple_images')) + " + reuse + LazyForeach").margin({top:10}).onClick(()=>{
router.push({
uri: 'pages/UserPage',
});
})
Button("长图显示").margin({top:10}).onClick(()=>{
Button($r('app.string.Display_long_image')).margin({top:10}).onClick(()=>{
router.push({
uri: 'pages/LongImagePage',
});
})
Button("缩放图片").margin({top:10}).onClick(()=>{
Button($r('app.string.Image_scaling')).margin({top:10}).onClick(()=>{
router.push({
uri: 'pages/TransformPage',
});
})
Button("消息+List").margin({top:10}).onClick(()=>{
Button(this.getResourceString($r('app.string.Message_list')) + " + List").margin({top:10}).onClick(()=>{
router.push({
uri: 'pages/TestImageFlash',
});
})
Button("自定义缓存key").margin({top:10}).onClick(()=>{
Button($r('app.string.Custom_cache_key')).margin({top:10}).onClick(()=>{
router.push({
uri: 'pages/SignatureTestPage',
});
})
Button("预加载图片到文件缓存").margin({top:10}).onClick(()=>{
Button($r('app.string.Preloading_images_to_cache')).margin({top:10}).onClick(()=>{
router.push({
uri: 'pages/TestPrefetchToFileCache',
});
})
Button("从缓存获取图片显示").margin({top:10}).onClick(()=>{
Button($r('app.string.Retrieve_image_display_from_cache')).margin({top:10}).onClick(()=>{
router.push({
uri: 'pages/TestIsUrlExist',
});
})
Button("测试单个请求头").margin({top:10}).onClick(()=>{
Button($r('app.string.Test_single_request_header')).margin({top:10}).onClick(()=>{
router.push({
uri: 'pages/TestHeader',
});
})
Button("测试写入缓存策略").margin({top:10}).onClick(()=>{
Button($r('app.string.Test_write_cache_strategy')).margin({top:10}).onClick(()=>{
router.push({
uri: 'pages/TestWriteCacheStage',
});
})
Button("图片变换").margin({top:10}).onClick(()=>{
Button($r('app.string.Image_Transformation')).margin({top:10}).onClick(()=>{
router.push({
uri: 'pages/ImageTransformation',
@ -122,7 +131,7 @@ struct Index {
})
Button("不同的ObjectFit").margin({top:10}).onClick(()=>{
Button($r('app.string.Different_ObjectFit')).margin({top:10}).onClick(()=>{
router.push({
uri: 'pages/ObjectFitPage',
@ -130,24 +139,24 @@ struct Index {
})
Button('测试图片加载成功/失败事件').margin({top:10}).onClick(()=>{
Button($r('app.string.Test_image_loading_success_or_failure_events')).margin({top:10}).onClick(()=>{
router.push({
uri: 'pages/LoadStatePage',
})
})
Button('测试移除图片缓存接口').margin({top:10}).onClick(()=>{
Button($r('app.string.Test_removing_image_cache_interface')).margin({top:10}).onClick(()=>{
router.push({
uri: 'pages/TestRemoveCache',
});
})
Button("测试错误图显示").margin({top:10}).onClick(()=>{
Button($r('app.string.Test_error_image_display')).margin({top:10}).onClick(()=>{
router.push({
uri: 'pages/TestErrorHolderPage',
});
})
Button("测试媒体url").margin({top:10}).onClick(()=>{
Button($r('app.string.Test_media_URL')).margin({top:10}).onClick(()=>{
router.push({
uri: 'pages/dataShareUriLoadPage',

View File

@ -33,9 +33,9 @@ struct SignatureTestPage {
Scroll() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Text("key固定为 1").fontSize(15)
Text($r('app.string.The_key_fixed_1')).fontSize(15)
Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Button("加载")
Button($r('app.string.Load'))
.onClick(() => {
this.imageKnifeOption1 = {
loadSrc: 'https://img-blog.csdn.net/20140514114029140',
@ -46,9 +46,9 @@ struct SignatureTestPage {
ImageKnifeComponent({ imageKnifeOption: this.imageKnifeOption1 }).width(300).height(300)
}.width('100%').backgroundColor(Color.Pink)
Text("key每次变化时间戳").fontSize(15)
Text($r('app.string.The_key_changes_timestamp')).fontSize(15)
Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Button("加载")
Button($r('app.string.Load'))
.onClick(() => {
this.imageKnifeOption2 = {
loadSrc: 'https://img-blog.csdn.net/20140514114029140',

View File

@ -42,7 +42,7 @@ struct SingleImage {
build() {
Scroll(this.scroller) {
Column() {
Text("本地资源svg图片")
Text($r('app.string.Local_SVG'))
.fontSize(30)
.fontWeight(FontWeight.Bold)
ImageKnifeComponent({
@ -56,7 +56,7 @@ struct SingleImage {
.onClick(()=>{
this.DrawingColorFilter = drawing.ColorFilter.createBlendModeColorFilter(this.color, drawing.BlendMode.SRC_IN);
})
Text("本地context files下文件")
Text($r('app.string.Under_context_file'))
.fontSize(30)
.fontWeight(FontWeight.Bold)
ImageKnifeComponent({
@ -67,7 +67,7 @@ struct SingleImage {
objectFit: ImageFit.Contain
}
}).width(100).height(100)
Text("网络图片")
Text($r('app.string.Network_images'))
.fontSize(30)
.fontWeight(FontWeight.Bold)
ImageKnifeComponent({
@ -79,7 +79,7 @@ struct SingleImage {
progressListener:(progress:number)=>{console.info("ImageKnife:: call back progress = " + progress)}
}
}).width(100).height(100)
Text("自定义下载")
Text($r('app.string.Custom_network_download'))
.fontSize(30)
.fontWeight(FontWeight.Bold)
ImageKnifeComponent({
@ -92,7 +92,7 @@ struct SingleImage {
transformation: new BlurTransformation(10)
}
}).width(100).height(100)
Text("pixelMap加载图片")
Text($r('app.string.PixelMap_loads_images'))
.fontSize(30)
.fontWeight(FontWeight.Bold)
ImageKnifeComponent({

View File

@ -111,12 +111,12 @@ struct ImageTestPage {
})
}
Row(){
Text("点击尺寸加50")
Text($r('app.string.Click_on_add'))
.onClick(()=> {
this.imageSize = this.imageSize + 50
})
.width('50%').backgroundColor(0x88ff0000).textAlign(TextAlign.Center).height(50)
Text("点击尺寸减50")
Text($r('app.string.Click_on_reduce'))
.onClick(()=> {
this.imageSize = Math.max(this.imageSize - 50, 0)
})

View File

@ -28,11 +28,11 @@ struct TestIsUrlExist {
build() {
Column() {
Flex() {
Button("预加载gif图").onClick(() => {
Button($r('app.string.Preloading_GIF')).onClick(() => {
this.imageKnifeOption.loadSrc =
"https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658"
})
Button("内存缓存获取gif").onClick(() => {
Button($r('app.string.Retrieve_GIF_from_memory')).onClick(() => {
ImageKnife.getInstance()
.getCacheImage("https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658",
CacheStrategy.Memory)
@ -40,7 +40,7 @@ struct TestIsUrlExist {
this.source = data !== undefined ? data.source : $r("app.media.startIcon")
})
})
Button("文件缓存获取gif").onClick(() => {
Button($r('app.string.Retrieve_GIF_from_disk')).onClick(() => {
ImageKnife.getInstance()
.getCacheImage("https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658",
CacheStrategy.File)
@ -51,11 +51,11 @@ struct TestIsUrlExist {
}
Flex() {
Button("预加载静态图").onClick(() => {
Button($r('app.string.Preloading_static_images')).onClick(() => {
this.imageKnifeOption.loadSrc =
'https://hbimg.huabanimg.com/95a6d37a39aa0b70d48fa18dc7df8309e2e0e8e85571e-x4hhks_fw658/format/webp'
})
Button("内存缓存获取").onClick(() => {
Button($r('app.string.Retrieve_images_from_memory')).onClick(() => {
ImageKnife.getInstance()
.getCacheImage('https://hbimg.huabanimg.com/95a6d37a39aa0b70d48fa18dc7df8309e2e0e8e85571e-x4hhks_fw658/format/webp',
CacheStrategy.Memory)
@ -63,7 +63,7 @@ struct TestIsUrlExist {
this.source = data!.source
})
})
Button("文件缓存获取").onClick(() => {
Button($r('app.string.Retrieve_images_from_disk')).onClick(() => {
ImageKnife.getInstance()
.getCacheImage('https://hbimg.huabanimg.com/95a6d37a39aa0b70d48fa18dc7df8309e2e0e8e85571e-x4hhks_fw658/format/webp',
CacheStrategy.File)

View File

@ -32,13 +32,13 @@ struct TestPrefetchToFileCachePage {
}
build() {
Column() {
Button("url预加载图片到文件缓存").margin({top:10}).onClick(async ()=>{
Button($r('app.string.Preloading_images_to_file_cache_using_URL')).margin({top:10}).onClick(async ()=>{
await this.preload("https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658")
})
Button("option预加载图片到文件缓存").margin({top:10}).onClick(async ()=>{
Button($r('app.string.Preloading_images_to_file_cache_using_option')).margin({top:10}).onClick(async ()=>{
await this.preload1("https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658")
})
Button("加载图片(预加载后可断网加载)").margin({top:10}).onClick(()=>{
Button($r('app.string.Load_image_offline_after_preloading')).margin({top:10}).onClick(()=>{
this.imageKnifeOption.loadSrc = "https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658"
})
ImageKnifeComponent({

View File

@ -29,12 +29,12 @@ struct TestRemoveCache {
build() {
Column() {
Flex() {
Button("预加载gif图").onClick(() => {
Button($r('app.string.Preloading_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(() => {
Button($r('app.string.Retrieve_GIF_from_memory')).onClick(() => {
ImageKnife.getInstance()
.getCacheImage("https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658",
CacheStrategy.Memory)
@ -43,7 +43,7 @@ struct TestRemoveCache {
})
})
.margin({left:10})
Button("文件缓存获取gif").onClick(() => {
Button($r('app.string.Retrieve_GIF_from_disk')).onClick(() => {
ImageKnife.getInstance()
.getCacheImage("https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658",
CacheStrategy.File)
@ -55,12 +55,12 @@ struct TestRemoveCache {
}
Flex() {
Button("预加载静态图").onClick(() => {
Button($r('app.string.Preloading_static_images')).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(() => {
Button($r('app.string.Retrieve_images_from_memory')).onClick(() => {
ImageKnife.getInstance()
.getCacheImage('https://hbimg.huabanimg.com/95a6d37a39aa0b70d48fa18dc7df8309e2e0e8e85571e-x4hhks_fw658/format/webp',
CacheStrategy.Memory)
@ -69,7 +69,7 @@ struct TestRemoveCache {
})
})
.margin({left:10})
Button("文件缓存获取").onClick(() => {
Button($r('app.string.Retrieve_images_from_disk')).onClick(() => {
ImageKnife.getInstance()
.getCacheImage('https://hbimg.huabanimg.com/95a6d37a39aa0b70d48fa18dc7df8309e2e0e8e85571e-x4hhks_fw658/format/webp',
CacheStrategy.File)
@ -81,19 +81,19 @@ struct TestRemoveCache {
}.margin({ top: 10 })
Flex() {
Button("删除全部缓存").onClick(() => {
Button($r('app.string.Delete_all_caches')).onClick(() => {
ImageKnife.getInstance()
.removeAllMemoryCache()
ImageKnife.getInstance()
.removeAllFileCache()
})
.margin({left:5})
Button("删除全部内存缓存").onClick(() => {
Button($r('app.string.Delete_all_memory_caches')).onClick(() => {
ImageKnife.getInstance()
.removeAllMemoryCache()
})
.margin({left:5})
Button("删除全部文件缓存").onClick(() => {
Button($r('app.string.Delete_all_file_caches')).onClick(() => {
ImageKnife.getInstance()
.removeAllFileCache()
})
@ -101,12 +101,12 @@ struct TestRemoveCache {
}.margin({ top: 10 })
Flex() {
Button("删除自定义内存缓存").onClick(() => {
Button($r('app.string.Delete_all_custom_memory_caches')).onClick(() => {
ImageKnife.getInstance()
.removeMemoryCache(this.url)
})
.margin({left:20})
Button("删除自定义文件缓存").onClick(() => {
Button($r('app.string.Delete_all_custom_file_caches')).onClick(() => {
ImageKnife.getInstance()
.removeFileCache(this.url)
})

View File

@ -0,0 +1,79 @@
/*
* 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"
@ComponentV2
export struct ZuImage {
@Param @Require src: PixelMap | ResourceStr | string | undefined
@Param @Require placeholderSrc: PixelMap | ResourceStr | string | undefined
@Local errorholderSrc: PixelMap | ResourceStr | string | undefined
build() {
if (this.src) {
//当前版本存在bug
ImageKnifeComponent({
imageKnifeOption: {
loadSrc: this.src,
placeholderSrc: this.placeholderSrc,
errorholderSrc: this.errorholderSrc ?? this.placeholderSrc,
objectFit: ImageFit.Cover
}
})
} else {
Image(this.placeholderSrc)
.objectFit(ImageFit.Cover)
}
}
}
@Entry
@ComponentV2
struct TestTaskResourcePage {
@Local stateMenus: Array<string> = [
"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',
]
@Builder
_buildItem(item: string) {
Column({ space: 8 }) {
ZuImage({
src: item,
placeholderSrc: $r("app.media.loading")
}).width(44)
.height(44)
}
}
build() {
Grid() {
ForEach(this.stateMenus, (item: string) => {
GridItem() {
this._buildItem(item)
}
})
}.width("100%")
.columnsTemplate('1fr 1fr 1fr 1fr 1fr')
.rowsGap(24)
.padding({
top: 24,
bottom: 24,
left: 24,
right: 24
})
}
}

View File

@ -34,12 +34,12 @@ struct TransformPage {
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(()=>{
Button($r('app.string.Enlarge')).onClick(()=>{
this.custom_scale = this.custom_scale * 2
this.matrix1 = matrix4.identity().scale({ x: this.custom_scale, y: this.custom_scale })
})
Button("缩小").onClick(()=>{
Button($r('app.string.Reduce')).onClick(()=>{
this.custom_scale = this.custom_scale / 2
this.matrix1 = matrix4.identity().scale({ x: this.custom_scale, y: this.custom_scale })
})

View File

@ -32,9 +32,9 @@ struct DataShareUriLoadPage {
build() {
Scroll() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Text("获取媒体图库的uri用ImageKnife展示").fontSize(15)
Text($r('app.string.Retrieve_media_gallery')).fontSize(15)
Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Button("点击加载Uri并展示")
Button($r('app.string.Click_load_Uri'))
.onClick(async () => {
let photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;

View File

@ -19,6 +19,354 @@
{
"name": "app_permission_READ_IMAGEVIDEO",
"value": "获取读媒体资源权限"
},
{
"name": "Test_ImageAnimator",
"value": "Test ImageAnimator component"
},
{
"name": "Test_multiple_images",
"value": "Test loading multiple identical images"
},
{
"name": "Test_Task_error",
"value": "Test placeholder map Task error"
},
{
"name": "Test_HSP",
"value": "Test HSP scene preloading"
},
{
"name": "Test_SingleImage",
"value": "SingleImage"
},
{
"name": "Test_custom_download",
"value": "Global custom download"
},
{
"name": "Multiple_images",
"value": "Multiple images"
},
{
"name": "Display_long_image",
"value": "Display long image"
},
{
"name": "Image_scaling",
"value": "Image scaling"
},
{
"name": "Message_list",
"value": "Message list"
},
{
"name": "Custom_cache_key",
"value": "Custom cache key"
},
{
"name": "Preloading_images_to_cache",
"value": "Preloading images to file cache"
},
{
"name": "Retrieve_image_display_from_cache",
"value": "Retrieve image display from cache"
},
{
"name": "Test_single_request_header",
"value": "Test a single request header"
},
{
"name": "Test_write_cache_strategy",
"value": "Test write cache strategy"
},
{
"name": "Image_Transformation",
"value": "Image Transformation"
},
{
"name": "Different_ObjectFit",
"value": "Different ObjectFit"
},
{
"name": "Test_image_loading_success_or_failure_events",
"value": "Test image loading success/failure events"
},
{
"name": "Test_removing_image_cache_interface",
"value": "Test removing image cache interface"
},
{
"name": "Test_error_image_display",
"value": "Test error image display"
},
{
"name": "Test_media_URL",
"value": "Test media URL"
},
{
"name": "Display_the_first_frame",
"value": "Display the first frame of the animation"
},
{
"name": "Display_the_last_frame",
"value": "Display the last frame of the animation"
},
{
"name": "Play",
"value": "Play"
},
{
"name": "Pause",
"value": "Pause"
},
{
"name": "Stop",
"value": "Stop"
},
{
"name": "Infinite_loop",
"value": "Infinite loop"
},
{
"name": "Play_once",
"value": "Play once"
},
{
"name": "Play_twice",
"value": "Play twice"
},
{
"name": "Local_SVG",
"value": "Local SVG image"
},
{
"name": "Under_context_file",
"value": "Files under context file"
},
{
"name": "Network_images",
"value": "Network images"
},
{
"name": "Custom_network_download",
"value": "Custom network download"
},
{
"name": "PixelMap_loads_images",
"value": "PixelMap loads images"
},
{
"name": "Enlarge",
"value": "Enlarge"
},
{
"name": "Reduce",
"value": "Reduce"
},
{
"name": "Click_on_add",
"value": "Click on the size to add 50"
},
{
"name": "Click_on_reduce",
"value": "Click to reduce size by 50"
},
{
"name": "The_key_fixed_1",
"value": "The key is fixed at 1"
},
{
"name": "The_key_changes_timestamp",
"value": "Key changes every time: timestamp"
},
{
"name": "Load",
"value": "Load"
},
{
"name": "Preloading_images_to_file_cache_using_URL",
"value": "Preloading images to file cache using URL"
},
{
"name": "Preloading_images_to_file_cache_using_option",
"value": "Preloading images to file cache using option"
},
{
"name": "Load_image_offline_after_preloading",
"value": "Load image (can be loaded offline after preloading)"
},
{
"name": "Preloading_GIF",
"value": "Preloading GIF"
},
{
"name": "Retrieve_GIF_from_memory",
"value": "Retrieve GIF from memory cache"
},
{
"name": "Retrieve_GIF_from_disk",
"value": "Retrieve GIF from disk cache"
},
{
"name": "Preloading_static_images",
"value": "Preloading static images"
},
{
"name": "Retrieve_images_from_memory",
"value": "Retrieve images from memory cache"
},
{
"name": "Retrieve_images_from_disk",
"value": "Retrieve images from memory disk"
},
{
"name": "Write_memory_and_file",
"value": "Write to memory and file cache"
},
{
"name": "Write_memory",
"value": "Write to memory cache"
},
{
"name": "Write_file",
"value": "Write to file cache"
},
{
"name": "Main_image_Fill",
"value": "Main image Fill Stretch Fill"
},
{
"name": "Maintain_proportion_filling",
"value": "Maintain proportion filling in the placeholder map 'Include'"
},
{
"name": "Error_graph_None",
"value": "Error graph None remains unchanged"
},
{
"name": "Test_failure_success",
"value": "Test failure/success"
},
{
"name": "Custom_download_failed",
"value": "Custom download failed"
},
{
"name": "Retrieve_media_gallery",
"value": "Retrieve the URI of the media gallery and display it using ImageKnife"
},
{
"name": "Click_load_Uri",
"value": "Click to load Uri and display"
},
{
"name": "Delete_all_caches",
"value": "Delete all caches"
},
{
"name": "Delete_all_memory_caches",
"value": "Delete all memory caches"
},
{
"name": "Delete_all_file_caches",
"value": "Delete all file caches"
},
{
"name": "Delete_all_custom_memory_caches",
"value": "Delete all custom memory caches"
},
{
"name": "Delete_all_custom_file_caches",
"value": "Delete all custom file caches"
},
{
"name": "Blur_effect",
"value": "Blur effect"
},
{
"name": "Highlighting_effect",
"value": "Highlighting effect"
},
{
"name": "Ashing_effect",
"value": "Ashing effect"
},
{
"name": "Inverse_effect",
"value": "Inverse effect"
},
{
"name": "Animation_filter_effect",
"value": "Animation filter effect"
},
{
"name": "Crop_circular_effect",
"value": "Crop circular effect"
},
{
"name": "Crop_circular_with_border_effect",
"value": "Crop circular with border effect"
},
{
"name": "Contrast_effect",
"value": "Contrast effect"
},
{
"name": "Black_ink_filtering_effect",
"value": "Black ink filtering effect"
},
{
"name": "Rotate",
"value": "Rotate"
},
{
"name": "Corners",
"value": "Corners"
},
{
"name": "Kuwahara_Filter_effect",
"value": "Kuwahara filter effect"
},
{
"name": "Pixelated_Filter_effect",
"value": "Pixelated filtering effect"
},
{
"name": "Sketch_Filter_effect",
"value": "Sketch Filter effect"
},
{
"name": "Distortion_Filter_effect",
"value": "Distortion Filter effect"
},
{
"name": "Decorative_Filter_effect",
"value": "Decorative Filter effect"
},
{
"name": "Square_cutting_effect",
"value": "Square cutting effect"
},
{
"name": "Top_cutting_effect",
"value": "Top cutting effect"
},
{
"name": "Middle_cutting_effect",
"value": "Middle cutting effect"
},
{
"name": "Bottom_cutting_effect",
"value": "Bottom cutting effect"
},
{
"name": "Mask_effect",
"value": "Mask effect"
},
{
"name": "TIPS",
"value": "Please shut down the network first and ensure that there is no cache of images from this network in the test failure scenario locally"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 922 KiB

Binary file not shown.

View File

@ -22,6 +22,7 @@
"pages/TestCommonImage",
"pages/ImageAnimatorPage",
"pages/TestSetCustomImagePage",
"pages/TestErrorHolderPage"
"pages/TestErrorHolderPage",
"pages/TestTaskResourcePage"
]
}

View File

@ -19,6 +19,350 @@
{
"name": "app_permission_READ_IMAGEVIDEO",
"value": "获取读媒体资源权限"
},
{
"name": "Test_ImageAnimator",
"value": "测试ImageAnimator组件"
},
{
"name": "Test_multiple_images",
"value": "测试加载多张相同图片"
},
{
"name": "Test_Task_error",
"value": "测试占位图Task报错"
},
{
"name": "Test_HSP",
"value": "测试HSP场景预加载"
},
{
"name": "Test_SingleImage",
"value": "单个图片使用"
},
{
"name": "Test_custom_download",
"value": "全局自定义下载"
},
{
"name": "Multiple_images",
"value": "多图"
},
{
"name": "Display_long_image",
"value": "长图显示"
},
{
"name": "Image_scaling",
"value": "缩放图片"
},
{
"name": "Message_list",
"value": "消息列表"
},
{
"name": "Custom_cache_key",
"value": "自定义缓存key"
},
{
"name": "Preloading_images_to_cache",
"value": "预加载图片到文件缓存"
},
{
"name": "Retrieve_image_display_from_cache",
"value": "从缓存获取图片显示"
},
{
"name": "Test_single_request_header",
"value": "测试单个请求头"
},
{
"name": "Test_write_cache_strategy",
"value": "测试写入缓存策略"
},
{
"name": "Image_Transformation",
"value": "图片变换"
},
{
"name": "Different_ObjectFit",
"value": "不同的ObjectFit"
},
{
"name": "Test_image_loading_success_or_failure_events",
"value": "测试图片加载成功/失败事件"
},
{
"name": "Test_removing_image_cache_interface",
"value": "测试移除图片缓存接口"
},
{
"name": "Test_error_image_display",
"value": "测试错误图显示"
},
{
"name": "Display_the_first_frame",
"value": "动画显示第一帧"
},
{
"name": "Display_the_last_frame",
"value": "动画显示最后一帧"
},
{
"name": "Play",
"value": "播放"
},
{
"name": "Pause",
"value": "暂停"
},
{
"name": "Stop",
"value": "停止"
},
{
"name": "Infinite_loop",
"value": "无限循环"
},
{
"name": "Play_once",
"value": "播放一次"
},
{
"name": "Play_twice",
"value": "播放两次"
},
{
"name": "Local_SVG",
"value": "本地资源SVG图片"
},
{
"name": "Under_context_file",
"value": "本地context files下文件"
},
{
"name": "Network_images",
"value": "网络图片"
},
{
"name": "Custom_network_download",
"value": "自定义下载"
},
{
"name": "PixelMap_loads_images",
"value": "PixelMap加载图片"
},
{
"name": "Enlarge",
"value": "放大"
},
{
"name": "Reduce",
"value": "缩小"
},
{
"name": "Click_on_add",
"value": "点击尺寸加50"
},
{
"name": "Click_on_reduce",
"value": "点击尺寸减50"
},
{
"name": "The_key_fixed_1",
"value": "key固定为 1"
},
{
"name": "The_key_changes_timestamp",
"value": "key每次变化时间戳"
},
{
"name": "Load",
"value": "加载"
},
{
"name": "Preloading_images_to_file_cache_using_URL",
"value": "url预加载图片到文件缓存"
},
{
"name": "Preloading_images_to_file_cache_using_option",
"value": "option预加载图片到文件缓存"
},
{
"name": "Load_image_offline_after_preloading",
"value": "加载图片(预加载后可断网加载)"
},
{
"name": "Preloading_GIF",
"value": "预加载gif图"
},
{
"name": "Retrieve_GIF_from_memory",
"value": "内存缓存获取gif"
},
{
"name": "Retrieve_GIF_from_disk",
"value": "文件缓存获取gif"
},
{
"name": "Preloading_static_images",
"value": "预加载静态图"
},
{
"name": "Retrieve_images_from_memory",
"value": "内存缓存获取"
},
{
"name": "Retrieve_images_from_disk",
"value": "文件缓存获取"
},
{
"name": "Write_memory_and_file",
"value": "写入内存文件缓存"
},
{
"name": "Write_memory",
"value": "写入内存缓存"
},
{
"name": "Write_file",
"value": "写入文件缓存"
},
{
"name": "Main_image_Fill",
"value": "主图Fill拉伸填充"
},
{
"name": "Maintain_proportion_filling",
"value": "占位图Contain保持比例填充"
},
{
"name": "Error_graph_None",
"value": "错误图None不变化"
},
{
"name": "Test_failure_success",
"value": "测试失败/成功场景"
},
{
"name": "Custom_download_failed",
"value": "自定义下载失败"
},
{
"name": "Retrieve_media_gallery",
"value": "获取媒体图库的uri用ImageKnife展示"
},
{
"name": "Click_load_Uri",
"value": "点击加载Uri并展示"
},
{
"name": "Delete_all_caches",
"value": "删除全部缓存"
},
{
"name": "Delete_all_memory_caches",
"value": "删除全部内存缓存"
},
{
"name": "Delete_all_file_caches",
"value": "删除全部文件缓存"
},
{
"name": "Delete_all_custom_memory_caches",
"value": "删除自定义内存缓存"
},
{
"name": "Delete_all_custom_file_caches",
"value": "删除自定义文件缓存"
},
{
"name": "Blur_effect",
"value": "模糊效果"
},
{
"name": "Highlighting_effect",
"value": "高亮效果"
},
{
"name": "Ashing_effect",
"value": "灰化效果"
},
{
"name": "Inverse_effect",
"value": "反转效果"
},
{
"name": "Animation_filter_effect",
"value": "动画滤镜效果"
},
{
"name": "Crop_circular_effect",
"value": "裁剪圆形效果"
},
{
"name": "Crop_circular_with_border_effect",
"value": "裁剪圆形带边框效果"
},
{
"name": "Contrast_effect",
"value": "对比度效果"
},
{
"name": "Black_ink_filtering_effect",
"value": "乌墨色滤波效果"
},
{
"name": "Rotate",
"value": "旋转效果"
},
{
"name": "Corners",
"value": "圆角效果"
},
{
"name": "Kuwahara_Filter_effect",
"value": "桑原滤波效果"
},
{
"name": "Pixelated_Filter_effect",
"value": "像素化滤波效果"
},
{
"name": "Sketch_Filter_effect",
"value": "素描滤波效果"
},
{
"name": "Distortion_Filter_effect",
"value": "扭曲滤波效果"
},
{
"name": "Decorative_Filter_effect",
"value": "装饰滤波效果"
},
{
"name": "Square_cutting_effect",
"value": "正方形裁剪效果"
},
{
"name": "Top_cutting_effect",
"value": "上方裁剪效果"
},
{
"name": "Middle_cutting_effect",
"value": "中间裁剪效果"
},
{
"name": "Bottom_cutting_effect",
"value": "底下裁剪效果"
},
{
"name": "Mask_effect",
"value": "遮罩效果"
},
{
"name": "TIPS",
"value": "测试失败场景请先关闭网络,并保证本地没有此网络图片的缓存"
}
]
}

View File

@ -18,9 +18,9 @@ export { ImageKnifeAnimatorComponent } from './src/main/ets/components/ImageKnif
export { ImageKnife } from './src/main/ets/ImageKnife'
export { ImageKnifeOption , AnimatorOption } from './src/main/ets/ImageKnifeOption'
export { ImageKnifeOption , AnimatorOption } from './src/main/ets/model/ImageKnifeOption'
export { ImageKnifeRequest } from './src/main/ets/ImageKnifeRequest'
export { ImageKnifeRequest } from './src/main/ets/model/ImageKnifeRequest'
export { FileUtils } from './src/main/ets/utils/FileUtils'

View File

@ -14,7 +14,7 @@
"main": "index.ets",
"repository": "https://gitee.com/openharmony-tpc/ImageKnife",
"type": "module",
"version": "3.0.2-rc.1",
"version": "3.0.2",
"dependencies": {
"@ohos/gpu_transform": "^1.0.2"
},

View File

@ -12,14 +12,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ImageKnifeRequest } from './ImageKnifeRequest';
import { ImageKnifeRequest } from './model/ImageKnifeRequest';
import { CacheStrategy, ImageKnifeData, ImageKnifeRequestSource } from './model/ImageKnifeData';
import { MemoryLruCache } from './utils/MemoryLruCache';
import { IMemoryCache } from './utils/IMemoryCache'
import { FileCache } from './utils/FileCache';
import { MemoryLruCache } from './cache/MemoryLruCache';
import { IMemoryCache } from './cache/IMemoryCache'
import { FileCache } from './cache/FileCache';
import { ImageKnifeDispatcher } from './ImageKnifeDispatcher';
import { IEngineKey } from './key/IEngineKey';
import { HeaderOptions, ImageKnifeOption } from './ImageKnifeOption';
import { HeaderOptions, ImageKnifeOption } from './model/ImageKnifeOption';
import { FileTypeUtil } from './utils/FileTypeUtil';
import { util } from '@kit.ArkTS';
import { image } from '@kit.ImageKit';

View File

@ -12,24 +12,19 @@
* 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 { ImageKnifeRequest, ImageKnifeRequestState } from './model/ImageKnifeRequest'
import { DefaultJobQueue } from './queue/DefaultJobQueue'
import { IJobQueue } from './queue/IJobQueue'
import List from '@ohos.util.List';
import LightWeightMap from '@ohos.util.LightWeightMap';
import { LogUtil } from './utils/LogUtil';
import buffer from '@ohos.buffer';
import { FileCache } from './utils/FileCache';
import fs from '@ohos.file.fs';
import { ImageKnife } from './ImageKnife';
import { ImageKnifeData, CacheStrategy } from './model/ImageKnifeData';
import http from '@ohos.net.http';
import image from '@ohos.multimedia.image';
import emitter from '@ohos.events.emitter';
import { Constants } from './utils/Constants';
import taskpool from '@ohos.taskpool';
import { FileTypeUtil } from './utils/FileTypeUtil';
import util from '@ohos.util';
import { IEngineKey } from './key/IEngineKey';
import { DefaultEngineKey } from './key/DefaultEngineKey';
import {
@ -38,8 +33,9 @@ import {
RequestJobResult,
RequestJobRequest
} from './model/ImageKnifeData'
import { combineArrayBuffers } from './model/utils';
import { BusinessError } from '@kit.BasicServicesKit';
import { ImageKnifeLoader } from './ImageKnifeLoader'
export class ImageKnifeDispatcher {
// 最大并发
@ -52,7 +48,7 @@ export class ImageKnifeDispatcher {
private engineKey: IEngineKey = new DefaultEngineKey();
showFromMemomry(request: ImageKnifeRequest, imageSrc: string | PixelMap | Resource, requestSource: ImageKnifeRequestSource,isAnimator?: boolean): boolean {
LogUtil.log("ImageKnife_DataTime_showFromMemomry.start:" + request.imageKnifeOption.loadSrc)
LogUtil.log("ImageKnife_DataTime_showFromMemomry.start:" + request.imageKnifeOption.loadSrc + "requestSource=" + requestSource + " isAnimator=" + isAnimator)
let memoryCache: ImageKnifeData | undefined;
if ((typeof (request.imageKnifeOption.loadSrc as image.PixelMap).isEditable) == 'boolean') {
memoryCache = {
@ -65,13 +61,12 @@ export class ImageKnifeDispatcher {
.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()
request.imageKnifeOption.onLoadListener.onLoadStart()
LogUtil.log("ImageKnife_DataTime_MemoryCache_onLoadStart:" + request.imageKnifeOption.loadSrc)
}
LogUtil.log("ImageKnife_DataTime_MemoryCache_showPixelMap.start:" + request.imageKnifeOption.loadSrc)
@ -82,23 +77,22 @@ export class ImageKnifeDispatcher {
request.requestState = ImageKnifeRequestState.COMPLETE
// 回调请求开结束
if (request.imageKnifeOption.onLoadListener?.onLoadSuccess !== undefined) {
request.imageKnifeOption.onLoadListener?.onLoadSuccess(memoryCache.source,memoryCache)
request.imageKnifeOption.onLoadListener.onLoadSuccess(memoryCache.source,memoryCache)
LogUtil.log("ImageKnife_DataTime_MemoryCache_onLoadSuccess:" + request.imageKnifeOption.loadSrc)
}
} else if (requestSource == ImageKnifeRequestSource.ERROR_HOLDER) {
request.requestState = ImageKnifeRequestState.ERROR
}
}
LogUtil.log("ImageKnife_DataTime_showFromMemomry.end_true:" + request.imageKnifeOption.loadSrc)
LogUtil.log("ImageKnife_DataTime_showFromMemomry.end_hasmemory:" + request.imageKnifeOption.loadSrc)
return true
}
LogUtil.log("ImageKnife_DataTime_showFromMemomry.end_false:" + request.imageKnifeOption.loadSrc)
LogUtil.log("ImageKnife_DataTime_showFromMemomry.end_nomemory:" + request.imageKnifeOption.loadSrc)
return false
}
enqueue(request: ImageKnifeRequest,isAnimator?: boolean): void {
//1.内存有的话直接渲染
if (this.showFromMemomry(request, request.imageKnifeOption.loadSrc, ImageKnifeRequestSource.SRC,isAnimator)) {
return
@ -157,10 +151,21 @@ export class ImageKnifeDispatcher {
isWatchProgress = true
}
});
let src: string | number = ""
let moduleName: string = ""
let resName: string = ""
if((imageSrc as Resource).id != undefined) {
moduleName = (imageSrc as Resource).moduleName
src = (imageSrc as Resource).id
if(src == -1) {
resName = (imageSrc as Resource).params![0]
}
} else if(typeof imageSrc == "string") {
src = imageSrc
}
let request: RequestJobRequest = {
context: currentRequest.context,
src: imageSrc,
src: src,
headers: currentRequest.imageKnifeOption.headerOption,
allHeaders: currentRequest.headers,
componentWidth:currentRequest.componentWidth,
@ -176,7 +181,9 @@ export class ImageKnifeDispatcher {
isWatchProgress: isWatchProgress,
memoryKey: memoryKey,
fileCacheFolder: ImageKnife.getInstance().getFileCache()?.getCacheFolder(),
isAnimator:isAnimator
isAnimator:isAnimator,
moduleName: moduleName == "" ? undefined : moduleName,
resName: resName == "" ? undefined : resName
}
if(request.customGetImage == undefined) {
@ -191,9 +198,9 @@ export class ImageKnifeDispatcher {
emitter.on(Constants.PROGRESS_EMITTER + memoryKey, (data) => {
this.progressCallBack(requestList! , data?.data?.value as number)
});
}
}
LogUtil.log("ImageKnife_DataTime_getAndShowImage_execute.start:" + currentRequest.imageKnifeOption.loadSrc)
LogUtil.log("ImageKnife_DataTime_getAndShowImage_execute.start(subthread):" + currentRequest.imageKnifeOption.loadSrc)
taskpool.execute(task).then((res: Object) => {
this.doTaskCallback(res as RequestJobResult | undefined, requestList!, currentRequest, memoryKey, imageSrc, requestSource,isAnimator);
if (isWatchProgress){
@ -201,8 +208,9 @@ export class ImageKnifeDispatcher {
}
LogUtil.log("ImageKnife_DataTime_getAndShowImage_execute.end:"+currentRequest.imageKnifeOption.loadSrc)
LogUtil.log("ImageKnife_DataTime_getAndShowImage.end:"+currentRequest.imageKnifeOption.loadSrc)
}).catch((err:BusinessError)=>{
LogUtil.error("Fail to execute in sub thread src=" + imageSrc + " err=" + err)
}).catch((err: BusinessError) => {
LogUtil.error("Fail to requestJob in sub thread src=" + imageSrc + " err=" + err)
LogUtil.log("ImageKnife_DataTime_getAndShowImage.end:" + currentRequest.imageKnifeOption.loadSrc)
if (isWatchProgress){
emitter.off(Constants.PROGRESS_EMITTER + memoryKey)
}
@ -210,12 +218,14 @@ export class ImageKnifeDispatcher {
this.dispatchNextJob();
})
} else { //主线程请求
LogUtil.log("ImageKnife_DataTime_getAndShowImage_execute.start(mainthread):" + currentRequest.imageKnifeOption.loadSrc)
requestJob(request, requestList).then((res: RequestJobResult | undefined) => {
this.doTaskCallback(res, requestList!, currentRequest, memoryKey, imageSrc, requestSource,isAnimator);
LogUtil.log("ImageKnife_DataTime_getAndShowImage_execute.end:"+currentRequest.imageKnifeOption.loadSrc)
LogUtil.log("ImageKnife_DataTime_getAndShowImage.end:"+currentRequest.imageKnifeOption.loadSrc)
}).catch((err:BusinessError)=>{
LogUtil.error("Fail to execute in main thread src=" + imageSrc + " err=" + err)
}).catch((err: BusinessError) => {
LogUtil.error("Fail to requestJob in main thread src=" + imageSrc + " err=" + err)
LogUtil.log("ImageKnife_DataTime_getAndShowImage.end:" + currentRequest.imageKnifeOption.loadSrc)
this.executingJobMap.remove(memoryKey);
this.dispatchNextJob();
})
@ -244,6 +254,7 @@ export class ImageKnifeDispatcher {
}
let pixelmap = requestJobResult.pixelMap;
if (pixelmap === undefined) {
LogUtil.log("ImageKnife_DataTime_getAndShowImage_CallBack.pixelmap undefined:"+currentRequest.imageKnifeOption.loadSrc)
requestList.forEach((requestWithSource: ImageKnifeRequestWithSource) => {
// 回调请求失败
if (requestWithSource.source === ImageKnifeRequestSource.SRC &&
@ -263,6 +274,7 @@ export class ImageKnifeDispatcher {
}
});
this.executingJobMap.remove(memoryKey);
this.dispatchNextJob();
return;
}
// 保存文件缓存
@ -297,8 +309,6 @@ export class ImageKnifeDispatcher {
LogUtil.log("ImageKnife_DataTime_getAndShowImage_saveMemoryCache.end:"+currentRequest.imageKnifeOption.loadSrc)
}
if (requestList !== undefined) {
// todo 判断request生命周期已销毁的不需要再绘制
// key相同的request一起绘制
requestList.forEach((requestWithSource: ImageKnifeRequestWithSource) => {
if (requestWithSource.request.requestState !== ImageKnifeRequestState.DESTROY) {
@ -326,8 +336,8 @@ export class ImageKnifeDispatcher {
}
} else {
if (requestWithSource.source == ImageKnifeRequestSource.SRC && requestWithSource.request.imageKnifeOption.onLoadListener?.onLoadCancel) {
// 回调请求成功
requestWithSource.request.imageKnifeOption.onLoadListener.onLoadCancel("component has destroyed")
// 回调请求成功
requestWithSource.request.imageKnifeOption.onLoadListener.onLoadCancel("component has destroyed")
}
}
});
@ -346,11 +356,13 @@ export class ImageKnifeDispatcher {
while (true) {
let request = this.jobQueue.pop()
if (request === undefined) {
LogUtil.log("ImageKnife_DataTime_dispatchNextJob.end:no any job")
break // 队列已无任务
}
else if (request.requestState === ImageKnifeRequestState.PROGRESS) {
LogUtil.log("ImageKnife_DataTime_dispatchNextJob.start executeJob:" + request.imageKnifeOption.loadSrc)
this.executeJob(request)
LogUtil.log("ImageKnife_DataTime_dispatchNextJob.end:" + request.imageKnifeOption.loadSrc)
LogUtil.log("ImageKnife_DataTime_dispatchNextJob.end executeJob:" + request.imageKnifeOption.loadSrc)
break
}else if (request.requestState == ImageKnifeRequestState.DESTROY && request.imageKnifeOption.onLoadListener?.onLoadCancel) {
request.imageKnifeOption.onLoadListener.onLoadCancel("component has destroyed")
@ -381,292 +393,44 @@ export class ImageKnifeDispatcher {
*/
@Concurrent
async function requestJob(request: RequestJobRequest, requestList?: List<ImageKnifeRequestWithSource>): Promise<RequestJobResult | undefined> {
LogUtil.log("ImageKnife_DataTime_requestJob.start:" + request.src)
let resBuf: ArrayBuffer | undefined
let bufferSize: number = 0
let loadError: string = '';
LogUtil.log("ImageKnife_DataTime_requestJob.start:" + request.src + " requestSource=" + request.requestSource)
let src = typeof request.src == "number" ? request.resName != undefined ? request.resName : request.src + "" : request.src
// 生成文件缓存key
let fileKey = request.engineKey.generateFileKey(src, request.signature, request.isAnimator)
class RequestData {
receiveSize: number = 2000
totalSize: number = 2000
//获取图片资源
let resBuf: ArrayBuffer
try {
LogUtil.log("ImageKnife_DataTime_requestJob.getImageArrayBuffer.start:" + request.src)
resBuf = await ImageKnifeLoader.getImageArrayBuffer(request, requestList, fileKey)
LogUtil.log("ImageKnife_DataTime_requestJob.getImageArrayBuffer.end:" + request.src)
} catch (error) {
LogUtil.error("ImageKnife_DataTime_requestJob.end: getImageArrayBuffer error " + request.src + " err=" + error)
return ImageKnifeLoader.makeEmptyResult(error)
}
// 生成文件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<ArrayBuffer>()
const headerObj: Record<string, object> = {}
if (request.headers != undefined) {
request.headers.forEach((value) => {
headerObj[value.key] = value.value
})
} else if (request.allHeaders.size > 0) {
request.allHeaders.forEach((value, key) => {
headerObj[key] = value
})
}
httpRequest.on("dataReceive", (data: ArrayBuffer) => {
arrayBuffers.push(data)
});
if (request.isWatchProgress) {
httpRequest.on('dataReceiveProgress', (data: RequestData) => {
// 下载进度
if (data != undefined && (typeof data.receiveSize == 'number') && (typeof data.totalSize == 'number')) {
let percent = Math.round(((data.receiveSize * 1.0) / (data.totalSize * 1.0)) * 100)
if (progress !== percent) {
progress = percent
if (requestList === undefined) {
// 子线程
emitter.emit(Constants.PROGRESS_EMITTER + request.memoryKey, { data: { "value": progress } })
}else {
// 主线程请求
requestList!.forEach((requestWithSource: ImageKnifeRequestWithSource) => {
if (requestWithSource.request.imageKnifeOption.progressListener !== undefined && requestWithSource.source === ImageKnifeRequestSource.SRC) {
requestWithSource.request.imageKnifeOption.progressListener(progress)
}
})
}
}
}
})
}
let promise = httpRequest.requestInStream(request.src, {
header: headerObj,
method: http.RequestMethod.GET,
expectDataType: http.HttpDataType.ARRAY_BUFFER,
connectTimeout: 6000,
readTimeout: 6000,
// usingProtocol:http.HttpProtocol.HTTP1_1
// header: new Header('application/json')
});
await promise.then((data: number) => {
if (data == 200 || data == 204 || data == 201 || data == 206) {
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.closeSync(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);
// 获取图片类型
let typeValue = new FileTypeUtil().getFileType(resBuf);
if(typeValue == null) {
return {
pixelMap: undefined,
bufferSize: 0,
fileKey: '',
loadFail: "request is not a valid image source",
}
LogUtil.log("ImageKnife_DataTime_requestJob.end: getFileType is null " + request.src)
return ImageKnifeLoader.makeEmptyResult("request is not a valid image source")
}
let imageSource: image.ImageSource = image.createImageSource(resBuf);
let decodingOptions: image.DecodingOptions = {
editable: true,
}
if(request.isAnimator) {
if (typeValue === 'gif' || typeValue === 'webp') {
let pixelMapList: Array<PixelMap> = []
let delayList: Array<number> = []
await imageSource.createPixelMapList(decodingOptions).then(async (pixelList: Array<PixelMap>) => {
//sdk的api接口发生变更从.getDelayTime() 变为.getDelayTimeList()
await imageSource.getDelayTimeList().then(delayTimes => {
if (pixelList.length > 0) {
for (let i = 0; i < pixelList.length; i++) {
pixelMapList.push(pixelList[i]);
if (i < delayTimes.length) {
delayList.push(delayTimes[i]);
} else {
delayList.push(delayTimes[delayTimes.length - 1])
}
}
imageSource.release();
}
})
})
return {
pixelMap: "",
bufferSize: bufferSize,
fileKey: fileKey,
type: typeValue,
pixelMapList,
delayList
}
} else {
return {
pixelMap: undefined,
bufferSize: 0,
fileKey: '',
loadFail: "ImageKnifeAnimatorComponent组件仅支持动态图",
}
}
}
let resPixelmap: PixelMap | undefined = undefined
if (typeValue === 'gif' || typeValue === 'webp') {
let frameCount = await imageSource.getFrameCount()
let size = (await imageSource.getImageInfo()).size
if (frameCount == undefined || frameCount == 1) {
} else {
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()
})
// 解析图片
LogUtil.log("ImageKnife_DataTime_requestJob.parseImage.start:" + request.src)
let result: RequestJobResult = await ImageKnifeLoader.parseImage(resBuf, typeValue, fileKey, request)
LogUtil.log("ImageKnife_DataTime_requestJob.parseImage.end:" + request.src)
// 图形变化
if (request.requestSource === ImageKnifeRequestSource.SRC && request.transformation !== undefined) {
resPixelmap = await request.transformation?.transform(request.context, resPixelmap!, request.componentWidth, request.componentHeight);
if (request.requestSource === ImageKnifeRequestSource.SRC && request.transformation !== undefined && result?.pixelMap !== undefined && typeof result.pixelMap !== 'string') {
LogUtil.log("ImageKnife_DataTime_requestJob.transform.start:" + request.src)
result.pixelMap = await request.transformation?.transform(request.context, result.pixelMap, request.componentWidth, request.componentHeight);
LogUtil.log("ImageKnife_DataTime_requestJob.transform.end:" + request.src)
}
LogUtil.log("ImageKnife_DataTime_requestJob_createPixelMap.end:"+request.src)
LogUtil.log("ImageKnife_DataTime_requestJob.end:"+request.src)
return {
pixelMap: resPixelmap,
bufferSize: bufferSize,
fileKey: fileKey,
size:size,
type:typeValue
};
LogUtil.log("ImageKnife_DataTime_requestJob.end:" + request.src)
return result
}

View File

@ -0,0 +1,357 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
CacheStrategy,
ImageKnifeRequestSource,
ImageKnifeRequestWithSource, RequestJobRequest } from './model/ImageKnifeData';
import List from '@ohos.util.List'
import { FileCache } from './cache/FileCache';
import { LogUtil } from './utils/LogUtil';
import { Constants } from './utils/Constants';
import http from '@ohos.net.http';
import { combineArrayBuffers } from './utils/ArrayBufferUtils';
import { BusinessError } from '@kit.BasicServicesKit';
import fs from '@ohos.file.fs';
import emitter from '@ohos.events.emitter';
import image from '@ohos.multimedia.image';
import { RequestJobResult } from './model/ImageKnifeData'
import util from '@ohos.util';
class RequestData {
receiveSize: number = 2000
totalSize: number = 2000
}
/**
* ImageKnifeDispatcher 抽取出来的方法,因@Concurrent只能import方法故抽取到另一个类
*/
export class ImageKnifeLoader {
static async parseImage(resBuf: ArrayBuffer, typeValue: string, fileKey: string,
request: RequestJobRequest): Promise<RequestJobResult> {
if(request.isAnimator) {
return ImageKnifeLoader.parseForAnimatorComponent(resBuf ,typeValue ,fileKey, request)
}
if (typeValue === 'gif' || typeValue === 'webp') {
return ImageKnifeLoader.parseAnimatorImage(resBuf ,typeValue ,fileKey , request)
} else if(typeValue == "svg") {
return ImageKnifeLoader.parseSvgImage(resBuf ,typeValue ,fileKey , request)
}
return ImageKnifeLoader.parseNormalImage(resBuf, typeValue, fileKey, request)
}
static makeEmptyResult(error: string): RequestJobResult{
return {
pixelMap: undefined,
bufferSize: 0,
fileKey: '',
loadFail: error,
}
}
static async parseNormalImage(resBuf: ArrayBuffer, typeValue: string, fileKey: string, request: RequestJobRequest):Promise<RequestJobResult> {
let resPixelmap: PixelMap | undefined = undefined
let decodingOptions: image.DecodingOptions = {
editable: request.requestSource === ImageKnifeRequestSource.SRC && request.transformation !== undefined ? true : false,
}
let imageSource: image.ImageSource = image.createImageSource(resBuf)
if (imageSource === undefined){
return ImageKnifeLoader.makeEmptyResult("image.createImageSource failed")
}
let size = (await imageSource.getImageInfo()).size
await imageSource.createPixelMap(decodingOptions)
.then((pixelmap: PixelMap) => {
resPixelmap = pixelmap
imageSource.release()
}).catch((error: BusinessError) => {
imageSource.release()
return ImageKnifeLoader.makeEmptyResult(JSON.stringify(error))
})
return {
pixelMap: resPixelmap,
bufferSize: resBuf.byteLength,
fileKey: fileKey,
size:size,
type:typeValue
};
}
static async parseSvgImage(resBuf: ArrayBuffer, typeValue: string, fileKey: string,
request: RequestJobRequest): Promise<RequestJobResult> {
let resPixelmap: PixelMap | undefined = undefined
let imageSource: image.ImageSource = image.createImageSource(resBuf)
if (imageSource === undefined){
return ImageKnifeLoader.makeEmptyResult("image.createImageSource failed")
}
let size = (await imageSource.getImageInfo()).size
let scale = size.height / size.width
let hValue = Math.round(request.componentHeight);
let wValue = Math.round(request.componentWidth);
let defaultSize: image.Size = {
height: vp2px(wValue) * scale,
width: vp2px(wValue)
};
let opts: image.DecodingOptions = {
editable: true,
desiredSize: defaultSize
};
await imageSource.createPixelMap(opts)
.then((pixelmap: PixelMap) => {
resPixelmap = pixelmap
imageSource.release()
}).catch((error: BusinessError) => {
imageSource.release()
return ImageKnifeLoader.makeEmptyResult(JSON.stringify(error))
})
return {
pixelMap: resPixelmap,
bufferSize: resBuf.byteLength,
fileKey: fileKey,
type:typeValue
};
}
static async parseAnimatorImage(resBuf: ArrayBuffer, typeValue: string,
fileKey: string,request: RequestJobRequest): Promise<RequestJobResult> {
let imageSource: image.ImageSource = image.createImageSource(resBuf)
if (imageSource === undefined){
return ImageKnifeLoader.makeEmptyResult("image.createImageSource failed")
}
let frameCount = await imageSource.getFrameCount()
let size = (await imageSource.getImageInfo()).size
imageSource.release()
if(frameCount == undefined || frameCount == 1) {
} else {
let base64str = "data:image/" + typeValue + ";base64," + new util.Base64Helper().encodeToStringSync(new Uint8Array(resBuf))
return {
pixelMap: base64str,
bufferSize: resBuf.byteLength,
fileKey: fileKey,
size:size,
type:typeValue
};
}
return ImageKnifeLoader.parseNormalImage(resBuf, typeValue, fileKey, request)
}
// 为AnimatorComponent解析动图
static async parseForAnimatorComponent(resBuf: ArrayBuffer, typeValue: string, fileKey: string,request: RequestJobRequest): Promise<RequestJobResult> {
if (typeValue === 'gif' || typeValue === 'webp') {
let imageSource: image.ImageSource = image.createImageSource(resBuf);
if (imageSource === undefined){
return ImageKnifeLoader.makeEmptyResult("image.createImageSource failed")
}
let decodingOptions: image.DecodingOptions = {
editable: request.requestSource === ImageKnifeRequestSource.SRC && request.transformation !== undefined ? true : false,
}
let pixelMapList: Array<PixelMap> = []
let delayList: Array<number> = []
await imageSource.createPixelMapList(decodingOptions).then(async (pixelList: Array<PixelMap>) => {
//sdk的api接口发生变更从.getDelayTime() 变为.getDelayTimeList()
await imageSource.getDelayTimeList().then(delayTimes => {
if (pixelList.length > 0) {
for (let i = 0; i < pixelList.length; i++) {
pixelMapList.push(pixelList[i]);
if (i < delayTimes.length) {
delayList.push(delayTimes[i]);
} else {
delayList.push(delayTimes[delayTimes.length - 1])
}
}
imageSource.release();
}
})
}).catch((error: BusinessError) => {
imageSource.release()
return ImageKnifeLoader.makeEmptyResult(JSON.stringify(error))
})
return {
pixelMap: "",
bufferSize: resBuf.byteLength,
fileKey: fileKey,
type: typeValue,
pixelMapList,
delayList
}
} else {
return ImageKnifeLoader.makeEmptyResult("ImageKnifeAnimatorComponent组件仅支持动态图")
}
}
// 获取图片资源
static async getImageArrayBuffer(request: RequestJobRequest, requestList: List<ImageKnifeRequestWithSource> | undefined,fileKey:string): Promise<ArrayBuffer> {
let resBuf: ArrayBuffer | undefined
// 判断自定义下载
if (request.customGetImage !== undefined && request.requestSource == ImageKnifeRequestSource.SRC && typeof request.src == "string") {
// 先从文件缓存获取
resBuf = FileCache.getFileCacheByFile(request.context, fileKey , request.fileCacheFolder)
if (resBuf === undefined) {
LogUtil.log("start customGetImage src=" + request.src)
try {
resBuf = await request.customGetImage(request.context, request.src)
LogUtil.log("end customGetImage src=" + request.src)
} catch (err) {
throw new Error('customGetImage loadFile failed! err = ' + err)
}
if (resBuf === undefined) {
throw new Error('customGetImage loadFile failed!')
}
// 保存文件缓存
if (request.writeCacheStrategy !== CacheStrategy.Memory) {
LogUtil.log("ImageKnife_DataTime_requestJob_saveFileCacheOnlyFile.start:" + request.src)
FileCache.saveFileCacheOnlyFile(request.context, fileKey, resBuf, request.fileCacheFolder)
LogUtil.log("ImageKnife_DataTime_requestJob_saveFileCacheOnlyFile.end:" + request.src)
}
}
}
else {
if (typeof request.src === 'string') {
if (request.src.indexOf("http://") == 0 || request.src.indexOf("https://") == 0) { //从网络下载
// 先从文件缓存获取
resBuf = FileCache.getFileCacheByFile(request.context, fileKey , request.fileCacheFolder)
if (resBuf !== undefined){
LogUtil.log("success get image from filecache for key = " + fileKey + " src = " + request.src)
}
else if (request.onlyRetrieveFromCache != true) {
LogUtil.log("HttpDownloadClient.start:" + request.src)
let httpRequest = http.createHttp();
let progress: number = 0
let arrayBuffers = new Array<ArrayBuffer>()
const headerObj: Record<string, object> = {}
if (request.headers != undefined) {
request.headers.forEach((value) => {
headerObj[value.key] = value.value
})
} else if (request.allHeaders.size > 0) {
request.allHeaders.forEach((value, key) => {
headerObj[key] = value
})
}
httpRequest.on("dataReceive", (data: ArrayBuffer) => {
arrayBuffers.push(data)
});
if (request.isWatchProgress) {
httpRequest.on('dataReceiveProgress', (data: RequestData) => {
// 下载进度
if (data != undefined && (typeof data.receiveSize == 'number') && (typeof data.totalSize == 'number')) {
let percent = Math.round(((data.receiveSize * 1.0) / (data.totalSize * 1.0)) * 100)
if (progress !== percent) {
progress = percent
if (requestList === undefined) {
// 子线程
emitter.emit(Constants.PROGRESS_EMITTER + request.memoryKey, { data: { "value": progress } })
}else {
// 主线程请求
requestList!.forEach((requestWithSource: ImageKnifeRequestWithSource) => {
if (requestWithSource.request.imageKnifeOption.progressListener !== undefined && requestWithSource.source === ImageKnifeRequestSource.SRC) {
requestWithSource.request.imageKnifeOption.progressListener(progress)
}
})
}
}
}
})
}
let promise = httpRequest.requestInStream(request.src, {
header: headerObj,
method: http.RequestMethod.GET,
expectDataType: http.HttpDataType.ARRAY_BUFFER,
connectTimeout: 60000,
readTimeout: 0,
// usingProtocol:http.HttpProtocol.HTTP1_1
// header: new Header('application/json')
});
await promise.then((data: number) => {
if (data == 200 || data == 206 || data == 204) {
resBuf = combineArrayBuffers(arrayBuffers)
} else {
throw new Error("HttpDownloadClient has error, http code =" + JSON.stringify(data))
}
}).catch((err: Error) => {
throw new Error("HttpDownloadClient download ERROR : err = " + JSON.stringify(err))
});
LogUtil.log("HttpDownloadClient.end:" + request.src)
// 保存文件缓存
if (resBuf !== undefined && request.writeCacheStrategy !== CacheStrategy.Memory) {
LogUtil.log("ImageKnife_DataTime_requestJob_saveFileCacheOnlyFile.start:"+request.src)
FileCache.saveFileCacheOnlyFile(request.context, fileKey, resBuf , request.fileCacheFolder)
LogUtil.log("ImageKnife_DataTime_requestJob_saveFileCacheOnlyFile.end:"+request.src)
}
}
else {
throw new Error('onlyRetrieveFromCache,do not fetch image src = ' + request.src)
}
} else if (request.src.startsWith('datashare://') || request.src.startsWith('file://')) {
await fs.open(request.src, fs.OpenMode.READ_ONLY).then(async (file) => {
await fs.stat(file.fd).then(async (stat) =>{
let buf = new ArrayBuffer(stat.size);
await fs.read(file.fd, buf).then((readLen) => {
resBuf = buf;
fs.closeSync(file.fd);
}).catch((err:BusinessError) => {
throw new Error('LoadDataShareFileClient fs.read err happened uri=' + request.src + " err.msg=" + err?.message + " err.code=" + err?.code)
})
}).catch((err:BusinessError) => {
throw new Error('LoadDataShareFileClient fs.stat err happened uri=' + request.src + " err.msg=" + err?.message + " err.code=" + err?.code)
})
}).catch((err:BusinessError) => {
throw new Error('LoadDataShareFileClient fs.open err happened uri=' + request.src + " err.msg=" + err?.message + " err.code=" + err?.code)
})
} else { //从本地文件获取
try {
let stat = fs.statSync(request.src);
if (stat.size > 0) {
let file = fs.openSync(request.src, fs.OpenMode.READ_ONLY);
resBuf = new ArrayBuffer(stat.size);
fs.readSync(file.fd, resBuf);
fs.closeSync(file);
}
} catch (err) {
throw new Error(err)
}
}
} else if (typeof request.src == "number") { //从资源文件获取
let manager = request.context.createModuleContext(request.moduleName).resourceManager
if (resBuf == undefined && request.onlyRetrieveFromCache != true && request.requestSource == ImageKnifeRequestSource.SRC) {
if(request.src == -1) {
let resName = request.resName as string
resBuf = (await manager.getMediaByName(resName.substring(10))).buffer as ArrayBuffer
} else {
resBuf = manager.getMediaContentSync(request.src).buffer as ArrayBuffer
}
} else if (resBuf == undefined && request.requestSource != ImageKnifeRequestSource.SRC) {
if(request.src == -1) {
let resName = request.resName as string
resBuf = (await manager.getMediaByName(resName.substring(10))).buffer as ArrayBuffer
} else {
resBuf = manager.getMediaContentSync(request.src).buffer as ArrayBuffer
}
}
}
}
if (resBuf === undefined){
throw new Error('getImageArrayBuffer undefined')
}
return resBuf
}
}

View File

@ -13,12 +13,12 @@
* limitations under the License.
*/
import util from '@ohos.util';
import { FileUtils } from './FileUtils';
import { FileUtils } from '../utils/FileUtils';
import fs from '@ohos.file.fs';
import { LogUtil } from './LogUtil';
import { LogUtil } from '../utils/LogUtil';
import { SparkMD5 } from '../3rd_party/sparkmd5/spark-md5';
const INT_MAX = 2147483647
/**
* 二级文件缓存
* 主线程通过lruCache管理缓存大小
@ -34,12 +34,12 @@ export class FileCache {
private isInited: boolean = false
private context?: Context
readonly defaultMaxSize: number = 512;
readonly defaultSize: number = 128;
readonly defaultSize: number = INT_MAX;
readonly defaultMaxMemorySize: number = 512 * 1024 * 1024;
readonly defaultMemorySize: number = 128 * 1024 * 1024;
constructor(context: Context, size: number, memory: number) {
if (size <= 0) {
if (size <= 0 || size > INT_MAX) {
size = this.defaultSize
}
if (memory <= 0 || memory > this.defaultMaxMemorySize) {
@ -232,18 +232,18 @@ export class FileCache {
}
else if (value != undefined) {
this.currentMemory -= value.byteLength
LogUtil.info("FileCache removeMemorySize: " + value.byteLength + " currentMemory" + this.currentMemory)
LogUtil.debug("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)
LogUtil.debug("FileCache addMemorySize: " + value + " currentMemory" + this.currentMemory)
}
else if (value != undefined) {
this.currentMemory += value.byteLength
LogUtil.info("FileCache addMemorySize: " + value.byteLength + " currentMemory" + this.currentMemory)
LogUtil.debug("FileCache addMemorySize: " + value.byteLength + " currentMemory" + this.currentMemory)
}
}

View File

@ -12,8 +12,8 @@
* 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 { AnimatorOption, ImageKnifeOption } from '../model/ImageKnifeOption';
import { ImageKnifeRequest, ImageKnifeRequestState } from '../model/ImageKnifeRequest';
import common from '@ohos.app.ability.common';
import { ImageKnife } from '../ImageKnife';
import { LogUtil } from '../utils/LogUtil';
@ -79,6 +79,11 @@ export struct ImageKnifeAnimatorComponent {
}
}
})
.onStart(this.animatorOption.onStart)
.onFinish(this.animatorOption.onFinish)
.onPause(this.animatorOption.onPause)
.onCancel(this.animatorOption.onCancel)
.onRepeat(this.animatorOption.onRepeat)
}
watchImageKnifeOption() {

View File

@ -12,8 +12,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ImageKnifeOption } from '../ImageKnifeOption';
import { ImageKnifeRequest, ImageKnifeRequestState } from '../ImageKnifeRequest';
import { ImageKnifeOption } from '../model/ImageKnifeOption';
import { ImageKnifeRequest, ImageKnifeRequestState } from '../model/ImageKnifeRequest';
import common from '@ohos.app.ability.common';
import { ImageKnife } from '../ImageKnife';
import { LogUtil } from '../utils/LogUtil';
@ -38,43 +38,47 @@ export struct ImageKnifeComponent {
private currentContext: common.UIAbilityContext | undefined = undefined
aboutToAppear(): void {
//闪动问题失效,注释相应代码后续修复
if(this.syncLoad) {
this.objectFit = this.imageKnifeOption.objectFit === undefined ? ImageFit.Contain : this.imageKnifeOption.objectFit
if(this.syncLoad) { //针对部分消息列表最新消息的图片闪动问题建议使用同步方式在aboutToAppear时加载图片
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))
//画主图
LogUtil.log("aboutToAppear success load loadSrc from memory cache for loadSrc = "+ this.imageKnifeOption.loadSrc)
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;
}else{
LogUtil.log("aboutToAppear fail load loadSrc from memory cache for loadSrc = "+ this.imageKnifeOption.loadSrc)
if (this.imageKnifeOption.placeholderSrc !== undefined){
let memoryCachePlace: ImageKnifeData | undefined = ImageKnife.getInstance()
.loadFromMemoryCache(engineKey.generateMemoryKey(this.imageKnifeOption.placeholderSrc!,ImageKnifeRequestSource.PLACE_HOLDER,this.imageKnifeOption))
if (memoryCachePlace !== undefined){
LogUtil.log("aboutToAppear success load placeholderSrc from memory cache for placeholderSrc = " + this.imageKnifeOption.placeholderSrc)
this.pixelMap = memoryCachePlace.source;
}else{
LogUtil.log("aboutToAppear fail load placeholderSrc from memory cache for placeholderSrc = " + this.imageKnifeOption.placeholderSrc)
}
}
}
}
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
}
this.clearLastRequest()
}
aboutToRecycle() {
this.clearLastRequest()
}
/**
* 对已DESTROY的组件不再发起请求
*/
private clearLastRequest(){
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)
@ -103,11 +107,13 @@ export struct ImageKnifeComponent {
}
watchImageKnifeOption() {
if (this.request !== undefined) {
this.request.requestState = ImageKnifeRequestState.DESTROY
}
this.request = undefined
this.clearLastRequest()
this.componentVersion++
this.objectFit = this.imageKnifeOption.objectFit === undefined ? ImageFit.Contain : this.imageKnifeOption.objectFit
LogUtil.log("watchImageKnifeOption execute request:width=" + this.currentWidth + " height= " + this.currentHeight
+ " loadSrc = " + this.request?.imageKnifeOption.loadSrc
+ " placeholderSrc = " + this.request?.imageKnifeOption.placeholderSrc
+ " errorholderSrc = " + this.request?.imageKnifeOption.errorholderSrc)
ImageKnife.getInstance().execute(this.getRequest(this.currentWidth, this.currentHeight))
}

View File

@ -13,7 +13,7 @@
* limitations under the License.
*/
import { SparkMD5 } from '../3rd_party/sparkmd5/spark-md5';
import { ImageKnifeOption } from '../ImageKnifeOption';
import { ImageKnifeOption } from '../model/ImageKnifeOption';
import { IEngineKey } from './IEngineKey';
import { PixelMapTransformation } from '../transform/PixelMapTransformation';
import { ImageKnifeRequestSource } from '../model/ImageKnifeData';

View File

@ -12,7 +12,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ImageKnifeOption } from '../ImageKnifeOption'
import { ImageKnifeOption } from '../model/ImageKnifeOption'
import { ImageKnifeRequestSource } from '../model/ImageKnifeData'
export interface IEngineKey {

View File

@ -12,8 +12,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { HeaderOptions } from '../ImageKnifeOption'
import { ImageKnifeRequest } from '../ImageKnifeRequest'
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';
@ -86,7 +86,7 @@ export interface RequestJobResult {
*/
export interface RequestJobRequest {
context: common.UIAbilityContext,
src: string | PixelMap | Resource,
src: string | number,
headers?: Array<HeaderOptions>,
allHeaders: Map<string, Object>,
componentWidth: number,
@ -101,6 +101,8 @@ export interface RequestJobRequest {
isWatchProgress: boolean
memoryKey: string
fileCacheFolder: string,
isAnimator?: boolean
isAnimator?: boolean,
moduleName?:string,
resName?: string
}

View File

@ -14,8 +14,8 @@
*/
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 { CacheStrategy, ImageKnifeData,EventImage } from './ImageKnifeData';
import { PixelMapTransformation } from '../transform/PixelMapTransformation';
import { drawing } from '@kit.ArkGraphics2D';
export interface HeaderOptions {
@ -31,6 +31,16 @@ export class AnimatorOption {
iterations?: number = -1
@Track
reverse?: boolean = false
@Track
onStart?:()=>void
@Track
onFinish?:()=>void
@Track
onPause?:()=>void
@Track
onCancel?:()=>void
@Track
onRepeat?:()=>void
}
@Observed

View File

@ -14,7 +14,7 @@
*/
import { ImageKnifeOption } from './ImageKnifeOption';
import common from '@ohos.app.ability.common';
import { ImageKnifeRequestSource } from './model/ImageKnifeData';
import { ImageKnifeRequestSource } from './ImageKnifeData';
export class ImageKnifeRequest {

View File

@ -12,7 +12,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ImageKnifeRequest } from '../ImageKnifeRequest';
import { ImageKnifeRequest } from '../model/ImageKnifeRequest';
import { IJobQueue } from './IJobQueue'
import Queue from '@ohos.util.Queue';
import { taskpool,Stack } from '@kit.ArkTS';

View File

@ -12,7 +12,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ImageKnifeRequest } from '../ImageKnifeRequest'
import { ImageKnifeRequest } from '../model/ImageKnifeRequest'
export interface IJobQueue {

View File

@ -12,44 +12,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { hilog } from '@kit.PerformanceAnalysisKit';
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 readonly DOMAIN: number = 0xD002220;
public static readonly TAG: string = "ImageKnife::";
public static debug(message: string, ...args: Object[]) {
if (LogUtil.mLogLevel >= LogUtil.DEBUG) {
console.debug(LogUtil.TAG + message, args)
}
hilog.debug(LogUtil.DOMAIN, LogUtil.TAG, message, args)
}
public static info(message: string, ...args: Object[]) {
if (LogUtil.mLogLevel >= LogUtil.INFO) {
console.info(LogUtil.TAG + message, args)
}
hilog.info(LogUtil.DOMAIN, LogUtil.TAG, message, args)
}
public static log(message: string, ...args: Object[]) {
if (LogUtil.mLogLevel >= LogUtil.LOG) {
console.log(LogUtil.TAG + message, args)
}
hilog.debug(LogUtil.DOMAIN, LogUtil.TAG, message, args)
}
public static warn(message: string, ...args: Object[]) {
if (LogUtil.mLogLevel >= LogUtil.WARN) {
console.warn(LogUtil.TAG + message, args)
}
hilog.warn(LogUtil.DOMAIN, LogUtil.TAG, message, args)
}
public static error(message: string, ...args: Object[]) {
if (LogUtil.mLogLevel >= LogUtil.ERROR) {
console.error(LogUtil.TAG + message, args)
}
hilog.error(LogUtil.DOMAIN, LogUtil.TAG, message, args)
}
}

View File

@ -1,35 +0,0 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { SparkMD5 } from '../3rd_party/sparkmd5/spark-md5'
import util from '@ohos.util'
export class Tools {
private static keyCache: util.LRUCache<string,string> = 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
}
}
}