diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ec10d5..43ad547 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - 修复错误图绘制完后变成占位图 - 提供图片加载成功/失败的事件 - 修复懒加载在多次点击出现卡死的问题 +- 支持多种组合变换 ## 2.2.0-rc.2 - ImageKnife支持下采样 diff --git a/README.md b/README.md index 0c54b80..5b4c3d5 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,7 @@ ImageKnifeGlobal.getInstance().getImageKnife()?.replaceDataFetch(new CustomDataF | isCacheable | boolean | 是否开启一级内存缓存(可选) | | gif | {
// 返回一周期动画gif消耗的时间
loopFinish?: (loopTime?) => void
// gif播放速率相关
speedFactory?: number
// 直接展示gif第几帧数据
seekTo?: number
playTimes?: number
} | GIF播放控制能力(可选) | | transformation | BaseTransform | 单个变换(可选) | -| transformations | Array> | 多个变换,目前仅支持单个变换(可选) | +| transformations | Array> | 多个变换(可选) | | allCacheInfoCallback | IAllCacheInfoCallback | 输出缓存相关内容和信息(可选) | | signature | ObjectKey | 自定key(可选) | | **drawLifeCycle** | **IDrawLifeCycle** | **用户自定义实现绘制方案(可选)** | @@ -610,9 +610,7 @@ HSP场景适配: ## 遗留问题 -1.目前只支持一种图片变换效果。 - -2.目前svg和gif动图不支持变换效果。 +1.目前svg和gif动图不支持变换效果。 ## 补充说明 ### SVG标签说明 diff --git a/entry/src/main/ets/pages/imageknifeTestCaseIndex.ets b/entry/src/main/ets/pages/imageknifeTestCaseIndex.ets index 0f07455..f8c5f35 100644 --- a/entry/src/main/ets/pages/imageknifeTestCaseIndex.ets +++ b/entry/src/main/ets/pages/imageknifeTestCaseIndex.ets @@ -173,6 +173,11 @@ struct IndexFunctionDemo { console.log("测试图片变换") router.pushUrl({ url: "pages/transformPixelMapPage" }); }).margin({ top: 5, left: 3 }) + Button("图片组合变换") + .onClick(() => { + console.log("图片组合变换") + router.pushUrl({ url: "pages/transformsPage" }); + }).margin({ top: 5, left: 3 }) Button("测试图片压缩") .onClick(() => { console.log("测试图片压缩") diff --git a/entry/src/main/ets/pages/transformsPage.ets b/entry/src/main/ets/pages/transformsPage.ets new file mode 100644 index 0000000..7804142 --- /dev/null +++ b/entry/src/main/ets/pages/transformsPage.ets @@ -0,0 +1,461 @@ +/* + * 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 { + GrayscaleTransformation, + ImageKnifeComponent, + ImageKnifeOption, + RotateImageTransformation, + SketchFilterTransformation, + CropCircleTransformation, + ToonFilterTransform, + VignetteFilterTransform, + BaseTransform, + BlurTransformation, + BrightnessFilterTransformation, + InvertFilterTransformation, + CropCircleWithBorderTransformation, + CropSquareTransformation, + CropTransformation, + CropType, + ContrastFilterTransformation, + PixelationFilterTransformation, + RoundedCornersTransformation, + SepiaFilterTransformation, + MaskTransformation, + SwirlFilterTransformation, + KuwaharaFilterTransform +} from '@ohos/libraryimageknife' + +@Entry +@Component +struct transformsPage { + @State imageKnifeOption: ImageKnifeOption = + { + loadSrc: $r('app.media.jpgSample'), + placeholderSrc: $r('app.media.icon_loading'), + errorholderSrc: $r('app.media.icon_failed') + }; + private transformations: Array> = new Array(); + private blurTransformation: BlurTransformation = new BlurTransformation(15, 3); + private brightnessFilterTransformation: BrightnessFilterTransformation = new BrightnessFilterTransformation(0.5); + private contrastFilterTransformation: ContrastFilterTransformation = new ContrastFilterTransformation(2.0); + private cropCircleTransformation: CropCircleTransformation = new CropCircleTransformation(); + private cropCircleWithBorderTransformation: CropCircleWithBorderTransformation = + new CropCircleWithBorderTransformation(16, { r_color: 100, g_color: 100, b_color: 100 }); + private cropSquareTransformation: CropSquareTransformation = new CropSquareTransformation(); + private cropTransformation: CropTransformation = new CropTransformation(100, 100, CropType.CENTER); + private grayscaleTransformation: GrayscaleTransformation = new GrayscaleTransformation(); + private invertFilterTransformation: InvertFilterTransformation = new InvertFilterTransformation(); + private pixelationFilterTransformation: PixelationFilterTransformation = new PixelationFilterTransformation(5.0); + private rotateImageTransformation: RotateImageTransformation = new RotateImageTransformation(90); + private roundedCornersTransformation: RoundedCornersTransformation = new RoundedCornersTransformation({ + top_left: 5, + bottom_left: 5, + top_right: 5, + bottom_right: 5 + }); + private sepiaFilterTransformation: SepiaFilterTransformation = new SepiaFilterTransformation(); + private sketchFilterTransformation: SketchFilterTransformation = new SketchFilterTransformation(); + private maskTransformation: MaskTransformation = new MaskTransformation($r('app.media.mask_starfish')); + private swirlFilterTransformation: SwirlFilterTransformation = new SwirlFilterTransformation(80); + private kuwaharaFilterTransform: KuwaharaFilterTransform = new KuwaharaFilterTransform(10); + private toonFilterTransform: ToonFilterTransform = new ToonFilterTransform(0.3, 10.0); + private vignetteFilterTransform: VignetteFilterTransform = new VignetteFilterTransform([0.5, 0.5], [0.0, 0.0, 0.0], [0.3, 0.75]); + + build() { + Scroll() { + Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { + Flex({ direction: FlexDirection.Row }) { + Row() { + Checkbox({ name: '模糊' }) + .width(32) + .height(32) + .onChange((value: boolean) => { + if (value) { + this.transformations.push(this.blurTransformation); + } else { + for (let index = 0; index < this.transformations.length; index++) { + if (this.transformations[index].getClassName() == this.blurTransformation.getClassName()) { + this.transformations.splice(index, 1); + } + } + } + }) + Text('模糊变换').margin({ left: 16 }) + }.margin({ left: 16 }) + + Row() { + Checkbox({ name: '亮度滤波' }) + .width(32) + .height(32) + .onChange((value: boolean) => { + if (value) { + this.transformations.push(this.brightnessFilterTransformation); + } else { + for (let index = 0; index < this.transformations.length; index++) { + if (this.transformations[index].getClassName() == this.brightnessFilterTransformation.getClassName()) { + this.transformations.splice(index, 1); + } + } + } + }) + Text('亮度滤波').margin({ left: 16 }) + }.margin({ left: 16 }) + }.margin({ top: 32, left: 16 }) + + Flex({ direction: FlexDirection.Row }) { + Row() { + Checkbox({ name: '对比度' }) + .width(32) + .height(32) + .onChange((value: boolean) => { + if (value) { + this.transformations.push(this.contrastFilterTransformation); + } else { + for (let index = 0; index < this.transformations.length; index++) { + if (this.transformations[index].getClassName() == this.contrastFilterTransformation.getClassName()) { + this.transformations.splice(index, 1); + } + } + } + }) + Text('对比度').margin({ left: 16 }) + }.margin({ left: 16 }) + + Row() { + Checkbox({ name: '圆形裁剪' }) + .width(32) + .height(32) + .onChange((value: boolean) => { + if (value) { + this.transformations.push(this.cropCircleTransformation); + } else { + for (let index = 0; index < this.transformations.length; index++) { + if (this.transformations[index].getClassName() == this.cropCircleTransformation.getClassName()) { + this.transformations.splice(index, 1); + } + } + } + }) + Text('圆形裁剪').margin({ left: 16 }) + }.margin({ left: 16 }) + }.margin({ top: 32, left: 16 }) + + Flex({ direction: FlexDirection.Row }) { + Row() { + Checkbox({ name: '圆环' }) + .width(32) + .height(32) + .onChange((value: boolean) => { + if (value) { + this.transformations.push(this.cropCircleWithBorderTransformation); + } else { + for (let index = 0; index < this.transformations.length; index++) { + if (this.transformations[index].getClassName() == this.cropCircleWithBorderTransformation.getClassName()) { + this.transformations.splice(index, 1); + } + } + } + }) + Text('圆环').margin({ left: 16 }) + }.margin({ left: 16 }) + + Row() { + Checkbox({ name: '正方形裁剪' }) + .width(32) + .height(32) + .onChange((value: boolean) => { + if (value) { + this.transformations.push(this.cropSquareTransformation); + } else { + for (let index = 0; index < this.transformations.length; index++) { + if (this.transformations[index].getClassName() == this.cropSquareTransformation.getClassName()) { + this.transformations.splice(index, 1); + } + } + } + }) + Text('正方形裁剪').margin({ left: 16 }) + }.margin({ left: 16 }) + }.margin({ top: 32, left: 16 }) + + Flex({ direction: FlexDirection.Row }) { + Row() { + Checkbox({ name: '自定义矩形' }) + .width(32) + .height(32) + .onChange((value: boolean) => { + if (value) { + this.transformations.push(this.cropTransformation); + } else { + for (let index = 0; index < this.transformations.length; index++) { + if (this.transformations[index].getClassName() == this.cropTransformation.getClassName()) { + this.transformations.splice(index, 1); + } + } + } + }) + Text('自定义矩形').margin({ left: 16 }) + }.margin({ left: 16 }) + + Row() { + Checkbox({ name: '灰度' }) + .width(32) + .height(32) + .onChange((value: boolean) => { + if (value) { + this.transformations.push(this.grayscaleTransformation); + } else { + for (let index = 0; index < this.transformations.length; index++) { + if (this.transformations[index].getClassName() == this.grayscaleTransformation.getClassName()) { + this.transformations.splice(index, 1); + } + } + } + }) + Text('灰度').margin({ left: 16 }) + }.margin({ left: 16 }) + }.margin({ top: 32, left: 16 }) + + Flex({ direction: FlexDirection.Row }) { + Row() { + Checkbox({ name: '反转' }) + .width(32) + .height(32) + .onChange((value: boolean) => { + if (value) { + this.transformations.push(this.invertFilterTransformation); + } else { + for (let index = 0; index < this.transformations.length; index++) { + if (this.transformations[index].getClassName() == this.invertFilterTransformation.getClassName()) { + this.transformations.splice(index, 1); + } + } + } + }) + Text('反转').margin({ left: 16 }) + }.margin({ left: 16 }) + + Row() { + Checkbox({ name: '像素化' }) + .width(32) + .height(32) + .onChange((value: boolean) => { + if (value) { + this.transformations.push(this.pixelationFilterTransformation); + } else { + for (let index = 0; index < this.transformations.length; index++) { + if (this.transformations[index].getClassName() == this.pixelationFilterTransformation.getClassName()) { + this.transformations.splice(index, 1); + } + } + } + }) + Text('像素化').margin({ left: 16 }) + }.margin({ left: 16 }) + }.margin({ top: 32, left: 16 }) + + Flex({ direction: FlexDirection.Row }) { + Row() { + Checkbox({ name: '旋转' }) + .width(32) + .height(32) + .onChange((value: boolean) => { + if (value) { + this.transformations.push(this.rotateImageTransformation); + } else { + for (let index = 0; index < this.transformations.length; index++) { + if (this.transformations[index].getClassName() == this.rotateImageTransformation.getClassName()) { + this.transformations.splice(index, 1); + } + } + } + }) + Text('旋转').margin({ left: 16 }) + }.margin({ left: 16 }) + + Row() { + Checkbox({ name: '圆角' }) + .width(32) + .height(32) + .onChange((value: boolean) => { + if (value) { + this.transformations.push(this.roundedCornersTransformation); + } else { + for (let index = 0; index < this.transformations.length; index++) { + if (this.transformations[index].getClassName() == this.roundedCornersTransformation.getClassName()) { + this.transformations.splice(index, 1); + } + } + } + }) + Text('圆角').margin({ left: 16 }) + }.margin({ left: 16 }) + }.margin({ top: 32, left: 16 }) + + Flex({ direction: FlexDirection.Row }) { + Row() { + Checkbox({ name: '乌墨色滤波' }) + .width(32) + .height(32) + .onChange((value: boolean) => { + if (value) { + this.transformations.push(this.sepiaFilterTransformation); + } else { + for (let index = 0; index < this.transformations.length; index++) { + if (this.transformations[index].getClassName() == this.sepiaFilterTransformation.getClassName()) { + this.transformations.splice(index, 1); + } + } + } + }) + Text('乌墨色滤波').margin({ left: 16 }) + }.margin({ left: 16 }) + + Row() { + Checkbox({ name: '素描' }) + .width(32) + .height(32) + .onChange((value: boolean) => { + if (value) { + this.transformations.push(this.sketchFilterTransformation); + } else { + for (let index = 0; index < this.transformations.length; index++) { + if (this.transformations[index].getClassName() == this.sketchFilterTransformation.getClassName()) { + this.transformations.splice(index, 1); + } + } + } + }) + Text('素描').margin({ left: 16 }) + }.margin({ left: 16 }) + }.margin({ top: 32, left: 16 }) + + Flex({ direction: FlexDirection.Row }) { + Row() { + Checkbox({ name: '遮罩' }) + .width(32) + .height(32) + .onChange((value: boolean) => { + if (value) { + this.transformations.push(this.maskTransformation); + } else { + for (let index = 0; index < this.transformations.length; index++) { + if (this.transformations[index].getClassName() == this.maskTransformation.getClassName()) { + this.transformations.splice(index, 1); + } + } + } + }) + Text('遮罩').margin({ left: 16 }) + }.margin({ left: 16 }) + + Row() { + Checkbox({ name: '扭曲滤波' }) + .width(32) + .height(32) + .onChange((value: boolean) => { + if (value) { + this.transformations.push(this.swirlFilterTransformation); + } else { + for (let index = 0; index < this.transformations.length; index++) { + if (this.transformations[index].getClassName() == this.swirlFilterTransformation.getClassName()) { + this.transformations.splice(index, 1); + } + } + } + }) + Text('扭曲滤波').margin({ left: 16 }) + }.margin({ left: 16 }) + }.margin({ top: 32, left: 16 }) + + Flex({ direction: FlexDirection.Row }) { + Row() { + Checkbox({ name: '桑原滤波' }) + .width(32) + .height(32) + .onChange((value: boolean) => { + if (value) { + this.transformations.push(this.kuwaharaFilterTransform); + } else { + for (let index = 0; index < this.transformations.length; index++) { + if (this.transformations[index].getClassName() == this.kuwaharaFilterTransform.getClassName()) { + this.transformations.splice(index, 1); + } + } + } + }) + Text('桑原滤波').margin({ left: 16 }) + }.margin({ left: 16 }) + + Row() { + Checkbox({ name: '动画滤波' }) + .width(32) + .height(32) + .onChange((value: boolean) => { + if (value) { + this.transformations.push(this.toonFilterTransform); + } else { + for (let index = 0; index < this.transformations.length; index++) { + if (this.transformations[index].getClassName() == this.toonFilterTransform.getClassName()) { + this.transformations.splice(index, 1); + } + } + } + }) + Text('动画滤波').margin({ left: 16 }) + }.margin({ left: 16 }) + }.margin({ top: 32, left: 16 }) + + Flex({ direction: FlexDirection.Row }) { + Row() { + Checkbox({ name: '装饰滤波' }) + .width(32) + .height(32) + .onChange((value: boolean) => { + if (value) { + this.transformations.push(this.vignetteFilterTransform); + } else { + for (let index = 0; index < this.transformations.length; index++) { + if (this.transformations[index].getClassName() == this.vignetteFilterTransform.getClassName()) { + this.transformations.splice(index, 1); + } + } + } + }) + Text('装饰滤波').margin({ left: 16 }) + }.margin({ left: 16 }) + }.margin({ top: 32, left: 16 }) + + Button("显示效果").onClick(() => { + this.imageKnifeOption = { + loadSrc: $r('app.media.jpgSample'), + placeholderSrc: $r('app.media.icon_loading'), + errorholderSrc: $r('app.media.icon_failed'), + enableGpu: true, + transformations: this.transformations + }; + }).margin({ left: 5 }).backgroundColor(Color.Blue) + Text("下面为展示图片区域").margin({ top: 5 }) + Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { + ImageKnifeComponent({ imageKnifeOption: this.imageKnifeOption }).width(300).height(300) + }.width(400).height(400).margin({ top: 10 }).backgroundColor(Color.Pink) + } + } + .width('100%') + .height('100%') + } +} + diff --git a/entry/src/main/resources/base/profile/main_pages.json b/entry/src/main/resources/base/profile/main_pages.json index 61781a0..476a18a 100644 --- a/entry/src/main/resources/base/profile/main_pages.json +++ b/entry/src/main/resources/base/profile/main_pages.json @@ -11,6 +11,7 @@ "pages/pngjTestCasePage", "pages/showErrorholderTestCasePage", "pages/transformPixelMapPage", + "pages/transformsPage", "pages/testPreloadPage", "pages/testDiskPreLoadPage", "pages/testImageKnifeOptionChangedPage", diff --git a/library/src/main/ets/components/imageknife/requestmanage/RequestManager.ets b/library/src/main/ets/components/imageknife/requestmanage/RequestManager.ets index c9f687f..88c2534 100644 --- a/library/src/main/ets/components/imageknife/requestmanage/RequestManager.ets +++ b/library/src/main/ets/components/imageknife/requestmanage/RequestManager.ets @@ -33,7 +33,9 @@ import { GIFFrame } from '../utils/gif/GIFFrame' import { LogUtil } from '../../imageknife/utils/LogUtil' import { BusinessError } from '@ohos.base' import { DataFetchResult } from '../networkmanage/DataFetchResult' +import { BaseTransform } from '../transform/BaseTransform' +const imagePackerApi = image.createImagePacker(); export enum Stage { @@ -173,16 +175,7 @@ export class RequestManager { this.svgProcess(request, onComplete, onError, arrayBuffer, typeValue) } else { if (request.transformations[0]) { - request.transformations[0].transform(arrayBuffer, request, { - asyncTransform: (error: BusinessError | string, pixelMap: PixelMap | null) => { - // 输出给Image - if (pixelMap) { - onComplete(pixelMap); - } else { - onError(error); - } - } - }) + this.getTransformImage(request, arrayBuffer, request.transformations, 0, onComplete, onError); } else { let success = (value: PixelMap) => { @@ -201,6 +194,60 @@ export class RequestManager { } + private getTransformImage(request: RequestOption, source: ArrayBuffer, transformations: Array>, index: number, + onComplete: (value: PixelMap | GIFFrame[]) => void | PromiseLike, onError: (reason?: BusinessError | string) => void) { + transformations[index].transform(source, request, { + asyncTransform: (error: BusinessError | string, pixelMap: PixelMap | null) => { + // 输出给Image + if (pixelMap) { + if (index == request.transformations.length - 1) { + onComplete(pixelMap) + } else { + let packOpts: image.PackingOption = { format: "image/png", quality: 98 }; + imagePackerApi.packing(pixelMap, packOpts).then((data: ArrayBuffer) => { + // data 为打包获取到的文件流,写入文件保存即可得到一张图片 + index++; + this.getTransformImage(request, data, transformations, index, onComplete, onError); + }).catch((error: BusinessError) => { + onError(error); + console.error('requestManager Failed to pack the image. And the error is: ' + JSON.stringify(error)); + }) + } + } else { + onError(error); + console.error('requestManager getTransformImage error! ' + JSON.stringify(error)); + } + } + }) + } + + private saveTransformImage(request: RequestOption, source: ArrayBuffer, transformations: Array>, index: number, filetype: string, + onComplete: (value: PixelMap) => void | PromiseLike, onError: (reason?: BusinessError | string) => void) { + transformations[index].transform(source, request, { + asyncTransform: (error: BusinessError | string, pixelMap: PixelMap | null) => { + // 输出给Image + if (pixelMap) { + if (index == request.transformations.length - 1) { + this.saveCacheAndDisk(pixelMap, filetype, onComplete, source); + } else { + let packOpts: image.PackingOption = { format: "image/png", quality: 98 }; + imagePackerApi.packing(pixelMap, packOpts).then((data: ArrayBuffer) => { + // data 为打包获取到的文件流,写入文件保存即可得到一张图片 + index++; + this.saveTransformImage(request, data, transformations, index, filetype, onComplete, onError); + }).catch((error: BusinessError) => { + onError(error); + console.error('requestManager Failed to pack the image. And the error is: ' + JSON.stringify(error)); + }) + } + } else { + onError(error); + console.error('requestManager saveTransformImage error! ' + JSON.stringify(error)); + } + } + }) + } + // 加载磁盘缓存 原图 private loadDiskFromSource(request: RequestOption, onComplete: (value: PixelMap | GIFFrame[]) => void | PromiseLike, onError: (reason?: BusinessError | string) => void) { LogUtil.log("ImageKnife RequestManager loadDiskFromSource") @@ -286,37 +333,13 @@ export class RequestManager { } let thumbCallback = this.options.thumbholderOnComplete; let thumbError = this.options.thumbholderOnError; - this.options.transformations[0].transform(source, thumbOption, { - asyncTransform: (error: BusinessError | string, pixelMap: PixelMap | null) => { - if (pixelMap) { - thumbCallback(pixelMap); - } else { - thumbError(error); - } - } - }) + this.getTransformImage(thumbOption, source, this.options.transformations, 0, thumbCallback, thumbError); setTimeout(() => { - this.options.transformations[0].transform(source, request, { - asyncTransform: (error: BusinessError | string, pixelMap: PixelMap | null) => { - if (pixelMap) { - onComplete(pixelMap); - } else { - onError(error); - } - } - }) + this.getTransformImage(request, source, this.options.transformations, 0, onComplete, onError); }, this.options.thumbDelayTime); } else { - this.options.transformations[0].transform(source, request, { - asyncTransform: (error: BusinessError | string, pixelMap: PixelMap | null) => { - if (pixelMap) { - onComplete(pixelMap); - } else { - onError(error); - } - } - }) + this.getTransformImage(request, source, this.options.transformations, 0, onComplete, onError); } } else { // thumbnail 缩略图部分 @@ -427,17 +450,7 @@ export class RequestManager { this.thumbnailProcess(source, filetype, onComplete, onError); } } else { - this.options.transformations[0].transform(source, this.options, { - asyncTransform: (error: BusinessError | string, pixelMap: PixelMap | null) => { - if (pixelMap) { - if (filetype != null) { - this.saveCacheAndDisk(pixelMap, filetype, onComplete, source); - } - } else { - onError(error); - } - } - }) + this.saveTransformImage(this.options, source, this.options.transformations, 0, filetype, onComplete, onError); } } else { // thumbnail 缩略图部分 @@ -507,25 +520,9 @@ export class RequestManager { }) let thumbCallback = this.options.thumbholderOnComplete let thumbError = this.options.thumbholderOnError - this.options.transformations[0].transform(source, thumbOption, { - asyncTransform: (error: BusinessError | string, pixelMap: PixelMap | null) => { - if (pixelMap) { - thumbCallback(pixelMap); - } else { - thumbError(error); - } - } - }) + this.getTransformImage(thumbOption, source, this.options.transformations, 0, thumbCallback, thumbError); setTimeout(() => { - this.options.transformations[0].transform(source, this.options, { - asyncTransform: (error: BusinessError | string, pixelMap: PixelMap | null) => { - if (pixelMap) { - this.saveCacheAndDisk(pixelMap, filetype, onComplete, source); - } else { - onError(error); - } - } - }) + this.saveTransformImage(this.options, source, this.options.transformations, 0, filetype, onComplete, onError); }, this.options.thumbDelayTime) }