From 7cf9a129e9dcb112040399ac02028be8a2b84488 Mon Sep 17 00:00:00 2001 From: zhoulisheng1 Date: Mon, 14 Nov 2022 03:44:42 -0800 Subject: [PATCH] =?UTF-8?q?1.=E9=87=8D=E6=9E=84imageknife=E6=95=B4?= =?UTF-8?q?=E4=B8=AA=E6=B8=B2=E6=9F=93=E5=B1=82=20a.=E9=87=8D=E6=9E=84Imag?= =?UTF-8?q?eKnifeComponent=20b.=E9=87=8D=E6=9E=84ImageOption=20c.=E6=8A=BD?= =?UTF-8?q?=E8=B1=A1=E7=BB=98=E5=88=B6=E7=94=9F=E5=91=BD=E5=91=A8=E6=9C=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhoulisheng1 --- .../ets/components/imageknife/ImageKnife.ets | 80 +- .../imageknife/ImageKnifeComponent.ets | 879 ++++++++++++++---- .../components/imageknife/ImageKnifeData.ets | 88 +- .../imageknife/ImageKnifeDrawFactory.ets | 481 ++++++++++ .../imageknife/ImageKnifeOption.ets | 101 +- .../components/imageknife/RequestOption.ets | 70 +- .../imageknife/interface/IDrawExtension.ets | 18 + .../imageknife/interface/IDrawLifeCycle.ets | 37 + .../networkmanage/DownloadClient.ets | 2 +- .../requestmanage/RequstManager.ets | 202 ++-- 10 files changed, 1515 insertions(+), 443 deletions(-) create mode 100644 imageknife/src/main/ets/components/imageknife/ImageKnifeDrawFactory.ets create mode 100644 imageknife/src/main/ets/components/imageknife/interface/IDrawExtension.ets create mode 100644 imageknife/src/main/ets/components/imageknife/interface/IDrawLifeCycle.ets diff --git a/imageknife/src/main/ets/components/imageknife/ImageKnife.ets b/imageknife/src/main/ets/components/imageknife/ImageKnife.ets index 7ff0827..6cf3b9d 100644 --- a/imageknife/src/main/ets/components/imageknife/ImageKnife.ets +++ b/imageknife/src/main/ets/components/imageknife/ImageKnife.ets @@ -19,6 +19,7 @@ import {EngineKeyFactories} from "../cache/key/EngineKeyFactories" import {RequestOption} from "../imageknife/RequestOption" import {AsyncCallback} from "../imageknife/interface/asynccallback" import {PlaceHolderManager} from "../imageknife/holder/PlaceHolderManager" +import {RetryHolderManager} from "../imageknife/holder/RetryHolderManager" import {ErrorHolderManager} from "../imageknife/holder/ErrorHolderManager" import {RequestManager} from "../imageknife/requestmanage/RequstManager" import {NONE} from "../cache/diskstrategy/enum/NONE" @@ -27,12 +28,12 @@ import {DownloadClient} from '../imageknife/networkmanage/DownloadClient' import {IDataFetch} from '../imageknife/networkmanage/IDataFetch' import {ParseResClient} from '../imageknife/resourcemanage/ParseResClient' import {IResourceFetch} from '../imageknife/resourcemanage/IResourceFetch' -import {ImageKnifeData} from '../imageknife/ImageKnifeData' +import {ImageKnifeData,ImageKnifeType} from '../imageknife/ImageKnifeData' import {FileUtils} from '../cache/FileUtils' import {FileReader} from '../cache/FileReader' import image from "@ohos.multimedia.image" -import featureAbility from '@ohos.ability.featureAbility'; import {CompressBuilder} from "../imageknife/compress/CompressBuilder" +import { IDrawLifeCycle } from '../imageknife/interface/IDrawLifeCycle' export class ImageKnife { static readonly SEPARATOR: string = '/' @@ -49,11 +50,15 @@ export class ImageKnife { private pendingRequest: Array; private fileTypeUtil: FileTypeUtil; // 通用文件格式辨别 private diskCacheFolder: string = "ImageKnifeDiskCache" - private svgAndGifFolder: string = "svgAndGifFolder"; // svg和gif的文件路径地址 - private svgAndGifCommitFile: string = "svgAndGifCommitFile" // svg和gif提交记录 + private defaultListener: AsyncCallback; // 全局监听器 + // gifWorker + private gifWorker; + + private defaultLifeCycle: IDrawLifeCycle; + private constructor(imgCtx) { this.imageKnifeContext = imgCtx; @@ -71,7 +76,6 @@ export class ImageKnife { // 初始化本地 文件保存 this.filesPath = this.imageKnifeContext.filesDir; - this.initSvgAndGifEnvironment(); this.runningRequest = new Array(); this.pendingRequest = new Array(); @@ -103,16 +107,6 @@ export class ImageKnife { return this.fileTypeUtil; } - - - getSvgAndGifFolder(): string{ - return this.svgAndGifFolder; - } - - setSvgAndGifFolder(folderPath: string){ - this.svgAndGifFolder = folderPath; - } - getImageKnifeContext() { return this.imageKnifeContext; } @@ -125,31 +119,21 @@ export class ImageKnife { return this.defaultListener; } - private initSvgAndGifEnvironment() { - let folderExist = FileUtils.getInstance().existFolder(this.filesPath + "/" + this.svgAndGifFolder) - let fileExist = - FileUtils.getInstance() - .exist(this.filesPath + "/" + this.svgAndGifFolder + "/" + this.svgAndGifCommitFile) - if (folderExist && fileExist) { - // 创建完成,需要删除上次使用的文件 - var fileReader = new FileReader(this.filesPath + "/" + this.svgAndGifFolder + "/" + this.svgAndGifCommitFile) - var line: string = '' - while (!fileReader.isEnd()) { - line = fileReader.readLine() - line = line.replace('\n', "").replace('\r', "") - FileUtils.getInstance().deleteFile(this.filesPath + "/" + this.svgAndGifFolder + "/" + line) - } - FileUtils.getInstance().clearFile(this.filesPath + "/" + this.svgAndGifFolder + "/" + this.svgAndGifCommitFile) - } else { - if (!folderExist) { - FileUtils.getInstance().createFolder(this.filesPath + "/" + this.svgAndGifFolder); - } - if (!fileExist) { - FileUtils.getInstance().createFile(this.filesPath + "/" + this.svgAndGifFolder + "/" + this.svgAndGifCommitFile) - } - } - + setGifWorker(worker){ + this.gifWorker = worker } + getGifWorker(){ + return this.gifWorker; + } + + getDefaultLifeCycle(){ + return this.defaultLifeCycle; + } + + setDefaultLifeCycle(viewLifeCycle:IDrawLifeCycle){ + this.defaultLifeCycle = viewLifeCycle; + } + private static sInstance: ImageKnife; @@ -195,9 +179,7 @@ export class ImageKnife { preload(request: RequestOption) { // 每个request 公共信息补充 request.setFilesPath(this.filesPath); - // svg特殊处理 - request._svgAndGifFolder = this.svgAndGifFolder; - request._svgAndGifCommitFile = this.svgAndGifCommitFile; + return this.parseSource(request); } @@ -211,16 +193,16 @@ export class ImageKnife { // 每个request 公共信息补充 request.setFilesPath(this.filesPath); - // svg特殊处理 - request._svgAndGifFolder = this.svgAndGifFolder; - request._svgAndGifCommitFile = this.svgAndGifCommitFile; - // 首先执行占位图 解析任务 if (request.placeholderSrc) { PlaceHolderManager.execute(request) } + // 其次执行重试占位图 解析任务 + if (request.retryholderSrc) { + RetryHolderManager.execute(request) + } - // 其次解析错误占位图 + // 最后解析错误占位图 if (request.errorholderSrc) { ErrorHolderManager.execute(request) } @@ -374,9 +356,7 @@ export class ImageKnife { parseSource(request: RequestOption) { if ((typeof (request.loadSrc as image.PixelMap).isEditable) == 'boolean') { - let imageKnifeData = new ImageKnifeData(); - imageKnifeData.imageKnifeType = ImageKnifeData.PIXELMAP - imageKnifeData.imageKnifeValue = request.loadSrc as PixelMap + let imageKnifeData = ImageKnifeData.createImagePixelMap(ImageKnifeType.PIXELMAP, request.loadSrc as PixelMap) request.loadComplete(imageKnifeData); } else if (typeof request.loadSrc == 'string') { diff --git a/imageknife/src/main/ets/components/imageknife/ImageKnifeComponent.ets b/imageknife/src/main/ets/components/imageknife/ImageKnifeComponent.ets index f15f6bd..8614dab 100644 --- a/imageknife/src/main/ets/components/imageknife/ImageKnifeComponent.ets +++ b/imageknife/src/main/ets/components/imageknife/ImageKnifeComponent.ets @@ -13,98 +13,176 @@ * limitations under the License. */ -import {ImageKnifeOption} from '../imageknife/ImageKnifeOption' -import {TransformType} from '../imageknife/transform/TransformType' -import {RequestOption} from '../imageknife/RequestOption' -import {ImageKnifeData} from '../imageknife/ImageKnifeData' -import {PixelMapPack} from '../imageknife/PixelMapPack' +import { ImageKnifeOption } from '../imageknife/ImageKnifeOption' +import { TransformType } from '../imageknife/transform/TransformType' +import { RequestOption } from '../imageknife/RequestOption' +import { ImageKnifeData } from '../imageknife/ImageKnifeData' +import { GIFFrame } from '../imageknife/utils/gif/GIFFrame' +import { IDrawLifeCycle } from '../imageknife/interface/IDrawLifeCycle' + @Component export struct ImageKnifeComponent { @Watch('watchImageKnifeOption') @Link imageKnifeOption: ImageKnifeOption; - @State imageKnifePixelMapPack: PixelMapPack = new PixelMapPack(); - @State imageKnifeResource: Resource = undefined - @State imageKnifeString: string = '' - @State normalPixelMap: boolean = false; - @State normalResource: boolean = true; - previousData: ImageKnifeData = null; - nowData: ImageKnifeData = null; - @State percentVisible: Visibility = Visibility.Visible - @State retryVisible: Visibility = Visibility.Visible - @State imageVisible: Visibility = Visibility.Visible - @State percent: string = '0%' - @State percentWidth: string = '0%'; - @State percentHeight: string = '0%'; - @State retryWidth: string = '0%'; - @State retryHeight: string = '0%'; - @State imageWidth: string = '100%'; - @State imageHeight: string = '100%'; - @State imageKnifeRetry: Resource = undefined; + // 有效onAreaChanged触发计数 + private onAreaCount:number = 0 - hasRetry:boolean = false; + @State componentWidth: string = '100%' + @State componentHeight: string = '100%' + + @State gifPixelMap:PixelMap = undefined; + + drawLifeCycle: IDrawLifeCycle + autoPlay = true + private preSize: { + width: string, + height: string + } = { width: '0', height: '0' } + private settings: RenderingContextSettings = new RenderingContextSettings(true) + private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings) + private hasDisplayRetryholder = false; + private currentWidth: number = 0 + private currentHeight: number = 0 + + // 定时器id + private gifTimerId: number = 0 + // 完整gif播放时间 + private gifLoopDuration: number = 0 + private startGifLoopTime: number = 0 + private endGifLoopTime: number = 0 + defaultLifeCycle: IDrawLifeCycle = { + + // 展示占位图 + displayPlaceholder: (context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) => { + // @ts-ignore + this.drawPlaceholder(context, data, imageKnifeOption, compWidth, compHeight, setGifTimeId) + return true; + }, + // 展示加载进度 + displayProgress: (context: CanvasRenderingContext2D, progress: number, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) => { + // @ts-ignore + this.drawProgress(context, progress, imageKnifeOption, compWidth, compHeight, setGifTimeId) + return true; + }, + // 展示缩略图 + displayThumbSizeMultiplier: (context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) => { + // @ts-ignore + this.drawThumbSizeMultiplier(context, data, imageKnifeOption, compWidth, compHeight, setGifTimeId) + return true; + }, + + // 展示主图 + displayMainSource: (context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) => { + // @ts-ignore + this.drawMainSource(context, data, imageKnifeOption, compWidth, compHeight, setGifTimeId) + return true; + }, + + // 展示重试图层 + displayRetryholder: (context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) => { + // @ts-ignore + this.drawRetryholder(context, data, imageKnifeOption, compWidth, compHeight, setGifTimeId) + return true; + }, + + // 展示失败占位图 + displayErrorholder: (context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) => { + // @ts-ignore + this.drawErrorholder(context, data, imageKnifeOption, compWidth, compHeight, setGifTimeId) + return true; + } + + } build() { - Stack() { - - Text(this.percent) - .fontSize(this.imageKnifeOption.size ? Math.min(this.imageKnifeOption.size.height, this.imageKnifeOption.size.width) / 2 : 24) - .fontWeight(FontWeight.Bold) - .visibility(this.retryVisible) - .width(this.percentWidth) - .height(this.percentHeight) - - Image(this.imageKnifeRetry) - .onClick(()=>{ - this.retryClick(); - }) - .visibility(this.retryVisible) - .width(this.retryWidth) - .height(this.retryHeight) - - - - Image(this.normalPixelMap ? this.imageKnifePixelMapPack.pixelMap : (this.normalResource ? this.imageKnifeResource : this.imageKnifeString)) - .objectFit(this.imageKnifeOption.imageFit ? this.imageKnifeOption.imageFit : ImageFit.Fill) - .visibility(this.imageVisible) - .width(this.imageWidth) - .height(this.imageHeight) - } - .width(this.imageKnifeOption.size ? this.imageKnifeOption.size.width : '100%') - .height(this.imageKnifeOption.size ? this.imageKnifeOption.size.height : '100%') - .backgroundColor(this.imageKnifeOption.backgroundColor ? this.imageKnifeOption.backgroundColor : Color.White) - .margin(this.imageKnifeOption.margin ? this.imageKnifeOption.margin : { left: 0, top: 0, right: 0, bottom: 0 }) + Canvas(this.context) + .width(this.componentWidth) + .height(this.componentHeight) + .onAreaChange((oldValue: Area, newValue: Area) => { + this.currentWidth = newValue.width as number + this.currentHeight = newValue.height as number + console.log('onAreaChange stack currentWidth =' + this.currentWidth + ' currentHeight=' + this.currentHeight) + if (this.onAreaCount > 0) { + this.onAreaCount--; + this.imageKnifeExecute() + } + }) + .backgroundColor(this.imageKnifeOption.backgroundColor ? this.imageKnifeOption.backgroundColor : Color.White) + .margin(this.imageKnifeOption.margin ? this.imageKnifeOption.margin : { left: 0, top: 0, right: 0, bottom: 0 }) + .onReady(() => { + }) + .onClick(() => { + if (this.imageKnifeOption.canRetryClick && this.hasDisplayRetryholder) { + this.retryClick() + } + }) } watchImageKnifeOption() { - this.imageKnifeExecute(); + console.log('watchImageKnifeOption is happened!') + + if (this.imageKnifeOption.sizeAnimate) { + animateTo(this.imageKnifeOption.sizeAnimate, () => { + this.resetGifData(); + this.whetherWaitSize(); + }) + } else { + this.resetGifData(); + this.whetherWaitSize(); + } + } + whetherWaitSize(){ + this.componentWidth = this.imageKnifeOption.size.width + this.componentHeight = this.imageKnifeOption.size.height + if(this.newSizeEqualPreSize(this.imageKnifeOption.size)){ + console.log('whetherWaitSize 宽高不变 直接发送请求') + this.imageKnifeExecute() + }else{ + this.onAreaCount ++; + // waitSize changed + this.preSize = this.imageKnifeOption.size + console.log('whetherWaitSize 宽高改变 等待组件回调onAreaChange后发送请求') + } } - retryClick(){ - this.hasRetry = true; + newSizeEqualPreSize(newSize:{width: string, height: string}):boolean{ + if(this.preSize.width == newSize.width && this.preSize.height == newSize.height){ + return true; + } + return false; + } + + retryClick() { + this.hasDisplayRetryholder = false; this.imageKnifeExecute(); } aboutToAppear() { console.log('imageKnifeComponent aboutToAppear happened!') - this.imageKnifeExecute(); + this.onAreaCount ++; + this.componentWidth = this.imageKnifeOption.size.width + this.componentHeight = this.imageKnifeOption.size.height } - configNecessary(request: RequestOption){ + configNecessary(request: RequestOption) { request.load(this.imageKnifeOption.loadSrc) - .addListener((err, data) => { - console.log('request.load callback') - this.imageKnifeChangeSource(data) - this.animateTo('image'); - return false; - }) + .addListener((err, data) => { + console.log('request.load callback') + this.displayMainSource(data) + return false; + }) if (this.imageKnifeOption.size) { - request.setImageViewSize(this.imageKnifeOption.size) + let realSize = { + width: this.currentWidth, + height:this.currentHeight + } + request.setImageViewSize(realSize) } - } + } - configCacheStrategy(request: RequestOption){ + configCacheStrategy(request: RequestOption) { if (this.imageKnifeOption.onlyRetrieveFromCache) { request.retrieveDataFromCache(this.imageKnifeOption.onlyRetrieveFromCache) } @@ -119,36 +197,30 @@ export struct ImageKnifeComponent { if (this.imageKnifeOption.allCacheInfoCallback) { request.addAllCacheInfoCallback(this.imageKnifeOption.allCacheInfoCallback) } - } + } - configDisplay(request: RequestOption){ - if(this.imageKnifeOption.animateDuration >= 0){ - request.animateDuration = this.imageKnifeOption.animateDuration; - } + configDisplay(request: RequestOption) { if (this.imageKnifeOption.placeholderSrc) { request.placeholder(this.imageKnifeOption.placeholderSrc, (data) => { console.log('request.placeholder callback') - this.imageKnifeChangeSource(data) - this.animateTo('image'); + this.displayPlaceholder(data) + }) } if (this.imageKnifeOption.thumbSizeMultiplier) { request.thumbnail(this.imageKnifeOption.thumbSizeMultiplier, (data) => { console.log('request.thumbnail callback') - this.imageKnifeChangeSource(data) - this.animateTo('image'); - }) + this.displayThumbSizeMultiplier(data) + }, this.imageKnifeOption.thumbSizeDelay) } if (this.imageKnifeOption.errorholderSrc) { request.errorholder(this.imageKnifeOption.errorholderSrc, (data) => { console.log('request.errorholder callback') - this.imageKnifeChangeSource(data) - this.animateTo('image'); + this.displayErrorholder(data) + }) } - if (this.imageKnifeOption.retryholderSrc) { - this.imageKnifeRetry = this.imageKnifeOption.retryholderSrc - } + if (this.imageKnifeOption.transform) { this.requestAddTransform(request) } @@ -164,164 +236,301 @@ export struct ImageKnifeComponent { if (this.imageKnifeOption.displayProgress) { - request.addProgressListener((percentValue: string) => { + request.addProgressListener((percentValue: number) => { // 如果进度条百分比 未展示大小,展示其动画 - this.percent = percentValue; - if(this.imageKnifeOption.displayProgressListener){ - this.imageKnifeOption.displayProgressListener(percentValue); - } - this.animateTo('progress'); + this.displayProgress(percentValue) + }) } - if(this.imageKnifeOption.retryLoad){ - request.addRetryListener((error: any) => { + if (this.imageKnifeOption.retryholderSrc) { + request.retryholder(this.imageKnifeOption.retryholderSrc, (data) => { console.log("RetryListener callback!") - this.animateTo('retry'); + this.hasDisplayRetryholder = true + this.displayRetryholder(data) }) } } // imageknife 第一次启动和数据刷新后重新发送请求 imageKnifeExecute() { - let request = new RequestOption(); - this.configNecessary(request); - this.configCacheStrategy(request); - this.configDisplay(request); + let request = new RequestOption(); + this.configNecessary(request); + this.configCacheStrategy(request); + this.configDisplay(request); globalThis.ImageKnife.call(request); } - imageKnifeChangeSource(data:ImageKnifeData) { - this.imageKnifeSpecialFixed(data); - } + displayPlaceholder(data: ImageKnifeData) { - displayPixelMap(data:ImageKnifeData){ - console.log('displayPixelMap start') - let pixelMapPack2 = new PixelMapPack(); - pixelMapPack2.pixelMap = data.imageKnifeValue as PixelMap; - this.imageKnifePixelMapPack = pixelMapPack2; - this.normalPixelMap = true; - this.normalResource = true; - console.log('displayPixelMap end') - } - - displayResource(data:ImageKnifeData){ - console.log('displayResource start') - this.imageKnifeResource = data.imageKnifeValue as Resource; - this.normalPixelMap = false; - this.normalResource = true; - console.log('displayResource end') - } - - displayString(data:ImageKnifeData){ - console.log('displayString start') - let imageKnifeNeedStr = 'file://' + data.imageKnifeValue; - console.log('imageKnifeNeedStr='+imageKnifeNeedStr) - this.imageKnifeString = imageKnifeNeedStr; - this.normalPixelMap = false; - this.normalResource = false; - console.log('displayString end') - } - imageKnifeSpecialFixed(data:ImageKnifeData) { - if (data.isPixelMap()) { - this.displayPixelMap(data); - } - else if (data.isString()) { - this.displayString(data); - } else if (data.isResource()) { - this.displayResource(data); - } else { - } - } - - animateTo(name: string) { - if (name == 'progress') { - this.percentVisible = Visibility.Visible; - this.imageVisible = Visibility.Hidden; - this.retryVisible = Visibility.Hidden; - if (this.percentWidth == '0%' || this.percentHeight == '0%') { - animateTo({ duration: this.imageKnifeOption.animateDuration , curve: Curve.Linear }, () => { - this.percentWidth = '100%'; - this.percentHeight = '100%'; - this.imageWidth = '0%'; - this.imageHeight = '0%'; - this.retryWidth = '0%'; - this.retryHeight = '0%'; - }) - } - } else if (name == 'image') { - this.imageVisible = Visibility.Visible; - this.percentVisible = Visibility.Hidden; - this.retryVisible = Visibility.Hidden; - if (this.imageWidth == '0%' || this.imageHeight == '0%') { - animateTo({ duration: this.imageKnifeOption.animateDuration, curve: Curve.Linear }, () => { - this.imageWidth = '100%'; - this.imageHeight = '100%'; - this.percentWidth = '0%'; - this.percentHeight = '0%'; - this.retryWidth = '0%'; - this.retryHeight = '0%'; - }) - } - } else if (name == 'retry') { - this.retryVisible = Visibility.Visible; - this.imageVisible = Visibility.Hidden; - this.percentVisible = Visibility.Hidden; - if (this.retryWidth == '0%' || this.retryHeight == '0%') { - animateTo({ duration: this.imageKnifeOption.animateDuration, curve: Curve.Linear }, () => { - this.imageWidth = '0%'; - this.imageHeight = '0%'; - this.percentWidth = '0%'; - this.percentHeight = '0%'; - this.retryWidth = '100%'; - this.retryHeight = '100%'; + if (!this.drawLifeCycleHasConsumed(this.imageKnifeOption.drawLifeCycle, 'displayPlaceholder', this.context, data, this.imageKnifeOption, + this.currentWidth, this.currentHeight, (gifTimeId) => { + this.setGifTimeId(gifTimeId) + })) { + if (!this.drawLifeCycleHasConsumed(globalThis.ImageKnife.getDefaultLifeCycle(), 'displayPlaceholder', this.context, data, this.imageKnifeOption, + this.currentWidth, this.currentHeight, (gifTimeId) => { + this.setGifTimeId(gifTimeId) + })) { + this.defaultLifeCycle.displayPlaceholder(this.context, data, this.imageKnifeOption, this.currentWidth, this.currentHeight, (gifTimeId) => { + this.setGifTimeId(gifTimeId) }) } } + + } - requestAddTransform(request:RequestOption){ - if(TransformType.BlurTransformation == this.imageKnifeOption.transform.transformType){ + displayProgress(percent: number) { + + if (!this.drawLifeCycleHasConsumed(this.imageKnifeOption.drawLifeCycle, 'displayProgress', this.context, percent, this.imageKnifeOption, + this.currentWidth, this.currentHeight, (gifTimeId) => { + this.setGifTimeId(gifTimeId) + })) { + if (!this.drawLifeCycleHasConsumed(globalThis.ImageKnife.getDefaultLifeCycle(), 'displayProgress', this.context, percent, this.imageKnifeOption, + this.currentWidth, this.currentHeight, (gifTimeId) => { + this.setGifTimeId(gifTimeId) + })) { + this.defaultLifeCycle.displayProgress(this.context, percent, this.imageKnifeOption, this.currentWidth, this.currentHeight, (gifTimeId) => { + this.setGifTimeId(gifTimeId) + }) + } + } + + + } + + displayThumbSizeMultiplier(data: ImageKnifeData) { + + if (!this.drawLifeCycleHasConsumed(this.imageKnifeOption.drawLifeCycle, 'displayThumbSizeMultiplier', this.context, data, this.imageKnifeOption, + this.currentWidth, this.currentHeight, (gifTimeId) => { + this.setGifTimeId(gifTimeId) + })) { + if (!this.drawLifeCycleHasConsumed(globalThis.ImageKnife.getDefaultLifeCycle(), 'displayThumbSizeMultiplier', this.context, data, this.imageKnifeOption, + this.currentWidth, this.currentHeight, (gifTimeId) => { + this.setGifTimeId(gifTimeId) + })) { + this.defaultLifeCycle.displayThumbSizeMultiplier(this.context, data, this.imageKnifeOption, this.currentWidth, this.currentHeight, (gifTimeId) => { + this.setGifTimeId(gifTimeId) + }) + } + } + + + } + + displayMainSource(data: ImageKnifeData) { + + if (!this.drawLifeCycleHasConsumed(this.imageKnifeOption.drawLifeCycle, 'displayMainSource', this.context, data, this.imageKnifeOption, + this.currentWidth, this.currentHeight, (gifTimeId) => { + this.setGifTimeId(gifTimeId) + })) { + if (!this.drawLifeCycleHasConsumed(globalThis.ImageKnife.getDefaultLifeCycle(), 'displayMainSource', this.context, data, this.imageKnifeOption, + this.currentWidth, this.currentHeight, (gifTimeId) => { + this.setGifTimeId(gifTimeId) + })) { + this.defaultLifeCycle.displayMainSource(this.context, data, this.imageKnifeOption, this.currentWidth, this.currentHeight, (gifTimeId) => { + this.setGifTimeId(gifTimeId) + }) + } + } + + + } + + displayRetryholder(data: ImageKnifeData) { + if (!this.drawLifeCycleHasConsumed(this.imageKnifeOption.drawLifeCycle, 'displayRetryholder', this.context, data, this.imageKnifeOption, + this.currentWidth, this.currentHeight, (gifTimeId) => { + this.setGifTimeId(gifTimeId) + })) { + if (!this.drawLifeCycleHasConsumed(globalThis.ImageKnife.getDefaultLifeCycle(), 'displayRetryholder', this.context, data, this.imageKnifeOption, + this.currentWidth, this.currentHeight, (gifTimeId) => { + this.setGifTimeId(gifTimeId) + })) { + this.defaultLifeCycle.displayRetryholder(this.context, data, this.imageKnifeOption, this.currentWidth, this.currentHeight, (gifTimeId) => { + this.setGifTimeId(gifTimeId) + }) + } + } + + } + + displayErrorholder(data: ImageKnifeData) { + + if (!this.drawLifeCycleHasConsumed(this.imageKnifeOption.drawLifeCycle, 'displayErrorholder', this.context, data, this.imageKnifeOption, + this.currentWidth, this.currentHeight, (gifTimeId) => { + this.setGifTimeId(gifTimeId) + })) { + if (!this.drawLifeCycleHasConsumed(globalThis.ImageKnife.getDefaultLifeCycle(), 'displayErrorholder', this.context, data, this.imageKnifeOption, + this.currentWidth, this.currentHeight, (gifTimeId) => { + this.setGifTimeId(gifTimeId) + })) { + this.defaultLifeCycle.displayErrorholder(this.context, data, this.imageKnifeOption, this.currentWidth, this.currentHeight, (gifTimeId) => { + this.setGifTimeId(gifTimeId) + }) + } + } + + + } + + drawPlaceholder(context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) { + console.log('default drawPlaceholder start!') + // @ts-ignore + data.drawPixelMap.imagePixelMap.getImageInfo().then((imageInfo) => { + console.log('imageinfo widht =' + imageInfo.size.width + ' height=' + imageInfo.size.height) + let scaleType = (typeof imageKnifeOption.placeholderScaleType == 'number') ? imageKnifeOption.placeholderScaleType : ScaleType.FIT_CENTER + context.save(); + context.clearRect(0, 0, compWidth, compHeight) + ScaleTypeHelper.drawImageWithScaleType(context, scaleType, data.drawPixelMap.imagePixelMap, px2vp(imageInfo.size.width), px2vp(imageInfo.size.height), compWidth, compHeight,0,0) + context.restore(); + console.log('default drawPlaceholder end!') + }) + } + + drawProgress(context: CanvasRenderingContext2D, progress: number, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) { + let pi = Math.PI * 2 / 100; //pi 讲圆的周长划分为100份 + let rate = progress - 25; + let diameter = compWidth > compHeight ? compHeight : compWidth + context.lineWidth = Math.floor(diameter * 0.03) + context.lineCap = "round" + context.fillStyle = "#10a5ff" + context.font = Math.floor(diameter * 0.3 * px2vp(1))+'px' + + let x0 = (compWidth - diameter) / 2.0 + Math.floor(diameter * 0.5) + let y0 = (compHeight - diameter) / 2.0 + Math.floor(diameter * 0.1) + + let x1 = (compWidth - diameter) / 2.0 + Math.floor(diameter * 0.5) + let y1 = (compHeight - diameter) / 2.0 + Math.floor(diameter * 0.8) + let gradient = context.createLinearGradient(x0, y0, x1, y1) + gradient.addColorStop(0, "#11ffe4") + gradient.addColorStop(0.5, "#03c6fd") + gradient.addColorStop(1, "#10a5ff") + + context.clearRect(0, 0, compWidth, compHeight) + context.shadowBlur = 0 + context.beginPath() + context.strokeStyle = "#15222d" + let radius = Math.floor(diameter * 0.3) + let arcX = compWidth / 2.0 + let arcY = compHeight / 2.0 + context.arc(arcX, arcY, radius, 0, Math.PI * 2, true) + context.stroke() + context.beginPath() + let showText = rate + 25 + '%' + let metrics = context.measureText(showText) + let textX = (compWidth / 2.0) - metrics.width / 2.0 + let textY = (compHeight / 2.0) + metrics.height * 0.3 + context.fillText(showText, textX, textY) + context.stroke() + context.beginPath() + context.strokeStyle = gradient + context.arc(arcX, arcY, radius, pi * -25, pi * rate) + context.stroke(); + } + + drawThumbSizeMultiplier(context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) { + console.log('default drawThumbSizeMultiplier start!') + // @ts-ignore + data.drawPixelMap.imagePixelMap.getImageInfo().then((imageInfo) => { + console.log('imageinfo widht =' + imageInfo.size.width + ' height=' + imageInfo.size.height) + let scaleType = (typeof imageKnifeOption.thumbSizeMultiplierScaleType == 'number') ? imageKnifeOption.thumbSizeMultiplierScaleType : ScaleType.FIT_CENTER + context.save(); + context.clearRect(0, 0, compWidth, compHeight) + ScaleTypeHelper.drawImageWithScaleType(context, scaleType, data.drawPixelMap.imagePixelMap, px2vp(imageInfo.size.width), px2vp(imageInfo.size.height), compWidth, compHeight,0,0) + context.restore(); + console.log('default drawThumbSizeMultiplier end!') + }) + } + + drawMainSource(context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) { + console.log('default drawMainSource start!') + if (data.isPixelMap()) { + // @ts-ignore + data.drawPixelMap.imagePixelMap.getImageInfo().then((imageInfo) => { + let scaleType = (typeof imageKnifeOption.mainScaleType == 'number') ? imageKnifeOption.mainScaleType : ScaleType.FIT_CENTER + console.log('imageinfo width =' + imageInfo.size.width + ' height=' + imageInfo.size.height + 'scaleType=' + scaleType) + context.save(); + context.clearRect(0, 0, compWidth, compHeight) + ScaleTypeHelper.drawImageWithScaleType(context, scaleType, data.drawPixelMap.imagePixelMap, px2vp(imageInfo.size.width), px2vp(imageInfo.size.height), compWidth, compHeight,0,0) + context.restore(); + console.log('default drawMainSource end!') + }) + } else if (data.isGIFFrame()) { + this.drawGIFFrame(context, data, imageKnifeOption, compWidth, compHeight, setGifTimeId) + } + } + + drawRetryholder(context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) { + console.log('default drawRetryholder start!') + // @ts-ignore + data.drawPixelMap.imagePixelMap.getImageInfo().then((imageInfo) => { + console.log('imageinfo width =' + imageInfo.size.width + ' height=' + imageInfo.size.height) + let scaleType = (typeof imageKnifeOption.retryholderScaleType == 'number') ? imageKnifeOption.retryholderScaleType : ScaleType.FIT_CENTER + context.save(); + context.clearRect(0, 0, compWidth, compHeight) + ScaleTypeHelper.drawImageWithScaleType(context, scaleType, data.drawPixelMap.imagePixelMap, px2vp(imageInfo.size.width), px2vp(imageInfo.size.height), compWidth, compHeight,0,0) + context.restore(); + console.log('default drawRetryholder end!') + }) + } + + drawErrorholder(context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) { + console.log('default drawErrorholder start!') + // @ts-ignore + data.drawPixelMap.imagePixelMap.getImageInfo().then((imageInfo) => { + console.log('imageinfo widht =' + imageInfo.size.width + ' height=' + imageInfo.size.height) + let scaleType = (typeof imageKnifeOption.errorholderSrcScaleType == 'number') ? imageKnifeOption.errorholderSrcScaleType : ScaleType.FIT_CENTER + context.save(); + context.clearRect(0, 0, compWidth, compHeight) + ScaleTypeHelper.drawImageWithScaleType(context, scaleType, data.drawPixelMap.imagePixelMap, px2vp(imageInfo.size.width), px2vp(imageInfo.size.height), compWidth, compHeight,0,0) + context.restore(); + console.log('default drawErrorholder end!') + }) + } + + requestAddTransform(request: RequestOption) { + if (TransformType.BlurTransformation == this.imageKnifeOption.transform.transformType) { request.blur(this.imageKnifeOption.transform.blur) - }else if(TransformType.BrightnessFilterTransformation == this.imageKnifeOption.transform.transformType){ + } else if (TransformType.BrightnessFilterTransformation == this.imageKnifeOption.transform.transformType) { request.brightnessFilter(this.imageKnifeOption.transform.brightnessFilter) - }else if(TransformType.ContrastFilterTransformation == this.imageKnifeOption.transform.transformType){ + } else if (TransformType.ContrastFilterTransformation == this.imageKnifeOption.transform.transformType) { request.contrastFilter(this.imageKnifeOption.transform.contrastFilter) - }else if(TransformType.CropCircleTransformation == this.imageKnifeOption.transform.transformType){ + } else if (TransformType.CropCircleTransformation == this.imageKnifeOption.transform.transformType) { request.cropCircle() - }else if(TransformType.CropCircleWithBorderTransformation == this.imageKnifeOption.transform.transformType){ + } else if (TransformType.CropCircleWithBorderTransformation == this.imageKnifeOption.transform.transformType) { request.cropCircleWithBorder(this.imageKnifeOption.transform.cropCircleWithBorder.border, this.imageKnifeOption.transform.cropCircleWithBorder.obj) - }else if(TransformType.CropSquareTransformation == this.imageKnifeOption.transform.transformType){ + } else if (TransformType.CropSquareTransformation == this.imageKnifeOption.transform.transformType) { request.cropSquare() - }else if(TransformType.CropTransformation == this.imageKnifeOption.transform.transformType){ - request.crop(this.imageKnifeOption.transform.crop.width,this.imageKnifeOption.transform.crop.height,this.imageKnifeOption.transform.crop.cropType) - }else if(TransformType.GrayscaleTransformation == this.imageKnifeOption.transform.transformType){ + } else if (TransformType.CropTransformation == this.imageKnifeOption.transform.transformType) { + request.crop(this.imageKnifeOption.transform.crop.width, this.imageKnifeOption.transform.crop.height, this.imageKnifeOption.transform.crop.cropType) + } else if (TransformType.GrayscaleTransformation == this.imageKnifeOption.transform.transformType) { request.grayscale() - }else if(TransformType.InvertFilterTransformation == this.imageKnifeOption.transform.transformType){ + } else if (TransformType.InvertFilterTransformation == this.imageKnifeOption.transform.transformType) { request.invertFilter() - }else if(TransformType.MaskTransformation == this.imageKnifeOption.transform.transformType){ + } else if (TransformType.MaskTransformation == this.imageKnifeOption.transform.transformType) { request.mask(this.imageKnifeOption.transform.mask) - }else if(TransformType.PixelationFilterTransformation == this.imageKnifeOption.transform.transformType){ + } else if (TransformType.PixelationFilterTransformation == this.imageKnifeOption.transform.transformType) { request.pixelationFilter(this.imageKnifeOption.transform.pixelationFilter) - }else if(TransformType.RotateImageTransformation == this.imageKnifeOption.transform.transformType){ + } else if (TransformType.RotateImageTransformation == this.imageKnifeOption.transform.transformType) { request.rotateImage(this.imageKnifeOption.transform.rotateImage) - }else if(TransformType.RoundedCornersTransformation == this.imageKnifeOption.transform.transformType){ + } else if (TransformType.RoundedCornersTransformation == this.imageKnifeOption.transform.transformType) { request.roundedCorners(this.imageKnifeOption.transform.roundedCorners) - }else if(TransformType.SepiaFilterTransformation == this.imageKnifeOption.transform.transformType){ + } else if (TransformType.SepiaFilterTransformation == this.imageKnifeOption.transform.transformType) { request.sepiaFilter() - }else if(TransformType.SketchFilterTransformation == this.imageKnifeOption.transform.transformType){ + } else if (TransformType.SketchFilterTransformation == this.imageKnifeOption.transform.transformType) { request.sketchFilter() - }else if(TransformType.SwirlFilterTransformation == this.imageKnifeOption.transform.transformType){ + } else if (TransformType.SwirlFilterTransformation == this.imageKnifeOption.transform.transformType) { request.swirlFilter(this.imageKnifeOption.transform.swirlFilter) - }else if(TransformType.CenterCrop == this.imageKnifeOption.transform.transformType){ + } else if (TransformType.CenterCrop == this.imageKnifeOption.transform.transformType) { request.centerCrop() - }else if(TransformType.CenterInside == this.imageKnifeOption.transform.transformType){ + } else if (TransformType.CenterInside == this.imageKnifeOption.transform.transformType) { request.centerInside() - }else if(TransformType.FitCenter == this.imageKnifeOption.transform.transformType){ + } else if (TransformType.FitCenter == this.imageKnifeOption.transform.transformType) { request.fitCenter() } } aboutToDisappear() { + this.resetGifData(); } onPageShow() { @@ -332,6 +541,278 @@ export struct ImageKnifeComponent { onBackPress() { } + + setGifTimeId(timeId: number) { + this.gifTimerId = timeId; + } + + private drawLifeCycleHasConsumed(drawLifeCycle: IDrawLifeCycle, methodName: string, + context: CanvasRenderingContext2D, data: K, imageKnifeOption: T, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void + ) { + if (drawLifeCycle && drawLifeCycle[methodName]) { + return drawLifeCycle[methodName](context, data, imageKnifeOption, compWidth, compHeight, setGifTimeId) + } + return false; + } + + private drawGIFFrame(context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) { + let frames = data.drawGIFFrame.imageGIFFrames as GIFFrame[] + console.log('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.bind(this, frames, 0, context, compWidth, compHeight)() + } + } + + private resetGifData() { + clearTimeout(this.gifTimerId) + this.gifLoopDuration = 0; + this.startGifLoopTime = 0; + this.endGifLoopTime = 0; + } + + /** + * 绘制直接到第几帧方法,由于gif非第一帧数据可能是不全的,这里采用逐帧渲染的方式来绘制保证图像的完整性 + */ + private drawSeekToFrame(frames: GIFFrame[], context: CanvasRenderingContext2D, compWidth: number, compHeight: number) { + for (let i = 0; i < this.imageKnifeOption.gif; i++) { + this.drawFrame(frames, i, context, compWidth, compHeight); + } + } + + private renderFrames(frames: GIFFrame[], index: number, context: CanvasRenderingContext2D, compWidth: number, compHeight: number) { + console.log('renderFrames frames length =' + frames.length) + let start = new Date().getTime(); + if (index === 0) { + // 如果是第一帧,我们只从开始渲染前记录时间 + this.startGifLoopTime = start; + } + // draw Frame + this.drawFrame(frames, index, context, compWidth, compHeight); + + // 记录渲染结束时间点 + let end = new Date().getTime(); + let diff = end - start + + if (this.autoPlay) { + + // 理论上该帧在屏幕上保留的时间 + let stayTime = frames[index].delay; + if (this.imageKnifeOption.gif && this.imageKnifeOption.gif.speedFactory) { + stayTime = frames[index].delay / (this.imageKnifeOption.gif.speedFactory * 1.0); + } + // 减去程序执行消耗,剩余的准确延迟时间 + let delayTime = Math.max(0, Math.floor(stayTime - diff)); + + this.endGifLoopTime = end; + // 当前gif到第N帧,所对应的N渲染时间,和N-1的停留时间。(第一帧只有渲染时间没有停留时间) + let loopStayTime = this.endGifLoopTime - this.startGifLoopTime; + this.startGifLoopTime = end; + // 整个gif累计的时长; + this.gifLoopDuration += loopStayTime; + // 返回gif一次循环结束回调,并且把当前循环的时间给出 + if (index === (frames.length - 1) && this.imageKnifeOption.gif && this.imageKnifeOption.gif.loopFinish) { + this.imageKnifeOption.gif.loopFinish(this.gifLoopDuration) + this.gifLoopDuration = 0; + } + // update the frame index + index++ + if (index >= frames.length) { + index = 0; + } + this.gifTimerId = setTimeout(this.renderFrames.bind(this, frames, index, context, compWidth, compHeight), delayTime) + } + } + + private drawFrame(frames: GIFFrame[], index: number, context: CanvasRenderingContext2D, compWidth: number, compHeight: number) { + // get current frame + let frame = frames[index]; + if (!frame || !context) { + // 数据保护,绘制保护 + return; + } + + if (!frame['drawPixelMap']) { + // 解码之后已经转为PixelMap + } else { + this.canvasDrawPixelMap(frames, index, context, compWidth, compHeight) + } + + } + + // 具体绘制过程 + private canvasDrawPixelMap(frames: GIFFrame[], index: number, context: CanvasRenderingContext2D, compWidth: number, compHeight: number) { + console.log('canvasDrawPixelMap index=' + index) + let frame = frames[index]; + let pixelmap = frame['drawPixelMap'] + let disposal = 0 + // disposal value is from preFrame + if (index >= 1) { + let preFrame = frames[index-1] + disposal = preFrame.disposalType + } + if (disposal === FrameDisposalType.DISPOSE_RestoreBackground) { + context.clearRect(0, 0, compWidth, compHeight) + } + let scaleType = (typeof this.imageKnifeOption.mainScaleType == 'number') ? this.imageKnifeOption.mainScaleType : ScaleType.FIT_CENTER + context.save(); + let frameW = frames[0].dims.left+frames[0].dims.width + let frameH = frames[0].dims.top+frames[0].dims.height + ScaleTypeHelper.drawImageWithScaleType(context, scaleType, pixelmap, px2vp(frameW), px2vp(frameH), compWidth, compHeight,px2vp(frame.dims.left),px2vp(frame.dims.top)) + // tips:worker如果不是在展示页面中创建,使用子线程回来的数据创建的图片,会导致canvas绘制不出来 + context.restore(); + console.log('default drawMainSource end!') + } +} + +export enum FrameDisposalType { + // 0 - No disposal specified. The decoder is not required to take any action. + // 不使用处置方法 + DISPOSE_Nothing = 0, + // 1-Do not dispose. The graphic is to be left in place. + // 不处置图形,把图形从当前位置移去 + DISPOSE_NotResolve = 1, + // 2 - Restore to background color. The area used by the graphic must be restored to the background color. + // 回复到背景色 + DISPOSE_RestoreBackground = 2, + // 3-Restore to previous + DISPOSE_PreviousStatus = 3 +} + +export enum ScaleType { + // 图像位于用户设置组件左上角显示,图像会缩放至全部展示 + FIT_START = 1, + // 图像位于用户设置组件右下角显示,图像会缩放至全部展示 + FIT_END = 2, + // 图像位于用户设置组件居中,图像会缩放至全部展示 + FIT_CENTER = 3, + // 图像绝对居中展示,不缩放 + CENTER = 4, + // 宽高中,短的部分缩放至组件大小,超出的全部裁剪 + CENTER_CROP = 5, + // 图像拉伸至组件大小 + FIT_XY = 6, + // 如果图像大于组件则执行FIT_CENTER,小于组件则CENTER + CENTER_INSIDE = 7, + // 如果不想适配,直接展示原图大小 + NONE = 8 +} + +export class ScaleTypeHelper { + static drawImageWithScaleType(context: CanvasRenderingContext2D, scaleType: ScaleType, source: PixelMap | ImageBitmap, imageWidth: number, imageHeight: number, compWidth: number, compHeight: number, imageOffsetX:number,imageOffsetY:number) { + let scaleW = compWidth / imageWidth + let scaleH = compHeight / imageHeight + let minScale = scaleW > scaleH ? scaleH : scaleW + let maxScale = scaleW > scaleH ? scaleW : scaleH + + switch (scaleType) { + case ScaleType.FIT_START: + ScaleTypeHelper.drawFitStart(context, source, minScale, imageWidth, imageHeight, imageOffsetX,imageOffsetY) + break; + case ScaleType.FIT_END: + ScaleTypeHelper.drawFitEnd(context, source, minScale, imageWidth, imageHeight, compWidth, compHeight, imageOffsetX,imageOffsetY) + break; + case ScaleType.FIT_CENTER: + ScaleTypeHelper.drawFitCenter(context, source, minScale, imageWidth, imageHeight, compWidth, compHeight, imageOffsetX,imageOffsetY) + break; + case ScaleType.CENTER: + ScaleTypeHelper.drawCenter(context, source, imageWidth, imageHeight, compWidth, compHeight, imageOffsetX,imageOffsetY) + break; + case ScaleType.CENTER_CROP: + ScaleTypeHelper.drawCenterCrop(context, source, maxScale, imageWidth, imageHeight, compWidth, compHeight, imageOffsetX,imageOffsetY) + break; + case ScaleType.FIT_XY: + ScaleTypeHelper.drawFitXY(context, source, scaleW, scaleH, imageWidth, imageHeight, imageOffsetX,imageOffsetY) + break; + case ScaleType.CENTER_INSIDE: + ScaleTypeHelper.drawCenterInside(context, source, minScale, imageWidth, imageHeight, compWidth, compHeight, imageOffsetX,imageOffsetY) + break; + case ScaleType.NONE: + ScaleTypeHelper.drawNone(context, source, imageWidth, imageHeight, imageOffsetX,imageOffsetY) + break; + default: + ScaleTypeHelper.drawNone(context, source, imageWidth, imageHeight, imageOffsetX,imageOffsetY) + break + } + + } + + static drawFitStart(context: CanvasRenderingContext2D, source: PixelMap | ImageBitmap, minScale: number, imageWidth: number, imageHeight: number, imageOffsetX?:number,imageOffsetY?:number) { + context.setTransform(minScale, 0, 0, minScale, 0, 0) + let dx = 0 + let dy = 0 + let dw = imageWidth; + let dh = imageHeight; + context.drawImage(source, dx+imageOffsetX, dy+imageOffsetY) + } + + static drawFitEnd(context: CanvasRenderingContext2D, source: PixelMap | ImageBitmap, minScale: number, imageWidth: number, imageHeight: number, compWidth: number, compHeight: number, imageOffsetX?:number,imageOffsetY?:number) { + context.setTransform(minScale, 0, 0, minScale, 0, 0) + + let dx = (compWidth - imageWidth * minScale) / (minScale * 1.0); + let dy = (compHeight - imageHeight * minScale) / (minScale * 1.0); + let dw = imageWidth; + let dh = imageHeight; + + context.drawImage(source, dx+imageOffsetX, dy+imageOffsetY) + } + + static drawFitCenter(context: CanvasRenderingContext2D, source: PixelMap | ImageBitmap, minScale: number, imageWidth: number, imageHeight: number, compWidth: number, compHeight: number, imageOffsetX?:number,imageOffsetY?:number) { + context.setTransform(minScale, 0, 0, minScale, 0, 0) + let dx = (compWidth - imageWidth * minScale) / (minScale * 2.0); + let dy = (compHeight - imageHeight * minScale) / (minScale * 2.0); + let dw = imageWidth; + let dh = imageHeight; + + context.drawImage(source, dx+imageOffsetX, dy+imageOffsetY) + } + + static drawCenter(context: CanvasRenderingContext2D, source: PixelMap | ImageBitmap, imageWidth: number, imageHeight: number, compWidth: number, compHeight: number, imageOffsetX?:number,imageOffsetY?:number) { + let dx = (compWidth - imageWidth) / 2.0; + let dy = (compHeight - imageHeight) / 2.0; + let dw = imageWidth; + let dh = imageHeight; + context.drawImage(source, dx+imageOffsetX, dy+imageOffsetY) + } + + static drawCenterCrop(context: CanvasRenderingContext2D, source: PixelMap | ImageBitmap, maxScale: number, imageWidth: number, imageHeight: number, compWidth: number, compHeight: number, imageOffsetX?:number,imageOffsetY?:number) { + context.setTransform(maxScale, 0, 0, maxScale, 0, 0) + let dx = (compWidth - imageWidth * maxScale) / (maxScale * 2.0); + let dy = (compHeight - imageHeight * maxScale) / (maxScale * 2.0); + let dw = imageWidth; + let dh = imageHeight; + context.drawImage(source, dx+imageOffsetX, dy+imageOffsetY) + } + + static drawFitXY(context: CanvasRenderingContext2D, source: PixelMap | ImageBitmap, scaleW: number, scaleH: number, imageWidth: number, imageHeight: number, imageOffsetX?:number,imageOffsetY?:number) { + context.setTransform(scaleW, 0, 0, scaleH, 0, 0) + let dx = 0; + let dy = 0; + let dw = imageWidth; + let dh = imageHeight; + context.drawImage(source, dx+imageOffsetX, dy+imageOffsetY) + } + + static drawCenterInside(context: CanvasRenderingContext2D, source: PixelMap | ImageBitmap, minScale: number, imageWidth: number, imageHeight: number, compWidth: number, compHeight: number, imageOffsetX?:number,imageOffsetY?:number) { + if (minScale < 1) { + ScaleTypeHelper.drawFitCenter(context, source, minScale, imageWidth, imageHeight, compWidth, compHeight, imageOffsetX,imageOffsetY) + } else { + ScaleTypeHelper.drawCenter(context, source, imageWidth, imageHeight, compWidth, compHeight, imageOffsetX,imageOffsetY) + } + } + + static drawNone(context: CanvasRenderingContext2D, source: PixelMap | ImageBitmap, imageWidth: number, imageHeight: number, imageOffsetX?:number,imageOffsetY?:number) { + let dx = 0; + let dy = 0; + let dw = imageWidth; + let dh = imageHeight; + context.drawImage(source, dx+imageOffsetX, dy+imageOffsetY) + } } diff --git a/imageknife/src/main/ets/components/imageknife/ImageKnifeData.ets b/imageknife/src/main/ets/components/imageknife/ImageKnifeData.ets index cd0a4aa..79a4bd2 100644 --- a/imageknife/src/main/ets/components/imageknife/ImageKnifeData.ets +++ b/imageknife/src/main/ets/components/imageknife/ImageKnifeData.ets @@ -12,63 +12,73 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { GIFFrame } from './utils/gif/GIFFrame' + +export enum ImageKnifeType { + PIXELMAP = 'PixelMap', + STRING = 'String', + RESOURCE = 'Resource', + GIFFRAME = 'GIFFrame' +} + +export class DrawPixelMap { + imagePixelMap: PixelMap +} + +export class DrawString { + imageString: string +} + +export class DrawResource { + imageResource: Resource +} + +export class DrawGIFFrame { + imageGIFFrames: GIFFrame[] +} export class ImageKnifeData { - - static PIXELMAP = 'PixelMap' - static STRING = 'string' - static RESOURCE ='Resource' - static SVG = 'svg'; static GIF = 'gif'; static JPG = 'jpg'; static PNG = 'png'; static BMP = 'bmp'; static WEBP = 'webp'; + imageKnifeType: ImageKnifeType; + drawPixelMap: DrawPixelMap; + drawGIFFrame: DrawGIFFrame; + drawResource: DrawResource; + drawString: DrawString; - - imageKnifeType:string = ''; - imageKnifeValue:PixelMap|string|Resource; - imageKnifeSourceType:string = '' - - isSvg():boolean{ - return ImageKnifeData.SVG == this.imageKnifeSourceType; + static createImagePixelMap(type: ImageKnifeType, value: PixelMap) { + let data = new ImageKnifeData(); + data.imageKnifeType = type; + data.drawPixelMap = new DrawPixelMap(); + data.drawPixelMap.imagePixelMap = value; + return data; } - isGif():boolean{ - return ImageKnifeData.GIF == this.imageKnifeSourceType; + static createImageGIFFrame(type: ImageKnifeType, value: GIFFrame[]) { + let data = new ImageKnifeData(); + data.imageKnifeType = type; + data.drawGIFFrame = new DrawGIFFrame(); + data.drawGIFFrame.imageGIFFrames = value; + return data; } - isJpg():boolean{ - return ImageKnifeData.JPG == this.imageKnifeSourceType; + isPixelMap(): boolean { + return ImageKnifeType.PIXELMAP == this.imageKnifeType; } - isPng():boolean{ - return ImageKnifeData.PNG == this.imageKnifeSourceType; + isGIFFrame(): boolean { + return ImageKnifeType.GIFFRAME == this.imageKnifeType; } - isBmp():boolean{ - return ImageKnifeData.BMP == this.imageKnifeSourceType; + isString(): boolean { + return ImageKnifeType.STRING == this.imageKnifeType; } - isWebp():boolean{ - return ImageKnifeData.WEBP == this.imageKnifeSourceType; + isResource(): boolean { + return ImageKnifeType.RESOURCE == this.imageKnifeType; } - - - isString():boolean{ - return ImageKnifeData.STRING == this.imageKnifeType; - } - isPixelMap():boolean{ - return ImageKnifeData.PIXELMAP == this.imageKnifeType; - } - isResource():boolean{ - return ImageKnifeData.RESOURCE == this.imageKnifeType; - } - - - can2PixelMap(){ // 可以转换为PixelMap的数据源 - return this.isPixelMap() || this.isBmp() || this.isJpg() || this.isWebp() || this.isPng(); - } - } \ No newline at end of file diff --git a/imageknife/src/main/ets/components/imageknife/ImageKnifeDrawFactory.ets b/imageknife/src/main/ets/components/imageknife/ImageKnifeDrawFactory.ets new file mode 100644 index 0000000..8f10f73 --- /dev/null +++ b/imageknife/src/main/ets/components/imageknife/ImageKnifeDrawFactory.ets @@ -0,0 +1,481 @@ +/* + * Copyright (C) 2022 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 { 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' + +export class ImageKnifeDrawFactory{ + + /** + * 绘制PixelMap内容情况下,主图的椭圆裁剪,可添加边框 + * @param borderWidth 外边框 borderWidth vp + * @param colorString 例如 "#FF00FF" + */ + public static createOvalLifeCycle(borderWidth:number, colorString:string):IDrawLifeCycle{ + let viewLifeCycle = { + // 展示占位图 + displayPlaceholder: (context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) => { + // @ts-ignore + return false; + }, + // 展示加载进度 + displayProgress: (context: CanvasRenderingContext2D, progress: number, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) => { + return false; + }, + // 展示缩略图 + displayThumbSizeMultiplier: (context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) => { + // @ts-ignore + return false; + }, + + // 展示主图 + displayMainSource: (context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) => { + if (data.isPixelMap()) { + // @ts-ignore + data.drawPixelMap.imagePixelMap.getImageInfo().then((imageInfo) => { + let scaleType = (typeof imageKnifeOption.mainScaleType == 'number') ? imageKnifeOption.mainScaleType : ScaleType.FIT_CENTER + + context.clearRect(0,0,compWidth,compHeight) + context.save(); + // 绘制适配后的图像 + context.save(); + ScaleTypeHelper.drawImageWithScaleType(context, scaleType, data.drawPixelMap.imagePixelMap, px2vp(imageInfo.size.width), px2vp(imageInfo.size.height), compWidth, compHeight,0,0) + context.restore(); + + // 使用 destination-in 裁剪出椭圆 + context.save(); + context.globalCompositeOperation = 'destination-in' + context.beginPath(); + this.setOval(context, scaleType, data.drawPixelMap.imagePixelMap, px2vp(imageInfo.size.width), px2vp(imageInfo.size.height), compWidth, compHeight,0,0,borderWidth) + context.closePath(); + context.fill() + context.restore(); + + // 给椭圆加上边框 + context.save(); + if(borderWidth > 0) { + context.strokeStyle = colorString + context.lineWidth = borderWidth; + context.globalCompositeOperation = 'source-over' + context.beginPath(); + this.setOval(context, scaleType, data.drawPixelMap.imagePixelMap, px2vp(imageInfo.size.width), px2vp(imageInfo.size.height), compWidth, compHeight, 0, 0, borderWidth) + context.closePath(); + context.stroke() + context.restore(); + } + context.restore(); + }) + return true; + } + return false; + }, + // 展示重试图层 + displayRetryholder: (context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) => { + return false; + }, + // 展示失败占位图 + displayErrorholder: (context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) => { + return false; + } + } + return viewLifeCycle; + } + + /** + * 绘制椭圆的计算和绘制,可借鉴,私有方法不对外提供 + * @param context + * @param scaleType + * @param source + * @param imageWidth + * @param imageHeight + * @param compWidth + * @param compHeight + * @param imageOffsetX + * @param imageOffsetY + * @param borderWidth + */ + private static setOval(context: CanvasRenderingContext2D, scaleType: ScaleType, source: PixelMap | ImageBitmap, imageWidth: number, imageHeight: number, compWidth: number, compHeight: number, imageOffsetX:number,imageOffsetY:number + ,borderWidth:number) { + let scaleW = compWidth / imageWidth + let scaleH = compHeight / imageHeight + let minScale = scaleW > scaleH ? scaleH : scaleW + let maxScale = scaleW > scaleH ? scaleW : scaleH + let circleX = compWidth / 2 + let circleY = compHeight / 2 + switch (scaleType) { + case ScaleType.FIT_START: + circleX = imageWidth * minScale / 2 + circleY = imageWidth * minScale / 2 + context.ellipse(circleX,circleY, (imageWidth * minScale - borderWidth) / 2, (imageHeight * minScale - borderWidth) / 2, 0, 0, Math.PI * 2) + break + case ScaleType.FIT_END: + if(scaleW >= scaleH){ + circleX = compWidth - imageWidth * minScale/2 + circleY = imageHeight * minScale / 2 + }else{ + circleX = imageWidth * minScale / 2 + circleY = compHeight - imageHeight * minScale / 2 + } + context.ellipse(circleX, circleY, (imageWidth * minScale - borderWidth) / 2, (imageHeight * minScale - borderWidth) / 2, 0, 0, Math.PI * 2) + break + case ScaleType.FIT_CENTER: + circleX = compWidth / 2; + circleY = compHeight / 2; + context.ellipse(circleX, circleY, (imageWidth * minScale - borderWidth) / 2, (imageHeight * minScale - borderWidth) / 2, 0, 0, Math.PI * 2) + break + case ScaleType.CENTER: + circleX = compWidth / 2; + circleY = compWidth / 2; + let centerRadiusX = (Math.min(compWidth,imageWidth) - borderWidth)/2 + let centerRadiusY = (Math.min(compHeight,imageHeight) -borderWidth)/2 + context.ellipse(circleX, circleY, centerRadiusX, centerRadiusY, 0, 0, Math.PI * 2) + break + case ScaleType.CENTER_CROP: + // 肯定会占满全屏 + circleX = compWidth / 2; + circleY = compHeight / 2; + let centerCropRadiusX = (compWidth-borderWidth)/2 + let centerCropRadiusY = (compHeight-borderWidth)/2 + + context.ellipse(circleX, circleY, centerCropRadiusX, centerCropRadiusY, 0, 0, Math.PI * 2) + break + case ScaleType.FIT_XY: + context.ellipse(compWidth / 2, compHeight / 2, (imageWidth * scaleW - borderWidth) / 2, (imageHeight * scaleH - borderWidth) / 2, 0, 0, Math.PI * 2) + break + case ScaleType.CENTER_INSIDE: + if(minScale < 1){ // FIT_CENTER + circleX = compWidth / 2; + circleY = compHeight / 2; + context.ellipse(circleX,circleY,(imageWidth * minScale - borderWidth) / 2, (imageHeight * minScale - borderWidth) / 2, 0, 0, Math.PI * 2) + }else{ // CENTER + circleX = compWidth / 2; + circleY = compWidth / 2; + let centerRadiusX = (Math.min(compWidth,imageWidth) - borderWidth)/2 + let centerRadiusY = (Math.min(compHeight,imageHeight) -borderWidth)/2 + context.ellipse(circleX, circleY, centerRadiusX, centerRadiusY, 0, 0, Math.PI * 2) + } + break + case ScaleType.NONE: + circleX = Math.min(compWidth,imageWidth)/2 + circleY = Math.min(compHeight,imageHeight)/2 + let noneRadiusX = (Math.min(compWidth,imageWidth)-borderWidth)/2 + let noneRadiusY = (Math.min(compHeight,imageHeight)-borderWidth)/2 + context.ellipse(circleX,circleY,noneRadiusX,noneRadiusY,0,0, Math.PI*2) + break; + default: + break; + } + } + + + /** + * 绘制PixelMap内容情况下,主图的圆角裁剪,可添加边框 + * @param borderWidth 边框宽度 + * @param colorString 边框颜色string + * @param connerRadius 圆角半径 + */ + public static createRoundLifeCycle(borderWidth:number, colorString:string, connerRadius:number){ + let viewLifeCycle = { + // 展示占位图 + displayPlaceholder: (context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) => { + // @ts-ignore + return false; + }, + // 展示加载进度 + displayProgress: (context: CanvasRenderingContext2D, progress: number, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) => { + return false; + }, + // 展示缩略图 + displayThumbSizeMultiplier: (context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) => { + // @ts-ignore + return false; + }, + + // 展示主图 + displayMainSource: (context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) => { + if (data.isPixelMap()) { + // @ts-ignore + data.drawPixelMap.imagePixelMap.getImageInfo().then((imageInfo) => { + let scaleType = (typeof imageKnifeOption.mainScaleType == 'number') ? imageKnifeOption.mainScaleType : ScaleType.FIT_CENTER + + context.clearRect(0,0,compWidth,compHeight) + context.save(); + + // 绘制适配后的图像 + context.save(); + ScaleTypeHelper.drawImageWithScaleType(context, scaleType, data.drawPixelMap.imagePixelMap, px2vp(imageInfo.size.width), px2vp(imageInfo.size.height), compWidth, compHeight,0,0) + context.restore(); + + // 通过 destination-in 裁剪出圆角 + context.save(); + context.globalCompositeOperation = 'destination-in' + this.setRect(context, scaleType, data.drawPixelMap.imagePixelMap, px2vp(imageInfo.size.width), px2vp(imageInfo.size.height), compWidth, compHeight,0,0,borderWidth,connerRadius) + context.fill() + context.restore(); + if(borderWidth > 0){ + // 为圆角添加边框 + context.save(); + context.strokeStyle = colorString + context.lineWidth = borderWidth + context.globalCompositeOperation = 'source-over' + this.setRect(context, scaleType, data.drawPixelMap.imagePixelMap, px2vp(imageInfo.size.width), px2vp(imageInfo.size.height), compWidth, compHeight,0,0,borderWidth,connerRadius) + context.stroke() + context.restore(); + } + context.restore(); + console.log('TestImageKnifeOptionChangedPage4 drawMainSource end!') + }) + return true; + } + return false; + }, + // 展示重试图层 + displayRetryholder: (context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) => { + return false; + }, + // 展示失败占位图 + displayErrorholder: (context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) => { + return false; + } + } + return viewLifeCycle; + } + + /** + * 绘制圆角的计算,可借鉴,私有方法不对外提供 + * @param context + * @param scaleType + * @param source + * @param imageWidth + * @param imageHeight + * @param compWidth + * @param compHeight + * @param imageOffsetX + * @param imageOffsetY + * @param borderWidth + * @param cornerRadius + */ + private static setRect(context: CanvasRenderingContext2D, scaleType: ScaleType, source: PixelMap | ImageBitmap, imageWidth: number, imageHeight: number, compWidth: number, compHeight: number, imageOffsetX:number,imageOffsetY:number + ,borderWidth:number,cornerRadius:number) { + let scaleW = compWidth / imageWidth + let scaleH = compHeight / imageHeight + let minScale = scaleW > scaleH ? scaleH : scaleW + let maxScale = scaleW > scaleH ? scaleW : scaleH + let x1 = borderWidth/2 + let y1 = borderWidth/2 + let w1 = compWidth - borderWidth; + let h1 = compWidth - borderWidth; + switch (scaleType) { + case ScaleType.FIT_START: + x1 = borderWidth/2 + y1 = borderWidth/2 + w1 = imageWidth * minScale - borderWidth; + h1 = imageHeight * minScale - borderWidth; + this.roundRect(context, x1, y1, w1, h1, cornerRadius) + break + case ScaleType.FIT_END: + x1 = compWidth - imageWidth * minScale + borderWidth / 2 + y1 = compHeight - imageHeight * minScale + borderWidth / 2 + w1 = imageWidth * minScale - borderWidth; + h1 = imageHeight * minScale - borderWidth; + this.roundRect(context, x1, y1, w1, h1, cornerRadius) + break + case ScaleType.FIT_CENTER: + x1 = (compWidth - imageWidth * minScale) / 2 + borderWidth / 2 + y1 = (compHeight - imageHeight * minScale) / 2 + borderWidth / 2 + w1 = imageWidth * minScale - borderWidth + h1 = imageHeight * minScale - borderWidth + this.roundRect(context, x1, y1, w1, h1, cornerRadius) + break + case ScaleType.CENTER: + x1 = Math.max(0,(compWidth - Math.min(compWidth, imageWidth)))/2 + borderWidth/2 + y1 = Math.max(0,(compHeight - Math.min(compHeight, imageHeight)))/2 + borderWidth/2 + + w1 = Math.min(compWidth, imageWidth) - borderWidth; + h1 = Math.min(compHeight, imageHeight) - borderWidth; + + this.roundRect(context, x1, y1, w1, h1, cornerRadius) + break + case ScaleType.CENTER_CROP: + x1 = borderWidth/2 + y1 = borderWidth/2 + + w1 = compWidth - borderWidth; + h1 = compWidth - borderWidth; + this.roundRect(context, x1, y1, w1, h1, cornerRadius) + break + case ScaleType.FIT_XY: + x1 = borderWidth/2 + y1 = borderWidth/2 + + w1 = compWidth - borderWidth; + h1 = compWidth - borderWidth; + this.roundRect(context, x1, y1, w1, h1, cornerRadius) + break + case ScaleType.CENTER_INSIDE: + if(minScale < 1){ // FIT_CENTER + x1 = (compWidth - imageWidth * minScale) / 2 + borderWidth / 2 + y1 = (compHeight - imageHeight * minScale) / 2 + borderWidth / 2 + w1 = imageWidth * minScale - borderWidth + h1 = imageHeight * minScale - borderWidth + this.roundRect(context, x1, y1, w1, h1, cornerRadius) + }else{ // CENTER + x1 = Math.max(0,(compWidth - Math.min(compWidth, imageWidth)))/2 + borderWidth/2 + y1 = Math.max(0,(compHeight - Math.min(compHeight, imageHeight)))/2 + borderWidth/2 + + w1 = Math.min(compWidth, imageWidth) - borderWidth; + h1 = Math.min(compHeight, imageHeight) - borderWidth; + + this.roundRect(context, x1, y1, w1, h1, cornerRadius) + } + + break; + case ScaleType.NONE: + + x1 = borderWidth/2 + y1 = borderWidth/2 + + w1 = Math.min(compWidth, imageWidth) - borderWidth; + h1 = Math.min(compHeight, imageHeight) - borderWidth; + + this.roundRect(context, x1, y1, w1, h1, cornerRadius) + break; + } + } + + /** + * 绘制圆角的绘制,可借鉴,私有方法不对外提供 + * @param context + * @param x + * @param y + * @param w + * @param h + * @param r + */ + private static roundRect(context: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, r: number) { + if (w < 2 * r) { + r = w / 2; + } + if (h < 2 * r) { + r = h / 2; + } + context.beginPath(); + context.moveTo(x + r, y); + context.arcTo(x + w, y, x + w, y + h, r); + context.arcTo(x + w, y + h, x, y + h, r); + context.arcTo(x, y + h, x, y, r); + context.arcTo(x, y, x + w, y, r); + context.closePath(); + } + + /** + * 绘制下载进度条 + * @param fontColor 文字颜色 + * @param fontSizeRate 文字占半径比率[0,1] + */ + public static createProgressLifeCycle(fontColor:string, fontSizeRate:number){ + let viewLifeCycle: IDrawLifeCycle = { + // 展示占位图 + displayPlaceholder: (context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) => { + // @ts-ignore + return false; + }, + // 展示加载进度 + displayProgress: (context: CanvasRenderingContext2D, progress: number, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) => { + // @ts-ignore + this.drawDefaultProgress(context, progress, imageKnifeOption, compWidth, compHeight,fontColor,fontSizeRate,setGifTimeId) + return true; + }, + // 展示缩略图 + displayThumbSizeMultiplier: (context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) => { + // @ts-ignore + return false; + }, + + // 展示主图 + displayMainSource: (context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) => { + // @ts-ignore + return false; + }, + + // 展示重试图层 + displayRetryholder: (context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) => { + // @ts-ignore + return false; + }, + + // 展示失败占位图 + displayErrorholder: (context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) => { + // @ts-ignore + return false; + } + } + return viewLifeCycle; + } + /** + * 绘制默认的网络下载百分比 + * @param context + * @param progress + * @param imageKnifeOption + * @param compWidth + * @param compHeight + * @param fontColor + * @param fontSizeRate + * @param setGifTimeId + */ + private static drawDefaultProgress(context: CanvasRenderingContext2D, progress: number, imageKnifeOption: ImageKnifeOption, + compWidth: number, compHeight: number, + fontColor:string, fontSizeRate:number,setGifTimeId?: (timeId: number) => void,){ + let pi = Math.PI * 2 / 100; //pi 讲圆的周长划分为100份 + let rate = progress - 25; + let diameter = compWidth > compHeight ? compHeight : compWidth + context.lineWidth = Math.floor(diameter * 0.03) + context.lineCap = "round" + context.fillStyle = fontColor//"#10a5ff" + context.font = Math.floor(diameter * fontSizeRate *px2vp(1))+'px' + let x0 = (compWidth - diameter) / 2.0 + Math.floor(diameter * 0.5) + let y0 = (compHeight - diameter) / 2.0 + Math.floor(diameter * 0.1) + let x1 = (compWidth - diameter) / 2.0 + Math.floor(diameter * 0.5) + let y1 = (compHeight - diameter) / 2.0 + Math.floor(diameter * 0.8) + let gradient = context.createLinearGradient(x0, y0, x1, y1) + gradient.addColorStop(0, "#11ffe4") + gradient.addColorStop(0.5, "#03c6fd") + gradient.addColorStop(1, "#10a5ff") + context.clearRect(0, 0, compWidth, compHeight) + context.shadowBlur = 0 + context.beginPath() + context.strokeStyle = "#15222d" + let radius = Math.floor(diameter * 0.3) + let arcX = compWidth / 2.0 + let arcY = compHeight / 2.0 + context.arc(arcX, arcY, radius, 0, Math.PI * 2, true) + context.stroke() + context.beginPath() + let showText = rate + 25 + '%' + let metrics = context.measureText(showText) + let textX = (compWidth / 2.0) - metrics.width / 2.0 + let textY = (compHeight / 2.0) + metrics.height * 0.3 + context.fillText(showText, textX, textY) + context.stroke() + context.beginPath() + context.strokeStyle = gradient + context.arc(arcX, arcY, radius, pi * -25, pi * rate) + context.stroke(); + } + + +} \ No newline at end of file diff --git a/imageknife/src/main/ets/components/imageknife/ImageKnifeOption.ets b/imageknife/src/main/ets/components/imageknife/ImageKnifeOption.ets index 9459789..4f4b21f 100644 --- a/imageknife/src/main/ets/components/imageknife/ImageKnifeOption.ets +++ b/imageknife/src/main/ets/components/imageknife/ImageKnifeOption.ets @@ -13,17 +13,26 @@ * limitations under the License. */ -import {AUTOMATIC} from "../cache/diskstrategy/enum/AUTOMATIC" -import {DiskStrategy} from "../cache/diskstrategy/DiskStrategy" -import {BaseTransform} from "../imageknife/transform/BaseTransform" -import {TransformType} from "../imageknife/transform/TransformType" -import {CropType} from "../imageknife/transform/CropTransformation" -import {AllCacheInfo, IAllCacheInfoCallback} from "../imageknife/interface/IAllCacheInfoCallback" +import { AUTOMATIC } from '../cache/diskstrategy/enum/AUTOMATIC' + +import { DiskStrategy } from '../cache/diskstrategy/DiskStrategy' +import { BaseTransform } from '../imageknife/transform/BaseTransform' +import { TransformType } from '../imageknife/transform/TransformType' +import { CropType } from '../imageknife/transform/CropTransformation' +import { AllCacheInfo, IAllCacheInfoCallback } from '../imageknife/interface/IAllCacheInfoCallback' +import { IDrawLifeCycle } from '../imageknife/interface/IDrawLifeCycle' +import { ScaleType } from '../imageknife/ImageKnifeComponent' export class ImageKnifeOption { + // 组件大小 + size: { + width: string, + height: string + }; // 主图资源 loadSrc: string | PixelMap | Resource; + mainScaleType?: ScaleType = ScaleType.FIT_CENTER // 磁盘缓存策略 strategy?: DiskStrategy = new AUTOMATIC(); @@ -33,59 +42,83 @@ export class ImageKnifeOption { // 占位图 placeholderSrc?: PixelMap | Resource; + placeholderScaleType?: ScaleType = ScaleType.FIT_CENTER // 失败占位图 errorholderSrc?: PixelMap | Resource; + errorholderSrcScaleType?: ScaleType = ScaleType.FIT_CENTER // 重试占位图 retryholderSrc?: Resource; + retryholderScaleType?: ScaleType = ScaleType.FIT_CENTER // 缩略图,范围(0,1) thumbSizeMultiplier?: number; + // 缩略图展示时间 + thumbSizeDelay?:number; + // 缩略图展示类型 + thumbSizeMultiplierScaleType?: ScaleType = ScaleType.FIT_CENTER // 进度条 displayProgress?: boolean; - // 进度条回调 - displayProgressListener?; - // 重试图层 - retryLoad?: boolean; - - // 动画时长 - animateDuration?: number = 500; + // 重试图层 可点击 + canRetryClick?: boolean; // 仅使用缓存加载数据 onlyRetrieveFromCache?: boolean = false; - // 是否开启第一级内存缓存 + // 是否开启一级内存缓存 isCacheable?: boolean = true; - // 变换相关 - transform?:{ - transformType:number, - blur?:number, - roundedCorners?:{ top_left: number, top_right: number, bottom_left: number, bottom_right: number } - cropCircleWithBorder?:{border:number, obj:{ r_color: number, g_color: number, b_color: number }} - crop?:{width: number, height: number, cropType: CropType} - brightnessFilter?:number, - contrastFilter?:number, - pixelationFilter?:number, - swirlFilter?:number, - mask?:Resource, - rotateImage?:number + + // 用户自定义实现 绘制方案 + drawLifeCycle?: IDrawLifeCycle; + + gif?: { + loopFinish?: (loopTime?) => void + speedFactory?: number + seekTo?: number } - transformation?:BaseTransform; - transformations?:Array>; + // 变换相关 + transform?: { + transformType: number, + blur?: number, + roundedCorners?: { + top_left: number, + top_right: number, + bottom_left: number, + bottom_right: number + } + cropCircleWithBorder?: { + border: number, + obj: { + r_color: number, + g_color: number, + b_color: number + } + } + crop?: { + width: number, + height: number, + cropType: CropType + } + brightnessFilter?: number, + contrastFilter?: number, + pixelationFilter?: number, + swirlFilter?: number, + mask?: Resource, + rotateImage?: number + } + transformation?: BaseTransform; + transformations?: Array>; // 输出缓存相关内容和信息 allCacheInfoCallback?: IAllCacheInfoCallback; - size: { - width: number, - height: number - }; + imageFit?: ImageFit; backgroundColor?: Color | number | string | Resource; margin?: { @@ -95,7 +128,7 @@ export class ImageKnifeOption { left?: number | string | Resource } | number | string | Resource - + sizeAnimate?: AnimateParam constructor() { diff --git a/imageknife/src/main/ets/components/imageknife/RequestOption.ets b/imageknife/src/main/ets/components/imageknife/RequestOption.ets index f443e7b..acbdf82 100644 --- a/imageknife/src/main/ets/components/imageknife/RequestOption.ets +++ b/imageknife/src/main/ets/components/imageknife/RequestOption.ets @@ -55,19 +55,19 @@ export class RequestOption { errorholderData: ImageKnifeData; thumbSizeMultiplier: number; - // 如果存在缩略图,则主图延时3000ms加载 - thumbDelayTime: number = 3000 + // 如果存在缩略图,则主图延时1s加载 + thumbDelayTime: number = 1000 thumbHolderFunc: AsyncSuccess; requestListeners: Array>; // 进度条 - progressFunc: AsyncSuccess; + progressFunc: AsyncSuccess; // 重试图层 - retryFunc: AsyncSuccess + retryholderSrc: PixelMap | Resource; + retryholderFunc: AsyncSuccess + retryholderData: ImageKnifeData - // 图层切换时长 - animateDuration: number = 500; size: { width: number, height: number @@ -98,12 +98,13 @@ export class RequestOption { loadMainReady = false; // 失败占位图展示状态 当true 表示主图加载失败需要展示失败占位图 - loadErrorReady = false + loadErrorReady = false; + + // 重试占位图展示状态 当true 表示主图加载失败需要展示失败占位图 + loadRetryReady = false; // 缩略图展示 loadThumbnailReady = false; - _svgAndGifFolder: string = "svgAndGifFolder"; // svg和gif的文件路径地址 - _svgAndGifCommitFile: string = "svgAndGifCommitFile"; // svg和gif提交记录 constructor() { // 初始化全局监听 @@ -166,21 +167,27 @@ export class RequestOption { return this; } - thumbnail(sizeMultiplier: number, func?: AsyncSuccess) { - this.thumbSizeMultiplier = sizeMultiplier; - this.thumbHolderFunc = func; + retryholder(src: PixelMap | Resource, func?: AsyncSuccess) { + this.retryholderSrc = src; + this.retryholderFunc = func; return this; } - addProgressListener(func?: AsyncSuccess) { + thumbnail(sizeMultiplier: number, func?: AsyncSuccess,displayTime?:number) { + this.thumbSizeMultiplier = sizeMultiplier; + this.thumbHolderFunc = func; + if(displayTime){ + this.thumbDelayTime = displayTime; + } + return this; + } + + addProgressListener(func?: AsyncSuccess) { this.progressFunc = func; return this; } - addRetryListener(func?: AsyncSuccess) { - this.retryFunc = func; - return this; - } + addListener(func: AsyncCallback) { this.requestListeners.push(func); @@ -322,7 +329,7 @@ export class RequestOption { placeholderOnComplete(imageKnifeData: ImageKnifeData) { console.log("placeholderOnComplete has called!"); console.log("Main Image is Ready:" + this.loadMainReady); - if (!this.loadMainReady && !this.loadErrorReady && !this.loadThumbnailReady) { + if (!this.loadMainReady && !(this.loadErrorReady || this.loadRetryReady) && !this.loadThumbnailReady) { // 主图未加载成功,并且未加载失败 显示占位图 主图加载成功或者加载失败后=>不展示占位图 this.placeholderFunc(imageKnifeData) } @@ -333,9 +340,12 @@ export class RequestOption { console.log("占位图解析失败 error =" + error) } + + + // 缩略图解析成功 thumbholderOnComplete(imageKnifeData: ImageKnifeData) { - if (!this.loadMainReady && !this.loadErrorReady) { + if (!this.loadMainReady && !(this.loadErrorReady || this.loadRetryReady)) { //主图未加载成功,并且未加载失败 显示占位图 主图加载成功或者加载失败后=>不展示占位图 this.thumbHolderFunc(imageKnifeData) } @@ -360,6 +370,17 @@ export class RequestOption { console.log("失败占位图解析失败 error =" + error) } + retryholderOnComplete(imageKnifeData: ImageKnifeData){ + this.retryholderData = imageKnifeData; + if(this.loadRetryReady){ + this.retryholderFunc(imageKnifeData) + } + } + + retryholderOnError(error){ + console.log("重试占位图解析失败 error ="+ error) + } + loadComplete(imageKnifeData: ImageKnifeData) { this.loadMainReady = true; // 三级缓存数据加载成功 @@ -375,12 +396,17 @@ export class RequestOption { loadError(err) { console.log("loadError:"+err); + console.log("loadError stack=:"+JSON.stringify(err.stack)); //失败占位图展示规则 - this.loadErrorReady = true; - if (this.retryFunc) { + if (this.retryholderFunc) { // 重试图层优先于加载失败展示 - this.retryFunc(err) + this.loadRetryReady = true; + if(this.retryholderData != null){ + this.retryholderFunc(this.retryholderData) + } } else { + // 失败图层标记,如果已经有数据直接展示失败图层 + this.loadErrorReady = true; if (this.errorholderData != null) { this.errorholderFunc(this.errorholderData) } diff --git a/imageknife/src/main/ets/components/imageknife/interface/IDrawExtension.ets b/imageknife/src/main/ets/components/imageknife/interface/IDrawExtension.ets new file mode 100644 index 0000000..5532021 --- /dev/null +++ b/imageknife/src/main/ets/components/imageknife/interface/IDrawExtension.ets @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2022 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} from '../ImageKnifeData' +export interface IDrawExtension{ + (context: CanvasRenderingContext2D,data: ImageKnifeData, imageKnifeOption:T, compWidth:number,compHeight:number, setGifTimeId?:(timeId:number)=>void) +} \ No newline at end of file diff --git a/imageknife/src/main/ets/components/imageknife/interface/IDrawLifeCycle.ets b/imageknife/src/main/ets/components/imageknife/interface/IDrawLifeCycle.ets new file mode 100644 index 0000000..db472ad --- /dev/null +++ b/imageknife/src/main/ets/components/imageknife/interface/IDrawLifeCycle.ets @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 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 {ImageKnifeOption} from '../ImageKnifeOption' +import {ImageKnifeData} from '../ImageKnifeData' +export interface IDrawLifeCycle{ + + // 展示占位图 + displayPlaceholder?: (context: CanvasRenderingContext2D,data: ImageKnifeData, imageKnifeOption:ImageKnifeOption, compWidth:number,compHeight:number, setGifTimeId?:(timeId:number)=>void)=>boolean + + // 展示加载进度 + displayProgress?: (context: CanvasRenderingContext2D,progress: number, imageKnifeOption:ImageKnifeOption, compWidth:number,compHeight:number, setGifTimeId?:(timeId:number)=>void)=>boolean + + // 展示缩略图 + displayThumbSizeMultiplier?: (context: CanvasRenderingContext2D,data: ImageKnifeData, imageKnifeOption:ImageKnifeOption, compWidth:number,compHeight:number, setGifTimeId?:(timeId:number)=>void)=>boolean + + // 展示主图 + displayMainSource?: (context: CanvasRenderingContext2D,data: ImageKnifeData, imageKnifeOption:ImageKnifeOption, compWidth:number,compHeight:number, setGifTimeId?:(timeId:number)=>void)=>boolean + + // 展示重试图层 + displayRetryholder?: (context: CanvasRenderingContext2D,data: ImageKnifeData, imageKnifeOption:ImageKnifeOption, compWidth:number,compHeight:number, setGifTimeId?:(timeId:number)=>void)=>boolean + + // 展示失败占位图 + displayErrorholder?: (context: CanvasRenderingContext2D,data: ImageKnifeData, imageKnifeOption:ImageKnifeOption, compWidth:number,compHeight:number, setGifTimeId?:(timeId:number)=>void)=>boolean + +} \ No newline at end of file diff --git a/imageknife/src/main/ets/components/imageknife/networkmanage/DownloadClient.ets b/imageknife/src/main/ets/components/imageknife/networkmanage/DownloadClient.ets index 765d98d..51f099e 100644 --- a/imageknife/src/main/ets/components/imageknife/networkmanage/DownloadClient.ets +++ b/imageknife/src/main/ets/components/imageknife/networkmanage/DownloadClient.ets @@ -46,7 +46,7 @@ export class DownloadClient implements IDataFetch { loadTask = downloadTask; loadTask.on('progress', (receivedSize, totalSize) => { - let percent = Math.round(((receivedSize * 1.0) / (totalSize * 1.0)) * 100) + "%" + let percent = Math.round(((receivedSize * 1.0) / (totalSize * 1.0)) * 100) if (request.progressFunc) { request.progressFunc(percent); } diff --git a/imageknife/src/main/ets/components/imageknife/requestmanage/RequstManager.ets b/imageknife/src/main/ets/components/imageknife/requestmanage/RequstManager.ets index c55fc47..7fb4f6f 100644 --- a/imageknife/src/main/ets/components/imageknife/requestmanage/RequstManager.ets +++ b/imageknife/src/main/ets/components/imageknife/requestmanage/RequstManager.ets @@ -23,11 +23,14 @@ import{DiskCacheProxy} from "../requestmanage/DiskCacheProxy" import{FileTypeUtil} from "../utils/FileTypeUtil" import{IDataFetch} from "../../imageknife/networkmanage/IDataFetch" import{IResourceFetch} from "../../imageknife/resourcemanage/IResourceFetch" -import{ImageKnifeData} from "../ImageKnifeData" +import{ImageKnifeData,ImageKnifeType} from "../ImageKnifeData" import {AllCacheInfo, IAllCacheInfoCallback} from "../../imageknife/interface/IAllCacheInfoCallback" import{ParseImageUtil} from '../utils/ParseImageUtil' import{IParseImage} from '../interface/IParseImage' import image from "@ohos.multimedia.image" +import { SVGParseImpl } from '../utils/svg/SVGParseImpl' +import { GIFParseImpl } from '../utils/gif/GIFParseImpl' +import { GIFFrame } from '../utils/gif/GIFFrame' export interface AsyncString { (data: string): void; @@ -198,39 +201,24 @@ export class RequestManager { let fileTypeUtil = new FileTypeUtil(); let typeValue = fileTypeUtil.getFileType(arrayBuffer) console.log("RequstManager - 文件类型为= " + typeValue) - if ((ImageKnifeData.GIF == typeValue && !request.dontAnimateFlag) || ImageKnifeData.SVG == typeValue) { - // 将图片资源 转换为文件地址 - let folderPath = this.options.getFilesPath() + "/" + this.options._svgAndGifFolder; - let filePath = this.options.getFilesPath() + "/" + this.options._svgAndGifFolder + "/" - + Md5.hashStr(this.options.generateDataKey) + "." + typeValue; - let filename = Md5.hashStr(this.options.generateDataKey) + "." + typeValue; - new Promise((resolve, reject) => { - // 存文件至svg目录,并且记录文件名 - resolve("svg gif 文件开始本地保存!"); + // gif处理 + if(ImageKnifeData.GIF == typeValue && !request.dontAnimateFlag){ + // 处理gif + this.gifProcess(onComplete,onError, arrayBuffer,typeValue,(imageKnifeData)=>{ + this.mMemoryCacheProxy.putValue(this.options.generateCacheKey, imageKnifeData) }) - .then((res) => { - // 创建文件 - FileUtils.getInstance() - .createFileProcess(folderPath, filePath, arrayBuffer); - // 写入记录 - FileUtils.getInstance() - .writeData(this.options.getFilesPath() + - "/" + this.options._svgAndGifFolder + "/" + this.options._svgAndGifCommitFile, filename + "\n"); - console.log("svg gif 本地保存成功 输出!"); - let imageKnifeData = this.createImageKnifeData(ImageKnifeData.STRING, filePath, typeValue); - this.mMemoryCacheProxy.putValue(this.options.generateCacheKey, imageKnifeData); - onComplete(imageKnifeData); + }else if(ImageKnifeData.SVG == typeValue){ + // 处理svg + this.svgProcess(onComplete,onError,arrayBuffer,typeValue,(imageKnifeData)=>{ + this.mMemoryCacheProxy.putValue(this.options.generateCacheKey,imageKnifeData) }) - .catch((err) => { - onError(err) - }) - } - else { + } else { if (request.transformations[0]) { request.transformations[0].transform(arrayBuffer, request, (error, pixelMap: PixelMap) => { // 输出给Image if (pixelMap) { - let imageKnifeData = this.createImageKnifeData(ImageKnifeData.PIXELMAP, pixelMap, typeValue); + + let imageKnifeData = this.createImagePixelMap(ImageKnifeType.PIXELMAP, pixelMap); this.mMemoryCacheProxy.putValue(request.generateCacheKey, imageKnifeData); onComplete(imageKnifeData); } else { @@ -240,7 +228,7 @@ export class RequestManager { } else { let success = (value: PixelMap) => { - let imageKnifeData = this.createImageKnifeData(ImageKnifeData.PIXELMAP, value, typeValue); + let imageKnifeData = this.createImagePixelMap(ImageKnifeType.PIXELMAP, value); this.mMemoryCacheProxy.putValue(request.generateCacheKey, imageKnifeData); onComplete(imageKnifeData); } @@ -310,29 +298,16 @@ export class RequestManager { // 步骤一:文件转为pixelMap 然后变换 给Image组件 let fileTypeUtil = new FileTypeUtil(); let typeValue = fileTypeUtil.getFileType(source); - if (((ImageKnifeData.GIF == typeValue && !request.dontAnimateFlag) || ImageKnifeData.SVG == typeValue)) { - // 将图片资源 转换为文件地址 - let folderPath = this.options.getFilesPath() + "/" + this.options._svgAndGifFolder; - let filePath = this.options.getFilesPath() + "/" + this.options._svgAndGifFolder + "/" - + Md5.hashStr(this.options.generateDataKey) + "." + typeValue; - let filename = Md5.hashStr(this.options.generateDataKey) + "." + typeValue; - new Promise((resolve, reject) => { - // 存文件至svg目录,并且记录文件名 - resolve("svg gif 文件开始本地保存!"); - }).then((res) => { - FileUtils.getInstance() - .createFileProcess(folderPath, filePath, source); - FileUtils.getInstance() - .writeData(this.options.getFilesPath() + - "/" + this.options._svgAndGifFolder + "/" + this.options._svgAndGifCommitFile, filename + "\n"); - let imageKnifeData = this.createImageKnifeData(ImageKnifeData.STRING, filePath, typeValue) - this.mMemoryCacheProxy.putValue(this.options.generateCacheKey, imageKnifeData); - onComplete(imageKnifeData); + // 解析磁盘文件 gif 和 svg + if(ImageKnifeData.GIF == typeValue && !request.dontAnimateFlag){ + // 处理gif + this.gifProcess(onComplete,onError,source,typeValue, (imageKnifeData)=>{ + this.mMemoryCacheProxy.putValue(this.options.generateCacheKey, imageKnifeData) }) - .catch((err) => { - onError(err) + }else if(ImageKnifeData.SVG == typeValue){ + this.svgProcess(onComplete,onError, source, typeValue, (imageKnifeData)=>{ + this.mMemoryCacheProxy.putValue(this.options.generateCacheKey, imageKnifeData) }) - } else { if (this.options.transformations[0]) { if (this.options.thumbSizeMultiplier) { @@ -345,7 +320,7 @@ export class RequestManager { let thumbError = this.options.thumbholderOnError.bind(this.options); this.options.transformations[0].transform(source, thumbOption, (error, pixelMap: PixelMap) => { if (pixelMap) { - let imageKnifeData = this.createImageKnifeData(ImageKnifeData.PIXELMAP, pixelMap, typeValue); + let imageKnifeData = this.createImagePixelMap(ImageKnifeType.PIXELMAP, pixelMap); thumbCallback(imageKnifeData); } else { thumbError(error); @@ -355,7 +330,7 @@ export class RequestManager { this.options.transformations[0].transform(source, request, (error, pixelMap: PixelMap) => { if (pixelMap) { // 保存一份变换后的图片PixelMap到MemoryCache - let imageKnifeData = this.createImageKnifeData(ImageKnifeData.PIXELMAP, pixelMap, typeValue); + let imageKnifeData = this.createImagePixelMap(ImageKnifeType.PIXELMAP, pixelMap); this.mMemoryCacheProxy.putValue(this.options.generateCacheKey, imageKnifeData); onComplete(imageKnifeData); } else { @@ -368,7 +343,7 @@ export class RequestManager { this.options.transformations[0].transform(source, request, (error, pixelMap: PixelMap) => { if (pixelMap) { // 保存一份变换后的图片PixelMap到MemoryCache - let imageKnifeData = this.createImageKnifeData(ImageKnifeData.PIXELMAP, pixelMap, typeValue); + let imageKnifeData = this.createImagePixelMap(ImageKnifeType.PIXELMAP, pixelMap); this.mMemoryCacheProxy.putValue(this.options.generateCacheKey, imageKnifeData); onComplete(imageKnifeData); } else { @@ -382,13 +357,13 @@ export class RequestManager { let thumbCallback = this.options.thumbholderOnComplete.bind(this.options); let thumbError = this.options.thumbholderOnError.bind(this.options); let thumbSuccess = (value: PixelMap) => { - let imageKnifeData = this.createImageKnifeData(ImageKnifeData.PIXELMAP, value, typeValue); + let imageKnifeData = this.createImagePixelMap(ImageKnifeType.PIXELMAP, value); thumbCallback(imageKnifeData); } this.mParseImageUtil.parseImageThumbnail(request.thumbSizeMultiplier, source, thumbSuccess, thumbError); setTimeout(()=>{ let success = (value: PixelMap) => { - let imageKnifeData = this.createImageKnifeData(ImageKnifeData.PIXELMAP, value, typeValue); + let imageKnifeData = this.createImagePixelMap(ImageKnifeType.PIXELMAP, value); this.mMemoryCacheProxy.putValue(this.options.generateCacheKey, imageKnifeData); onComplete(imageKnifeData); } @@ -397,7 +372,7 @@ export class RequestManager { } else { let success = (value: PixelMap) => { - let imageKnifeData = this.createImageKnifeData(ImageKnifeData.PIXELMAP, value, typeValue); + let imageKnifeData = this.createImagePixelMap(ImageKnifeType.PIXELMAP, value); this.mMemoryCacheProxy.putValue(this.options.generateCacheKey, imageKnifeData); onComplete(imageKnifeData); } @@ -416,13 +391,13 @@ export class RequestManager { let thumbCallback = this.options.thumbholderOnComplete.bind(this.options); let thumbError = this.options.thumbholderOnError.bind(this.options); let thumbSuccess = (value: PixelMap) => { - let imageKnifeData = this.createImageKnifeData(ImageKnifeData.PIXELMAP, value, typeValue); + let imageKnifeData = this.createImagePixelMap(ImageKnifeType.PIXELMAP, value); thumbCallback(imageKnifeData); } this.mParseImageUtil.parseImageThumbnail(request.thumbSizeMultiplier, source, thumbSuccess, thumbError); setTimeout(()=>{ let success = (value: PixelMap) => { - let imageKnifeData = this.createImageKnifeData(ImageKnifeData.PIXELMAP, value, typeValue); + let imageKnifeData = this.createImagePixelMap(ImageKnifeType.PIXELMAP, value); this.mMemoryCacheProxy.putValue(this.options.generateCacheKey, imageKnifeData); onComplete(imageKnifeData); } @@ -430,7 +405,7 @@ export class RequestManager { },this.options.thumbDelayTime) }else{ let success = (value: PixelMap) => { - let imageKnifeData = this.createImageKnifeData(ImageKnifeData.PIXELMAP, value, typeValue) + let imageKnifeData = this.createImagePixelMap(ImageKnifeType.PIXELMAP, value) this.mMemoryCacheProxy.putValue(this.options.generateCacheKey, imageKnifeData); onComplete(imageKnifeData); } @@ -456,40 +431,35 @@ export class RequestManager { onError("暂不支持 下载文件类型!类型=" + filetype); return; } - if ((ImageKnifeData.GIF == filetype && !this.options.dontAnimateFlag) || ImageKnifeData.SVG == filetype) { - // 将图片资源 转换为文件地址 - let folderPath = this.options.getFilesPath() + "/" + this.options._svgAndGifFolder; - let filePath = this.options.getFilesPath() + "/" + this.options._svgAndGifFolder + "/" - + Md5.hashStr(this.options.generateDataKey) + "." + filetype; - let filename = Md5.hashStr(this.options.generateDataKey) + "." + filetype; - // 1.文件保存 2.内存缓存 3.输出结果 - new Promise((resolve, reject) => { - // 存文件至svg目录,并且记录文件名 - resolve("svg gif 文件开始本地保存!"); + + // 解析磁盘文件 gif 和 svg + if(ImageKnifeData.GIF == filetype && !this.options.dontAnimateFlag){ + // 处理gif + this.gifProcess(onComplete,onError,source,filetype, (imageKnifeData)=>{ + this.mMemoryCacheProxy.putValue(this.options.generateCacheKey, imageKnifeData) }) - .then((res) => { - FileUtils.getInstance() - .createFileProcess(folderPath, filePath, source); - FileUtils.getInstance() - .writeData(this.options.getFilesPath() + - "/" + this.options._svgAndGifFolder + "/" + this.options._svgAndGifCommitFile, filename + "\n"); - let imageKnifeData = this.createImageKnifeData(ImageKnifeData.STRING, filePath, filetype); - this.mMemoryCacheProxy.putValue(this.options.generateCacheKey, imageKnifeData); - onComplete(imageKnifeData); - }) - .catch((err) => { - onError(err) - }) // 保存二级磁盘缓存 - new Promise((resolve, reject) => { - resolve(source) - }) - .then(async (arraybuffer: ArrayBuffer) => { + Promise.resolve(source) + .then(async (arraybuffer: ArrayBuffer)=>{ await this.mDiskCacheProxy.putValue(this.options.generateDataKey, arraybuffer) }) - .catch((err) => { - console.log("save diskLruCache error=" + err); + .catch(err=>{ + console.log('download file is ='+ImageKnifeData.GIF+'and save diskLruCache error ='+ err) + }) + }else if(ImageKnifeData.SVG == filetype){ + // 处理svg + this.svgProcess(onComplete,onError, source, filetype, (imageKnifeData)=>{ + this.mMemoryCacheProxy.putValue(this.options.generateCacheKey, imageKnifeData) + }) + + // 保存二级磁盘缓存 + Promise.resolve(source) + .then(async (arraybuffer: ArrayBuffer)=>{ + await this.mDiskCacheProxy.putValue(this.options.generateDataKey, arraybuffer) + }) + .catch(err=>{ + console.log('download file is ='+ImageKnifeData.SVG+'and save diskLruCache error ='+ err) }) } else { // 进行变换 @@ -512,7 +482,7 @@ export class RequestManager { let thumbCallback = this.options.thumbholderOnComplete.bind(this.options); let thumbError = this.options.thumbholderOnError.bind(this.options); let thumbSuccess = (value: PixelMap) => { - let imageKnifeData = this.createImageKnifeData(ImageKnifeData.PIXELMAP, value, filetype); + let imageKnifeData = this.createImagePixelMap(ImageKnifeType.PIXELMAP, value); thumbCallback(imageKnifeData); } this.mParseImageUtil.parseImageThumbnail(this.options.thumbSizeMultiplier, source, thumbSuccess, thumbError); @@ -532,17 +502,18 @@ export class RequestManager { } } - - createImageKnifeData(imageKnifeType:string, imageKnifeValue:PixelMap|string|Resource, imageKnifeSourceType:string):ImageKnifeData{ - let imageKnifeData = new ImageKnifeData(); - imageKnifeData.imageKnifeType = imageKnifeType; - imageKnifeData.imageKnifeValue = imageKnifeValue; - imageKnifeData.imageKnifeSourceType = imageKnifeSourceType; - return imageKnifeData; + createImagePixelMap(imageKnifeType: ImageKnifeType, imageKnifeValue: PixelMap): ImageKnifeData{ + return ImageKnifeData.createImagePixelMap(imageKnifeType,imageKnifeValue); } + createImageGIFFrame(imageKnifeType: ImageKnifeType, imageKnifeValue: GIFFrame[]): ImageKnifeData{ + return ImageKnifeData.createImageGIFFrame(imageKnifeType,imageKnifeValue); + } + + + private saveCacheAndDisk(value: PixelMap, filetype:string, onComplete, source:ArrayBuffer) { - let imageKnifeData = this.createImageKnifeData(ImageKnifeData.PIXELMAP, value, filetype); + let imageKnifeData = this.createImagePixelMap(ImageKnifeType.PIXELMAP, value); this.mMemoryCacheProxy.putValue(this.options.generateCacheKey, imageKnifeData); let save2DiskCache = async (arraybuffer) => { await this.mDiskCacheProxy.putValue(this.options.generateDataKey, arraybuffer) @@ -565,7 +536,7 @@ export class RequestManager { let thumbError = this.options.thumbholderOnError.bind(this.options); this.options.transformations[0].transform(source, thumbOption, (error, pixelMap: PixelMap) => { if (pixelMap) { - let imageKnifeData = this.createImageKnifeData(ImageKnifeData.PIXELMAP, pixelMap, filetype); + let imageKnifeData = this.createImagePixelMap(ImageKnifeType.PIXELMAP, pixelMap); thumbCallback(imageKnifeData); } else { thumbError(error); @@ -581,6 +552,41 @@ export class RequestManager { }) }, this.options.thumbDelayTime) } + private svgProcess(onComplete, onError, arraybuffer, typeValue, cacheStrategy?: (cacheData: ImageKnifeData) => void) { + let svgParseImpl = new SVGParseImpl() + let size = { width: this.options.size.width, height: this.options.size.height } + svgParseImpl.parseSvg(arraybuffer, size).then((value: PixelMap) => { + let imageKnifeData = ImageKnifeData.createImagePixelMap(ImageKnifeType.PIXELMAP, value) + if(cacheStrategy){ + cacheStrategy(imageKnifeData) + } + onComplete(imageKnifeData) + }).catch(err => { + onError(err) + }) + } + + private gifProcess(onComplete, onError, arraybuffer, typeValue, cacheStrategy?: (cacheData: ImageKnifeData) => void) { + let gifParseImpl = new GIFParseImpl() + gifParseImpl.parseGifs(arraybuffer, (data?,err?)=>{ + if(err){ + onError(err) + } + console.log("gifProcess data is null:"+(data == null)); + if(!!data){ + let imageKnifeData = this.createImageGIFFrame(ImageKnifeType.GIFFRAME,data) + console.log('gifProcess 生成gif 返回数据类型') + if(cacheStrategy){ + console.log('gifProcess 生成gif并且存入了缓存策略') + cacheStrategy(imageKnifeData) + } + onComplete(imageKnifeData) + }else{ + onError('Parse GIF callback data is null, you need check callback data!') + } + }) + } + }