Compare commits

...

23 Commits

Author SHA1 Message Date
landwind bc55de9e2e Modify the README file
Signed-off-by: landwind <mamingshuai1@huawei.com>
2025-04-28 10:56:30 +08:00
openharmony_ci c1426cd35a
!463 项目配置文件新增设备类型,样例设置混淆
Merge pull request !463 from zgf/master
2025-02-20 03:00:15 +00:00
zgf adb78853af 项目配置文件新增设备类型,样例设置混淆
Signed-off-by: zgf <zenggaofeng2@h-partners.com>
2025-02-19 17:39:29 +08:00
openharmony_ci 9e387602f6
!461 支持图片携带的EXIF元数据作为显示方向
Merge pull request !461 from yang/master
2025-02-19 09:24:27 +00:00
yang 115118e238 支持图片携带的EXIF元数据作为显示方向
Signed-off-by: yang <yangweiping7@h-partners.com>
2025-02-19 14:46:37 +08:00
openharmony_ci acb45a66bc
!460 修改版本号3.2.2-rc.0
Merge pull request !460 from zgf/master
2025-02-18 07:51:13 +00:00
zgf 25b7899746 修改版本号3.2.2-rc.0
Signed-off-by: zgf <zenggaofeng2@h-partners.com>
2025-02-18 15:03:11 +08:00
openharmony_ci e41fce46bf
!459 增加ImageKnifeComponent组件销毁网络请求中断
Merge pull request !459 from zgf/master
2025-02-18 03:56:03 +00:00
zgf 8601f87865 增加ImageKnifeComponent组件销毁网络请求中断
Signed-off-by: zgf <zenggaofeng2@h-partners.com>
2025-02-13 16:15:49 +08:00
openharmony_ci 933cc284ec
!458 优化ImageKnifeLoader类的getImageArrayBuffer方法
Merge pull request !458 from 刘海康/master
2025-02-07 09:12:11 +00:00
openharmony_ci fbf4cc88bd
!456 增加图片下载失败各种错误信息样例
Merge pull request !456 from zgf/master
2025-02-07 02:48:13 +00:00
liuhaikang a21a8f025f 优化ImageKnifeLoader类的getImageArrayBuffer方法
Signed-off-by: liuhaikang <liuhaikang1@h-partners.com>
2025-02-06 20:09:06 +08:00
zgf 996fc5c909
增加图片下载失败各种错误信息样例
增加图片下载失败各种错误信息样例

Signed-off-by: zgf <zenggaofeng2@h-partners.com>
2025-02-06 08:16:36 +00:00
zgf 677ea73337 增加图片下载失败各种错误信息样例
Signed-off-by: zgf <zenggaofeng2@h-partners.com>
2025-02-06 15:56:37 +08:00
openharmony_ci 5c22496b96
!455 发布正式版本3.2.1
Merge pull request !455 from 刘海康/master
2025-01-23 03:38:42 +00:00
liuhaikang 425aa3a5bc 发布正式版本3.2.1
Signed-off-by: liuhaikang <liuhaikang1@h-partners.com>
2025-01-23 11:04:36 +08:00
openharmony_ci 226d9939be
!454 判断loadSrc传入pixelmap类型处增加保护
Merge pull request !454 from zgf/master
2025-01-21 08:04:35 +00:00
zgf 2c7473f422 判断loadSrc传入pixelmap类型处增加保护
Signed-off-by: zgf <zenggaofeng2@h-partners.com>
2025-01-21 15:25:12 +08:00
openharmony_ci 17e3787cfa
!453 优化文件缓存初始化效率
Merge pull request !453 from 刘海康/master
2025-01-21 02:29:39 +00:00
liuhaikang 915eded1ab 优化文件缓存初始化效率
Signed-off-by: liuhaikang <liuhaikang1@h-partners.com>
2025-01-20 15:34:07 +08:00
openharmony_ci a1e8e0b15f
!452 修复自定义矩形裁剪异常、清除文件缓存接口导致文件缓存失效
Merge pull request !452 from zgf/master
2025-01-13 07:40:45 +00:00
zgf 1615da7b7c 修复自定义矩形裁剪异常、清除文件缓存接口导致文件缓存失效
Signed-off-by: zgf <zenggaofeng2@h-partners.com>
2025-01-13 14:49:46 +08:00
madixin 3378d36046 优化ImageFit.Auto的demo,使用滚动条调整宽度,体现高度的自适应
Signed-off-by: madixin <42690727@qq.com>
2025-01-12 11:57:05 +08:00
39 changed files with 1113 additions and 341 deletions

View File

@ -1,3 +1,19 @@
## 3.2.2-rc.1
- Support EXIF metadata carried by images as display orientation
## 3.2.2-rc.0
- Add ImageKnifeComponent to destroy network request interruption
- Code refactoring during the download of image resources stage
## 3.2.1
- Release official version
## 3.2.1-rc.0
- Fix bug: CropTransformation is used to crop the original image
- Fix bug: After calling the clear all file cache interfaces, the file cache becomes invalid
- Optimize the efficiency of file cache initialization.
- Add protection at the location where loadSrc is passed in the pixelmap type
## 3.2.0
- When successfully requesting the network, return the httpcode as well
- Fix bug: Network error code httpCode returns no data

View File

@ -1,3 +1,9 @@
## 🚨 **重要提示 | IMPORTANT**
>
> **⚠️ 此代码仓已归档。新地址请访问 [ImageKnife](https://gitcode.com/openharmony-tpc/ImageKnife)。| ⚠️ This repository has been archived. For the new address, please visit [ImageKnife](https://gitcode.com/openharmony-tpc/ImageKnife).**
>
---
>
# ImageKnife
ImageKnife is a specially crafted image loading and caching library for OpenHarmony, optimized for efficiency, lightness, and simplicity.
@ -361,6 +367,14 @@ This project has been verified in the following version:
DevEco Studio: NEXT Beta1-5.0.3.806, SDK: API12 Release(5.0.0.66)
## About obfuscation
- Code obfuscation, please see[Code Obfuscation](https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/arkts-utils/source-obfuscation.md)
- If you want the imageknife library not to be obfuscated during code obfuscation, you need to add corresponding exclusion rules in the obfuscation rule configuration file obfuscation-rules.txt
```
-keep
./oh_modules/@ohos/imageknife
```
## 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.

View File

@ -1,3 +1,9 @@
## 🚨 **重要提示 | IMPORTANT**
>
> **⚠️ 此代码仓已归档。新地址请访问 [ImageKnife](https://gitcode.com/openharmony-tpc/ImageKnife)。| ⚠️ This repository has been archived. For the new address, please visit [ImageKnife](https://gitcode.com/openharmony-tpc/ImageKnife).**
>
---
>
# ImageKnife
**专门为OpenHarmony打造的一款图像加载缓存库致力于更高效、更轻便、更简单。**
@ -457,6 +463,14 @@ async function custom(context: Context, src: string | PixelMap | Resource,header
在下述版本验证通过:
DevEco Studio: NEXT Beta1-5.0.3.806, SDK: API12 Release(5.0.0.66)
## 关于混淆
- 代码混淆,请查看[代码混淆简介](https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/arkts-utils/source-obfuscation.md)
- 如果希望imageknife库在代码混淆过程中不会被混淆需要在混淆规则配置文件obfuscation-rules.txt中添加相应的排除规则
```
-keep
./oh_modules/@ohos/imageknife
```
## 贡献代码
使用过程中发现任何问题都可以提 [issue](https://gitee.com/openharmony-tpc/ImageKnife/issues)

View File

@ -15,4 +15,7 @@
# Keep options:
# -keep-property-name: specifies property names that you want to keep
# -keep-global-name: specifies names that you want to keep in the global scope
# -keep-global-name: specifies names that you want to keep in the global scope
-keep
./oh_modules/@ohos/imageknife

View File

@ -12,49 +12,47 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
ImageKnifeComponent,
ImageKnifeData,
ImageKnifeRequest, LogUtil
} from '@ohos/libraryimageknife';
import { ImageKnifeComponent } from '@ohos/libraryimageknife';
import { display } from '@kit.ArkUI';
@Entry
@Component
struct AutoImageFit {
@State width1: Length = '100%'
@State imageWidth: number = 200;
private maxWidth: number = px2vp(display.getDefaultDisplaySync().width);
build() {
Scroll() {
Column() {
this.Slider()
Column() {
Button($r('app.string.adjust_size')).onClick(() => {
if (this.width1.toString() == '100%') {
this.width1 = '60%'
} else {
this.width1 = '100%'
}
}).width('100%')
Text('Image')
Image('https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/56/v3/8MdhfSsCSMKj4sA6okUWrg/5uBx56tLTUO3RYQl-E5JiQ.jpg').width('100%').objectFit(ImageFit.Auto)
Image('https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/56/v3/8MdhfSsCSMKj4sA6okUWrg/5uBx56tLTUO3RYQl-E5JiQ.jpg')
.width('100%')
.objectFit(ImageFit.Auto)
Text('ImageKnife')
ImageKnifeComponent({
imageKnifeOption: {
loadSrc: 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/56/v3/8MdhfSsCSMKj4sA6okUWrg/5uBx56tLTUO3RYQl-E5JiQ.jpg',
objectFit: ImageFit.Auto,
onLoadListener: {
onLoadStart: (request?: ImageKnifeRequest) => {
LogUtil.info('onLoadStart')
},
onLoadSuccess: (data: string | PixelMap | undefined, imageKnifeData: ImageKnifeData,
request?: ImageKnifeRequest) => {
LogUtil.info('onLoadSuccess')
}
}
}
}).width('100%')
}.width(this.width1).border({ width: 1 })
}.width(this.imageWidth).border({ width: 1 })
}
}
}
@Builder
Slider() {
Slider({
value: this.imageWidth,
min: 100,
max: this.maxWidth,
style: SliderStyle.OutSet
})
.blockColor(Color.White)
.width('100%')
.onChange((value: number) => {
this.imageWidth = value;
})
}
}

View File

@ -0,0 +1,93 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ImageKnifeComponent } from '@ohos/libraryimageknife'
@Entry
@Component
struct ErrorMessageDownload {
@State httpCode: string = ''
@State httpError: string = ''
@State storageError: string = ''
@State fileError: string = ''
@State notPic: string = ''
build() {
Column() {
Text(this.httpCode)
ImageKnifeComponent({
imageKnifeOption:{
loadSrc:'https://gitee.com/openharmony-tpc/ImageKnife/issues/1111111',
errorholderSrc:$r('app.media.failed'),
onLoadListener:{
onLoadFailed:(err)=>{
this.httpCode = err
}
}
}
}).width(100).height(100).margin({bottom:10})
Text(this.httpError)
ImageKnifeComponent({
imageKnifeOption:{
loadSrc:'https://xx.xx.xx',
errorholderSrc:$r('app.media.failed'),
onLoadListener:{
onLoadFailed:(err)=>{
this.httpError = err
}
}
}
}).width(100).height(100).margin({bottom:10})
Text(this.storageError)
ImageKnifeComponent({
imageKnifeOption:{
loadSrc:'/data/storage/el2/base/haps/entry/cache/a/b',
errorholderSrc:$r('app.media.failed'),
onLoadListener:{
onLoadFailed:(err)=>{
this.storageError = err
}
}
}
}).width(100).height(100).margin({bottom:10})
Text(this.fileError)
ImageKnifeComponent({
imageKnifeOption:{
loadSrc:'file://xx.xx.xx',
errorholderSrc:$r('app.media.failed'),
onLoadListener:{
onLoadFailed:(err)=>{
this.fileError = err
}
}
}
}).width(100).height(100).margin({bottom:10})
Text(this.notPic)
ImageKnifeComponent({
imageKnifeOption:{
loadSrc:'xx.xx.xx',
errorholderSrc:$r('app.media.failed'),
onLoadListener:{
onLoadFailed:(err)=>{
this.notPic = err
}
}
}
}).width(100).height(100).margin({bottom:10})
}
}
}

View File

@ -398,13 +398,13 @@ struct ImageTransformation {
transformations.push(new CropSquareTransformation());
}
if (this.isCropTop) {
transformations.push(new CropTransformation(25, 25, 0));
transformations.push(new CropTransformation(100, 100, 0));
}
if (this.isCropCenter) {
transformations.push(new CropTransformation(25, 25, 1));
transformations.push(new CropTransformation(100, 100, 1));
}
if (this.isCropBottom) {
transformations.push(new CropTransformation(25, 25, 2));
transformations.push(new CropTransformation(100, 100, 2));
}
if (this.isSepia) {
transformations.push(new SepiaTransformation());

View File

@ -88,6 +88,11 @@ struct Index {
});
})
Button($r('app.string.Error_Message')).margin({top:10}).onClick(()=>{
router.push({
uri: 'pages/ErrorMessageDownload',
});
})
Button($r('app.string.Test_custom_download')).margin({top:10}).onClick(()=>{
router.push({
uri: 'pages/TestSetCustomImagePage',
@ -219,6 +224,11 @@ struct Index {
});
})
Button($r('app.string.test_exif')).margin({ top: 10 }).onClick(() => {
router.push({
uri: 'pages/TestImageExif',
});
})
}
} .width('100%')
.height('100%')

View File

@ -108,7 +108,7 @@ struct MultipleImageCallBack {
console.log('image load multiple loadFail:' + this.failIndex)
},
onLoadCancel:(message,request)=>{
let flag = request?.imageKnifeData?.type ? true : false
let flag = request?.imageKnifeData?.timeInfo?.netRequestStartTime ? true : false
if (flag) {
this.cancelLoadIndex++
} else {

View File

@ -0,0 +1,92 @@
/*
* Copyright (C) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ImageKnifeComponent } from '@ohos/libraryimageknife';
import fs from '@ohos.file.fs';
@Entry
@Component
struct LocalImage {
scroller: Scroller = new Scroller;
build() {
Scroll(this.scroller) {
Column() {
Column() {
Text($r('app.string.base_image'))
.fontSize(20)
.fontWeight(FontWeight.Bold)
Row() {
Image($rawfile('rotate/rotate.jpg')).width(100).height(100).margin({ right: 10 })
ImageKnifeComponent({
imageKnifeOption: {
loadSrc: $rawfile('rotate/rotate.jpg'),
objectFit: ImageFit.Contain
}
}).width(100).height(100)
}
}
.margin({ bottom: 20 })
Column() {
Text($r('app.string.rotate_mirror'))
.fontSize(20)
.fontWeight(FontWeight.Bold)
Row() {
Image($rawfile('rotate/rotate_mirror.jpg')).width(100).height(100).margin({ right: 10 })
ImageKnifeComponent({
imageKnifeOption: {
loadSrc: $rawfile('rotate/rotate_mirror.jpg'),
objectFit: ImageFit.Contain
}
}).width(100).height(100)
}
}.margin({ bottom: 20 })
Column() {
Text($r('app.string.rotate_rotate90'))
.fontSize(20)
.fontWeight(FontWeight.Bold)
Row() {
Image($rawfile('rotate/rotate_rotate90.jpg')).width(100).height(100).margin({ right: 10 })
ImageKnifeComponent({
imageKnifeOption: {
loadSrc: $rawfile('rotate/rotate_rotate90.jpg'),
objectFit: ImageFit.Contain
}
}).width(100).height(100)
}
}.margin({ bottom: 20 })
Column() {
Text($r('app.string.rotate_mirror_rotate270'))
.fontSize(20)
.fontWeight(FontWeight.Bold)
Row() {
Image($rawfile('rotate/rotate_mirror_rotate270.jpg')).width(100).height(100).margin({ right: 10 })
ImageKnifeComponent({
imageKnifeOption: {
loadSrc: $rawfile('rotate/rotate_mirror_rotate270.jpg'),
objectFit: ImageFit.Contain
}
}).width(100).height(100)
}
}
}
.width('100%')
}
.height('100%')
}
}

View File

@ -6,7 +6,11 @@
"mainElement": "EntryAbility",
"deviceTypes": [
"default",
"tablet"
"tablet",
"tv",
"wearable",
"car",
"2in1"
],
"deliveryWithInstall": true,
"installationFree": false,

View File

@ -683,6 +683,30 @@
{
"name": "Multiple_CallBack",
"value": "Multiple image callback"
},
{
"name": "Error_Message",
"value": "error message"
},
{
"name": "test_exif",
"value": "Test display orientation base on the EXIF metadata "
},
{
"name": "base_image",
"value": "The image don't carry rotation information"
},
{
"name": "rotate_mirror",
"value": "Mirror horizontal"
},
{
"name": "rotate_rotate90",
"value": "Rotate 90°"
},
{
"name": "rotate_mirror_rotate270",
"value": "Mirror horizontal and rotate 270°"
}
]
}

View File

@ -41,6 +41,8 @@
"pages/AutoImageFit",
"pages/SingleImageCallBack",
"pages/MultipleImageCallBack",
"pages/LocalImage"
"pages/LocalImage",
"pages/ErrorMessageDownload",
"pages/TestImageExif"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -675,6 +675,30 @@
{
"name": "Multiple_CallBack",
"value": "多张图片回调"
},
{
"name": "Error_Message",
"value": "错误信息"
},
{
"name": "base_image",
"value": "图片不携带旋转信息"
},
{
"name": "test_exif",
"value": "测试图片携带的EXIF元数据作为显示方向"
},
{
"name": "rotate_mirror",
"value": "水平翻转"
},
{
"name": "rotate_rotate90",
"value": "顺时针90°"
},
{
"name": "rotate_mirror_rotate270",
"value": "水平翻转后再顺时针270°"
}
]
}

View File

@ -6,7 +6,11 @@
"mainElement": "TestAbility",
"deviceTypes": [
"default",
"tablet"
"tablet",
"tv",
"wearable",
"car",
"2in1"
],
"deliveryWithInstall": true,
"installationFree": false,

View File

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

View File

@ -25,6 +25,7 @@ import { util } from '@kit.ArkTS';
import { image } from '@kit.ImageKit';
import { common } from '@kit.AbilityKit';
import { LogUtil } from './utils/LogUtil';
import { emitter } from '@kit.BasicServicesKit';
export class ImageKnife {
@ -176,6 +177,9 @@ export class ImageKnife {
* @param request 图片请求request
*/
cancel(request:ImageKnifeRequest) {
if (typeof request?.imageKnifeOption.loadSrc === 'string' && !request?.drawMainSuccess) {
emitter.emit(request.imageKnifeOption.loadSrc + request.componentId)
}
request.requestState = ImageKnifeRequestState.DESTROY
}
/**
@ -462,12 +466,12 @@ export class ImageKnife {
return false
}
async execute(request: ImageKnifeRequest,isAnimator?: boolean): Promise<void> {
async execute(request: ImageKnifeRequest): Promise<void> {
LogUtil.log('ImageKnife_DataTime_execute.start:'+request.imageKnifeOption.loadSrc)
if (this.headerMap.size > 0) {
request.addHeaderMap(this.headerMap)
}
this.dispatcher.enqueue(request,isAnimator)
this.dispatcher.enqueue(request)
LogUtil.log('ImageKnife_DataTime_execute.end:'+request.imageKnifeOption.loadSrc)
}

View File

@ -51,7 +51,7 @@ export class ImageKnifeDispatcher {
LogUtil.log('showFromMemomry.start:' + request.componentId + ',srcType:' + requestSource + ',version:' + request.componentVersion + ' isAnimator=' + isAnimator)
let memoryCache: ImageKnifeData | undefined;
let memoryCheckStartTime = Date.now();
if ((typeof (request.imageKnifeOption.loadSrc as image.PixelMap).isEditable) == 'boolean') {
if ((typeof (request.imageKnifeOption.loadSrc as image.PixelMap)?.isEditable) == 'boolean') {
memoryCache = {
source: request.imageKnifeOption.loadSrc as image.PixelMap,
imageWidth: 0,
@ -87,6 +87,7 @@ export class ImageKnifeDispatcher {
if (requestSource == ImageKnifeRequestSource.SRC) {
request.requestState = ImageKnifeRequestState.COMPLETE
request.drawMainSuccess = true
// 回调请求开结束
if (request.imageKnifeOption.onLoadListener?.onLoadSuccess !== undefined) {
this.copyMemoryCacheInfo(memoryCache, request.imageKnifeData);
@ -150,11 +151,11 @@ export class ImageKnifeDispatcher {
request.imageKnifeData = callBackData;
}
enqueue(request: ImageKnifeRequest,isAnimator?: boolean): void {
enqueue(request: ImageKnifeRequest,): void {
//初始化加载回调信息
this.initCallData(request);
//1.内存有的话直接渲染
if (this.showFromMemomry(request, request.imageKnifeOption.loadSrc, ImageKnifeRequestSource.SRC,isAnimator)) {
if (this.showFromMemomry(request, request.imageKnifeOption.loadSrc, ImageKnifeRequestSource.SRC,request.animator)) {
return
}
// 2.内存获取占位图
@ -168,18 +169,20 @@ export class ImageKnifeDispatcher {
this.jobQueue.add(request)
return
}
this.executeJob(request,isAnimator)
this.executeJob(request)
}
executeJob(request: ImageKnifeRequest,isAnimator?: boolean): void {
executeJob(request: ImageKnifeRequest): void {
LogUtil.log('executeJob.start:' + request.componentId + ',version:' + request.componentVersion)
// 加载占位符
if (request.imageKnifeOption.placeholderSrc !== undefined && request.drawPlayHolderSuccess == false) {
this.getAndShowImage(request, request.imageKnifeOption.placeholderSrc, ImageKnifeRequestSource.PLACE_HOLDER)
}
if (request.imageKnifeOption.onLoadListener?.onLoadStart !== undefined) {
request.imageKnifeOption.onLoadListener?.onLoadStart(request)
}
// 加载主图
this.getAndShowImage(request, request.imageKnifeOption.loadSrc, ImageKnifeRequestSource.SRC,isAnimator)
this.getAndShowImage(request, request.imageKnifeOption.loadSrc, ImageKnifeRequestSource.SRC,request.animator)
LogUtil.log('executeJob.end:' + request.componentId + ',version:' + request.componentVersion)
}
@ -188,9 +191,6 @@ export class ImageKnifeDispatcher {
*/
getAndShowImage(currentRequest: ImageKnifeRequest, imageSrc: string | PixelMap | Resource, requestSource: ImageKnifeRequestSource,isAnimator?: boolean): void {
LogUtil.log('getAndShowImage.start:' + currentRequest.componentId + ',srcType:' + requestSource + ',version:' + currentRequest.componentVersion)
if (requestSource === ImageKnifeRequestSource.SRC && currentRequest.imageKnifeOption.onLoadListener?.onLoadStart !== undefined) {
currentRequest.imageKnifeOption.onLoadListener?.onLoadStart(currentRequest)
}
let memoryKey: string = this.engineKey.generateMemoryKey(imageSrc, requestSource, currentRequest.imageKnifeOption,isAnimator, currentRequest.componentWidth, currentRequest.componentHeight)
let requestList: List<ImageKnifeRequestWithSource> | undefined = this.executingJobMap.get(memoryKey)
@ -214,9 +214,7 @@ export class ImageKnifeDispatcher {
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]
}
resName = (imageSrc as Resource).params![0]
} else if(typeof imageSrc == 'string') {
src = imageSrc
}
@ -355,17 +353,64 @@ export class ImageKnifeDispatcher {
}
let pixelmap = requestJobResult.pixelMap;
// 请求取消
if (currentRequest.requestState === ImageKnifeRequestState.DESTROY) {
this.executingJobMap.remove(memoryKey);
requestList.forEach((requestWithSource: ImageKnifeRequestWithSource) => {
if (currentRequest.componentId !== requestWithSource.request.componentId && requestWithSource.request.requestState !== ImageKnifeRequestState.DESTROY) {
// 加载占位符
if (requestWithSource.source === ImageKnifeRequestSource.PLACE_HOLDER &&
requestWithSource.request.imageKnifeOption.placeholderSrc !== undefined &&
requestWithSource.request.drawPlayHolderSuccess == false) {
this.getAndShowImage(requestWithSource.request, requestWithSource.request.imageKnifeOption.placeholderSrc, ImageKnifeRequestSource.PLACE_HOLDER)
} else if (requestWithSource.source === ImageKnifeRequestSource.ERROR_HOLDER &&
requestWithSource.request.imageKnifeOption.errorholderSrc !== undefined) {
this.getAndShowImage(requestWithSource.request, requestWithSource.request.imageKnifeOption.errorholderSrc, ImageKnifeRequestSource.ERROR_HOLDER)
} else if (requestWithSource.source === ImageKnifeRequestSource.SRC) {
// 加载主图
this.getAndShowImage(requestWithSource.request, requestWithSource.request.imageKnifeOption.loadSrc, ImageKnifeRequestSource.SRC,requestWithSource.request.animator)
}
} else {
if (pixelmap !== undefined && typeof pixelmap !== 'string') {
(pixelmap as PixelMap).release()
}
if (requestWithSource.source == ImageKnifeRequestSource.SRC && requestWithSource.request.imageKnifeOption.onLoadListener?.onLoadCancel) {
// 回调请求成功
// 回调请求成功
//设置失败回调的时间点
let callBackData = requestWithSource.request.imageKnifeData;
if (requestJobResult.imageKnifeData && requestJobResult.imageKnifeData.timeInfo) {
requestJobResult.imageKnifeData.timeInfo.requestCancelTime = Date.now();
if (requestJobResult.imageKnifeData.errorInfo) {
requestJobResult.imageKnifeData.errorInfo.phase = LoadPhase.PHASE_WILL_SHOW;
requestJobResult.imageKnifeData.errorInfo.code = LoadPixelMapCode.IMAGE_LOAD_CANCEL_FAILED_CODE;
}
}
this.assembleImageKnifeData(callBackData,requestJobResult.imageKnifeData,requestWithSource.request)
LogUtil.log('getAndShowImage cancel:' + requestWithSource.request.componentId + ',srcType:' + requestSource + ',version:' + requestWithSource.request.componentVersion)
requestWithSource.request.imageKnifeOption.onLoadListener.onLoadCancel(requestJobResult.loadFail ?? 'component has destroyed from load', requestWithSource.request)
}
}
})
this.dispatchNextJob()
return
}
// 请求失败
if (pixelmap === undefined) {
this.executingJobMap.remove(memoryKey);
requestList.forEach((requestWithSource: ImageKnifeRequestWithSource) => {
requestWithSource.request.requestState = ImageKnifeRequestState.ERROR
LogUtil.error('getAndShowImage_CallBack.pixelmap failed:' + currentRequest.componentId + ',srcType:' + requestSource + ',version:' + currentRequest.componentVersion + " error: " + requestJobResult.loadFail)
LogUtil.error('getAndShowImage_CallBack.pixelmap failed:' + currentRequest.componentId + ',srcType:' +
requestSource + ',version:' + currentRequest.componentVersion + " error: " + requestJobResult.loadFail)
// 回调请求失败
if (requestWithSource.source === ImageKnifeRequestSource.SRC &&
requestWithSource.request.imageKnifeOption.onLoadListener?.onLoadFailed !== undefined &&
requestJobResult.loadFail) {
this.assembleImageKnifeData(requestWithSource.request.imageKnifeData, requestJobResult.imageKnifeData, requestWithSource.request)
requestWithSource.request.imageKnifeOption.onLoadListener.onLoadFailed(requestJobResult.loadFail,requestWithSource.request);
this.assembleImageKnifeData(requestWithSource.request.imageKnifeData, requestJobResult.imageKnifeData,
requestWithSource.request)
requestWithSource.request.imageKnifeOption.onLoadListener.onLoadFailed(requestJobResult.loadFail,
requestWithSource.request);
}
if (requestWithSource.source === ImageKnifeRequestSource.SRC &&
requestWithSource.request.imageKnifeOption.errorholderSrc !== undefined) {
@ -414,8 +459,8 @@ export class ImageKnifeDispatcher {
//构建缓存保存的ImageKnifeData
let saveCacheImageData: ImageKnifeData = {
source: pixelmap!,
imageWidth: requestJobResult.size == undefined ? 0 : requestJobResult.size.width,
imageHeight: requestJobResult.size == undefined ? 0 : requestJobResult.size.height,
imageWidth: requestJobResult.size?.width ?? 0,
imageHeight: requestJobResult.size?.height ?? 0,
type: requestJobResult.type,
bufSize: requestJobResult.bufferSize,
imageAnimator: imageKnifeData.imageAnimator
@ -432,50 +477,31 @@ export class ImageKnifeDispatcher {
if (requestList !== undefined) {
// key相同的request一起绘制
requestList.forEach((requestWithSource: ImageKnifeRequestWithSource) => {
if (requestWithSource.request.requestState !== ImageKnifeRequestState.DESTROY) {
// 画主图
if (requestWithSource.source === ImageKnifeRequestSource.SRC ||
requestWithSource.source === ImageKnifeRequestSource.ERROR_HOLDER
|| (requestWithSource.source === ImageKnifeRequestSource.PLACE_HOLDER &&
requestWithSource.request.requestState === ImageKnifeRequestState.PROGRESS)) {
requestWithSource.request.ImageKnifeRequestCallback.showPixelMap(requestWithSource.request.componentVersion,
imageKnifeData.source, { width: imageKnifeData.imageWidth, height: imageKnifeData.imageHeight },
requestWithSource.source, imageKnifeData.imageAnimator);
}
// 画主图
if (requestWithSource.source === ImageKnifeRequestSource.SRC ||
requestWithSource.source === ImageKnifeRequestSource.ERROR_HOLDER
|| (requestWithSource.source === ImageKnifeRequestSource.PLACE_HOLDER &&
requestWithSource.request.requestState === ImageKnifeRequestState.PROGRESS)) {
requestWithSource.request.ImageKnifeRequestCallback.showPixelMap(requestWithSource.request.componentVersion,
imageKnifeData.source, { width: imageKnifeData.imageWidth, height: imageKnifeData.imageHeight },
requestWithSource.source, imageKnifeData.imageAnimator);
}
if (requestWithSource.source == ImageKnifeRequestSource.SRC) {
requestWithSource.request.requestState = ImageKnifeRequestState.COMPLETE;
if (requestWithSource.request.imageKnifeOption.onLoadListener &&
requestWithSource.request.imageKnifeOption.onLoadListener.onLoadSuccess) {
// 回调请求成功
this.assembleImageKnifeData(requestWithSource.request.imageKnifeData, imageKnifeData,requestWithSource.request);
requestWithSource.request.imageKnifeOption.onLoadListener.onLoadSuccess(imageKnifeData.source,
saveCacheImageData, requestWithSource.request);
}
} else if (requestWithSource.source == ImageKnifeRequestSource.ERROR_HOLDER) {
requestWithSource.request.requestState = ImageKnifeRequestState.ERROR;
}
} else {
if (requestWithSource.source == ImageKnifeRequestSource.SRC && requestWithSource.request.imageKnifeOption.onLoadListener?.onLoadCancel) {
if (requestWithSource.source == ImageKnifeRequestSource.SRC) {
requestWithSource.request.requestState = ImageKnifeRequestState.COMPLETE;
requestWithSource.request.drawMainSuccess = true
if (requestWithSource.request.imageKnifeOption.onLoadListener &&
requestWithSource.request.imageKnifeOption.onLoadListener.onLoadSuccess) {
// 回调请求成功
// 回调请求成功
//设置失败回调的时间点
let callBackData = requestWithSource.request.imageKnifeData;
if (requestJobResult.imageKnifeData && requestJobResult.imageKnifeData.timeInfo) {
requestJobResult.imageKnifeData.timeInfo.requestCancelTime = Date.now();
if (requestJobResult.imageKnifeData.errorInfo) {
requestJobResult.imageKnifeData.errorInfo.phase = LoadPhase.PHASE_WILL_SHOW;
requestJobResult.imageKnifeData.errorInfo.code = LoadPixelMapCode.IMAGE_LOAD_CANCEL_FAILED_CODE;
}
}
this.assembleImageKnifeData(callBackData,requestJobResult.imageKnifeData,requestWithSource.request)
LogUtil.log('getAndShowImage cancel:' + requestWithSource.request.componentId + ',srcType:' + requestSource + ',version:' + requestWithSource.request.componentVersion)
requestWithSource.request.imageKnifeOption.onLoadListener.onLoadCancel('component has destroyed from load', requestWithSource.request)
this.assembleImageKnifeData(requestWithSource.request.imageKnifeData, imageKnifeData,
requestWithSource.request);
requestWithSource.request.imageKnifeOption.onLoadListener.onLoadSuccess(imageKnifeData.source,
saveCacheImageData, requestWithSource.request);
}
} else if (requestWithSource.source == ImageKnifeRequestSource.ERROR_HOLDER) {
requestWithSource.request.requestState = ImageKnifeRequestState.ERROR;
}
});
this.executingJobMap.remove(memoryKey);
this.dispatchNextJob();
} else {

View File

@ -16,6 +16,7 @@ import {
CacheStrategy,
DecodeImageInfo,
ErrorInfo,
FlipRotate,
ImageKnifeData,
ImageKnifeRequestSource,
ImageKnifeRequestWithSource, RequestJobRequest,
@ -36,6 +37,7 @@ import { FileTypeUtil } from './utils/FileTypeUtil';
import { DownsampleStrategy } from './downsampling/DownsampleStartegy';
import { Downsampler } from './downsampling/Downsampler';
import { common } from '@kit.AbilityKit';
import { ImageLoaderFactory } from './loaderStrategy/ImageLoaderFactory';
class RequestData {
receiveSize: number = 2000
@ -163,6 +165,14 @@ export class ImageKnifeLoader {
}
timeInfo.decodeStartTime = Date.now();
// 获取旋转信息
let exif: string | undefined = undefined;
await imageSource.getImageProperty(image.PropertyKey.ORIENTATION).then((res)=>{
exif = res;
}).catch((error: BusinessError)=>{
LogUtil.info("The normal image don't have rotation information, " + error.message);
})
await imageSource.createPixelMap(decodingOptions)
.then((pixelmap: PixelMap) => {
timeInfo.decodeEndTime = Date.now();
@ -185,6 +195,17 @@ export class ImageKnifeLoader {
} catch (e) {
LogUtil.error('PixelMap setTransferDetached failed:' + JSON.stringify(e))
}
// 设置翻转和旋转角度
if(exif && exif !== 'Top-left'){
let result = ImageKnifeLoader.getOrientation(exif);
if(result.horizontal || result.vertical) {
resPixelmap?.flipSync(result.horizontal, result.vertical);
}
if(result.rotate > 0) {
resPixelmap?.rotateSync(result.rotate);
}
LogUtil.log('The normal image set flip , horizontal=' + result.horizontal + ', vertical=' +result.vertical + ', rotate=' + result.rotate);
}
//获取各个pixelMap的大小
if (resPixelmap !== undefined) {
@ -252,6 +273,15 @@ export class ImageKnifeLoader {
return
}
timeInfo.decodeStartTime = Date.now();
// 获取旋转信息
let exif: string | undefined = undefined;
await imageSource.getImageProperty(image.PropertyKey.ORIENTATION).then((res)=>{
exif = res;
}).catch((error: BusinessError)=>{
LogUtil.info("Svg image don't have rotation information, " + error.message);
})
await imageSource.createPixelMap(opts)
.then((pixelmap: PixelMap) => {
timeInfo.decodeEndTime = Date.now();
@ -259,6 +289,17 @@ export class ImageKnifeLoader {
imageSource.release()
try {
resPixelmap.setTransferDetached(true)
// 设置翻转和旋转角度
if(exif && exif !== 'Top-left'){
let result = ImageKnifeLoader.getOrientation(exif);
if(result.horizontal || result.vertical) {
resPixelmap?.flipSync(result.horizontal, result.vertical);
}
if(result.rotate > 0) {
resPixelmap?.rotateSync(result.rotate);
}
LogUtil.log('Svg image set flip , horizontal=' + result.horizontal + ', vertical=' +result.vertical + ', rotate=' + result.rotate);
}
} catch (e) {
LogUtil.error('PixelMap setTransferDetached failed:' + JSON.stringify(e))
}
@ -445,178 +486,14 @@ export class ImageKnifeLoader {
errorInfo: error
};
// 判断自定义下载
if (request.customGetImage !== undefined && request.requestSource == ImageKnifeRequestSource.SRC && typeof request.src == 'string') {
// 先从文件缓存获取
ImageKnifeLoader.assembleError(callBackData,LoadPhase.PHASE_CUSTOM_LOAD)
callBackTimeInfo.diskCheckStartTime = Date.now();
resBuf = FileCache.getFileCacheByFile(request.context, fileKey , request.fileCacheFolder)
callBackTimeInfo.diskCheckEndTime = Date.now();
if (resBuf === undefined) {
LogUtil.log('start customGetImage src=' + request.componentId + ',srcType:' + request.requestSource + ',' + request.componentVersion)
const headerObj: Record<string, Object> = ImageKnifeLoader.getHeaderObj(request)
try {
request.customGetImage(request.context, request.src, headerObj)
.then((buffer)=>{
if(buffer != undefined) {
ImageKnifeLoader.FileCacheParseImage(request,buffer,fileKey,callBackData)
} else {
loadError = 'customGetImage loadFail undefined'
ImageKnifeLoader.makeEmptyResult(request,loadError, ImageKnifeLoader.assembleError(callBackData, LoadPhase.PHASE_CUSTOM_LOAD, LoadPixelMapCode.IMAGE_CUSTOM_LOAD_FAILED_CODE))
}
}).catch((err:string)=>{
ImageKnifeLoader.makeEmptyResult(request,err, ImageKnifeLoader.assembleError(callBackData, LoadPhase.PHASE_CUSTOM_LOAD, LoadPixelMapCode.IMAGE_CUSTOM_LOAD_FAILED_CODE))
})
} catch (e) {
loadError = 'customGetImage loadFail failed'
ImageKnifeLoader.makeEmptyResult(request,loadError + e, ImageKnifeLoader.assembleError(callBackData, LoadPhase.PHASE_CUSTOM_LOAD, LoadPixelMapCode.IMAGE_CUSTOM_LOAD_FAILED_CODE))
}
LogUtil.log('end customGetImage src=' + request.componentId + ',srcType:' + request.requestSource + ',' + request.componentVersion)
return
}
}
else {
if (typeof request.src === 'string') {
if (request.src.indexOf('http://') == 0 || request.src.indexOf('https://') == 0) { //从网络下载
// 先从文件缓存获取
ImageKnifeLoader.assembleError(callBackData,LoadPhase.PHASE_NET)
LogUtil.log('get fileCache buffer start:' + request.componentId + ',srcType:' + request.requestSource + ',' + request.componentVersion)
callBackTimeInfo.diskCheckStartTime = Date.now()
resBuf = FileCache.getFileCacheByFile(request.context, fileKey , request.fileCacheFolder)
callBackTimeInfo.diskCheckEndTime = Date.now()
LogUtil.log('get fileCache buffer end:' + request.componentId + ',srcType:' + request.requestSource + ',' + request.componentVersion)
if (resBuf !== undefined){
LogUtil.log('success get image from filecache for key = ' + fileKey + ' src = ' + request.componentId + ',srcType:' + request.requestSource + ',' + request.componentVersion)
}
else if (request.onlyRetrieveFromCache != true) {
LogUtil.log('HttpDownloadClient.start:' + request.componentId + ',srcType:' + request.requestSource + ',' + request.componentVersion)
callBackTimeInfo.netRequestStartTime = Date.now();
let httpRequest = http.createHttp();
let progress: number = 0
let arrayBuffers:ArrayBuffer[] = []
const headerObj: Record<string, Object> = ImageKnifeLoader.getHeaderObj(request)
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: request.connectTimeout == undefined ? 60000 : request.connectTimeout,
readTimeout: request.readTimeout == undefined ? 30000 : request.readTimeout,
caPath: request.caPath === undefined ? undefined : request.caPath,
});
promise.then((data: number) => {
LogUtil.log('HttpDownloadClient.end:' + request.componentId + ',srcType:' + request.requestSource + ',' + request.componentVersion)
callBackData.httpCode = data
ImageKnifeLoader.assembleError(callBackData,LoadPhase.PHASE_NET, undefined)
callBackTimeInfo.netRequestEndTime = Date.now();
if (data == 200 || data == 206 || data == 204) {
resBuf = combineArrayBuffers(arrayBuffers)
ImageKnifeLoader.FileCacheParseImage(request,resBuf,fileKey, callBackData)
} else {
loadError = 'HttpDownloadClient has error, http code =' + JSON.stringify(data)
ImageKnifeLoader.makeEmptyResult(request,loadError, ImageKnifeLoader.assembleError(callBackData,LoadPhase.PHASE_NET, LoadPixelMapCode.IMAGE_HTTPS_LOAD_FAILED_CODE))
}
}).catch((err: BusinessError) => {
callBackData.httpCode = err.code
loadError = 'HttpDownloadClient download ERROR : err = ' + JSON.stringify(err)
callBackTimeInfo.netRequestEndTime = Date.now();
ImageKnifeLoader.makeEmptyResult(request,loadError, ImageKnifeLoader.assembleError(callBackData,LoadPhase.PHASE_NET, LoadPixelMapCode.IMAGE_HTTPS_LOAD_FAILED_CODE))
});
return
}
else {
callBackTimeInfo.netRequestEndTime = Date.now();
loadError = 'onlyRetrieveFromCache,do not fetch image src = ' + request.src
}
} else if (request.src.startsWith('datashare://') || request.src.startsWith('file://')) {
ImageKnifeLoader.assembleError(callBackData,LoadPhase.PHASE_SHARE_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) => {
ImageKnifeLoader.assembleError(callBackData,LoadPhase.PHASE_SHARE_FILE, LoadPixelMapCode.IMAGE_LOAD_SHARE_FILE_FAILED_CODE)
loadError = 'LoadDataShareFileClient fs.read err happened uri=' + request.src + ' err.msg=' + err?.message + ' err.code=' + err?.code
})
}).catch((err:BusinessError) => {
ImageKnifeLoader.assembleError(callBackData,LoadPhase.PHASE_SHARE_FILE, LoadPixelMapCode.IMAGE_LOAD_SHARE_FILE_FAILED_CODE)
loadError = 'LoadDataShareFileClient fs.stat err happened uri=' + request.src + ' err.msg=' + err?.message + ' err.code=' + err?.code
})
}).catch((err:BusinessError) => {
ImageKnifeLoader.assembleError(callBackData,LoadPhase.PHASE_SHARE_FILE, LoadPixelMapCode.IMAGE_LOAD_SHARE_FILE_FAILED_CODE)
loadError = 'LoadDataShareFileClient fs.open err happened uri=' + request.src + ' err.msg=' + err?.message + ' err.code=' + err?.code
})
} else if (ImageKnifeLoader.isLocalLoadSrc(request.context, request.src)) { //从本地文件获取
ImageKnifeLoader.assembleError(callBackData,LoadPhase.PHASE_LOCAL_FILE)
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) {
ImageKnifeLoader.assembleError(callBackData,LoadPhase.PHASE_LOCAL_FILE, LoadPixelMapCode.IMAGE_LOAD_LOCAL_FILE_FAILED_CODE)
loadError = 'LocalLoadSrc:' + request.src + ',err:' + err
}
} else {
loadError = 'Parameter not supported:' + request.src
}
} 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(resName.lastIndexOf(".") + 1))).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(resName.lastIndexOf(".") + 1))).buffer as ArrayBuffer
} else {
resBuf = manager.getMediaContentSync(request.src).buffer as ArrayBuffer
}
}
}
}
if (resBuf === undefined){
const loaderStrategy = ImageLoaderFactory.getLoaderStrategy(request);
if (loaderStrategy) {
await loaderStrategy.loadImage(request, requestList, fileKey, callBackData, callBackTimeInfo);
} else {
loadError = `Unsupported request type: ${request.src}`;
callBackTimeInfo.requestEndTime = Date.now();
ImageKnifeLoader.makeEmptyResult(request,loadError ,callBackData)
return
ImageKnifeLoader.makeEmptyResult(request, loadError, callBackData);
}
ImageKnifeLoader.parseImage(resBuf,fileKey,request, callBackData)
}
static isLocalLoadSrc(context: Object | undefined, loadSrc: string): boolean {
if (context != undefined) {
@ -652,4 +529,39 @@ export class ImageKnifeLoader {
}
}
}
static getOrientation(orientation: string | undefined){
let horizontal: boolean = false;
let vertical: boolean = false;
let rotate: number= 0;
switch (orientation){
case 'Top-left': break
case 'Top-right':
horizontal = true;
break;
case 'Bottom-left':
vertical = true;
break
case 'Bottom-right':
rotate = 180;
break;
case 'Left-top':
horizontal = true;
rotate = 270;
break
case 'Right-top':
rotate = 90;
break;
case 'Left-bottom':
rotate = 270;
break
case 'Right-bottom':
horizontal = true;
rotate = 90;
break;
}
let data: FlipRotate = { horizontal: horizontal, vertical: vertical, rotate:rotate };
return data
}
}

View File

@ -70,6 +70,7 @@ export class FileCache {
interface CacheFileInfo {
file: string;
ctime: number;
size: number;
}
// 按照上次访问该文件的时间排序
@ -78,26 +79,25 @@ export class FileCache {
let stat: fs.Stat | undefined = await FileUtils.getInstance().Stat(this.path + filenames[i])
cachefiles.push({
file: filenames[i],
ctime: stat === undefined ? 0 : stat.ctime
ctime: stat === undefined ? 0 : stat.ctime,
size: stat?.size ?? 0
})
}
let sortedCachefiles: CacheFileInfo[] = cachefiles.sort((a, b) => a.ctime - b.ctime)
for (let i = 0; i < sortedCachefiles.length; i++) {
let buf: ArrayBuffer | undefined = await FileUtils.getInstance().readFile(this.path + sortedCachefiles[i].file)
if (buf !== undefined) {
// 处理数量超过size的场景移除即将排除的文件
if (this.lruCache.length == this.maxSize && !this.lruCache.contains(sortedCachefiles[i].file)) {
let remove: number | undefined = this.lruCache.remove(this.lruCache.keys()[0])
if (remove !== undefined) {
FileUtils.getInstance().deleteFile(this.path + this.lruCache.keys()[0])
this.removeMemorySize(buf)
}
const fileSize: number = sortedCachefiles[i].size;
// 处理数量超过size的场景移除即将排除的文件
if (this.lruCache.length == this.maxSize && !this.lruCache.contains(sortedCachefiles[i].file)) {
let remove: number | undefined = this.lruCache.remove(this.lruCache.keys()[0])
if (remove !== undefined) {
FileUtils.getInstance().deleteFile(this.path + this.lruCache.keys()[0])
this.removeMemorySize(fileSize)
}
this.lruCache.put(sortedCachefiles[i].file, buf.byteLength)
this.addMemorySize(buf)
}
this.lruCache.put(sortedCachefiles[i].file, fileSize)
this.addMemorySize(fileSize)
}
this.trimToSize();
@ -192,7 +192,6 @@ export class FileCache {
if (!this.isInited) {
return
}
this.isInited = false
this.lruCache.clear()
this.currentMemory = 0;

View File

@ -18,6 +18,7 @@ import common from '@ohos.app.ability.common';
import { ImageKnife } from '../ImageKnife';
import { LogUtil } from '../utils/LogUtil';
import { ImageKnifeRequestSource } from '../model/ImageKnifeData';
import { emitter } from '@kit.BasicServicesKit';
@Component
export struct ImageKnifeAnimatorComponent {
@ -26,11 +27,13 @@ export struct ImageKnifeAnimatorComponent {
@State pixelMap: PixelMap | string | undefined = undefined
@State imageAnimator: Array<ImageFrameInfo> | undefined = undefined
@State adaptiveWidth: Length = '100%'
@State adaptiveHeight: Length = '100%'
@State adaptiveHeight: Length | undefined = '100%'
@State objectFit: ImageFit = ImageFit.Contain
private componentId: number = 0
private request: ImageKnifeRequest | undefined
private lastWidth: number = 0
private lastHeight: number = 0
private isImageFitAutoResize: boolean = false
private currentWidth: number = 0
private currentHeight: number = 0
private componentVersion: number = 0
@ -38,23 +41,33 @@ export struct ImageKnifeAnimatorComponent {
aboutToAppear(): void {
this.objectFit = this.imageKnifeOption.objectFit === undefined ? ImageFit.Contain : this.imageKnifeOption.objectFit
this.componentId = this.getUniqueId()
}
aboutToDisappear(): void {
if (this.request !== undefined) {
this.request.requestState = ImageKnifeRequestState.DESTROY
this.request = undefined
}
this.emitterDestroy()
this.clearLastRequest()
}
aboutToRecycle() {
this.emitterDestroy()
this.clearLastRequest()
}
emitterDestroy() {
if (typeof this.request?.imageKnifeOption.loadSrc === 'string' && !this.request?.drawMainSuccess) {
emitter.emit(this.request.imageKnifeOption.loadSrc + this.componentId)
}
}
/**
* 对已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() {
ImageAnimator()
.images(this.imageAnimator)
@ -79,8 +92,14 @@ export struct ImageKnifeAnimatorComponent {
' loadSrc = ' + this.imageKnifeOption.loadSrc +
' placeholderSrc = ' + this.imageKnifeOption.placeholderSrc +
' errorholderSrc = ' + this.imageKnifeOption.errorholderSrc +
' componentId = ' + this.getUniqueId())
ImageKnife.getInstance().execute(this.getRequest(this.currentWidth, this.currentHeight),true)
' componentId = ' + this.componentId)
if (this.imageKnifeOption.objectFit === ImageFit.Auto && this.isImageFitAutoResize) {
this.isImageFitAutoResize = false
} else {
ImageKnife.getInstance().execute(this.getRequest(
this.currentWidth, this.currentHeight, this.componentId))
}
}
}
})
@ -92,17 +111,17 @@ export struct ImageKnifeAnimatorComponent {
}
watchImageKnifeOption() {
if (this.request !== undefined) {
this.request.requestState = ImageKnifeRequestState.DESTROY
}
this.request = undefined
this.clearLastRequest()
this.componentVersion++
this.isImageFitAutoResize = false
this.objectFit = this.imageKnifeOption.objectFit === undefined ? ImageFit.Contain : this.imageKnifeOption.objectFit
LogUtil.log('watchImageKnifeOption execute request:width=' + this.currentWidth + ' height= ' + this.currentHeight +
' loadSrc = ' + this.imageKnifeOption.loadSrc +
' placeholderSrc = ' + this.imageKnifeOption.placeholderSrc +
' errorholderSrc = ' + this.imageKnifeOption.errorholderSrc +
' componentId = ' + this.getUniqueId())
ImageKnife.getInstance().execute(this.getRequest(this.currentWidth, this.currentHeight),true)
' componentId = ' + this.componentId)
ImageKnife.getInstance().execute(this.getRequest(
this.currentWidth, this.currentHeight, this.componentId))
}
getCurrentContext(): common.UIAbilityContext {
@ -112,43 +131,52 @@ export struct ImageKnifeAnimatorComponent {
return this.currentContext
}
getRequest(width: number, height: number): ImageKnifeRequest {
if (this.request == undefined) {
this.request = new ImageKnifeRequest(
this.imageKnifeOption,
this.imageKnifeOption.context !== undefined ? this.imageKnifeOption.context : this.getCurrentContext(),
width,
height,
this.componentVersion,
{
showPixelMap: (version: number, pixelMap: PixelMap | string,size: Size, requestSource: ImageKnifeRequestSource,imageAnimator?: Array<ImageFrameInfo>) => {
if (version !== this.componentVersion) {
return //针对reuse场景不显示历史图片
}
if (imageAnimator != undefined) {
this.imageAnimator = imageAnimator
} else {
this.imageAnimator = [
{
src: pixelMap
}
]
}
if (requestSource == ImageKnifeRequestSource.SRC) {
this.objectFit =
this.imageKnifeOption.objectFit === undefined ? ImageFit.Contain : this.imageKnifeOption.objectFit
} else if (requestSource == ImageKnifeRequestSource.PLACE_HOLDER) {
this.objectFit =
this.imageKnifeOption.placeholderObjectFit === undefined ? (this.imageKnifeOption.objectFit === undefined ? ImageFit.Contain : this.imageKnifeOption.objectFit) : this.imageKnifeOption.placeholderObjectFit
} else {
this.objectFit =
this.imageKnifeOption.errorholderObjectFit === undefined ? (this.imageKnifeOption.objectFit === undefined ? ImageFit.Contain : this.imageKnifeOption.objectFit) : this.imageKnifeOption.errorholderObjectFit
}
getRequest(width: number, height: number,componentId: number): ImageKnifeRequest {
this.request = new ImageKnifeRequest(
this.imageKnifeOption,
this.imageKnifeOption.context !== undefined ? this.imageKnifeOption.context : this.getCurrentContext(),
width,
height,
this.componentVersion,
{
showPixelMap: (version: number, pixelMap: PixelMap | string, size: Size, requestSource: ImageKnifeRequestSource,
imageAnimator?: Array<ImageFrameInfo>) => {
if (version !== this.componentVersion) {
return //针对reuse场景不显示历史图片
}
if (imageAnimator != undefined) {
this.imageAnimator = imageAnimator
} else {
this.imageAnimator = [
{
src: pixelMap
}
]
}
})
}
if (this.imageKnifeOption.objectFit === ImageFit.Auto && this.isImageFitAutoResize == false &&
requestSource == ImageKnifeRequestSource.SRC) {
this.adaptiveHeight = undefined
this.isImageFitAutoResize = true
}
if (requestSource == ImageKnifeRequestSource.SRC) {
this.objectFit =
this.imageKnifeOption.objectFit === undefined ? ImageFit.Contain : this.imageKnifeOption.objectFit
} else if (requestSource == ImageKnifeRequestSource.PLACE_HOLDER) {
this.objectFit =
this.imageKnifeOption.placeholderObjectFit === undefined ?
(this.imageKnifeOption.objectFit === undefined ? ImageFit.Contain : this.imageKnifeOption.objectFit) :
this.imageKnifeOption.placeholderObjectFit
} else {
this.objectFit =
this.imageKnifeOption.errorholderObjectFit === undefined ?
(this.imageKnifeOption.objectFit === undefined ? ImageFit.Contain : this.imageKnifeOption.objectFit) :
this.imageKnifeOption.errorholderObjectFit
}
}
})
this.request.animator = true
return this.request
}
}

View File

@ -20,6 +20,7 @@ import { LogUtil } from '../utils/LogUtil';
import { ImageKnifeData, ImageKnifeRequestSource } from '../model/ImageKnifeData';
import { IEngineKey } from '../key/IEngineKey';
import { DefaultEngineKey } from '../key/DefaultEngineKey';
import { emitter } from '@kit.BasicServicesKit';
@Component
export struct ImageKnifeComponent {
@ -29,6 +30,7 @@ export struct ImageKnifeComponent {
@State adaptiveWidth: Length = '100%'
@State adaptiveHeight: Length | undefined = '100%'
@State objectFit: ImageFit = ImageFit.Contain
private componentId: number = 0
private request: ImageKnifeRequest | undefined
private lastWidth: number = 0
private lastHeight: number = 0
@ -40,7 +42,7 @@ export struct ImageKnifeComponent {
aboutToAppear(): void {
this.objectFit = (this.imageKnifeOption.objectFit === undefined || this.imageKnifeOption.objectFit === ImageFit.Auto) ? ImageFit.Contain : this.imageKnifeOption.objectFit
this.componentId = this.getUniqueId()
if(this.syncLoad) { //针对部分消息列表最新消息的图片闪动问题建议使用同步方式在aboutToAppear时加载图片
let engineKey: IEngineKey = new DefaultEngineKey();
let memoryCacheSrc: ImageKnifeData | undefined = ImageKnife.getInstance()
@ -65,13 +67,21 @@ export struct ImageKnifeComponent {
}
aboutToDisappear(): void {
this.emitterDestroy()
this.clearLastRequest()
}
aboutToRecycle() {
this.pixelMap = ImageContent.EMPTY
this.emitterDestroy()
this.clearLastRequest()
}
emitterDestroy() {
if (typeof this.request?.imageKnifeOption.loadSrc === 'string' && !this.request?.drawMainSuccess) {
emitter.emit(this.request.imageKnifeOption.loadSrc + this.componentId)
}
}
/**
* 对已DESTROY的组件不再发起请求
*/
@ -105,13 +115,13 @@ export struct ImageKnifeComponent {
' loadSrc = ' + this.imageKnifeOption.loadSrc +
' placeholderSrc = ' + this.imageKnifeOption.placeholderSrc +
' errorholderSrc = ' + this.imageKnifeOption.errorholderSrc +
' componentId = ' + this.getUniqueId())
' componentId = ' + this.componentId)
if (this.imageKnifeOption.objectFit === ImageFit.Auto && this.isImageFitAutoResize) {
this.isImageFitAutoResize = false
} else {
ImageKnife.getInstance().execute(this.getRequest(
this.currentWidth, this.currentHeight, this.getUniqueId()))
this.currentWidth, this.currentHeight, this.componentId))
}
}
}
@ -127,9 +137,9 @@ export struct ImageKnifeComponent {
' loadSrc = ' + this.imageKnifeOption.loadSrc +
' placeholderSrc = ' + this.imageKnifeOption.placeholderSrc +
' errorholderSrc = ' + this.imageKnifeOption.errorholderSrc +
' componentId = ' + this.getUniqueId())
' componentId = ' + this.componentId)
ImageKnife.getInstance().execute(this.getRequest(
this.currentWidth, this.currentHeight, this.getUniqueId()))
this.currentWidth, this.currentHeight, this.componentId))
}
getCurrentContext(): common.UIAbilityContext {

View File

@ -0,0 +1,75 @@
/*
* Copyright (C) 2025 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 { FileCache } from '../cache/FileCache';
import { IImageLoaderStrategy } from './IImageLoaderStrategy';
import { ImageKnifeLoader } from '../ImageKnifeLoader';
import { ImageKnifeData, ImageKnifeRequestWithSource, RequestJobRequest, TimeInfo } from '../model/ImageKnifeData';
import { LoadPhase, LoadPixelMapCode } from '../utils/Constants';
import { LogUtil } from '../utils/LogUtil';
import List from '@ohos.util.List';
// 自定义加载策略
export class CustomLoaderStrategy implements IImageLoaderStrategy {
async loadImage(
request: RequestJobRequest,
requestList: List<ImageKnifeRequestWithSource> | undefined,
fileKey: string,
callBackData: ImageKnifeData,
callBackTimeInfo: TimeInfo
): Promise<void> {
let resBuf: ArrayBuffer | undefined;
let loadError: string = '';
// 从文件缓存获取
ImageKnifeLoader.assembleError(callBackData, LoadPhase.PHASE_CUSTOM_LOAD);
callBackTimeInfo.diskCheckStartTime = Date.now();
resBuf = FileCache.getFileCacheByFile(request.context, fileKey, request.fileCacheFolder);
callBackTimeInfo.diskCheckEndTime = Date.now();
if (resBuf !== undefined) {
ImageKnifeLoader.parseImage(resBuf, fileKey, request, callBackData);
} else if (!request.onlyRetrieveFromCache) {
LogUtil.log('start customGetImage src=' + request.componentId + ',srcType:' + request.requestSource + ',' +
request.componentVersion);
const headerObj: Record<string, Object> = ImageKnifeLoader.getHeaderObj(request);
try {
request.customGetImage!(request.context, request.src as string, headerObj)
.then((buffer)=>{
if(buffer !== undefined && buffer !== null) {
ImageKnifeLoader.FileCacheParseImage(request,buffer,fileKey,callBackData);
} else {
loadError = 'customGetImage loadFail undefined';
ImageKnifeLoader.makeEmptyResult(request,loadError, ImageKnifeLoader.assembleError(callBackData,
LoadPhase.PHASE_CUSTOM_LOAD, LoadPixelMapCode.IMAGE_CUSTOM_LOAD_FAILED_CODE));
}
}).catch((err:string)=>{
ImageKnifeLoader.makeEmptyResult(request,err, ImageKnifeLoader.assembleError(callBackData,
LoadPhase.PHASE_CUSTOM_LOAD, LoadPixelMapCode.IMAGE_CUSTOM_LOAD_FAILED_CODE));
})
} catch (e) {
loadError = 'customGetImage loadFail failed';
ImageKnifeLoader.makeEmptyResult(request,loadError + e, ImageKnifeLoader.assembleError(callBackData,
LoadPhase.PHASE_CUSTOM_LOAD, LoadPixelMapCode.IMAGE_CUSTOM_LOAD_FAILED_CODE));
}
LogUtil.log('end customGetImage src=' + request.componentId + ',srcType:' +
request.requestSource + ',' + request.componentVersion);
return;
} else {
loadError = `onlyRetrieveFromCache, do not fetch image src = ${request.src}`;
ImageKnifeLoader.makeEmptyResult(request, loadError, callBackData);
}
return;
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright (C) 2025 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 { RequestJobRequest, ImageKnifeRequestWithSource, ImageKnifeData, TimeInfo } from '../model/ImageKnifeData';
import { IImageLoaderStrategy } from './IImageLoaderStrategy';
import List from '@ohos.util.List';
import { ImageKnifeLoader } from '../ImageKnifeLoader';
import { LoadPhase, LoadPixelMapCode } from '../utils/Constants';
import fs from '@ohos.file.fs';
export class FileLocalLoadStrategy implements IImageLoaderStrategy {
loadImage(request: RequestJobRequest, requestList: List<ImageKnifeRequestWithSource> | undefined, fileKey: string,
callBackData: ImageKnifeData, callBackTimeInfo: TimeInfo): Promise<void> {
let resBuf: ArrayBuffer | undefined;
let loadError: string = '';
if (typeof request.src === 'string' && ImageKnifeLoader.isLocalLoadSrc(request.context, request.src)) {
ImageKnifeLoader.assembleError(callBackData, LoadPhase.PHASE_LOCAL_FILE);
try {
const stat = fs.statSync(request.src);
if (stat.size > 0) {
const file = fs.openSync(request.src, fs.OpenMode.READ_ONLY);
resBuf = new ArrayBuffer(stat.size);
fs.readSync(file.fd, resBuf);
fs.closeSync(file);
}
} catch (err) {
ImageKnifeLoader.assembleError(callBackData, LoadPhase.PHASE_LOCAL_FILE,
LoadPixelMapCode.IMAGE_LOAD_LOCAL_FILE_FAILED_CODE);
loadError = `LocalLoadSrc: ${request.src}, err: ${err}`;
ImageKnifeLoader.makeEmptyResult(request, loadError,
ImageKnifeLoader.assembleError(
callBackData,
LoadPhase.PHASE_LOCAL_FILE,
LoadPixelMapCode.IMAGE_LOAD_LOCAL_FILE_FAILED_CODE
)
);
}
} else {
loadError = `Parameter not supported: ${request.src}`;
ImageKnifeLoader.makeEmptyResult(request, loadError, callBackData);
}
if (resBuf === undefined || resBuf === null) {
callBackTimeInfo.requestEndTime = Date.now();
ImageKnifeLoader.makeEmptyResult(request, loadError, callBackData);
} else {
ImageKnifeLoader.parseImage(resBuf, fileKey, request, callBackData);
}
return Promise.resolve();
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright (C) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ImageKnifeData, RequestJobRequest, TimeInfo, ImageKnifeRequestWithSource } from '../model/ImageKnifeData';
import fs from '@ohos.file.fs';
import { IImageLoaderStrategy } from './IImageLoaderStrategy';
import List from '@ohos.util.List';
import { ImageKnifeLoader } from '../ImageKnifeLoader';
import { LoadPhase, LoadPixelMapCode } from '../utils/Constants';
import { BusinessError } from '@kit.BasicServicesKit';
export class FileSystemLoaderStrategy implements IImageLoaderStrategy {
async loadImage(request: RequestJobRequest, requestList: List<ImageKnifeRequestWithSource> | undefined,
fileKey: string, callBackData: ImageKnifeData, callBackTimeInfo: TimeInfo
): Promise<void> {
let resBuf: ArrayBuffer | undefined;
let loadError: string = '';
if (typeof request.src === 'string' &&
(request.src.startsWith('datashare://') || request.src.startsWith('file://'))) {
ImageKnifeLoader.assembleError(callBackData, LoadPhase.PHASE_SHARE_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) => {
ImageKnifeLoader.assembleError(callBackData, LoadPhase.PHASE_SHARE_FILE,
LoadPixelMapCode.IMAGE_LOAD_SHARE_FILE_FAILED_CODE)
loadError = 'LoadDataShareFileClient fs.read err happened uri=' + request.src + ' err.msg=' + err?.message +
' err.code=' + err?.code
})
}).catch((err: BusinessError) => {
ImageKnifeLoader.assembleError(callBackData, LoadPhase.PHASE_SHARE_FILE,
LoadPixelMapCode.IMAGE_LOAD_SHARE_FILE_FAILED_CODE)
loadError = 'LoadDataShareFileClient fs.stat err happened uri=' + request.src + ' err.msg=' + err?.message +
' err.code=' + err?.code
})
}).catch((err: BusinessError) => {
ImageKnifeLoader.assembleError(callBackData, LoadPhase.PHASE_SHARE_FILE,
LoadPixelMapCode.IMAGE_LOAD_SHARE_FILE_FAILED_CODE)
loadError = 'LoadDataShareFileClient fs.open err happened uri=' + request.src + ' err.msg=' + err?.message +
' err.code=' + err?.code
})
}
if (resBuf === undefined || resBuf === null) {
callBackTimeInfo.requestEndTime = Date.now();
ImageKnifeLoader.makeEmptyResult(request, loadError, callBackData);
} else {
ImageKnifeLoader.parseImage(resBuf, fileKey, request, callBackData);
}
return;
}
}

View File

@ -0,0 +1,136 @@
/*
* Copyright (C) 2025 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 { FileCache } from '../cache/FileCache';
import { IImageLoaderStrategy } from './IImageLoaderStrategy';
import {
ImageKnifeData,
ImageKnifeRequestSource,
ImageKnifeRequestWithSource,
RequestJobRequest,
TimeInfo
} from '../model/ImageKnifeData';
import http from '@ohos.net.http';
import { ImageKnifeLoader } from '../ImageKnifeLoader';
import { combineArrayBuffers } from '../utils/ArrayBufferUtils';
import { BusinessError, emitter } from '@kit.BasicServicesKit';
import { Constants, LoadPhase, LoadPixelMapCode } from '../utils/Constants';
import { LogUtil } from '../utils/LogUtil';
import List from '@ohos.util.List';
class RequestData {
public receiveSize: number = 2000
public totalSize: number = 2000
}
// HTTP加载策略
export class HttpLoaderStrategy implements IImageLoaderStrategy {
async loadImage(
request: RequestJobRequest,
requestList: List<ImageKnifeRequestWithSource> | undefined,
fileKey: string,
callBackData: ImageKnifeData,
callBackTimeInfo: TimeInfo
): Promise<void> {
let resBuf: ArrayBuffer | undefined;
let loadError: string = '';
// 从文件缓存获取
ImageKnifeLoader.assembleError(callBackData, LoadPhase.PHASE_NET);
callBackTimeInfo.diskCheckStartTime = Date.now();
resBuf = FileCache.getFileCacheByFile(request.context, fileKey, request.fileCacheFolder);
callBackTimeInfo.diskCheckEndTime = Date.now();
if (resBuf !== undefined) {
LogUtil.log(`success get image from filecache for key = ${fileKey} src = ${request.componentId},
srcType:${request.requestSource}, ${request.componentVersion}`);
ImageKnifeLoader.parseImage(resBuf, fileKey, request, callBackData);
} else if (request.onlyRetrieveFromCache !== true) {
LogUtil.log(`HttpDownloadClient.start: ${request.componentId}, srcType:${request.requestSource},
${request.componentVersion}`);
callBackTimeInfo.netRequestStartTime = Date.now();
const httpRequest = http.createHttp();
emitter.once((request.src as string) + request.componentId,()=>{
httpRequest.destroy()
})
let progress: number = 0;
const arrayBuffers: ArrayBuffer[] = [];
const headerObj: Record<string, Object> = ImageKnifeLoader.getHeaderObj(request);
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') {
const 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 as string, {
header: headerObj,
method: http.RequestMethod.GET,
expectDataType: http.HttpDataType.ARRAY_BUFFER,
connectTimeout: request.connectTimeout ?? 60000,
readTimeout: request.readTimeout ?? 30000,
caPath: request.caPath
});
promise.then((data: number) => {
emitter.off((request.src as string) + request.componentId)
callBackData.httpCode = data;
ImageKnifeLoader.assembleError(callBackData,LoadPhase.PHASE_NET, undefined);
callBackTimeInfo.netRequestEndTime = Date.now();
if (data == 200 || data == 206 || data == 204) {
resBuf = combineArrayBuffers(arrayBuffers);
ImageKnifeLoader.FileCacheParseImage(request,resBuf,fileKey, callBackData);
} else {
loadError = 'HttpDownloadClient has error, http code =' + JSON.stringify(data);
ImageKnifeLoader.makeEmptyResult(request,loadError, ImageKnifeLoader.assembleError(callBackData,
LoadPhase.PHASE_NET, LoadPixelMapCode.IMAGE_HTTPS_LOAD_FAILED_CODE));
}
}).catch((err: BusinessError) => {
emitter.off((request.src as string) + request.componentId)
callBackData.httpCode = err.code;
loadError = 'HttpDownloadClient download ERROR : err = ' + JSON.stringify(err);
callBackTimeInfo.netRequestEndTime = Date.now();
ImageKnifeLoader.makeEmptyResult(request,loadError, ImageKnifeLoader.assembleError(callBackData,
LoadPhase.PHASE_NET, LoadPixelMapCode.IMAGE_HTTPS_LOAD_FAILED_CODE));
});
LogUtil.log('HttpDownloadClient.end:' + request.componentId + ',srcType:' +
request.requestSource + ',' + request.componentVersion);
return;
} else {
callBackTimeInfo.netRequestEndTime = Date.now();
loadError = `onlyRetrieveFromCache, do not fetch image src = ${request.src}`;
ImageKnifeLoader.makeEmptyResult(request, loadError, callBackData);
return;
}
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ImageKnifeData, ImageKnifeRequestWithSource, RequestJobRequest, TimeInfo } from '../model/ImageKnifeData';
import List from '@ohos.util.List';
// 定义图片加载策略接口
export interface IImageLoaderStrategy {
loadImage(
request: RequestJobRequest,
requestList: List<ImageKnifeRequestWithSource> | undefined,
fileKey: string,
callBackData: ImageKnifeData,
callBackTimeInfo: TimeInfo
): Promise<void>;
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (C) 2025 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 { CustomLoaderStrategy } from './CustomLoaderStrategy';
import { FileSystemLoaderStrategy } from './FileSystemLoaderStrategy';
import { HttpLoaderStrategy } from './HttpLoaderStrategy';
import { IImageLoaderStrategy } from './IImageLoaderStrategy';
import { ImageKnifeRequestSource, RequestJobRequest } from '../model/ImageKnifeData';
import { ResourceLoaderStrategy } from './ResourceLoaderStrategy';
import { FileLocalLoadStrategy } from './FileLocalLoadStrategy';
export class ImageLoaderFactory {
static getLoaderStrategy(request: RequestJobRequest): IImageLoaderStrategy | null {
if (request.customGetImage !== undefined &&
request.requestSource === ImageKnifeRequestSource.SRC &&
typeof request.src === 'string'
) {
return new CustomLoaderStrategy();
} else if (typeof request.src === 'string') {
if (request.src.startsWith('http://') || request.src.startsWith('https://')) {
return new HttpLoaderStrategy();
} else if (request.src.startsWith('datashare://') || request.src.startsWith('file://')) {
return new FileSystemLoaderStrategy();
} else {
return new FileLocalLoadStrategy();
}
} else if (typeof request.src === 'number') {
return new ResourceLoaderStrategy();
}
return null;
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright (C) 2025 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 { IImageLoaderStrategy } from './IImageLoaderStrategy';
import {
ImageKnifeData,
ImageKnifeRequestSource,
ImageKnifeRequestWithSource,
RequestJobRequest,
TimeInfo
} from '../model/ImageKnifeData';
import List from '@ohos.util.List';
import { application } from '@kit.AbilityKit';
import { ImageKnifeLoader } from '../ImageKnifeLoader';
export class ResourceLoaderStrategy implements IImageLoaderStrategy {
async loadImage(
request: RequestJobRequest,
requestList: List<ImageKnifeRequestWithSource> | undefined,
fileKey: string,
callBackData: ImageKnifeData,
callBackTimeInfo: TimeInfo
): Promise<void> {
let resBuf: ArrayBuffer | undefined;
let loadError: string = '';
if (typeof request.src === 'number') {
const moduleContext = await application.createModuleContext(request.context, request.moduleName);
const manager = moduleContext.resourceManager;
if ((resBuf == undefined && request.onlyRetrieveFromCache !== true &&
request.requestSource === ImageKnifeRequestSource.SRC) ||
(resBuf == undefined && request.requestSource !== ImageKnifeRequestSource.SRC)) {
if (request.src === -1) {
const resName = request.resName as string;
resBuf =
(await manager.getMediaByName(resName.substring(resName.lastIndexOf('.') + 1))).buffer as ArrayBuffer;
} else {
resBuf = request.resName ?
manager.getRawFileContentSync(request.resName).buffer.slice(0) :
(manager.getMediaContentSync(request.src)).buffer as ArrayBuffer;
}
}
}
if (resBuf === undefined || resBuf === null){
callBackTimeInfo.requestEndTime = Date.now();
loadError = 'Resource load error';
ImageKnifeLoader.makeEmptyResult(request, loadError ,callBackData);
} else {
ImageKnifeLoader.parseImage(resBuf,fileKey,request, callBackData);
}
return;
}
}

View File

@ -158,3 +158,9 @@ export interface RequestJobRequest {
readTimeout?: number
}
export interface FlipRotate{
horizontal: boolean,
vertical: boolean,
rotate: number,
}

View File

@ -22,6 +22,7 @@ export class ImageKnifeRequest {
componentWidth: number = 0
componentHeight: number = 0
drawPlayHolderSuccess: boolean = false
drawMainSuccess: boolean = false
imageKnifeOption: ImageKnifeOption
context: common.UIAbilityContext
ImageKnifeRequestCallback: ImageKnifeRequestCallback
@ -29,6 +30,7 @@ export class ImageKnifeRequest {
headers: Map<string,Object> = new Map<string,Object>()
imageKnifeData?: ImageKnifeData
componentId?: number
animator?: boolean
constructor(option: ImageKnifeOption,
uIAbilityContext: common.UIAbilityContext,
width: number,

View File

@ -56,11 +56,12 @@ export class CropTransformation extends PixelMapTransformation {
let scaledWidth: number = scale * pixelMapWidth;
let scaledHeight: number = scale * pixelMapHeight;
let left: number = (this.mWidth - scaledWidth) / 2;
let top: number = Math.abs(this.getTop(pixelMapHeight));
let top: number = Math.abs(this.getTop(scaledHeight));
toTransform.scaleSync(scale,scale)
let region: image.Region = {
size: {
width: scaledWidth > pixelMapWidth ? pixelMapWidth : scaledWidth,
height: scaledHeight > pixelMapHeight ? pixelMapHeight : scaledHeight
width: this.mWidth,
height: this.mHeight
},
x: left < 0 ? 0 : left,
y: top < 0 ? 0 : top

View File

@ -4,7 +4,11 @@
"type": "har",
"deviceTypes": [
"default",
"tablet"
"tablet",
"tv",
"wearable",
"car",
"2in1"
]
}
}

View File

@ -15,4 +15,7 @@
# Keep options:
# -keep-property-name: specifies property names that you want to keep
# -keep-global-name: specifies names that you want to keep in the global scope
# -keep-global-name: specifies names that you want to keep in the global scope
-keep
./oh_modules/@ohos/imageknife

View File

@ -5,7 +5,11 @@
"description": "$string:shared_desc",
"deviceTypes": [
"default",
"tablet"
"tablet",
"tv",
"wearable",
"car",
"2in1"
],
"deliveryWithInstall": true,
"pages": "$profile:main_pages"