From cbdc07a001c904196af1ad4f7482215680565882 Mon Sep 17 00:00:00 2001 From: dingchenjie Date: Fri, 12 Apr 2024 14:32:27 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E6=96=B0=E5=A2=9Egif=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E6=94=AF=E6=8C=81drawLifeCycle=20gif=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E6=88=90=E5=9C=86=E5=BD=A2=E5=A4=B4=E5=83=8F=E6=A0=B7=E5=BC=8F?= =?UTF-8?q?=EF=BC=8C=E4=BF=9D=E7=95=99=E8=BE=B9=E6=A1=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 + .../testImageKnifeOptionChangedPage5.ets | 55 +++++++++++++++++++ .../imageknife/ImageKnifeComponent.ets | 51 +++++++++++++++++ .../imageknife/ImageKnifeDrawFactory.ets | 23 ++++++-- 4 files changed, 128 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d11ed4e..8e96d9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 2.1.2-rc.14 +- 新增gif图片支持drawLifeCycle gif设置成圆形头像样式,保留边框 + ## 2.1.2-rc.13 - 修改ImageKnife跳过网络,从内存中获取图片 cacheType参数未使用bug - 新增WEBP图片解析能力。 diff --git a/entry/src/main/ets/pages/testImageKnifeOptionChangedPage5.ets b/entry/src/main/ets/pages/testImageKnifeOptionChangedPage5.ets index 47c1119..d993e9b 100644 --- a/entry/src/main/ets/pages/testImageKnifeOptionChangedPage5.ets +++ b/entry/src/main/ets/pages/testImageKnifeOptionChangedPage5.ets @@ -27,6 +27,13 @@ import { ImageKnifeDrawFactory } from '@ohos/libraryimageknife' import worker from '@ohos.worker'; + +let gifs: string[] = [ + 'http://p8.itc.cn/images01/20201108/45806c7f57fc4965855afff97d5eea84.gif', + 'https://p4.itc.cn/q_70/images03/20210201/6ee7c87aa4eb4bd380a05917d5fe683c.gif', + 'http://img.duoziwang.com/2018/06/201712314086753.gif' +] + @Entry @Component struct TestImageKnifeOptionChangedPage5 { @@ -83,6 +90,54 @@ struct TestImageKnifeOptionChangedPage5 { }; }).margin({ left: 5 }).backgroundColor(Color.Blue) }.margin({ top: 15 }) + Flex({ direction: FlexDirection.Row }) { + Button("网络gif") + .onClick(() => { + this.imageKnifeOption1 = { + loadSrc: gifs[2], + placeholderSrc: $r('app.media.icon_loading'), + errorholderSrc: $r('app.media.icon_failed'), + }; + }).margin({ left: 5 }).backgroundColor(Color.Blue) + Button("网络gif - 圆角小") + .onClick(() => { + this.imageKnifeOption1 = { + loadSrc: gifs[2], + placeholderSrc: $r('app.media.icon_loading'), + errorholderSrc: $r('app.media.icon_failed'), + drawLifeCycle:ImageKnifeDrawFactory.createRoundLifeCycle(3,"#ff0000", 30) + }; + }).margin({ left: 5 }).backgroundColor(Color.Blue) + Button("网络gif - 圆角大") + .onClick(() => { + this.imageKnifeOption1 = { + loadSrc: gifs[2], + placeholderSrc: $r('app.media.icon_loading'), + errorholderSrc: $r('app.media.icon_failed'), + drawLifeCycle:ImageKnifeDrawFactory.createRoundLifeCycle(5,"#ffff00", 300) + }; + }).margin({ left: 5 }).backgroundColor(Color.Blue) + }.margin({ top: 30 }) + Flex({ direction: FlexDirection.Row }) { + Button("网络gif-椭圆长方形资源") + .onClick(() => { + this.imageKnifeOption1 = { + loadSrc: gifs[0], + placeholderSrc: $r('app.media.icon_loading'), + errorholderSrc: $r('app.media.icon_failed'), + drawLifeCycle:ImageKnifeDrawFactory.createOvalLifeCycle(8,"#88ee00") + }; + }).margin({ left: 5 }).backgroundColor(Color.Blue) + Button("网络gif-椭圆正方形资源") + .onClick(() => { + this.imageKnifeOption1 = { + loadSrc: gifs[2], + placeholderSrc: $r('app.media.icon_loading'), + errorholderSrc: $r('app.media.icon_failed'), + drawLifeCycle:ImageKnifeDrawFactory.createOvalLifeCycle(5,"#ff00ff") + }; + }).margin({ left: 5 }).backgroundColor(Color.Blue) + }.margin({ top: 30 }) Text("下面为展示图片区域").margin({ top: 5 }) Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { diff --git a/library/src/main/ets/components/imageknife/ImageKnifeComponent.ets b/library/src/main/ets/components/imageknife/ImageKnifeComponent.ets index 2105747..656bedc 100644 --- a/library/src/main/ets/components/imageknife/ImageKnifeComponent.ets +++ b/library/src/main/ets/components/imageknife/ImageKnifeComponent.ets @@ -27,6 +27,7 @@ import { ObjectKey } from './ObjectKey' import componentUtils from '@ohos.arkui.componentUtils' import inspector from '@ohos.arkui.inspector' import util from '@ohos.util' +import { ImageKnifeDrawFactory } from './ImageKnifeDrawFactory' interface KeyCanvas { keyId:string @@ -414,6 +415,7 @@ export struct ImageKnifeComponent { } displayMainSource = (data: ImageKnifeData|number|undefined)=> { + ImageKnifeDrawFactory.type = undefined; if(data == undefined || typeof data == 'number'){ return } @@ -898,10 +900,59 @@ export struct ImageKnifeComponent { ScaleTypeHelper.drawImageWithScaleType(context, scaleType, pixelmap, px2vp(frameW), px2vp(frameH), compWidth, compHeight, px2vp(frame.dims.left), px2vp(frame.dims.top)) // tips:worker如果不是在展示页面中创建,使用子线程回来的数据创建的图片,会导致canvas绘制不出来 context.restore(); + if (ImageKnifeDrawFactory.type === undefined) { + LogUtil.log('ImageKnifeComponent canvasDrawPixelMap draw type is undefined!'); + return; + } + // 通过 destination-in 裁剪出圆角 + if (ImageKnifeDrawFactory?.type === DrawType.Round || ImageKnifeDrawFactory?.type === DrawType.Oval) { + context.save(); + context.globalCompositeOperation = 'destination-in'; + if (ImageKnifeDrawFactory.type === DrawType.Round) { + ImageKnifeDrawFactory.setRect(context, scaleType, pixelmap, px2vp(frameW), px2vp(frameH), compWidth, + compHeight, 0, 0, ImageKnifeDrawFactory.borderWidth, ImageKnifeDrawFactory.connerRadius); + } else { + context.beginPath(); + ImageKnifeDrawFactory.setOval(context, scaleType, pixelmap, px2vp(frameW), px2vp(frameH), compWidth, + compHeight, 0, 0, ImageKnifeDrawFactory.borderWidth); + context.closePath(); + } + context.fill(); + context.restore(); + if (ImageKnifeDrawFactory.borderWidth > 0) { + // 为圆角添加边框 + context.save(); + context.strokeStyle = ImageKnifeDrawFactory.colorString; + context.lineWidth = ImageKnifeDrawFactory.borderWidth; + context.globalCompositeOperation = 'source-over'; + if (ImageKnifeDrawFactory.type === DrawType.Round) { + ImageKnifeDrawFactory.setRect(context, scaleType, pixelmap, px2vp(frameW), px2vp(frameH), compWidth, + compHeight, 0, 0, ImageKnifeDrawFactory.borderWidth, ImageKnifeDrawFactory.connerRadius); + } else { + context.beginPath(); + ImageKnifeDrawFactory.setOval(context, scaleType, pixelmap, px2vp(frameW), px2vp(frameH), compWidth, + compHeight, 0, 0, ImageKnifeDrawFactory.borderWidth); + context.closePath(); + } + context.stroke(); + context.restore(); + } + context.restore(); + } LogUtil.log('ImageKnifeComponent canvasDrawPixelMap end!') } } +export enum DrawType{ + + Normal = 0, + + Oval = 1, + + Round = 2 + +} + export enum FrameDisposalType { // 0 - No disposal specified. The decoder is not required to take any action. // 不使用处置方法 diff --git a/library/src/main/ets/components/imageknife/ImageKnifeDrawFactory.ets b/library/src/main/ets/components/imageknife/ImageKnifeDrawFactory.ets index f67bfad..73f2493 100644 --- a/library/src/main/ets/components/imageknife/ImageKnifeDrawFactory.ets +++ b/library/src/main/ets/components/imageknife/ImageKnifeDrawFactory.ets @@ -16,9 +16,13 @@ import { ImageKnifeOption } from '../imageknife/ImageKnifeOption' import { ImageKnifeData } from '../imageknife/ImageKnifeData' import { GIFFrame } from '../imageknife/utils/gif/GIFFrame' import { IDrawLifeCycle } from '../imageknife/interface/IDrawLifeCycle' -import { ScaleTypeHelper,ScaleType } from '../imageknife/ImageKnifeComponent' +import { ScaleTypeHelper,ScaleType, DrawType } from '../imageknife/ImageKnifeComponent' -export class ImageKnifeDrawFactory{ +export class ImageKnifeDrawFactory { + public static borderWidth: number; + public static type: DrawType | undefined = undefined; + public static colorString: string; + public static connerRadius: number; /** * 绘制PixelMap内容情况下,主图的椭圆裁剪,可添加边框 @@ -81,6 +85,11 @@ export class ImageKnifeDrawFactory{ }) return true; } + if (data.isGIFFrame()) { + ImageKnifeDrawFactory.type = DrawType.Oval; + ImageKnifeDrawFactory.borderWidth = borderWidth; + ImageKnifeDrawFactory.colorString = colorString; + } return false; }, // 展示重试图层 @@ -108,7 +117,7 @@ export class ImageKnifeDrawFactory{ * @param imageOffsetY * @param borderWidth */ - private static setOval(context: CanvasRenderingContext2D, scaleType: ScaleType, source: PixelMap | undefined, imageWidth: number, imageHeight: number, compWidth: number, compHeight: number, imageOffsetX:number,imageOffsetY:number + public static setOval(context: CanvasRenderingContext2D, scaleType: ScaleType, source: PixelMap | undefined, imageWidth: number, imageHeight: number, compWidth: number, compHeight: number, imageOffsetX:number,imageOffsetY:number ,borderWidth:number) { let scaleW = compWidth / imageWidth let scaleH = compHeight / imageHeight @@ -240,6 +249,12 @@ export class ImageKnifeDrawFactory{ }) return true; } + if (data.isGIFFrame()) { + ImageKnifeDrawFactory.type = DrawType.Round; + ImageKnifeDrawFactory.borderWidth = borderWidth; + ImageKnifeDrawFactory.colorString = colorString; + ImageKnifeDrawFactory.connerRadius = connerRadius; + } return false; }, // 展示重试图层 @@ -268,7 +283,7 @@ export class ImageKnifeDrawFactory{ * @param borderWidth * @param cornerRadius */ - private static setRect(context: CanvasRenderingContext2D, scaleType: ScaleType, source: PixelMap | undefined, imageWidth: number, imageHeight: number, compWidth: number, compHeight: number, imageOffsetX:number,imageOffsetY:number + public static setRect(context: CanvasRenderingContext2D, scaleType: ScaleType, source: PixelMap | undefined, imageWidth: number, imageHeight: number, compWidth: number, compHeight: number, imageOffsetX:number,imageOffsetY:number ,borderWidth:number,cornerRadius:number) { let scaleW = compWidth / imageWidth let scaleH = compHeight / imageHeight From 26c550b638c848044e294481638c187f94dc4af1 Mon Sep 17 00:00:00 2001 From: ding_chengjie Date: Fri, 12 Apr 2024 17:51:15 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=96=B0=E5=A2=9Egif=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=9A=82=E5=81=9C=E6=92=AD=E6=94=BE=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + README.md | 5 ++ .../ets/pages/imageknifeTestCaseIndex.ets | 9 +++ .../main/ets/pages/testStopPlayingGifPage.ets | 75 +++++++++++++++++++ .../resources/base/profile/main_pages.json | 3 +- .../imageknife/ImageKnifeComponent.ets | 17 ++--- .../imageknife/ImageKnifeOption.ets | 2 + 7 files changed, 102 insertions(+), 10 deletions(-) create mode 100644 entry/src/main/ets/pages/testStopPlayingGifPage.ets diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e96d9a..85a780f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## 2.1.2-rc.14 - 新增gif图片支持drawLifeCycle gif设置成圆形头像样式,保留边框 +- 新增gif图片支持暂停播放功能 ## 2.1.2-rc.13 - 修改ImageKnife跳过网络,从内存中获取图片 cacheType参数未使用bug diff --git a/README.md b/README.md index 4535660..e6697d1 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,9 @@ svg类型图片即可。 加载GIF其实和普通流程也没有区别只要将 `loadSrc: $r('app.media.jpgSample'),` `改成一张 loadSrc: $r('app.media.gifSample'),` GIF图片即可。 +#### 4.1加载GIF图片 + +更改ImageKnifeOption对象的autoPlay(可选autoPlay = true为开始播放,autoPlay = false为暂停播放) ### 5.自定义Key 因为通常改变标识符比较困难或者根本不可能,所以ImageKnife也提供了 签名 API 来混合(你可以控制的)额外数据到你的缓存键中。 @@ -134,6 +137,7 @@ imageKnifeOption = { | **drawLifeCycle** | **IDrawLifeCycle** | **用户自定义实现绘制方案(可选)** | | imageSmoothingEnabled | boolean | 抗锯齿是否开启属性配置,设置为false时,imageSmoothingQuality失效 | | imageSmoothingQuality | AntiAliasing | 抗锯齿属性配置 | +| autoPlay | boolean | GIF播放暂停控制(可选) | 其他参数只需要在ImageKnifeOption对象上按需添加即可。 @@ -526,6 +530,7 @@ HSP场景适配: - testPreloadPage.ets # 预加载测试 - transformPixelMapPage.ets # 所有类型变换测试 - testSingleFrameGifPage.ets # 单帧gif加载测试 + - TestStopPlayingGifPage # gif播放暂停测试 - OptionTestPage.ets # 图片缓存测试 - testManyGifLoadWithPage # 测试gif加载页面 diff --git a/entry/src/main/ets/pages/imageknifeTestCaseIndex.ets b/entry/src/main/ets/pages/imageknifeTestCaseIndex.ets index f0c8049..e2bd68d 100644 --- a/entry/src/main/ets/pages/imageknifeTestCaseIndex.ets +++ b/entry/src/main/ets/pages/imageknifeTestCaseIndex.ets @@ -372,6 +372,15 @@ struct IndexFunctionDemo { router.pushUrl({ url: 'pages/testImageKnifeCache' }); }).margin({ top: 5, left: 3 }) }.width('100%').height(60).backgroundColor(Color.Pink) + + Text('测试gif暂停播放属性').fontSize(15) + Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { + Button('测试gif暂停播放属性') + .onClick(() => { + router.pushUrl({ url: 'pages/testStopPlayingGifPage' }); + }).margin({ top: 5, left: 3 }) + }.width('100%').height(60).backgroundColor(Color.Pink) + } } .width('100%') diff --git a/entry/src/main/ets/pages/testStopPlayingGifPage.ets b/entry/src/main/ets/pages/testStopPlayingGifPage.ets new file mode 100644 index 0000000..131e1b2 --- /dev/null +++ b/entry/src/main/ets/pages/testStopPlayingGifPage.ets @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ImageKnifeComponent, LogUtil } from '@ohos/libraryimageknife' +import { ImageKnifeOption } from '@ohos/libraryimageknife' +import display from '@ohos.display'; + +@Entry +@Component +struct TestStopPlayingGifPage { + @State message: string = 'gif暂停播放测试' + @State eventType: string = ''; + @State options: ImageKnifeOption = { + loadSrc: $r('app.media.app_icon'), + } + + build() { + Column() { + Column() { + Text(`${this.message}`) + .width("300vp") + .height("60vp") + .textAlign(TextAlign.Center) + .fontSize("30fp") + .fontWeight(FontWeight.Bold) + Button("加载gif图") + .margin(16) + .onClick(() => { + console.log("加载多帧gif") + this.options = { + loadSrc: $r('app.media.gifSample'), + placeholderSrc:$r('app.media.icon_loading'), + } + }) + Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { + Text("控制") + Button("开启") + .onClick(() => { + this.options.autoPlay = true; + }).margin({ left: 3 }) + + Button("暂停") + .onClick(() => { + this.options.autoPlay = false; + }).margin({ left: 3 }) + }.width('100%') + .height(40).backgroundColor(Color.Pink).margin({bottom:5}) + ImageKnifeComponent({ imageKnifeOption: this.options }) + .margin(16) + .width(300) + .height(300) + } + .width("100%") + .height("100%") + .justifyContent(FlexAlign.Center) + } + .width("100%") + .height("100%") + } + aboutToAppear() { + console.log('CTT:TestManyNetImageLoadWithPage display Height: '+ display.getDefaultDisplaySync().height) + } +} + diff --git a/entry/src/main/resources/base/profile/main_pages.json b/entry/src/main/resources/base/profile/main_pages.json index 326cc61..b79794a 100644 --- a/entry/src/main/resources/base/profile/main_pages.json +++ b/entry/src/main/resources/base/profile/main_pages.json @@ -49,6 +49,7 @@ "pages/testPriorityComponent", "pages/testReusePhotoPage", "pages/testImageKnifeCache", - "pages/webpImageTestPage" + "pages/webpImageTestPage", + "pages/testStopPlayingGifPage" ] } \ No newline at end of file diff --git a/library/src/main/ets/components/imageknife/ImageKnifeComponent.ets b/library/src/main/ets/components/imageknife/ImageKnifeComponent.ets index 656bedc..6254286 100644 --- a/library/src/main/ets/components/imageknife/ImageKnifeComponent.ets +++ b/library/src/main/ets/components/imageknife/ImageKnifeComponent.ets @@ -35,12 +35,13 @@ interface KeyCanvas { @Component export struct ImageKnifeComponent { @Watch('watchImageKnifeOption') @ObjectLink imageKnifeOption: ImageKnifeOption; - autoPlay: boolean = true private settings: RenderingContextSettings = new RenderingContextSettings(true) private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings) private hasDisplayRetryholder = false; private lastWidth: number = 0 private lastHeight: number = 0 + // 当前帧数位置 + private renderFrames_index = 0; // 定时器id private gifTimerId: number = -1 // 完整gif播放时间 @@ -766,15 +767,15 @@ export struct ImageKnifeComponent { let frames = data.drawGIFFrame?.imageGIFFrames as GIFFrame[] LogUtil.log('ImageKnifeComponent gifFrameLength =' + frames.length); if (imageKnifeOption.gif && (typeof (imageKnifeOption.gif.seekTo) == 'number') && imageKnifeOption.gif.seekTo >= 0) { - this.autoPlay = false; context.clearRect(0, 0, compWidth, compHeight) this.drawSeekToFrame(frames, context, compWidth, compHeight) } else { - this.autoPlay = true context.clearRect(0, 0, compWidth, compHeight) this.renderFrames_frames = frames - this.renderFrames_index = 0 + if (this.imageKnifeOption.autoPlay == undefined) { + this.renderFrames_index = 0 + } this.renderFrames_context = context this.renderFrames_compWidth = compWidth this.renderFrames_compHeight = compHeight @@ -798,13 +799,13 @@ export struct ImageKnifeComponent { private drawSeekToFrame(frames: GIFFrame[], context: CanvasRenderingContext2D, compWidth: number, compHeight: number) { if(this.imageKnifeOption.gif != undefined && this.imageKnifeOption.gif.seekTo != undefined) { for (let i = 0; i < this.imageKnifeOption.gif.seekTo; i++) { + this.renderFrames_index = i; this.drawFrame(frames, i, context, compWidth, compHeight); } } } renderFrames_frames: GIFFrame[] | undefined = undefined - renderFrames_index: number = 0; renderFrames_context: CanvasRenderingContext2D | undefined = undefined; renderFrames_compWidth: number = 0; renderFrames_compHeight: number = 0 @@ -822,7 +823,7 @@ export struct ImageKnifeComponent { if (this.renderFrames_frames != undefined && this.renderFrames_index === (this.renderFrames_frames.length - 1) && this.imageKnifeOption.gif != undefined ) { this.playTimes++ if (this.imageKnifeOption.gif.playTimes == this.playTimes){ - this.autoPlay = false + this.imageKnifeOption.autoPlay = false this.playTimes = 0 } } @@ -834,9 +835,7 @@ export struct ImageKnifeComponent { // 记录渲染结束时间点 let end = new Date().getTime(); let diff = end - start - - if (this.autoPlay) { - + if ((this.imageKnifeOption.autoPlay == undefined) || (this.imageKnifeOption.autoPlay)) { // 理论上该帧在屏幕上保留的时间 let stayTime:number= 0 if(this.renderFrames_frames != undefined) { diff --git a/library/src/main/ets/components/imageknife/ImageKnifeOption.ets b/library/src/main/ets/components/imageknife/ImageKnifeOption.ets index ba6c6a9..8df8ae9 100644 --- a/library/src/main/ets/components/imageknife/ImageKnifeOption.ets +++ b/library/src/main/ets/components/imageknife/ImageKnifeOption.ets @@ -69,6 +69,8 @@ export interface HeaderOptions { @Observed export class ImageKnifeOption { + //控制gif开关 + autoPlay?: boolean = true; // header请求列表 headerOption?: Array; // 主图资源