diff --git a/CHANGELOG.md b/CHANGELOG.md index 78011a1..2b27faa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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打包关闭混淆 diff --git a/README.md b/README.md index 3f50768..bf787e0 100644 --- a/README.md +++ b/README.md @@ -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自会图片 -- mainScaleType,border等参数,新版本与系统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 { - 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 = new collections.Array(); 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 | 设置请求头(可选) | -| 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\ | 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 | 全局设置http请求头 | -| deleteHeader | key: string | 全局删除http请求头 | -| setCustomGetImage | customGetImage?: (context: Context, src: string | PixelMap | Resource) => Promise | 全局设置自定义下载 | -| setEngineKeyImpl | IEngineKey | 全局配置缓存key生成策略 | -| putCacheImage | url: string, pixelMap: PixelMap, cacheType: CacheStrategy = CacheStrategy.Default, signature?: string | 写入内存磁盘缓存 | -| removeMemoryCache| url: string | ImageKnifeOption | 清理指定内存缓存 | -| removeFileCache | url: string | ImageKnifeOption | 清理指定磁盘缓存 | -### 图形变换类型(需要为GPUImage添加依赖项) +| 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 | 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 Canary3(5.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属性无法将图片变为圆角 \ No newline at end of file +## 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. diff --git a/README_zh.md b/README_zh.md new file mode 100644 index 0000000..97522da --- /dev/null +++ b/README_zh.md @@ -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自会图片 +- mainScaleType,border等参数,新版本与系统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 { + 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 = new collections.Array(); +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 | 设置请求头(可选) | +| 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 | 全局设置http请求头 | +| deleteHeader | key: string | 全局删除http请求头 | +| setCustomGetImage | customGetImage?: (context: Context, src: string | PixelMap | Resource) => Promise | 全局设置自定义下载 | +| setEngineKeyImpl | IEngineKey | 全局配置缓存key生成策略 | +| putCacheImage | url: string, pixelMap: PixelMap, cacheType: CacheStrategy = CacheStrategy.Default, signature?: string | 写入内存磁盘缓存 | +| removeMemoryCache| url: string | ImageKnifeOption | 清理指定内存缓存 | +| removeFileCache | url: string | ImageKnifeOption | 清理指定磁盘缓存 | +### 图形变换类型(需要为GPUImage添加依赖项) + +| 类型 | 相关描述 | +| ---------------------------------- | ----------------------------- | +| BlurTransformation | 模糊处理 | +| BrightnessTransformation | 亮度滤波器 | +| CropSquareTransformation | 正方形剪裁 | +| CropTransformation | 自定义矩形剪裁 | +| GrayScaleTransformation | 灰度级滤波器 | +| InvertTransformation | 反转滤波器 | +| KuwaharaTransformation | 桑原滤波器(使用GPUIImage) | +| MaskTransformation | 遮罩 | +| PixelationTransformation | 像素化滤波器(使用GPUIImage) | +| SepiaTransformation | 乌墨色滤波器(使用GPUIImage) | +| SketchTransformation | 素描滤波器(使用GPUIImage) | +| SwirlTransformation | 扭曲滤波器(使用GPUIImage) | +| ToonTransformation | 动画滤波器(使用GPUIImage) | +| VignetterTransformation | 装饰滤波器(使用GPUIImage) | + +## 下载安装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 Canary3(5.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属性无法将图片变为圆角 \ No newline at end of file diff --git a/entry/src/main/ets/entryability/EntryAbility.ets b/entry/src/main/ets/entryability/EntryAbility.ets index 488dcce..2a48190 100644 --- a/entry/src/main/ets/entryability/EntryAbility.ets +++ b/entry/src/main/ets/entryability/EntryAbility.ets @@ -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()) diff --git a/entry/src/main/ets/pages/ImageAnimatorPage.ets b/entry/src/main/ets/pages/ImageAnimatorPage.ets index bde0508..a783235 100644 --- a/entry/src/main/ets/pages/ImageAnimatorPage.ets +++ b/entry/src/main/ets/pages/ImageAnimatorPage.ets @@ -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%") } } \ No newline at end of file diff --git a/entry/src/main/ets/pages/ImageTransformation.ets b/entry/src/main/ets/pages/ImageTransformation.ets index 844ed62..5e4e007 100644 --- a/entry/src/main/ets/pages/ImageTransformation.ets +++ b/entry/src/main/ets/pages/ImageTransformation.ets @@ -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) { diff --git a/entry/src/main/ets/pages/Index.ets b/entry/src/main/ets/pages/Index.ets index 3383f6a..329e33a 100644 --- a/entry/src/main/ets/pages/Index.ets +++ b/entry/src/main/ets/pages/Index.ets @@ -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', diff --git a/entry/src/main/ets/pages/SignatureTestPage.ets b/entry/src/main/ets/pages/SignatureTestPage.ets index 42f49e4..5d60c3a 100644 --- a/entry/src/main/ets/pages/SignatureTestPage.ets +++ b/entry/src/main/ets/pages/SignatureTestPage.ets @@ -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', diff --git a/entry/src/main/ets/pages/SingleImage.ets b/entry/src/main/ets/pages/SingleImage.ets index b7fc9b3..079c8e4 100644 --- a/entry/src/main/ets/pages/SingleImage.ets +++ b/entry/src/main/ets/pages/SingleImage.ets @@ -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({ diff --git a/entry/src/main/ets/pages/TestImageFlash.ets b/entry/src/main/ets/pages/TestImageFlash.ets index 75144e0..53b9826 100644 --- a/entry/src/main/ets/pages/TestImageFlash.ets +++ b/entry/src/main/ets/pages/TestImageFlash.ets @@ -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) }) diff --git a/entry/src/main/ets/pages/TestIsUrlExist.ets b/entry/src/main/ets/pages/TestIsUrlExist.ets index 1225edc..ad4daa6 100644 --- a/entry/src/main/ets/pages/TestIsUrlExist.ets +++ b/entry/src/main/ets/pages/TestIsUrlExist.ets @@ -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) diff --git a/entry/src/main/ets/pages/TestPrefetchToFileCache.ets b/entry/src/main/ets/pages/TestPrefetchToFileCache.ets index eb735a7..a1a1787 100644 --- a/entry/src/main/ets/pages/TestPrefetchToFileCache.ets +++ b/entry/src/main/ets/pages/TestPrefetchToFileCache.ets @@ -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({ diff --git a/entry/src/main/ets/pages/TestRemoveCache.ets b/entry/src/main/ets/pages/TestRemoveCache.ets index 13e7def..8a25454 100644 --- a/entry/src/main/ets/pages/TestRemoveCache.ets +++ b/entry/src/main/ets/pages/TestRemoveCache.ets @@ -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) }) diff --git a/entry/src/main/ets/pages/TestTaskResourcePage.ets b/entry/src/main/ets/pages/TestTaskResourcePage.ets new file mode 100644 index 0000000..b9697c1 --- /dev/null +++ b/entry/src/main/ets/pages/TestTaskResourcePage.ets @@ -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 = [ + "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 + }) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/TransformPage.ets b/entry/src/main/ets/pages/TransformPage.ets index 7674156..29fbfd5 100644 --- a/entry/src/main/ets/pages/TransformPage.ets +++ b/entry/src/main/ets/pages/TransformPage.ets @@ -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 }) }) diff --git a/entry/src/main/ets/pages/dataShareUriLoadPage.ets b/entry/src/main/ets/pages/dataShareUriLoadPage.ets index bb71ac2..b3915b7 100644 --- a/entry/src/main/ets/pages/dataShareUriLoadPage.ets +++ b/entry/src/main/ets/pages/dataShareUriLoadPage.ets @@ -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; diff --git a/entry/src/main/resources/base/element/string.json b/entry/src/main/resources/base/element/string.json index 13c1fec..f982e81 100644 --- a/entry/src/main/resources/base/element/string.json +++ b/entry/src/main/resources/base/element/string.json @@ -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" } ] } \ No newline at end of file diff --git a/entry/src/main/resources/base/media/webpAtanta.webp b/entry/src/main/resources/base/media/webpAtanta.webp new file mode 100644 index 0000000..affe2a5 Binary files /dev/null and b/entry/src/main/resources/base/media/webpAtanta.webp differ diff --git a/entry/src/main/resources/base/media/yunHeic.heic b/entry/src/main/resources/base/media/yunHeic.heic new file mode 100644 index 0000000..161e3ca Binary files /dev/null and b/entry/src/main/resources/base/media/yunHeic.heic differ diff --git a/entry/src/main/resources/base/profile/main_pages.json b/entry/src/main/resources/base/profile/main_pages.json index 42b571c..de593dd 100644 --- a/entry/src/main/resources/base/profile/main_pages.json +++ b/entry/src/main/resources/base/profile/main_pages.json @@ -22,6 +22,7 @@ "pages/TestCommonImage", "pages/ImageAnimatorPage", "pages/TestSetCustomImagePage", - "pages/TestErrorHolderPage" + "pages/TestErrorHolderPage", + "pages/TestTaskResourcePage" ] } \ No newline at end of file diff --git a/entry/src/main/resources/zh_CN/element/string.json b/entry/src/main/resources/zh_CN/element/string.json index 6250115..74fedfe 100644 --- a/entry/src/main/resources/zh_CN/element/string.json +++ b/entry/src/main/resources/zh_CN/element/string.json @@ -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": "测试失败场景请先关闭网络,并保证本地没有此网络图片的缓存" } ] } \ No newline at end of file diff --git a/library/index.ets b/library/index.ets index 4576c08..1f67f70 100644 --- a/library/index.ets +++ b/library/index.ets @@ -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' diff --git a/library/oh-package.json5 b/library/oh-package.json5 index 97268ca..9fdcdc5 100644 --- a/library/oh-package.json5 +++ b/library/oh-package.json5 @@ -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" }, diff --git a/library/src/main/ets/ImageKnife.ets b/library/src/main/ets/ImageKnife.ets index d168038..69a6271 100644 --- a/library/src/main/ets/ImageKnife.ets +++ b/library/src/main/ets/ImageKnife.ets @@ -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'; diff --git a/library/src/main/ets/ImageKnifeDispatcher.ets b/library/src/main/ets/ImageKnifeDispatcher.ets index 3c2f4fb..fbbb3a8 100644 --- a/library/src/main/ets/ImageKnifeDispatcher.ets +++ b/library/src/main/ets/ImageKnifeDispatcher.ets @@ -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): Promise { - 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() - const headerObj: Record = {} - if (request.headers != undefined) { - request.headers.forEach((value) => { - headerObj[value.key] = value.value - }) - } else if (request.allHeaders.size > 0) { - request.allHeaders.forEach((value, key) => { - headerObj[key] = value - }) - } - httpRequest.on("dataReceive", (data: ArrayBuffer) => { - arrayBuffers.push(data) - }); - - if (request.isWatchProgress) { - httpRequest.on('dataReceiveProgress', (data: RequestData) => { - // 下载进度 - if (data != undefined && (typeof data.receiveSize == 'number') && (typeof data.totalSize == 'number')) { - let percent = Math.round(((data.receiveSize * 1.0) / (data.totalSize * 1.0)) * 100) - if (progress !== percent) { - progress = percent - if (requestList === undefined) { - // 子线程 - emitter.emit(Constants.PROGRESS_EMITTER + request.memoryKey, { data: { "value": progress } }) - }else { - // 主线程请求 - requestList!.forEach((requestWithSource: ImageKnifeRequestWithSource) => { - if (requestWithSource.request.imageKnifeOption.progressListener !== undefined && requestWithSource.source === ImageKnifeRequestSource.SRC) { - requestWithSource.request.imageKnifeOption.progressListener(progress) - } - }) - } - } - } - }) - } - let promise = httpRequest.requestInStream(request.src, { - header: headerObj, - method: http.RequestMethod.GET, - expectDataType: http.HttpDataType.ARRAY_BUFFER, - connectTimeout: 6000, - readTimeout: 6000, - // usingProtocol:http.HttpProtocol.HTTP1_1 - // header: new Header('application/json') - }); - - await promise.then((data: number) => { - if (data == 200 || 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 = [] - let delayList: Array = [] - await imageSource.createPixelMapList(decodingOptions).then(async (pixelList: Array) => { - //sdk的api接口发生变更:从.getDelayTime() 变为.getDelayTimeList() - await imageSource.getDelayTimeList().then(delayTimes => { - if (pixelList.length > 0) { - for (let i = 0; i < pixelList.length; i++) { - pixelMapList.push(pixelList[i]); - if (i < delayTimes.length) { - delayList.push(delayTimes[i]); - } else { - delayList.push(delayTimes[delayTimes.length - 1]) - } - } - imageSource.release(); - } - }) - }) - return { - pixelMap: "", - bufferSize: bufferSize, - fileKey: fileKey, - type: typeValue, - pixelMapList, - delayList - } - } else { - return { - pixelMap: undefined, - bufferSize: 0, - fileKey: '', - loadFail: "ImageKnifeAnimatorComponent组件仅支持动态图", - } - } - } - let resPixelmap: PixelMap | undefined = undefined - if (typeValue === 'gif' || typeValue === 'webp') { - let 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 } + + diff --git a/library/src/main/ets/ImageKnifeLoader.ets b/library/src/main/ets/ImageKnifeLoader.ets new file mode 100644 index 0000000..1c14d0c --- /dev/null +++ b/library/src/main/ets/ImageKnifeLoader.ets @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 = [] + let delayList: Array = [] + await imageSource.createPixelMapList(decodingOptions).then(async (pixelList: Array) => { + //sdk的api接口发生变更:从.getDelayTime() 变为.getDelayTimeList() + await imageSource.getDelayTimeList().then(delayTimes => { + if (pixelList.length > 0) { + for (let i = 0; i < pixelList.length; i++) { + pixelMapList.push(pixelList[i]); + if (i < delayTimes.length) { + delayList.push(delayTimes[i]); + } else { + delayList.push(delayTimes[delayTimes.length - 1]) + } + } + imageSource.release(); + } + }) + }).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 | undefined,fileKey:string): Promise { + 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() + const headerObj: Record = {} + if (request.headers != undefined) { + request.headers.forEach((value) => { + headerObj[value.key] = value.value + }) + } else if (request.allHeaders.size > 0) { + request.allHeaders.forEach((value, key) => { + headerObj[key] = value + }) + } + httpRequest.on("dataReceive", (data: ArrayBuffer) => { + arrayBuffers.push(data) + }); + + if (request.isWatchProgress) { + httpRequest.on('dataReceiveProgress', (data: RequestData) => { + // 下载进度 + if (data != undefined && (typeof data.receiveSize == 'number') && (typeof data.totalSize == 'number')) { + let percent = Math.round(((data.receiveSize * 1.0) / (data.totalSize * 1.0)) * 100) + if (progress !== percent) { + progress = percent + if (requestList === undefined) { + // 子线程 + emitter.emit(Constants.PROGRESS_EMITTER + request.memoryKey, { data: { "value": progress } }) + }else { + // 主线程请求 + requestList!.forEach((requestWithSource: ImageKnifeRequestWithSource) => { + if (requestWithSource.request.imageKnifeOption.progressListener !== undefined && requestWithSource.source === ImageKnifeRequestSource.SRC) { + requestWithSource.request.imageKnifeOption.progressListener(progress) + } + }) + } + } + } + }) + } + let promise = httpRequest.requestInStream(request.src, { + header: headerObj, + method: http.RequestMethod.GET, + expectDataType: http.HttpDataType.ARRAY_BUFFER, + connectTimeout: 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 + } +} \ No newline at end of file diff --git a/library/src/main/ets/utils/FileCache.ets b/library/src/main/ets/cache/FileCache.ets similarity index 94% rename from library/src/main/ets/utils/FileCache.ets rename to library/src/main/ets/cache/FileCache.ets index 8c47562..41978b8 100644 --- a/library/src/main/ets/utils/FileCache.ets +++ b/library/src/main/ets/cache/FileCache.ets @@ -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) } } diff --git a/library/src/main/ets/utils/IMemoryCache.ets b/library/src/main/ets/cache/IMemoryCache.ets similarity index 100% rename from library/src/main/ets/utils/IMemoryCache.ets rename to library/src/main/ets/cache/IMemoryCache.ets diff --git a/library/src/main/ets/utils/MemoryLruCache.ets b/library/src/main/ets/cache/MemoryLruCache.ets similarity index 100% rename from library/src/main/ets/utils/MemoryLruCache.ets rename to library/src/main/ets/cache/MemoryLruCache.ets diff --git a/library/src/main/ets/components/ImageKnifeAnimatorComponent.ets b/library/src/main/ets/components/ImageKnifeAnimatorComponent.ets index f3bc678..81a0f85 100644 --- a/library/src/main/ets/components/ImageKnifeAnimatorComponent.ets +++ b/library/src/main/ets/components/ImageKnifeAnimatorComponent.ets @@ -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() { diff --git a/library/src/main/ets/components/ImageKnifeComponent.ets b/library/src/main/ets/components/ImageKnifeComponent.ets index 57e7432..030a668 100644 --- a/library/src/main/ets/components/ImageKnifeComponent.ets +++ b/library/src/main/ets/components/ImageKnifeComponent.ets @@ -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)) } diff --git a/library/src/main/ets/key/DefaultEngineKey.ets b/library/src/main/ets/key/DefaultEngineKey.ets index 6d43be1..f28024b 100644 --- a/library/src/main/ets/key/DefaultEngineKey.ets +++ b/library/src/main/ets/key/DefaultEngineKey.ets @@ -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'; diff --git a/library/src/main/ets/key/IEngineKey.ets b/library/src/main/ets/key/IEngineKey.ets index 54be5bb..3f7a903 100644 --- a/library/src/main/ets/key/IEngineKey.ets +++ b/library/src/main/ets/key/IEngineKey.ets @@ -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 { diff --git a/library/src/main/ets/model/ImageKnifeData.ets b/library/src/main/ets/model/ImageKnifeData.ets index 79fe5d0..e8a4c2a 100644 --- a/library/src/main/ets/model/ImageKnifeData.ets +++ b/library/src/main/ets/model/ImageKnifeData.ets @@ -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, allHeaders: Map, componentWidth: number, @@ -101,6 +101,8 @@ export interface RequestJobRequest { isWatchProgress: boolean memoryKey: string fileCacheFolder: string, - isAnimator?: boolean + isAnimator?: boolean, + moduleName?:string, + resName?: string } diff --git a/library/src/main/ets/ImageKnifeOption.ets b/library/src/main/ets/model/ImageKnifeOption.ets similarity index 89% rename from library/src/main/ets/ImageKnifeOption.ets rename to library/src/main/ets/model/ImageKnifeOption.ets index 438849e..974b53b 100644 --- a/library/src/main/ets/ImageKnifeOption.ets +++ b/library/src/main/ets/model/ImageKnifeOption.ets @@ -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 diff --git a/library/src/main/ets/ImageKnifeRequest.ets b/library/src/main/ets/model/ImageKnifeRequest.ets similarity index 97% rename from library/src/main/ets/ImageKnifeRequest.ets rename to library/src/main/ets/model/ImageKnifeRequest.ets index cb2115b..c63f8be 100644 --- a/library/src/main/ets/ImageKnifeRequest.ets +++ b/library/src/main/ets/model/ImageKnifeRequest.ets @@ -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 { diff --git a/library/src/main/ets/utils/DefaultJobQueue.ets b/library/src/main/ets/queue/DefaultJobQueue.ets similarity index 96% rename from library/src/main/ets/utils/DefaultJobQueue.ets rename to library/src/main/ets/queue/DefaultJobQueue.ets index ac31956..53f3d2a 100644 --- a/library/src/main/ets/utils/DefaultJobQueue.ets +++ b/library/src/main/ets/queue/DefaultJobQueue.ets @@ -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'; diff --git a/library/src/main/ets/utils/IJobQueue.ets b/library/src/main/ets/queue/IJobQueue.ets similarity index 93% rename from library/src/main/ets/utils/IJobQueue.ets rename to library/src/main/ets/queue/IJobQueue.ets index 0a51ff0..39a3923 100644 --- a/library/src/main/ets/utils/IJobQueue.ets +++ b/library/src/main/ets/queue/IJobQueue.ets @@ -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 { diff --git a/library/src/main/ets/model/utils.ets b/library/src/main/ets/utils/ArrayBufferUtils.ets similarity index 100% rename from library/src/main/ets/model/utils.ets rename to library/src/main/ets/utils/ArrayBufferUtils.ets diff --git a/library/src/main/ets/utils/LogUtil.ets b/library/src/main/ets/utils/LogUtil.ets index 02c2d39..d59f582 100644 --- a/library/src/main/ets/utils/LogUtil.ets +++ b/library/src/main/ets/utils/LogUtil.ets @@ -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) } } \ No newline at end of file diff --git a/library/src/main/ets/utils/Tools.ets b/library/src/main/ets/utils/Tools.ets deleted file mode 100644 index a51faa8..0000000 --- a/library/src/main/ets/utils/Tools.ets +++ /dev/null @@ -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 = new util.LRUCache(1024) - public static generateMemoryKey(key: string | PixelMap | Resource): string{ - return typeof key == "string"? key : JSON.stringify(key) - } - // 生成唯一的key - public static generateKey(key: string | PixelMap | Resource): string{ - let keyCache = typeof key == "string"? key : JSON.stringify(key) - let result = Tools.keyCache.get(keyCache) - if(result != undefined) { - return result - } else { - result = SparkMD5.hashBinary(keyCache) - Tools.keyCache.put(keyCache,result) - return result - } - } -} \ No newline at end of file