diff --git a/entry/src/main/ets/pages/DownSamplePage.ets b/entry/src/main/ets/pages/DownSamplePage.ets new file mode 100644 index 0000000..0e6eeb1 --- /dev/null +++ b/entry/src/main/ets/pages/DownSamplePage.ets @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { DownsampleStrategy, ImageKnifeComponent } from '@ohos/imageknife'; +@Entry +@Component +struct DownSamplePage { + @State message: string = 'Hello World'; + build() { + Scroll() { + Column(){ + ImageKnifeComponent({ + imageKnifeOption: { + loadSrc: $r("app.media.jpgSample"), + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + downsampling: DownsampleStrategy.FIT_CENTER, + objectFit: ImageFit.Contain, + autoResize:true + } + }) + .height(300) + .width(300) + .borderWidth(1) + .borderColor(Color.Pink) + ImageKnifeComponent({ + imageKnifeOption: { + loadSrc: "http://g.hiphotos.baidu.com/image/pic/item/6d81800a19d8bc3e770bd00d868ba61ea9d345f2.jpg", + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + downsampling: DownsampleStrategy.AT_MOST, + objectFit: ImageFit.Contain, + autoResize:true + } + }) + .height(300) + .width(300) + .borderWidth(1) + .borderColor(Color.Pink) + ImageKnifeComponent({ + imageKnifeOption: { + loadSrc: $r("app.media.jpgSample"), + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + downsampling: DownsampleStrategy.AT_LEAST, + objectFit: ImageFit.Contain, + autoResize:true + } + }) + .height(300) + .width(300) + .borderWidth(1) + .borderColor(Color.Pink) + ImageKnifeComponent({ + imageKnifeOption: { + loadSrc: $r("app.media.jpgSample"), + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + downsampling: DownsampleStrategy.CENTER_INSIDE, + objectFit: ImageFit.Contain, + autoResize:true + } + }) + .height(300) + .width(300) + .borderWidth(1) + .borderColor(Color.Pink) + ImageKnifeComponent({ + imageKnifeOption: { + loadSrc: $r("app.media.jpgSample"), + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + downsampling: DownsampleStrategy.CENTER_OUTSIDE, + objectFit: ImageFit.Contain, + autoResize:true + } + }) + .height(300) + .width(300) + .borderWidth(1) + .borderColor(Color.Pink) + ImageKnifeComponent({ + imageKnifeOption: { + loadSrc: $r("app.media.jpgSample"), + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + downsampling: DownsampleStrategy.NONE, + objectFit: ImageFit.Contain, + autoResize:true + } + }) + .height(300) + .width(300) + .borderWidth(1) + .borderColor(Color.Pink) + } + + } + .height('100%') + .width('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/Index.ets b/entry/src/main/ets/pages/Index.ets index a799b6a..fdde674 100644 --- a/entry/src/main/ets/pages/Index.ets +++ b/entry/src/main/ets/pages/Index.ets @@ -17,8 +17,6 @@ import router from '@system.router'; @Entry @Component struct Index { - - aboutToAppear(): void { } @@ -26,73 +24,78 @@ struct Index { build() { Column() { - Button("单个图片使用").onClick(()=>{ + Button("单个图片使用").onClick(() => { router.push({ uri: 'pages/SingleImage', }); }) - Button("多图 + LazyForEach").margin({top:10}).onClick(()=>{ + Button("下采样").margin({ top: 10 }).onClick(() => { + router.push({ + uri: "pages/DownSamplePage", + }); + }) + Button("多图 + LazyForEach").margin({ top: 10 }).onClick(() => { router.push({ uri: 'pages/ManyPhotoShowPage', }); }) - Button("多图 + reuse + LazyForeach").margin({top:10}).onClick(()=>{ + Button("多图 + reuse + LazyForeach").margin({ top: 10 }).onClick(() => { router.push({ uri: 'pages/UserPage', }); }) - Button("长图显示").margin({top:10}).onClick(()=>{ + Button("长图显示").margin({ top: 10 }).onClick(() => { router.push({ uri: 'pages/LongImagePage', }); }) - Button("缩放图片").margin({top:10}).onClick(()=>{ + Button("缩放图片").margin({ top: 10 }).onClick(() => { router.push({ uri: 'pages/TransformPage', }); }) - Button("消息+List").margin({top:10}).onClick(()=>{ + Button("消息+List").margin({ top: 10 }).onClick(() => { router.push({ uri: 'pages/TestImageFlash', }); }) - Button("自定义缓存key").margin({top:10}).onClick(()=>{ + Button("自定义缓存key").margin({ top: 10 }).onClick(() => { router.push({ uri: 'pages/SignatureTestPage', }); }) - Button("预加载图片到文件缓存").margin({top:10}).onClick(()=>{ + Button("预加载图片到文件缓存").margin({ top: 10 }).onClick(() => { router.push({ uri: 'pages/TestPrefetchToFileCache', }); }) - Button("从缓存获取图片显示").margin({top:10}).onClick(()=>{ + Button("从缓存获取图片显示").margin({ top: 10 }).onClick(() => { router.push({ uri: 'pages/TestIsUrlExist', }); }) - Button("测试单个请求头").margin({top:10}).onClick(()=>{ + Button("测试单个请求头").margin({ top: 10 }).onClick(() => { router.push({ uri: 'pages/TestHeader', }); }) - Button("测试写入缓存策略").margin({top:10}).onClick(()=>{ + Button("测试写入缓存策略").margin({ top: 10 }).onClick(() => { router.push({ uri: 'pages/TestWriteCacheStage', }); }) - Button("图片变换").margin({top:10}).onClick(()=>{ + Button("图片变换").margin({ top: 10 }).onClick(() => { router.push({ uri: 'pages/ImageTransformation', @@ -100,7 +103,7 @@ struct Index { }) - Button("不同的ObjectFit").margin({top:10}).onClick(()=>{ + Button("不同的ObjectFit").margin({ top: 10 }).onClick(() => { router.push({ uri: 'pages/ObjectFitPage', diff --git a/entry/src/main/resources/base/media/jpgSample.jpg b/entry/src/main/resources/base/media/jpgSample.jpg new file mode 100644 index 0000000..0f04a7a Binary files /dev/null and b/entry/src/main/resources/base/media/jpgSample.jpg differ diff --git a/entry/src/main/resources/base/profile/main_pages.json b/entry/src/main/resources/base/profile/main_pages.json index 4d74d0d..77461fa 100644 --- a/entry/src/main/resources/base/profile/main_pages.json +++ b/entry/src/main/resources/base/profile/main_pages.json @@ -14,8 +14,9 @@ "pages/TestHeader", "pages/ImageTransformation", "pages/ObjectFitPage", - "pages/TestWriteCacheStage", "pages/LoadStatePage", - "pages/TestRemoveCache" + "pages/TestRemoveCache", + "pages/TestWriteCacheStage", + "pages/DownSamplePage" ] } \ No newline at end of file diff --git a/hvigor/hvigor-config.json5 b/hvigor/hvigor-config.json5 index 8c56bf6..14977ad 100644 --- a/hvigor/hvigor-config.json5 +++ b/hvigor/hvigor-config.json5 @@ -1,6 +1,7 @@ { "modelVersion": "5.0.0", "dependencies": { + "@ohos/hvigor-ohos-plugin": "4.1.2" }, "execution": { // "analyze": "default", /* Define the build analyze mode. Value: [ "default" | "verbose" | false ]. Default: "default" */ diff --git a/library/BuildProfile.ets b/library/BuildProfile.ets index 56a40f4..f787c60 100644 --- a/library/BuildProfile.ets +++ b/library/BuildProfile.ets @@ -1,5 +1,6 @@ export default class BuildProfile { - static readonly HAR_VERSION = '3.0.0-rc.0'; + static readonly HAR_VERSION = '3.0.0-rc.4'; static readonly BUILD_MODE_NAME = 'debug'; static readonly DEBUG = true; + static readonly TARGET_NAME = 'default'; } \ No newline at end of file diff --git a/library/index.ets b/library/index.ets index d0f552e..716467b 100644 --- a/library/index.ets +++ b/library/index.ets @@ -22,4 +22,6 @@ export { BrightnessTransformation } from './src/main/ets/transform/BrightnessTra export { BlurTransformation } from './src/main/ets/transform/BlurTransformation' +export { DownsampleStrategy } from './src/main/ets/downsampling/DownsampleStartegy' + diff --git a/library/src/main/ets/ImageKnifeDispatcher.ets b/library/src/main/ets/ImageKnifeDispatcher.ets index 34d5810..0c714aa 100644 --- a/library/src/main/ets/ImageKnifeDispatcher.ets +++ b/library/src/main/ets/ImageKnifeDispatcher.ets @@ -39,6 +39,9 @@ import { RequestJobRequest } from './model/ImageKnifeData' import { combineArrayBuffers } from './model/utils'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { Downsampler } from './downsampling/Downsampler' +import { DownsampleStrategy } from './downsampling/DownsampleStartegy'; export class ImageKnifeDispatcher { // 最大并发 @@ -144,7 +147,12 @@ export class ImageKnifeDispatcher { .isFileCacheInit() ? currentRequest.imageKnifeOption.writeCacheStrategy : CacheStrategy.Memory, // 未初始化文件缓存时,不写文件缓存 engineKey: this.engineKey, signature: currentRequest.imageKnifeOption.signature, - requestSource + requestSource, + targetWidth: currentRequest.componentWidth, + targetHeight: currentRequest.componentHeight, + downsampType: currentRequest.imageKnifeOption.downsampling, + autoResize:currentRequest.imageKnifeOption.autoResize + } // 启动线程下载和解码主图 let task = new taskpool.Task(requestJob, request) @@ -404,7 +412,6 @@ async function requestJob(request: RequestJobRequest): Promise; - // 自定义缓存关键字 signature?: string; - // 主图填充效果 objectFit?: ImageFit - // 占位图填充效果 placeholderObjectFit?: ImageFit - // 错误图填充效果 errorholderObjectFit?: ImageFit - customGetImage?: (context: Context, src: string | PixelMap | Resource) => Promise - border?: BorderOptions // 缓存策略 writeCacheStrategy?: CacheStrategy // 仅使用缓存加载数据 onlyRetrieveFromCache?: boolean = false; - - priority? : taskpool.Priority = taskpool.Priority.LOW - + priority?: taskpool.Priority = taskpool.Priority.LOW context?: common.UIAbilityContext; progressListener?: (progress: number)=>void; transformation?: PixelMapTransformation onLoadListener?: OnLoadCallBack | undefined; - + // 下采样 + downsampling?: DownsampleStrategy = DownsampleStrategy.NONE + autoResize?:boolean constructor() { } diff --git a/library/src/main/ets/ImageKnifeRequest.ets b/library/src/main/ets/ImageKnifeRequest.ets index 071c71b..c5fabc3 100644 --- a/library/src/main/ets/ImageKnifeRequest.ets +++ b/library/src/main/ets/ImageKnifeRequest.ets @@ -15,7 +15,7 @@ import { ImageKnifeOption } from './ImageKnifeOption'; import common from '@ohos.app.ability.common'; import { ImageKnifeRequestSource } from './model/ImageKnifeData'; - +import { DownsampleStrategy } from './downsampling/DownsampleStartegy'; export class ImageKnifeRequest { requestState: ImageKnifeRequestState = ImageKnifeRequestState.PROGRESS @@ -25,20 +25,29 @@ export class ImageKnifeRequest { context: common.UIAbilityContext ImageKnifeRequestCallback: ImageKnifeRequestCallback componentVersion: number = 0 - headers: Map = new Map() + headers: Map = new Map() + downsampType?: DownsampleStrategy + autoResizes?:boolean + constructor(option: ImageKnifeOption, uIAbilityContext: common.UIAbilityContext, width: number, height: number, version: number, - ImageKnifeRequestCallback: ImageKnifeRequestCallback) { + ImageKnifeRequestCallback: ImageKnifeRequestCallback, + downsampType?: DownsampleStrategy, + autoResizes?:boolean + ) { this.imageKnifeOption = option this.context = uIAbilityContext this.componentWidth = width this.componentHeight = height this.componentVersion = version this.ImageKnifeRequestCallback = ImageKnifeRequestCallback + this.downsampType = downsampType + this.autoResizes = autoResizes } + // RequestOption调用header对于的方法 addHeader(key: string, value: Object) { this.headers.set(key, value); @@ -63,5 +72,5 @@ export enum ImageKnifeRequestState { export interface ImageKnifeRequestCallback { - showPixelMap: (version: number, pixelMap: PixelMap | string , requestSource: ImageKnifeRequestSource) => void; + showPixelMap: (version: number, pixelMap: PixelMap | string, requestSource: ImageKnifeRequestSource) => void; } diff --git a/library/src/main/ets/components/ImageKnifeComponent.ets b/library/src/main/ets/components/ImageKnifeComponent.ets index 2d8c3bd..302a08f 100644 --- a/library/src/main/ets/components/ImageKnifeComponent.ets +++ b/library/src/main/ets/components/ImageKnifeComponent.ets @@ -18,6 +18,7 @@ import common from '@ohos.app.ability.common'; import { ImageKnife } from '../ImageKnife'; import { LogUtil } from '../utils/LogUtil'; import { ImageKnifeRequestSource } from '../model/ImageKnifeData'; +import { Downsampler } from '../downsampling/Downsampler'; @Component export struct ImageKnifeComponent { @@ -33,6 +34,8 @@ export struct ImageKnifeComponent { private currentHeight: number = 0 private componentVersion: number = 0 private currentContext: common.UIAbilityContext | undefined = undefined + private targetHeight: number =0 + private targetWidth: number =0 aboutToAppear(): void { //闪动问题失效,注释相应代码后续修复 @@ -82,7 +85,10 @@ export struct ImageKnifeComponent { ImageKnife.getInstance().execute(this.getRequest(this.currentWidth, this.currentHeight)) } } - }) + }).sourceSize({ + width:250, + height:250 + }) } watchImageKnifeOption() { @@ -116,8 +122,9 @@ export struct ImageKnifeComponent { } this.pixelMap = pixelMap if (typeof this.pixelMap !== 'string') { + let info =await this.pixelMap.getImageInfo() + //图片自适应处理 if (this.imageKnifeOption.objectFit === ImageFit.Auto) { - let info = await this.pixelMap.getImageInfo() this.adaptiveWidth = this.currentWidth this.adaptiveHeight = info.size.height * this.currentWidth / info.size.width diff --git a/library/src/main/ets/downsampling/BaseDownsampling.ets b/library/src/main/ets/downsampling/BaseDownsampling.ets new file mode 100644 index 0000000..9fc2916 --- /dev/null +++ b/library/src/main/ets/downsampling/BaseDownsampling.ets @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { lang } from '@kit.ArkTS'; +import { SampleSizeRounding } from './downsampleUtils'; +type ISendable = lang.ISendable; + +export interface BaseDownsampling extends ISendable { + getName(): string + + getScaleFactor(sourceWidth: number, sourceHeight: number, requestWidth: number, requestHeight: number): number + + getSampleSizeRounding(sourceWidth: number, sourceHeight: number, requestWidth: number, requestHeight: number): SampleSizeRounding +} \ No newline at end of file diff --git a/library/src/main/ets/downsampling/DownsampleStartegy.ets b/library/src/main/ets/downsampling/DownsampleStartegy.ets new file mode 100644 index 0000000..7d02b94 --- /dev/null +++ b/library/src/main/ets/downsampling/DownsampleStartegy.ets @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { BaseDownsampling } from './BaseDownsampling'; +import { highestOneBit,SampleSizeRounding } from './downsampleUtils'; +//FitCenter类实现DownsampleStartegy +@Sendable +export class FitCenter implements BaseDownsampling { + getName() { + return "FitCenter" + } + + //实现 getScaleFactor 方法 + getScaleFactor(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number,autoResize?:boolean): number { + const IS_BITMAP_FACTORY_SCALING_SUPPORTED = autoResize; //这里需要根据实际情况设置这个值 + if (IS_BITMAP_FACTORY_SCALING_SUPPORTED) { + const widthPercentage = requestedWidth / sourceWidth + const heightPercentage = requestedHeight / sourceHeight + return Math.min(widthPercentage, heightPercentage) + }else{ + //类似 AT_LEAST,但只要求一个维度或另一个维度大于等于请求的尺寸 + const maxIntegerFactor = Math.max(sourceHeight/requestedHeight, sourceWidth/requestedWidth); + const a = maxIntegerFactor === 0 ? 1 : 1 / highestOneBit(maxIntegerFactor) + return a; + } + } + + //实现 getSampleSizeRounding 方法 + getSampleSizeRounding(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number,autoResize?:boolean): number { + const IS_BITMAP_FACTORY_SCALING_SUPPORTED = autoResize; //这里需要根据实际情况设置这个值 + + if (!IS_BITMAP_FACTORY_SCALING_SUPPORTED) { + return 0; + } else { + // TODO: 这个逻辑可能不正确,但是如果不这样做,我们可能会跳过一个采样尺寸,因为 QUALITY 更喜欢宽度和高度缩放因子中比较小的那个 + // MEMORY 折中方案,他让我们更喜欢两者中较大的那个 + return SampleSizeRounding.MEMORY; + } + + } +} + +//None 类实现 DownsampleStrategy 接口 +@Sendable +export class None implements BaseDownsampling{ + getName(): string{ + return "DownsampleNone" + } + + //实现 getScaleFactor 方法 + public getScaleFactor(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number): number { + //不进行任何下采样,缩放因子为 1 + return 1; + } + + //实现 getSampleSizeRounding 方法 + public getSampleSizeRounding(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number): SampleSizeRounding { + //总是返回 QUALITY + return SampleSizeRounding.QUALITY + + } +} + + +/*宽高进行等比缩放宽高里面最小的比例先放进去 +然后再更据原图的缩放比去适配另一边*/ +@Sendable +export class CenterOutside implements BaseDownsampling { + getName() { + return "CenterOutside" + } + + //实现 getScaleFactor 方法 + getScaleFactor(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number): number { + const widthPercentage = requestedWidth / sourceWidth; + const heightPercentage = requestedHeight / sourceHeight; + //返回宽度和高度比例中最大的值 + return Math.max(widthPercentage, heightPercentage); + } + + //实现 getSampleSizeRounding 方法 + getSampleSizeRounding(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number): SampleSizeRounding { + //根据 CenterOutside 的逻辑,总是返回 QUALITY + return SampleSizeRounding.QUALITY; + } +} + +/*请求尺寸大于实际尺寸不进行放大,按照原图展示*/ +@Sendable +export class Atleast implements BaseDownsampling { + //构造函数 + constructor(){ + //TypeScript 默认构造函数不需要特殊标记 + } + + getName() { + return "AtLeast" + } + + //实现 getScaleFactor 方法 + public getScaleFactor(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number): SampleSizeRounding { + //计算最小整数因子 + const minIntegerFactor = Math.min(sourceHeight / requestedHeight, sourceWidth / requestedWidth) + //根据最小整数因子计算缩放因子 + return minIntegerFactor === 0 ? 1 : 1 / highestOneBit(minIntegerFactor); + } + //实现 getSampleSizeRounding 方法 + public getSampleSizeRounding(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number): SampleSizeRounding { + //总是返回 QUALITY + return SampleSizeRounding.QUALITY + } +} + +/*请求尺寸大于实际尺寸不进行放大,按照原图展示*/ +@Sendable +export class AtMost implements BaseDownsampling { + getName() { + return "AtMost" + } + + //实现 getScaleFactor 方法 + getScaleFactor(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number): number { + const maxIntegerFactor = Math.ceil(Math.max(sourceHeight / requestedHeight, sourceWidth / requestedWidth)); + let lesserOrEqualSampleSize = Math.max(1, highestOneBit(maxIntegerFactor)); + let greaterOrEqualSampleSize = lesserOrEqualSampleSize + // if (lesserOrEqualSampleSize < maxIntegerFactor) { + // let greaterOrEqualSampleSize = lesserOrEqualSampleSize <<= 1; // lest shift by 1 + // } + greaterOrEqualSampleSize = lesserOrEqualSampleSize << (lesserOrEqualSampleSize < maxIntegerFactor ? 1 : 0) + //返回缩放因子 + return 1 / greaterOrEqualSampleSize + } + + highestOneBit(i: number): number { + i |= (i >> 1); + i |= (i >> 2); + i |= (i >> 4); + i |= (i >> 8); + i |= (i >> 16); + return i - (i >>> 1); + } + + //实现 getSampleSizeRounding 方法 + getSampleSizeRounding(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestHeight: number): SampleSizeRounding { + //根据 AtMost 的逻辑,总是返回 MEMORY + return SampleSizeRounding.MEMORY + } +} + +/*宽高进行等比缩放宽高里面最大的比例先放进去 +然后再更据原图的缩放比去适配另一边*/ +@Sendable +export class CenterInside implements BaseDownsampling { + getName() { + return "CenterInside" + } + + //实现 getScaleFactor 方法 + getScaleFactor(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number,autoResize?:boolean): number { + //获取 FIT_CENTER 的缩放因子 + const fitCenterScaleFactor : ESObject = this.getScale(sourceWidth,sourceHeight,requestedWidth,requestedHeight,autoResize); + //实现 getScaleFactor 方法 + //返回不超过 1 的缩放因子,即尽量缩小图像以适应目标尺寸,但不会放大 + return Math.min(1,fitCenterScaleFactor); + + } + + getScale(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number,autoResize?:boolean): number { + const IS_BITMAP_FACTORY_SCALING_SUPPORTED = autoResize; //这里需要根据实际情况设置这个值 + if (IS_BITMAP_FACTORY_SCALING_SUPPORTED) { + const widthPercentage = requestedWidth / sourceWidth + const heightPercentage = requestedHeight / sourceHeight + return Math.min(widthPercentage, heightPercentage) + }else{ + //类似 AT_LEAST,但只要求一个维度或另一个维度大于等于请求的尺寸 + const maxIntegerFactor = Math.max(sourceHeight/requestedHeight, sourceWidth/requestedWidth); + + return maxIntegerFactor === 0 ? 1 : 1 / highestOneBit(maxIntegerFactor); + + } +} + + + //实现 getSampleSizeRounding 方法 + getSampleSizeRounding(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number,autoResize?:boolean): SampleSizeRounding { + //如果缩放因子为 1,表示没有缩放,优先选择质量 + if (this.getScaleFactor(sourceWidth,sourceHeight,requestedWidth,requestedHeight,autoResize) === 1) { + return SampleSizeRounding.QUALITY + } + //否则,使用 FIL_CENTER 的 SampleSizeRounding 值 + return this.getSampleSize(sourceWidth,sourceHeight,requestedWidth,requestedHeight); + } + + highestOneBit(i: number): number { + i |= (i >> 1); + i |= (i >> 2); + i |= (i >> 4); + i |= (i >> 8); + i |= (i >> 16); + return i - (i >>> 1); + } + + //实现 getSampleSizeRounding 方法 + getSampleSize(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number,autoResize?:boolean): SampleSizeRounding { + const IS_BITMAP_FACTORY_SCALING_SUPPORTED = autoResize; //这里需要根据实际情况设置这个值 + + if (IS_BITMAP_FACTORY_SCALING_SUPPORTED) { + return 0; + } else { + // TODO: 这个逻辑可能不正确,但是如果不这样做,我们可能会跳过一个采样尺寸,因为 QUALITY 更喜欢宽度和高度缩放因子中比较小的那个 + // MEMORY 折中方案,他让我们更喜欢两者中较大的那个 + // + // } + return SampleSizeRounding.MEMORY; + } + } +} + +export enum DownsampleStrategy{ + AT_LEAST, + AT_MOST, + FIT_CENTER, + CENTER_INSIDE, + CENTER_OUTSIDE, + NONE, +} \ No newline at end of file diff --git a/library/src/main/ets/downsampling/Downsampler.ets b/library/src/main/ets/downsampling/Downsampler.ets new file mode 100644 index 0000000..d324959 --- /dev/null +++ b/library/src/main/ets/downsampling/Downsampler.ets @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + Atleast, + AtMost, + CenterInside, + CenterOutside, + DownsampleStrategy, + FitCenter, + None, +} from './DownsampleStartegy'; +import { highestOneBit, SampleSizeRounding } from './downsampleUtils'; + +export interface calculateScaleType { + targetWidth: number, + targetHeight: number +} + +export class Downsampler { + calculateScaling( + typeValue: string | null, + sourceHeight: number, //原始宽高 + sourceWidth: number | undefined, + //原始宽高 7106 + requestHeight: number, //请求宽高 + requestWidth: number, //请求宽高 + downsampType: DownsampleStrategy | undefined, + autoResize:boolean |undefined + + ): calculateScaleType { + let degreesToRotate: ESObject = 0; //获取旋转角度 + const fileType = typeValue //获取图片类型 + let powerOfTwoWidth: number | null = null; + let powerOfTwoHeight: number | null = null; + let targetWidth: number = 0 + let targetHeight: number = 0 + if (sourceHeight <= 0 || sourceWidth == undefined || sourceWidth == null || sourceWidth <= 0) { + throw new Error("Cannot found width or height"); + } + let orientedSourceWidth = sourceWidth; + let orientedSourceHeight = sourceHeight; + if (this.isRotationRequired(degreesToRotate)) { + orientedSourceWidth = sourceHeight; + orientedSourceHeight = sourceWidth; + } + if (requestWidth && !requestHeight) { + targetWidth = this.round((requestHeight) * orientedSourceWidth / orientedSourceHeight) + } else if (requestHeight && !requestWidth) { + targetHeight = this.round((requestWidth) * orientedSourceHeight / orientedSourceWidth) + } else if (requestHeight && requestWidth) { + targetWidth = requestHeight == sourceWidth ? (this.isRotationRequired(degreesToRotate) ? sourceHeight : sourceWidth) : requestWidth; + targetHeight = requestHeight == sourceHeight ? (this.isRotationRequired(degreesToRotate) ? sourceWidth : sourceHeight) : requestWidth; + } else { + throw new Error("Cannot found width or height"); + } + /*安卓的模式*/ + let exactScaleFactor: number = new FitCenter() + .getScaleFactor(orientedSourceWidth, orientedSourceHeight, targetWidth, targetHeight,autoResize) + /*安卓的模式*/ + let rounding: SampleSizeRounding = new FitCenter() + .getSampleSizeRounding(orientedSourceWidth, orientedSourceHeight, targetWidth, targetHeight,autoResize) + switch (downsampType) { + case DownsampleStrategy.FIT_CENTER: + exactScaleFactor = new FitCenter() + .getScaleFactor(orientedSourceWidth, orientedSourceHeight, targetWidth, targetHeight) + rounding = new FitCenter() + .getSampleSizeRounding(orientedSourceWidth, orientedSourceHeight, targetWidth, targetHeight) + break; + case DownsampleStrategy.NONE: + exactScaleFactor = new None() + .getScaleFactor(orientedSourceWidth, orientedSourceHeight, targetWidth, targetHeight) + rounding = new None() + .getSampleSizeRounding(orientedSourceWidth, orientedSourceHeight, targetWidth, targetHeight) + break + case DownsampleStrategy.AT_MOST: + exactScaleFactor = new AtMost() + .getScaleFactor(orientedSourceWidth, orientedSourceHeight, targetWidth, targetHeight) + rounding = new AtMost() + .getSampleSizeRounding(orientedSourceWidth, orientedSourceHeight, targetWidth, targetHeight) + break + case DownsampleStrategy.CENTER_INSIDE: + exactScaleFactor = new CenterInside() + .getScaleFactor(orientedSourceWidth, orientedSourceHeight, targetWidth, targetHeight,autoResize) + rounding = new CenterInside() + .getSampleSizeRounding(orientedSourceWidth, orientedSourceHeight, targetWidth, targetHeight,autoResize) + break + case DownsampleStrategy.AT_LEAST: + exactScaleFactor = new Atleast() + .getScaleFactor(orientedSourceWidth, orientedSourceHeight, targetWidth, targetHeight) + rounding = new Atleast() + .getSampleSizeRounding(orientedSourceWidth, orientedSourceHeight, targetWidth, targetHeight) + break + case DownsampleStrategy.CENTER_OUTSIDE: + exactScaleFactor = new CenterOutside() + .getScaleFactor(orientedSourceWidth, orientedSourceHeight, targetWidth, targetHeight) + rounding = new CenterOutside() + .getSampleSizeRounding(orientedSourceWidth, orientedSourceHeight, targetWidth, targetHeight) + break + } + if (exactScaleFactor <= 0) { + throw new Error("Cannot round with exactScaleFactor"); + } + if (rounding == null) { + throw new Error("Cannot round with null rounding"); + } + let outWidth: number = this.round(exactScaleFactor * orientedSourceWidth); + let outHeight: number = this.round(exactScaleFactor * orientedSourceHeight); + let widthScaleFactor = orientedSourceWidth / outWidth; + let heightScaleFactor = orientedSourceHeight / outHeight; + let scaleFactor = rounding == 1 ? Math.max(widthScaleFactor, heightScaleFactor) : Math.min(widthScaleFactor, heightScaleFactor) //将整型的缩放因子转换为2的次幂采样大小 + let powerOfTwoSampleSize: number = scaleFactor; + powerOfTwoSampleSize = Math.max(1, highestOneBit(scaleFactor)) + if (rounding == 1 && (powerOfTwoSampleSize < (1 / exactScaleFactor))) { + powerOfTwoSampleSize = powerOfTwoSampleSize << 1; + } + //基于上一步得出的采样大小,根据不同的图片类型,计算采样后的图片尺寸 + if (fileType === "jpeg") { + let nativeScaling = Math.min(powerOfTwoSampleSize, 8); + powerOfTwoWidth = Math.ceil(orientedSourceWidth / nativeScaling); + powerOfTwoHeight = Math.ceil(orientedSourceHeight / nativeScaling); + let secondaryScaling = Math.floor(powerOfTwoSampleSize / 8); + if (secondaryScaling > 0) { + powerOfTwoWidth = powerOfTwoWidth / secondaryScaling; + powerOfTwoHeight = powerOfTwoHeight / secondaryScaling; + } + } else if (fileType === "png") { + powerOfTwoWidth = Math.floor(orientedSourceWidth / powerOfTwoSampleSize); + powerOfTwoHeight = Math.floor(orientedSourceHeight / powerOfTwoSampleSize); + } else if (fileType === "webp") { + powerOfTwoWidth = Math.round(orientedSourceWidth / powerOfTwoSampleSize); + powerOfTwoHeight = Math.round(orientedSourceHeight / powerOfTwoSampleSize); + } else { + powerOfTwoWidth = orientedSourceWidth / powerOfTwoSampleSize; + powerOfTwoHeight = orientedSourceHeight / powerOfTwoSampleSize; + } + console.log(`输出长宽 === 高:${powerOfTwoHeight}宽:${powerOfTwoWidth}`,); + let a: calculateScaleType = { + "targetWidth": powerOfTwoWidth, + "targetHeight": powerOfTwoHeight + } + return a + } + + round(value: number): number { + return Math.floor(value + 0.5); + } + + isRotationRequired(degreesToRotate: number): boolean { + return degreesToRotate == 90 || degreesToRotate == 270; + } + + getDensityMultiplier(adjustedScaleFactor: number): number { + return Math.round(Number.MAX_VALUE * (adjustedScaleFactor <= 1 ? adjustedScaleFactor : 1 / adjustedScaleFactor)); + } + + adjustTargetDensityForError(adjustedScaleFactor: number): number { + let densityMultiplier = this.getDensityMultiplier(adjustedScaleFactor); + let targetDensity = this.round(densityMultiplier * adjustedScaleFactor); + let scaleFactorWithError = targetDensity / densityMultiplier; + let difference = adjustedScaleFactor / scaleFactorWithError; + return this.round(difference * targetDensity) + } +} \ No newline at end of file diff --git a/library/src/main/ets/downsampling/downsampleUtils.ets b/library/src/main/ets/downsampling/downsampleUtils.ets new file mode 100644 index 0000000..e0e9ee4 --- /dev/null +++ b/library/src/main/ets/downsampling/downsampleUtils.ets @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export enum SampleSizeRounding { + QUALITY, + MEMORY +} + +export function highestOneBit(i: number): number { + i |= (i >> 1); + i |= (i >> 2); + i |= (i >> 4); + i |= (i >> 8); + i |= (i >> 16); + return i - (i >>> 1); +} \ No newline at end of file diff --git a/library/src/main/ets/model/ImageKnifeData.ets b/library/src/main/ets/model/ImageKnifeData.ets index f2abd25..ad15aa4 100644 --- a/library/src/main/ets/model/ImageKnifeData.ets +++ b/library/src/main/ets/model/ImageKnifeData.ets @@ -17,6 +17,7 @@ import { ImageKnifeRequest } from '../ImageKnifeRequest' import { IEngineKey } from '../key/IEngineKey' import { PixelMapTransformation } from '../transform/PixelMapTransformation' import common from '@ohos.app.ability.common'; +import { DownsampleStrategy} from '../downsampling/DownsampleStartegy' export interface ImageKnifeData { source: PixelMap | string, @@ -60,7 +61,10 @@ export interface RequestJobResult { fileKey: string loadFail?: string, } - +export interface targetCcaleType { + width: number, + height: number +} /** * request子线程处理时的请求参数 */ @@ -76,5 +80,9 @@ export interface RequestJobRequest { writeCacheStrategy?: CacheStrategy signature?: string engineKey: IEngineKey + targetWidth:number + targetHeight: number + downsampType?:DownsampleStrategy + autoResize?:boolean }