From 066c370b20f4796c3576ad7a24ee4c4e4541d89c Mon Sep 17 00:00:00 2001 From: tsm Date: Thu, 26 Sep 2024 19:36:53 +0800 Subject: [PATCH 01/12] =?UTF-8?q?=E9=99=8D=E9=87=87=E6=A0=B7=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: tsm --- CHANGELOG.md | 2 +- README.md | 23 +- entry/src/main/ets/pages/DownSamplePage.ets | 208 ++++++++++++++++++ entry/src/main/ets/pages/Index.ets | 5 + .../main/resources/base/element/string.json | 4 + .../resources/base/profile/main_pages.json | 3 +- .../main/resources/zh_CN/element/string.json | 4 + entry/src/ohosTest/ets/test/List.test.ets | 3 +- .../ohosTest/ets/test/SamplingTest.test.ets | 79 +++++++ library/index.ets | 4 +- library/src/main/ets/ImageKnifeDispatcher.ets | 6 +- library/src/main/ets/ImageKnifeLoader.ets | 42 +++- .../ets/components/ImageKnifeComponent.ets | 2 +- .../ets/downsampling/BaseDownsampling.ets | 26 +++ .../ets/downsampling/DownsampleStartegy.ets | 182 +++++++++++++++ .../main/ets/downsampling/DownsampleUtils.ets | 36 +++ .../src/main/ets/downsampling/Downsampler.ets | 103 +++++++++ library/src/main/ets/key/DefaultEngineKey.ets | 4 + library/src/main/ets/model/ImageKnifeData.ets | 6 +- .../src/main/ets/model/ImageKnifeOption.ets | 5 + .../src/main/ets/model/ImageKnifeRequest.ets | 7 +- 21 files changed, 744 insertions(+), 10 deletions(-) create mode 100644 entry/src/main/ets/pages/DownSamplePage.ets create mode 100644 entry/src/ohosTest/ets/test/SamplingTest.test.ets create mode 100644 library/src/main/ets/downsampling/BaseDownsampling.ets create mode 100644 library/src/main/ets/downsampling/DownsampleStartegy.ets create mode 100644 library/src/main/ets/downsampling/DownsampleUtils.ets create mode 100644 library/src/main/ets/downsampling/Downsampler.ets diff --git a/CHANGELOG.md b/CHANGELOG.md index 794a2f7..78aa788 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ ## 3.1.1-rc.0 - 重构代码:抽取ImageKnifeDispatcher子线程requestJob相关代码到ImageKnifeLoader中,降低函数复杂度 - +- 降采样功能 ## 3.1.0 - 部分静态webp图片有delay属性导致识别成动图,改用getFrameCount识别 - 修复加载错误图后未去请求排队队列中的请求 diff --git a/README.md b/README.md index bf787e0..c1ff405 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,17 @@ ImageKnifeAnimatorComponent({ }),animatorOption:this.animatorOption }).width(300).height(300).backgroundColor(Color.Orange).margin({top:30}) ``` +#### 11.图片降采样 示例 +``` +ImageKnifeComponent({ + imageKnifeOption:new ImageKnifeOption({ + loadSrc:$r("app.media.pngSample"), + placeholderSrc:$r('app.media.loading'), + errorholderSrc:$r('app.media.failed'), + downsampleOf: DownsampleStrategy.NONE + }),animatorOption:this.animatorOption + }).width(300).height(300) +``` #### Reuse Scenario Clear the component content in the **aboutToRecycle** lifecycle and trigger image loading through watch observeration. ## Available APIs @@ -308,7 +319,17 @@ Clear the component content in the **aboutToRecycle** lifecycle and trigger imag | drawingColorFilter | ColorFilter | Drawing color filter. Optional. | | onComplete | (event:EventImage \| undefined)=>void | Callback for image loading completion. Optional. | | onLoadListener | onLoadStart:()=>void,onLoadSuccess:(data:string\|Pixelmap)=>void | Callback for image loading events. Optional. | - +| downsampleOf | DownsampleStrategy | 降采样(可选) | +### 降采样类型 +| 类型 | 相关描述 | +|---------------------|-------------------| +| NONE | 不进行降采样 | +| AT_MOST | 请求尺寸大于实际尺寸不进行放大 | +| FIT_CENTER_MEMORY | 两边自适应内存优先 | +| FIT_CENTER_QUALITY | 两边自适应质量优先 | +| CENTER_INSIDE_MEMORY | 宽高缩放比最大的比例,进行缩放适配内存优先 | +| CENTER_INSIDE_QUALITY | 宽高缩放比最大的比例,进行缩放适配质量优先 | +| CENTER_OUTSIDE | 根据宽高的最小的比例,进行适配 | ### ImageKnife | Parameter | Type | Description | diff --git a/entry/src/main/ets/pages/DownSamplePage.ets b/entry/src/main/ets/pages/DownSamplePage.ets new file mode 100644 index 0000000..c7442e0 --- /dev/null +++ b/entry/src/main/ets/pages/DownSamplePage.ets @@ -0,0 +1,208 @@ +/* + * 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, ImageKnifeOption, } from '@ohos/imageknife'; +import { ImageKnifeComponent, BlurTransformation, } from '@ohos/libraryimageknife'; +import { image } from '@kit.ImageKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { Downsampler } from '@ohos/imageknife/src/main/ets/downsampling/Downsampler'; +import { FileTypeUtil } from '@ohos/imageknife/src/main/ets/utils/FileTypeUtil'; + +@Entry +@ComponentV2 +struct DownSamplePage { + @Local imageKnifeOption: ImageKnifeOption = new ImageKnifeOption( { + loadSrc: $r('app.media.startIcon'), + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + objectFit: ImageFit.Contain + }) + isBrightness: boolean = false + @Local beforeSampling: number = 0 + @Local afterSampling: number = 0 + @Local SamplingList: SamplingType[] = [ + new SamplingType(0, "NONE"), + new SamplingType(1, "AT_MOST"), + new SamplingType(2, "FIT_CENTER_MEMORY"), + new SamplingType(4, "FIT_CENTER_QUALITY"), + new SamplingType(5, "CENTER_INSIDE_MEMORY"), + new SamplingType(6, "CENTER_INSIDE_QUALITY"), + new SamplingType(7, "CENTER_OUTSIDE"), + ] + @Local checked: boolean = false + + updateImageKnifeOption(value: string) { + if (value === 'NONE') { + this.imageKnifeOption =new ImageKnifeOption( { + loadSrc: $r('app.media.pngSample'), + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + objectFit: ImageFit.Contain, + downsampleOf: DownsampleStrategy.NONE + }) + this.originalPixMap($r('app.media.pngSample')) + this.afterSamplingFunc($r('app.media.pngSample')) + } else if (value === 'AT_MOST') { + this.imageKnifeOption =new ImageKnifeOption( { + loadSrc: $r('app.media.pngSample'), + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + objectFit: ImageFit.Contain, + downsampleOf: DownsampleStrategy.AT_MOST + }) + this.originalPixMap($r('app.media.pngSample')) + this.afterSamplingFunc($r('app.media.pngSample')) + } else if (value === 'FIT_CENTER_MEMORY') { + this.imageKnifeOption =new ImageKnifeOption( { + loadSrc: $r('app.media.pngSample'), + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + objectFit: ImageFit.Contain, + downsampleOf: DownsampleStrategy.FIT_CENTER_MEMORY + }) + this.originalPixMap($r('app.media.pngSample')) + this.afterSamplingFunc($r('app.media.pngSample')) + } + else if (value ==='FIT_CENTER_QUALITY') { + this.imageKnifeOption =new ImageKnifeOption( { + loadSrc: $r('app.media.pngSample'), + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + objectFit: ImageFit.Contain, + downsampleOf: DownsampleStrategy.FIT_CENTER_QUALITY + }) + this.originalPixMap($r('app.media.pngSample')) + this.afterSamplingFunc($r('app.media.pngSample')) + } + else if (value === 'CENTER_INSIDE_MEMORY') { + this.imageKnifeOption = new ImageKnifeOption({ + loadSrc: $r('app.media.pngSample'), + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + objectFit: ImageFit.Contain, + downsampleOf: DownsampleStrategy.CENTER_INSIDE_MEMORY + }) + this.originalPixMap($r('app.media.pngSample')) + this.afterSamplingFunc($r('app.media.pngSample')) + } else if (value === 'CENTER_INSIDE_QUALITY') { + this.imageKnifeOption = new ImageKnifeOption({ + loadSrc: $r('app.media.pngSample'), + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + objectFit: ImageFit.Contain, + downsampleOf: DownsampleStrategy.CENTER_INSIDE_QUALITY + }) + this.originalPixMap($r('app.media.pngSample')) + this.afterSamplingFunc($r('app.media.pngSample')) + } + else { + this.imageKnifeOption = new ImageKnifeOption({ + loadSrc: $r('app.media.pngSample'), + placeholderSrc: $r("app.media.loading"), + errorholderSrc: $r("app.media.app_icon"), + objectFit: ImageFit.Contain, + downsampleOf: DownsampleStrategy.CENTER_OUTSIDE + }) + this.originalPixMap($r('app.media.pngSample')) + this.afterSamplingFunc($r('app.media.pngSample')) + } + } + + async afterSamplingFunc(imgs: Resource) { + let img: Uint8Array = await getContext(this).resourceManager.getMediaContent(imgs); + let imageSource: image.ImageSource = image.createImageSource(img.buffer.slice(0)); + let fileTypeUtil = new FileTypeUtil(); + let typeValue = fileTypeUtil.getFileType(img.buffer.slice(0)); + let decodingOptions: image.DecodingOptions = { + editable: true, + desiredPixelFormat: 3, + } + let imageInfo = await imageSource.getImageInfo() + let reqSize = + new Downsampler().calculateScaling(typeValue, imageInfo.size.width, imageInfo.size.height, 300, + 300, this.imageKnifeOption.downsampleOf) + decodingOptions = { + editable: true, + desiredSize: { + width: reqSize.targetWidth, + height: reqSize.targetHeight + } + } + // 创建pixelMap + imageSource.createPixelMap(decodingOptions).then((pixelMap: image.PixelMap) => { + this.afterSampling = pixelMap.getPixelBytesNumber() + }).catch((err: BusinessError) => { + console.error("Failed to create PixelMap") + }); + } + + async originalPixMap(imgs: Resource,) { + let img: Uint8Array = await getContext(this).resourceManager.getMediaContent(imgs); + let imageSource: image.ImageSource = image.createImageSource(img.buffer.slice(0)); + let decodingOptions: image.DecodingOptions = { + editable: true, + desiredPixelFormat: 3, + } + // 创建pixelMap + imageSource.createPixelMap(decodingOptions).then((pixelMap: image.PixelMap) => { + this.beforeSampling = pixelMap.getPixelBytesNumber() + }).catch((err: BusinessError) => { + console.error("Failed to create PixelMap") + }); + } + + build() { + Scroll() { + Column() { + ForEach(this.SamplingList, (item: SamplingType, index) => { + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Radio({ value: item.value + 'radio', group: 'radioGroup' }) + .height(50) + .width(50) + .checked(this.checked) + .onClick(() => { + this.updateImageKnifeOption(item.value) + }) + Text("降采样规格:" + item.value).fontSize(20) + } + }, (item: SamplingType) => JSON.stringify(item)) + Column() { + Text(`未降采样大小:${this.beforeSampling}`).fontSize(20) + Text(`降采样后大小:${this.afterSampling}`).fontSize(20) + } + + ImageKnifeComponent({ + imageKnifeOption: this.imageKnifeOption + }) + .height(300) + .width(300) + .borderWidth(1) + .borderColor(Color.Pink) + } + + } + .height('100%') + .width('100%') + } +} + +class SamplingType { + key: number + value: string + + constructor(key: number, value: string) { + this.key = key + this.value = value + } +} \ 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 a5bf801..54121fa 100644 --- a/entry/src/main/ets/pages/Index.ets +++ b/entry/src/main/ets/pages/Index.ets @@ -40,6 +40,11 @@ struct Index { uri: 'pages/TestCommonImage', }); }) + Button($r('app.string.Image_Downsampling_Functionality')).margin({top:10}).onClick(()=>{ + router.push({ + uri: 'pages/DownSamplePage', + }); + }) Button($r('app.string.Test_Task_error')).margin({top:10}).onClick(()=>{ router.push({ uri: 'pages/TestTaskResourcePage', diff --git a/entry/src/main/resources/base/element/string.json b/entry/src/main/resources/base/element/string.json index f982e81..aff88e0 100644 --- a/entry/src/main/resources/base/element/string.json +++ b/entry/src/main/resources/base/element/string.json @@ -367,6 +367,10 @@ { "name": "TIPS", "value": "Please shut down the network first and ensure that there is no cache of images from this network in the test failure scenario locally" + }, + { + "name": "Image_Downsampling_Functionality", + "value": "Downscale Image effect" } ] } \ No newline at end of file diff --git a/entry/src/main/resources/base/profile/main_pages.json b/entry/src/main/resources/base/profile/main_pages.json index de593dd..534b53b 100644 --- a/entry/src/main/resources/base/profile/main_pages.json +++ b/entry/src/main/resources/base/profile/main_pages.json @@ -23,6 +23,7 @@ "pages/ImageAnimatorPage", "pages/TestSetCustomImagePage", "pages/TestErrorHolderPage", - "pages/TestTaskResourcePage" + "pages/TestTaskResourcePage", + "pages/DownSamplePage" ] } \ No newline at end of file diff --git a/entry/src/main/resources/zh_CN/element/string.json b/entry/src/main/resources/zh_CN/element/string.json index 74fedfe..bd30a41 100644 --- a/entry/src/main/resources/zh_CN/element/string.json +++ b/entry/src/main/resources/zh_CN/element/string.json @@ -363,6 +363,10 @@ { "name": "TIPS", "value": "测试失败场景请先关闭网络,并保证本地没有此网络图片的缓存" + }, + { + "name": "Image_Downsampling_Functionality", + "value": "降采样功能" } ] } \ No newline at end of file diff --git a/entry/src/ohosTest/ets/test/List.test.ets b/entry/src/ohosTest/ets/test/List.test.ets index 8c04309..7bbeda5 100644 --- a/entry/src/ohosTest/ets/test/List.test.ets +++ b/entry/src/ohosTest/ets/test/List.test.ets @@ -18,7 +18,7 @@ import ImageKnifeOptionTest from './ImageKnifeOption.test'; import MemoryLruCacheTest from './MemoryLruCache.test'; import ImageKnifeTest from './ImageKnife.test'; import Transform from './transform.test'; - +import SamplingTest from './SamplingTest.test'; export default function testsuite() { MemoryLruCacheTest(); FileLruCacheTest(); @@ -26,4 +26,5 @@ export default function testsuite() { ImageKnifeOptionTest(); ImageKnifeTest(); Transform(); + SamplingTest() } \ No newline at end of file diff --git a/entry/src/ohosTest/ets/test/SamplingTest.test.ets b/entry/src/ohosTest/ets/test/SamplingTest.test.ets new file mode 100644 index 0000000..732f2ff --- /dev/null +++ b/entry/src/ohosTest/ets/test/SamplingTest.test.ets @@ -0,0 +1,79 @@ +/* + * 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium' +import { image } from '@kit.ImageKit' +import { BusinessError } from '@kit.BasicServicesKit' +import { calculateScaleType, Downsampler } from '@ohos/imageknife/src/main/ets/downsampling/Downsampler' +import { DownsampleStrategy } from '@ohos/imageknife' + +export default function SamplingTest() { + describe('SamplingTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('NONE', 0, () => { + let reqSize: calculateScaleType = + new Downsampler().calculateScaling('jpg', 1024, 1024, 200, + 200, DownsampleStrategy.NONE,) + let req = (reqSize.targetWidth == 1024 && reqSize.targetHeight == 1024) + expect(req).assertEqual(true); + }) + it('AT_MOST', 1, () => { + let reqSize: calculateScaleType = + new Downsampler().calculateScaling('jpg', 1024, 1024, 200, + 200, DownsampleStrategy.AT_MOST) + let req = (reqSize.targetWidth < 1024 && reqSize.targetHeight < 1024) + expect(req).assertEqual(true); + }) + it('FIT_CENTER', 2, () => { + let reqSize: calculateScaleType = + new Downsampler().calculateScaling('jpg', 1024, 1024, 200, + 200, DownsampleStrategy.FIT_CENTER_MEMORY) + let req = (reqSize.targetWidth < 1024 && reqSize.targetHeight < 1024) + expect(req).assertEqual(true); + }) + it('CENTER_INSIDE', 3, () => { + let reqSize: calculateScaleType = + new Downsampler().calculateScaling('jpg', 1024, 1024, 200, + 200, DownsampleStrategy.CENTER_INSIDE_MEMORY) + let req = (reqSize.targetWidth < 1024 && reqSize.targetHeight < 1024) + expect(req).assertEqual(true); + }) + it('CENTER_OUTSIDE', 4, () => { + let reqSize: calculateScaleType = + new Downsampler().calculateScaling('jpg', 1024, 1024, 200, + 200, DownsampleStrategy.CENTER_OUTSIDE) + let req = (reqSize.targetWidth < 1024 && reqSize.targetHeight < 1024) + expect(req).assertEqual(true); + }) + + }) +} diff --git a/library/index.ets b/library/index.ets index 1f67f70..058e860 100644 --- a/library/index.ets +++ b/library/index.ets @@ -66,4 +66,6 @@ export { CropTransformation } from './src/main/ets/transform/CropTransformation' export { MaskTransformation } from './src/main/ets/transform/MaskTransformation' -export { SepiaTransformation } from './src/main/ets/transform/SepiaTransformation' \ No newline at end of file +export { SepiaTransformation } from './src/main/ets/transform/SepiaTransformation' + +export { DownsampleStrategy } from './src/main/ets/downsampling/DownsampleStartegy' \ No newline at end of file diff --git a/library/src/main/ets/ImageKnifeDispatcher.ets b/library/src/main/ets/ImageKnifeDispatcher.ets index fbbb3a8..e2e1466 100644 --- a/library/src/main/ets/ImageKnifeDispatcher.ets +++ b/library/src/main/ets/ImageKnifeDispatcher.ets @@ -35,6 +35,7 @@ import { } from './model/ImageKnifeData' import { BusinessError } from '@kit.BasicServicesKit'; import { ImageKnifeLoader } from './ImageKnifeLoader' +import { DownsampleStrategy } from './downsampling/DownsampleStartegy'; export class ImageKnifeDispatcher { @@ -183,7 +184,10 @@ export class ImageKnifeDispatcher { fileCacheFolder: ImageKnife.getInstance().getFileCache()?.getCacheFolder(), isAnimator:isAnimator, moduleName: moduleName == "" ? undefined : moduleName, - resName: resName == "" ? undefined : resName + resName: resName == "" ? undefined : resName, + targetWidth: currentRequest.componentWidth, + targetHeight: currentRequest.componentHeight, + downsampType: currentRequest.imageKnifeOption.downsampleOf==undefined?DownsampleStrategy.NONE:currentRequest.imageKnifeOption.downsampleOf, } if(request.customGetImage == undefined) { diff --git a/library/src/main/ets/ImageKnifeLoader.ets b/library/src/main/ets/ImageKnifeLoader.ets index 1c14d0c..d1135ed 100644 --- a/library/src/main/ets/ImageKnifeLoader.ets +++ b/library/src/main/ets/ImageKnifeLoader.ets @@ -28,6 +28,8 @@ import emitter from '@ohos.events.emitter'; import image from '@ohos.multimedia.image'; import { RequestJobResult } from './model/ImageKnifeData' import util from '@ohos.util'; +import { DownsampleStrategy } from './downsampling/DownsampleStartegy'; +import { Downsampler } from './downsampling/Downsampler'; class RequestData { receiveSize: number = 2000 @@ -73,6 +75,15 @@ export class ImageKnifeLoader { } let size = (await imageSource.getImageInfo()).size + try{ + if ((request.downsampType !== DownsampleStrategy.NONE) && + request.requestSource == ImageKnifeRequestSource.SRC ) { + decodingOptions =await ImageKnifeLoader.downsamplerReqSize(typeValue,request,size,ImageKnifeRequestSource.SRC) + } + }catch(err){ + return ImageKnifeLoader.makeEmptyResult("image.createImageSource failed") + } + await imageSource.createPixelMap(decodingOptions) .then((pixelmap: PixelMap) => { resPixelmap = pixelmap @@ -110,6 +121,14 @@ export class ImageKnifeLoader { editable: true, desiredSize: defaultSize }; + try{ + if ((request.downsampType !== DownsampleStrategy.NONE) && + request.requestSource == ImageKnifeRequestSource.SRC ) { + opts =await ImageKnifeLoader.downsamplerReqSize(typeValue,request,size) + } + }catch(err){ + return ImageKnifeLoader.makeEmptyResult("image.createImageSource failed") + } await imageSource.createPixelMap(opts) .then((pixelmap: PixelMap) => { resPixelmap = pixelmap @@ -354,4 +373,25 @@ export class ImageKnifeLoader { } return resBuf } -} \ No newline at end of file + static async downsamplerReqSize(typeValue:string,request:RequestJobRequest ,size:Size,SRC?:ImageKnifeRequestSource): Promise{ + let reqSize = new Downsampler().calculateScaling(typeValue, size.width, size.height, request.targetWidth, request.targetHeight, request.downsampType) + if(typeValue=="svg") { + return ({ + editable: true, + desiredSize: ({ + height: vp2px(reqSize.targetHeight), + width: vp2px(reqSize.targetWidth) + } as Size) + } as image.DecodingOptions ) + }else { + return( { + editable: request.requestSource ===SRC && request.transformation !== undefined ? true : false, + desiredSize: ({ + width: reqSize.targetWidth, + height: reqSize.targetHeight + }as Size) + }as image.DecodingOptions) + } + } +} + diff --git a/library/src/main/ets/components/ImageKnifeComponent.ets b/library/src/main/ets/components/ImageKnifeComponent.ets index f96cecc..59812e9 100644 --- a/library/src/main/ets/components/ImageKnifeComponent.ets +++ b/library/src/main/ets/components/ImageKnifeComponent.ets @@ -38,7 +38,7 @@ export struct ImageKnifeComponent { @Param imageKnifeOption: ImageKnifeOption = new ImageKnifeOption(); @Monitor('imageKnifeOption', - "imageKnifeOption.loadSrc","imageKnifeOption.signature","imageKnifeOption.transformation","imageKnifeOption.border","imageKnifeOption.objectFit") + "imageKnifeOption.loadSrc","imageKnifeOption.signature","imageKnifeOption.transformation","imageKnifeOption.border","imageKnifeOption.objectFit",'imageKnifeOption.downsampleOf') watchImageKnifeOption() { this.clearLastRequest() this.componentVersion++ diff --git a/library/src/main/ets/downsampling/BaseDownsampling.ets b/library/src/main/ets/downsampling/BaseDownsampling.ets new file mode 100644 index 0000000..5b49b68 --- /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 { DownsampleStrategy } from './DownsampleStartegy'; + +import { SampleSizeRounding } from './DownsampleUtils'; + +export interface BaseDownsampling { + getName(): string + + getScaleFactor(sourceWidth: number, sourceHeight: number, requestWidth: number, requestHeight: number,downsampType?:DownsampleStrategy): number + + getSampleSizeRounding(sourceWidth: number, sourceHeight: number, requestWidth: number, + requestHeight: number,downsampType?:DownsampleStrategy): 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..05a3610 --- /dev/null +++ b/library/src/main/ets/downsampling/DownsampleStartegy.ets @@ -0,0 +1,182 @@ +/* + * 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'; + +export class FitCenter implements BaseDownsampling { + getName() { + return "FitCenter" + } + + getScaleFactor(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number,downsampType:DownsampleStrategy |undefined + ): number { + if (downsampType===DownsampleStrategy.FIT_CENTER_MEMORY) { + const widthPercentage = requestedWidth / sourceWidth + const heightPercentage = requestedHeight / sourceHeight + return Math.min(widthPercentage, heightPercentage) + } else { + 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,downsampType:DownsampleStrategy|undefined + ): number { + if (downsampType===DownsampleStrategy.FIT_CENTER_QUALITY) { + return SampleSizeRounding.QUALITY; + } else { + return SampleSizeRounding.MEMORY; + } + + } +} + +export class None implements BaseDownsampling { + getName(): string { + return "DownsampleNone" + } + + 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 { + return SampleSizeRounding.QUALITY + + } +} + + +/*宽高进行等比缩放宽高里面最小的比例先放进去 +然后再更据原图的缩放比去适配另一边*/ + +export class CenterOutside implements BaseDownsampling { + getName() { + return "CenterOutside" + } + + getScaleFactor(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number): number { + const widthPercentage = requestedWidth / sourceWidth; + const heightPercentage = requestedHeight / sourceHeight; + //返回宽度和高度比例中最大的值 + return Math.max(widthPercentage, heightPercentage); + } + + getSampleSizeRounding(sourceWidth: number, sourceHeight: number, requestedWidth: number, + requestedHeight: number): SampleSizeRounding { + return SampleSizeRounding.QUALITY; + } +} + +/*请求尺寸大于实际尺寸不进行放大,按照原图展示*/ + +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) { + greaterOrEqualSampleSize = lesserOrEqualSampleSize <<= 1; + } + greaterOrEqualSampleSize = lesserOrEqualSampleSize << (lesserOrEqualSampleSize < maxIntegerFactor ? 1 : 0) + //返回缩放因子 + return 1 / greaterOrEqualSampleSize + } + + + getSampleSizeRounding(sourceWidth: number, sourceHeight: number, requestedWidth: number, + requestHeight: number): SampleSizeRounding { + //根据 AtMost 的逻辑,总是返回 MEMORY + return SampleSizeRounding.MEMORY + } +} + +/*宽高进行等比缩放宽高里面最大的比例先放进去 +然后再更据原图的缩放比去适配另一边*/ +export class CenterInside implements BaseDownsampling { + getName() { + return "CenterInside" + } + + getScaleFactor(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number,downsampType:DownsampleStrategy|undefined + ): number { + //获取 FIT_CENTER 的缩放因子 + const fitCenterScaleFactor: ESObject = + this.getScale(sourceWidth, sourceHeight, requestedWidth, requestedHeight, downsampType); + //返回不超过 1 的缩放因子,即尽量缩小图像以适应目标尺寸,但不会放大 + return Math.min(1, fitCenterScaleFactor); + + } + + getScale(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number,downsampType:DownsampleStrategy|undefined + ): number { + if (downsampType===DownsampleStrategy.FIT_CENTER_MEMORY) { + const widthPercentage = requestedWidth / sourceWidth + const heightPercentage = requestedHeight / sourceHeight + return Math.min(widthPercentage, heightPercentage) + } else { + const maxIntegerFactor = Math.max(sourceHeight / requestedHeight, sourceWidth / requestedWidth); + const a = maxIntegerFactor === 0 ? 1 : 1 / highestOneBit(maxIntegerFactor) + return a; + } + } + + getSampleSizeRounding(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number,downsampType:DownsampleStrategy|undefined + ): SampleSizeRounding { + //如果缩放因子为 1,表示没有缩放,优先选择质量 + if (this.getScaleFactor(sourceWidth, sourceHeight, requestedWidth, requestedHeight, downsampType) === 1) { + return SampleSizeRounding.QUALITY + } + //否则,使用 FIL_CENTER 的 SampleSizeRounding 值 + return this.getSampleSize(sourceWidth, sourceHeight, requestedWidth, requestedHeight,downsampType); + } + + getSampleSize(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number,downsampType:DownsampleStrategy|undefined + ): SampleSizeRounding { + if (downsampType===DownsampleStrategy.CENTER_INSIDE_MEMORY) { + return SampleSizeRounding.MEMORY; + } else { + return SampleSizeRounding.QUALITY; + } + } +} + +export enum DownsampleStrategy { + //请求尺寸大于实际尺寸不进行放大 + AT_MOST, + //两边自适应内存优先 + FIT_CENTER_MEMORY , + //两边自适应质量优先 + FIT_CENTER_QUALITY, + //按照宽高比的最大比进行适配内存优先 + CENTER_INSIDE_MEMORY , + //按照宽高比的最大比进行适配质量优先 + CENTER_INSIDE_QUALITY, + //宽高进行等比缩放宽高里面最小的比例先放进去,然后再根据原图的缩放比去适配 + CENTER_OUTSIDE, + //不进行降采样 + NONE, +} \ 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..ef17f42 --- /dev/null +++ b/library/src/main/ets/downsampling/DownsampleUtils.ets @@ -0,0 +1,36 @@ +/* + * 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 { + /** + * Prefer to round the sample size up so that the image is downsampled to smaller than the + * requested size to use less memory. + */ + //(内存优先) + MEMORY, + /** + * Prefer to round the sample size down so that the image is downsampled to larger than the + * requested size to maintain quality at the expense of extra memory usage. + */ + //(质量优先) + QUALITY +} +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/downsampling/Downsampler.ets b/library/src/main/ets/downsampling/Downsampler.ets new file mode 100644 index 0000000..517fc34 --- /dev/null +++ b/library/src/main/ets/downsampling/Downsampler.ets @@ -0,0 +1,103 @@ +/* + * 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 { + 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, + sourceWidth: number, //原始宽高 + sourceHeight: number, //原始宽高 + requestWidth: number, //请求宽高 + requestHeight: number, //请求宽高 + downsampType: DownsampleStrategy, + ): calculateScaleType { + + if (sourceHeight <= 0 || sourceWidth <= 0) { + throw new Error(`Invalid width and height, sourceHeight:${sourceHeight}+ sourceWidth:${sourceWidth}`) + } + let downsampler = this.getDownsampler(downsampType); + let exactScaleFactor: number = + downsampler.getScaleFactor(sourceWidth, sourceHeight, requestWidth, requestHeight, downsampType); + let rounding: SampleSizeRounding = + downsampler.getSampleSizeRounding(sourceWidth, sourceHeight, requestWidth, requestHeight, downsampType); //采样类型 + //原始宽高和缩放系数的乘积 + let outSize:Size = { + width: this.round(exactScaleFactor * sourceWidth), + height: this.round(exactScaleFactor * sourceHeight) + } + let scaleFactor = + rounding == SampleSizeRounding.QUALITY ? Math.max(sourceWidth / outSize.width, sourceHeight / outSize.height) : + Math.min(sourceWidth / outSize.width, sourceHeight / outSize.height) //将整型的缩放因子转换为2的次幂采样大小 + scaleFactor = Math.max(1, highestOneBit(scaleFactor)) + if (rounding == 0 && (scaleFactor < (1 / exactScaleFactor))) { + scaleFactor = scaleFactor << 1; + } + //基于上一步得出的采样大小,根据不同的图片类型,计算采样后的图片尺寸 + if (typeValue === "png") { + return { + targetWidth: Math.floor(sourceWidth / scaleFactor), + targetHeight: Math.floor(sourceHeight / scaleFactor) + } + } else if (typeValue === "webp") { + return { + targetWidth: Math.round(sourceWidth / scaleFactor), + targetHeight: Math.round(sourceHeight / scaleFactor) + } + } else { + return { + targetWidth: sourceWidth / scaleFactor, + targetHeight: sourceHeight / scaleFactor + + } + } + } + + getDownsampler(downsampType: DownsampleStrategy) { + switch (downsampType) { + case DownsampleStrategy.FIT_CENTER_MEMORY: + case DownsampleStrategy.FIT_CENTER_QUALITY: + return new FitCenter(); + case DownsampleStrategy.NONE: + return new None(); + case DownsampleStrategy.AT_MOST: + return new AtMost(); + case DownsampleStrategy.CENTER_INSIDE_MEMORY: + case DownsampleStrategy.CENTER_INSIDE_QUALITY: + return new CenterInside(); + case DownsampleStrategy.CENTER_OUTSIDE: + return new CenterOutside(); + default: + throw new Error('Unsupported downsampling strategy'); + } + } + + round(value: number): number { + return Math.floor(value + 0.5); + } +} diff --git a/library/src/main/ets/key/DefaultEngineKey.ets b/library/src/main/ets/key/DefaultEngineKey.ets index f28024b..12f010f 100644 --- a/library/src/main/ets/key/DefaultEngineKey.ets +++ b/library/src/main/ets/key/DefaultEngineKey.ets @@ -17,6 +17,7 @@ import { ImageKnifeOption } from '../model/ImageKnifeOption'; import { IEngineKey } from './IEngineKey'; import { PixelMapTransformation } from '../transform/PixelMapTransformation'; import { ImageKnifeRequestSource } from '../model/ImageKnifeData'; +import { DownsampleStrategy } from '../downsampling/DownsampleStartegy'; @Sendable export class DefaultEngineKey implements IEngineKey { @@ -31,6 +32,9 @@ export class DefaultEngineKey implements IEngineKey { if (imageKnifeOption.transformation) { key += "transformation=" + this.getTransformation(imageKnifeOption.transformation) + ";" } + if ((imageKnifeOption.downsampleOf !== DownsampleStrategy.NONE && imageKnifeOption.downsampleOf !== undefined)) { + key += "downsampleOf" + imageKnifeOption.downsampleOf +"width="+width+"height="+ height + } } return key } diff --git a/library/src/main/ets/model/ImageKnifeData.ets b/library/src/main/ets/model/ImageKnifeData.ets index e8a4c2a..8fb3b61 100644 --- a/library/src/main/ets/model/ImageKnifeData.ets +++ b/library/src/main/ets/model/ImageKnifeData.ets @@ -18,6 +18,7 @@ import { IEngineKey } from '../key/IEngineKey' import { PixelMapTransformation } from '../transform/PixelMapTransformation' import common from '@ohos.app.ability.common'; import { Size } from '@kit.ArkUI' +import { DownsampleStrategy } from '../downsampling/DownsampleStartegy' export interface ImageKnifeData { source: PixelMap | string, @@ -103,6 +104,9 @@ export interface RequestJobRequest { fileCacheFolder: string, isAnimator?: boolean, moduleName?:string, - resName?: string + resName?: string, + targetWidth: number + targetHeight: number + downsampType: DownsampleStrategy } diff --git a/library/src/main/ets/model/ImageKnifeOption.ets b/library/src/main/ets/model/ImageKnifeOption.ets index 5c5f292..4e42b00 100644 --- a/library/src/main/ets/model/ImageKnifeOption.ets +++ b/library/src/main/ets/model/ImageKnifeOption.ets @@ -17,6 +17,7 @@ import common from '@ohos.app.ability.common' import { CacheStrategy, ImageKnifeData,EventImage } from './ImageKnifeData'; import { PixelMapTransformation } from '../transform/PixelMapTransformation'; import { drawing } from '@kit.ArkGraphics2D'; +import { DownsampleStrategy } from '../downsampling/DownsampleStartegy'; export interface HeaderOptions { key: string; @@ -90,6 +91,7 @@ interface ImageOption { onLoadListener?: OnLoadCallBack | undefined; onComplete?:(event:EventImage | undefined) => void drawingColorFilter?: ColorFilter | drawing.ColorFilter + downsampleOf?: DownsampleStrategy } @ObservedV2 export class ImageKnifeOption { @@ -121,6 +123,8 @@ export class ImageKnifeOption { onLoadListener?: OnLoadCallBack | undefined; onComplete?:(event:EventImage | undefined) => void drawingColorFilter?: ColorFilter | drawing.ColorFilter + // 下采样 + @Trace downsampleOf: DownsampleStrategy = DownsampleStrategy.NONE constructor(option?:ImageOption) { this.loadSrc = option?.loadSrc == undefined ? "" : option?.loadSrc this.placeholderSrc = option?.placeholderSrc @@ -141,6 +145,7 @@ export class ImageKnifeOption { this.onLoadListener = option?.onLoadListener this.onComplete = option?.onComplete this.drawingColorFilter = option?.drawingColorFilter + this.downsampleOf = option?.downsampleOf==undefined?DownsampleStrategy.NONE:option?.downsampleOf } } diff --git a/library/src/main/ets/model/ImageKnifeRequest.ets b/library/src/main/ets/model/ImageKnifeRequest.ets index c63f8be..530b707 100644 --- a/library/src/main/ets/model/ImageKnifeRequest.ets +++ b/library/src/main/ets/model/ImageKnifeRequest.ets @@ -15,6 +15,7 @@ import { ImageKnifeOption } from './ImageKnifeOption'; import common from '@ohos.app.ability.common'; import { ImageKnifeRequestSource } from './ImageKnifeData'; +import { DownsampleStrategy } from '../downsampling/DownsampleStartegy'; export class ImageKnifeRequest { @@ -27,18 +28,22 @@ export class ImageKnifeRequest { ImageKnifeRequestCallback: ImageKnifeRequestCallback componentVersion: number = 0 headers: Map = new Map() + downsampType?: DownsampleStrategy constructor(option: ImageKnifeOption, uIAbilityContext: common.UIAbilityContext, width: number, height: number, version: number, - ImageKnifeRequestCallback: ImageKnifeRequestCallback) { + ImageKnifeRequestCallback: ImageKnifeRequestCallback, + downsampType?: DownsampleStrategy + ) { this.imageKnifeOption = option this.context = uIAbilityContext this.componentWidth = width this.componentHeight = height this.componentVersion = version this.ImageKnifeRequestCallback = ImageKnifeRequestCallback + this.downsampType = downsampType } // RequestOption调用header对于的方法 addHeader(key: string, value: Object) { From f80dd101d011866612dda1f426e4d8b368881876 Mon Sep 17 00:00:00 2001 From: tsm Date: Fri, 27 Sep 2024 14:34:18 +0800 Subject: [PATCH 02/12] =?UTF-8?q?=E5=8E=BB=E9=99=A4typevalue=20=E7=9A=84nu?= =?UTF-8?q?ll?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: tsm --- entry/src/main/ets/pages/DownSamplePage.ets | 25 ++- library/src/main/ets/ImageKnifeLoader.ets | 163 ++++++++++-------- .../src/main/ets/downsampling/Downsampler.ets | 2 +- 3 files changed, 103 insertions(+), 87 deletions(-) diff --git a/entry/src/main/ets/pages/DownSamplePage.ets b/entry/src/main/ets/pages/DownSamplePage.ets index c7442e0..9355a40 100644 --- a/entry/src/main/ets/pages/DownSamplePage.ets +++ b/entry/src/main/ets/pages/DownSamplePage.ets @@ -13,7 +13,7 @@ * limitations under the License. */ import { DownsampleStrategy, ImageKnifeOption, } from '@ohos/imageknife'; -import { ImageKnifeComponent, BlurTransformation, } from '@ohos/libraryimageknife'; +import { ImageKnifeComponent } from '@ohos/libraryimageknife'; import { image } from '@kit.ImageKit'; import { BusinessError } from '@kit.BasicServicesKit'; import { Downsampler } from '@ohos/imageknife/src/main/ets/downsampling/Downsampler'; @@ -22,7 +22,7 @@ import { FileTypeUtil } from '@ohos/imageknife/src/main/ets/utils/FileTypeUtil'; @Entry @ComponentV2 struct DownSamplePage { - @Local imageKnifeOption: ImageKnifeOption = new ImageKnifeOption( { + @Local imageKnifeOption: ImageKnifeOption = new ImageKnifeOption({ loadSrc: $r('app.media.startIcon'), placeholderSrc: $r("app.media.loading"), errorholderSrc: $r("app.media.app_icon"), @@ -44,7 +44,7 @@ struct DownSamplePage { updateImageKnifeOption(value: string) { if (value === 'NONE') { - this.imageKnifeOption =new ImageKnifeOption( { + this.imageKnifeOption = new ImageKnifeOption({ loadSrc: $r('app.media.pngSample'), placeholderSrc: $r("app.media.loading"), errorholderSrc: $r("app.media.app_icon"), @@ -54,7 +54,7 @@ struct DownSamplePage { this.originalPixMap($r('app.media.pngSample')) this.afterSamplingFunc($r('app.media.pngSample')) } else if (value === 'AT_MOST') { - this.imageKnifeOption =new ImageKnifeOption( { + this.imageKnifeOption = new ImageKnifeOption({ loadSrc: $r('app.media.pngSample'), placeholderSrc: $r("app.media.loading"), errorholderSrc: $r("app.media.app_icon"), @@ -64,7 +64,7 @@ struct DownSamplePage { this.originalPixMap($r('app.media.pngSample')) this.afterSamplingFunc($r('app.media.pngSample')) } else if (value === 'FIT_CENTER_MEMORY') { - this.imageKnifeOption =new ImageKnifeOption( { + this.imageKnifeOption = new ImageKnifeOption({ loadSrc: $r('app.media.pngSample'), placeholderSrc: $r("app.media.loading"), errorholderSrc: $r("app.media.app_icon"), @@ -73,9 +73,8 @@ struct DownSamplePage { }) this.originalPixMap($r('app.media.pngSample')) this.afterSamplingFunc($r('app.media.pngSample')) - } - else if (value ==='FIT_CENTER_QUALITY') { - this.imageKnifeOption =new ImageKnifeOption( { + } else if (value === 'FIT_CENTER_QUALITY') { + this.imageKnifeOption = new ImageKnifeOption({ loadSrc: $r('app.media.pngSample'), placeholderSrc: $r("app.media.loading"), errorholderSrc: $r("app.media.app_icon"), @@ -84,8 +83,7 @@ struct DownSamplePage { }) this.originalPixMap($r('app.media.pngSample')) this.afterSamplingFunc($r('app.media.pngSample')) - } - else if (value === 'CENTER_INSIDE_MEMORY') { + } else if (value === 'CENTER_INSIDE_MEMORY') { this.imageKnifeOption = new ImageKnifeOption({ loadSrc: $r('app.media.pngSample'), placeholderSrc: $r("app.media.loading"), @@ -95,7 +93,7 @@ struct DownSamplePage { }) this.originalPixMap($r('app.media.pngSample')) this.afterSamplingFunc($r('app.media.pngSample')) - } else if (value === 'CENTER_INSIDE_QUALITY') { + } else if (value === 'CENTER_INSIDE_QUALITY') { this.imageKnifeOption = new ImageKnifeOption({ loadSrc: $r('app.media.pngSample'), placeholderSrc: $r("app.media.loading"), @@ -105,8 +103,7 @@ struct DownSamplePage { }) this.originalPixMap($r('app.media.pngSample')) this.afterSamplingFunc($r('app.media.pngSample')) - } - else { + } else { this.imageKnifeOption = new ImageKnifeOption({ loadSrc: $r('app.media.pngSample'), placeholderSrc: $r("app.media.loading"), @@ -123,7 +120,7 @@ struct DownSamplePage { let img: Uint8Array = await getContext(this).resourceManager.getMediaContent(imgs); let imageSource: image.ImageSource = image.createImageSource(img.buffer.slice(0)); let fileTypeUtil = new FileTypeUtil(); - let typeValue = fileTypeUtil.getFileType(img.buffer.slice(0)); + let typeValue = fileTypeUtil.getFileType(img.buffer.slice(0)) as string; let decodingOptions: image.DecodingOptions = { editable: true, desiredPixelFormat: 3, diff --git a/library/src/main/ets/ImageKnifeLoader.ets b/library/src/main/ets/ImageKnifeLoader.ets index d1135ed..0864b3c 100644 --- a/library/src/main/ets/ImageKnifeLoader.ets +++ b/library/src/main/ets/ImageKnifeLoader.ets @@ -15,7 +15,9 @@ import { CacheStrategy, ImageKnifeRequestSource, - ImageKnifeRequestWithSource, RequestJobRequest } from './model/ImageKnifeData'; + ImageKnifeRequestWithSource, + RequestJobRequest +} from './model/ImageKnifeData'; import List from '@ohos.util.List' import { FileCache } from './cache/FileCache'; import { LogUtil } from './utils/LogUtil'; @@ -42,20 +44,20 @@ class RequestData { export class ImageKnifeLoader { static async parseImage(resBuf: ArrayBuffer, typeValue: string, fileKey: string, request: RequestJobRequest): Promise { - if(request.isAnimator) { - return ImageKnifeLoader.parseForAnimatorComponent(resBuf ,typeValue ,fileKey, request) + if (request.isAnimator) { + return ImageKnifeLoader.parseForAnimatorComponent(resBuf, typeValue, fileKey, request) } if (typeValue === 'gif' || typeValue === 'webp') { - return ImageKnifeLoader.parseAnimatorImage(resBuf ,typeValue ,fileKey , request) - } else if(typeValue == "svg") { - return ImageKnifeLoader.parseSvgImage(resBuf ,typeValue ,fileKey , request) + return ImageKnifeLoader.parseAnimatorImage(resBuf, typeValue, fileKey, request) + } else if (typeValue == "svg") { + return ImageKnifeLoader.parseSvgImage(resBuf, typeValue, fileKey, request) } return ImageKnifeLoader.parseNormalImage(resBuf, typeValue, fileKey, request) } - static makeEmptyResult(error: string): RequestJobResult{ + static makeEmptyResult(error: string): RequestJobResult { return { pixelMap: undefined, bufferSize: 0, @@ -64,23 +66,25 @@ export class ImageKnifeLoader { } } - static async parseNormalImage(resBuf: ArrayBuffer, typeValue: string, fileKey: string, request: RequestJobRequest):Promise { + static async parseNormalImage(resBuf: ArrayBuffer, typeValue: string, fileKey: string, + request: RequestJobRequest): Promise { let resPixelmap: PixelMap | undefined = undefined let decodingOptions: image.DecodingOptions = { - editable: request.requestSource === ImageKnifeRequestSource.SRC && request.transformation !== undefined ? true : false, + editable: request.requestSource === ImageKnifeRequestSource.SRC && request.transformation !== undefined ? true : + false, } let imageSource: image.ImageSource = image.createImageSource(resBuf) - if (imageSource === undefined){ + if (imageSource === undefined) { return ImageKnifeLoader.makeEmptyResult("image.createImageSource failed") } let size = (await imageSource.getImageInfo()).size - try{ + try { if ((request.downsampType !== DownsampleStrategy.NONE) && - request.requestSource == ImageKnifeRequestSource.SRC ) { - decodingOptions =await ImageKnifeLoader.downsamplerReqSize(typeValue,request,size,ImageKnifeRequestSource.SRC) + request.requestSource == ImageKnifeRequestSource.SRC) { + decodingOptions = ImageKnifeLoader.downsamplerReqSize(typeValue, request, size, ImageKnifeRequestSource.SRC) } - }catch(err){ + } catch (err) { return ImageKnifeLoader.makeEmptyResult("image.createImageSource failed") } @@ -97,15 +101,16 @@ export class ImageKnifeLoader { pixelMap: resPixelmap, bufferSize: resBuf.byteLength, fileKey: fileKey, - size:size, - type:typeValue + size: size, + type: typeValue }; } + static async parseSvgImage(resBuf: ArrayBuffer, typeValue: string, fileKey: string, - request: RequestJobRequest): Promise { + request: RequestJobRequest): Promise { let resPixelmap: PixelMap | undefined = undefined let imageSource: image.ImageSource = image.createImageSource(resBuf) - if (imageSource === undefined){ + if (imageSource === undefined) { return ImageKnifeLoader.makeEmptyResult("image.createImageSource failed") } @@ -121,12 +126,12 @@ export class ImageKnifeLoader { editable: true, desiredSize: defaultSize }; - try{ + try { if ((request.downsampType !== DownsampleStrategy.NONE) && - request.requestSource == ImageKnifeRequestSource.SRC ) { - opts =await ImageKnifeLoader.downsamplerReqSize(typeValue,request,size) + request.requestSource == ImageKnifeRequestSource.SRC) { + opts = ImageKnifeLoader.downsamplerReqSize(typeValue, request, size) } - }catch(err){ + } catch (err) { return ImageKnifeLoader.makeEmptyResult("image.createImageSource failed") } await imageSource.createPixelMap(opts) @@ -142,13 +147,14 @@ export class ImageKnifeLoader { pixelMap: resPixelmap, bufferSize: resBuf.byteLength, fileKey: fileKey, - type:typeValue + type: typeValue }; } + static async parseAnimatorImage(resBuf: ArrayBuffer, typeValue: string, - fileKey: string,request: RequestJobRequest): Promise { + fileKey: string, request: RequestJobRequest): Promise { let imageSource: image.ImageSource = image.createImageSource(resBuf) - if (imageSource === undefined){ + if (imageSource === undefined) { return ImageKnifeLoader.makeEmptyResult("image.createImageSource failed") } @@ -156,28 +162,32 @@ export class ImageKnifeLoader { let size = (await imageSource.getImageInfo()).size imageSource.release() - if(frameCount == undefined || frameCount == 1) { + if (frameCount == undefined || frameCount == 1) { } else { - let base64str = "data:image/" + typeValue + ";base64," + new util.Base64Helper().encodeToStringSync(new Uint8Array(resBuf)) + let base64str = + "data:image/" + typeValue + ";base64," + new util.Base64Helper().encodeToStringSync(new Uint8Array(resBuf)) return { pixelMap: base64str, bufferSize: resBuf.byteLength, fileKey: fileKey, - size:size, - type:typeValue + size: size, + type: typeValue }; } return ImageKnifeLoader.parseNormalImage(resBuf, typeValue, fileKey, request) } + // 为AnimatorComponent解析动图 - static async parseForAnimatorComponent(resBuf: ArrayBuffer, typeValue: string, fileKey: string,request: RequestJobRequest): Promise { + static async parseForAnimatorComponent(resBuf: ArrayBuffer, typeValue: string, fileKey: string, + request: RequestJobRequest): Promise { if (typeValue === 'gif' || typeValue === 'webp') { let imageSource: image.ImageSource = image.createImageSource(resBuf); - if (imageSource === undefined){ + if (imageSource === undefined) { return ImageKnifeLoader.makeEmptyResult("image.createImageSource failed") } let decodingOptions: image.DecodingOptions = { - editable: request.requestSource === ImageKnifeRequestSource.SRC && request.transformation !== undefined ? true : false, + editable: request.requestSource === ImageKnifeRequestSource.SRC && request.transformation !== undefined ? true : + false, } let pixelMapList: Array = [] let delayList: Array = [] @@ -214,13 +224,15 @@ export class ImageKnifeLoader { } // 获取图片资源 - static async getImageArrayBuffer(request: RequestJobRequest, requestList: List | undefined,fileKey:string): Promise { + static async getImageArrayBuffer(request: RequestJobRequest, + requestList: List | undefined, fileKey: string): Promise { let resBuf: ArrayBuffer | undefined // 判断自定义下载 - if (request.customGetImage !== undefined && request.requestSource == ImageKnifeRequestSource.SRC && typeof request.src == "string") { + if (request.customGetImage !== undefined && request.requestSource == ImageKnifeRequestSource.SRC && + typeof request.src == "string") { // 先从文件缓存获取 - resBuf = FileCache.getFileCacheByFile(request.context, fileKey , request.fileCacheFolder) + resBuf = FileCache.getFileCacheByFile(request.context, fileKey, request.fileCacheFolder) if (resBuf === undefined) { LogUtil.log("start customGetImage src=" + request.src) try { @@ -239,16 +251,14 @@ export class ImageKnifeLoader { LogUtil.log("ImageKnife_DataTime_requestJob_saveFileCacheOnlyFile.end:" + request.src) } } - } - else { + } else { if (typeof request.src === 'string') { if (request.src.indexOf("http://") == 0 || request.src.indexOf("https://") == 0) { //从网络下载 // 先从文件缓存获取 - resBuf = FileCache.getFileCacheByFile(request.context, fileKey , request.fileCacheFolder) - if (resBuf !== undefined){ + resBuf = FileCache.getFileCacheByFile(request.context, fileKey, request.fileCacheFolder) + if (resBuf !== undefined) { LogUtil.log("success get image from filecache for key = " + fileKey + " src = " + request.src) - } - else if (request.onlyRetrieveFromCache != true) { + } else if (request.onlyRetrieveFromCache != true) { LogUtil.log("HttpDownloadClient.start:" + request.src) let httpRequest = http.createHttp(); let progress: number = 0 @@ -277,10 +287,11 @@ export class ImageKnifeLoader { if (requestList === undefined) { // 子线程 emitter.emit(Constants.PROGRESS_EMITTER + request.memoryKey, { data: { "value": progress } }) - }else { + } else { // 主线程请求 requestList!.forEach((requestWithSource: ImageKnifeRequestWithSource) => { - if (requestWithSource.request.imageKnifeOption.progressListener !== undefined && requestWithSource.source === ImageKnifeRequestSource.SRC) { + if (requestWithSource.request.imageKnifeOption.progressListener !== undefined && + requestWithSource.source === ImageKnifeRequestSource.SRC) { requestWithSource.request.imageKnifeOption.progressListener(progress) } }) @@ -311,29 +322,31 @@ export class ImageKnifeLoader { LogUtil.log("HttpDownloadClient.end:" + request.src) // 保存文件缓存 if (resBuf !== undefined && request.writeCacheStrategy !== CacheStrategy.Memory) { - LogUtil.log("ImageKnife_DataTime_requestJob_saveFileCacheOnlyFile.start:"+request.src) - FileCache.saveFileCacheOnlyFile(request.context, fileKey, resBuf , request.fileCacheFolder) - LogUtil.log("ImageKnife_DataTime_requestJob_saveFileCacheOnlyFile.end:"+request.src) + LogUtil.log("ImageKnife_DataTime_requestJob_saveFileCacheOnlyFile.start:" + request.src) + FileCache.saveFileCacheOnlyFile(request.context, fileKey, resBuf, request.fileCacheFolder) + LogUtil.log("ImageKnife_DataTime_requestJob_saveFileCacheOnlyFile.end:" + request.src) } - } - else { + } else { throw new Error('onlyRetrieveFromCache,do not fetch image src = ' + request.src) } } else if (request.src.startsWith('datashare://') || request.src.startsWith('file://')) { await fs.open(request.src, fs.OpenMode.READ_ONLY).then(async (file) => { - await fs.stat(file.fd).then(async (stat) =>{ + await fs.stat(file.fd).then(async (stat) => { let buf = new ArrayBuffer(stat.size); await fs.read(file.fd, buf).then((readLen) => { resBuf = buf; fs.closeSync(file.fd); - }).catch((err:BusinessError) => { - throw new Error('LoadDataShareFileClient fs.read err happened uri=' + request.src + " err.msg=" + err?.message + " err.code=" + err?.code) + }).catch((err: BusinessError) => { + throw new Error('LoadDataShareFileClient fs.read err happened uri=' + request.src + " err.msg=" + + err?.message + " err.code=" + err?.code) }) - }).catch((err:BusinessError) => { - throw new Error('LoadDataShareFileClient fs.stat err happened uri=' + request.src + " err.msg=" + err?.message + " err.code=" + err?.code) + }).catch((err: BusinessError) => { + throw new Error('LoadDataShareFileClient fs.stat err happened uri=' + request.src + " err.msg=" + + err?.message + " err.code=" + err?.code) }) - }).catch((err:BusinessError) => { - throw new Error('LoadDataShareFileClient fs.open err happened uri=' + request.src + " err.msg=" + err?.message + " err.code=" + err?.code) + }).catch((err: BusinessError) => { + throw new Error('LoadDataShareFileClient fs.open err happened uri=' + request.src + " err.msg=" + + err?.message + " err.code=" + err?.code) }) } else { //从本地文件获取 try { @@ -350,15 +363,16 @@ export class ImageKnifeLoader { } } else if (typeof request.src == "number") { //从资源文件获取 let manager = request.context.createModuleContext(request.moduleName).resourceManager - if (resBuf == undefined && request.onlyRetrieveFromCache != true && request.requestSource == ImageKnifeRequestSource.SRC) { - if(request.src == -1) { + if (resBuf == undefined && request.onlyRetrieveFromCache != true && + request.requestSource == ImageKnifeRequestSource.SRC) { + if (request.src == -1) { let resName = request.resName as string resBuf = (await manager.getMediaByName(resName.substring(10))).buffer as ArrayBuffer } else { resBuf = manager.getMediaContentSync(request.src).buffer as ArrayBuffer } } else if (resBuf == undefined && request.requestSource != ImageKnifeRequestSource.SRC) { - if(request.src == -1) { + if (request.src == -1) { let resName = request.resName as string resBuf = (await manager.getMediaByName(resName.substring(10))).buffer as ArrayBuffer } else { @@ -368,30 +382,35 @@ export class ImageKnifeLoader { } } - if (resBuf === undefined){ + if (resBuf === undefined) { throw new Error('getImageArrayBuffer undefined') } return resBuf } - static async downsamplerReqSize(typeValue:string,request:RequestJobRequest ,size:Size,SRC?:ImageKnifeRequestSource): Promise{ - let reqSize = new Downsampler().calculateScaling(typeValue, size.width, size.height, request.targetWidth, request.targetHeight, request.downsampType) - if(typeValue=="svg") { + + static downsamplerReqSize(typeValue: string, request: RequestJobRequest, size: Size, + SRC?: ImageKnifeRequestSource) { + let reqSize = + new Downsampler().calculateScaling(typeValue, size.width, size.height, request.targetWidth, request.targetHeight, + request.downsampType) + if (typeValue == "svg") { return ({ editable: true, desiredSize: ({ height: vp2px(reqSize.targetHeight), width: vp2px(reqSize.targetWidth) } as Size) - } as image.DecodingOptions ) - }else { - return( { - editable: request.requestSource ===SRC && request.transformation !== undefined ? true : false, - desiredSize: ({ - width: reqSize.targetWidth, - height: reqSize.targetHeight - }as Size) - }as image.DecodingOptions) - } + + } as image.DecodingOptions) + } else { + return ({ + editable: request.requestSource === SRC && request.transformation !== undefined ? true : false, + desiredSize: ({ + width: reqSize.targetWidth, + height: reqSize.targetHeight + } as Size) + } as image.DecodingOptions) + } } } diff --git a/library/src/main/ets/downsampling/Downsampler.ets b/library/src/main/ets/downsampling/Downsampler.ets index 517fc34..a1bb180 100644 --- a/library/src/main/ets/downsampling/Downsampler.ets +++ b/library/src/main/ets/downsampling/Downsampler.ets @@ -30,7 +30,7 @@ export interface calculateScaleType { export class Downsampler { calculateScaling( - typeValue: string |null, + typeValue: string, sourceWidth: number, //原始宽高 sourceHeight: number, //原始宽高 requestWidth: number, //请求宽高 From 6e708e1c177f44955870a76d1eb888491bebaae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E5=8F=8C=E6=98=8E?= Date: Wed, 9 Oct 2024 08:39:28 +0000 Subject: [PATCH 03/12] update library/src/main/ets/downsampling/DownsampleStartegy.ets. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 田双明 --- .../ets/downsampling/DownsampleStartegy.ets | 133 +++++++++++------- 1 file changed, 79 insertions(+), 54 deletions(-) diff --git a/library/src/main/ets/downsampling/DownsampleStartegy.ets b/library/src/main/ets/downsampling/DownsampleStartegy.ets index 05a3610..50387f0 100644 --- a/library/src/main/ets/downsampling/DownsampleStartegy.ets +++ b/library/src/main/ets/downsampling/DownsampleStartegy.ets @@ -13,30 +13,36 @@ * limitations under the License. */ import { BaseDownsampling } from './BaseDownsampling'; -import { highestOneBit, SampleSizeRounding } from './DownsampleUtils'; +import { getScale, highestOneBit, round, SampleSizeRounding } from './DownsampleUtils'; export class FitCenter implements BaseDownsampling { getName() { return "FitCenter" } - getScaleFactor(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number,downsampType:DownsampleStrategy |undefined - ): number { - if (downsampType===DownsampleStrategy.FIT_CENTER_MEMORY) { - const widthPercentage = requestedWidth / sourceWidth - const heightPercentage = requestedHeight / sourceHeight - return Math.min(widthPercentage, heightPercentage) - } else { - const maxIntegerFactor = Math.max(sourceHeight / requestedHeight, sourceWidth / requestedWidth); - const a = maxIntegerFactor === 0 ? 1 : 1 / highestOneBit(maxIntegerFactor) - return a; + getScaleFactor(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number, + downsampType: DownsampleStrategy + ): number { + //重新计算宽高比; + let outSize: Size = { + width: round(getScale(sourceWidth, sourceHeight, requestedWidth, requestedHeight, downsampType) * sourceWidth), + height:round(getScale(sourceWidth, sourceHeight, requestedWidth, requestedHeight, downsampType) * sourceHeight) } + let scaleFactor = this.getSampleSizeType(sourceWidth, sourceHeight, requestedWidth, requestedHeight, downsampType)=== + SampleSizeRounding.QUALITY? Math.max(1, highestOneBit(Math.max(sourceWidth / outSize.width, sourceHeight / outSize.height))) : + Math.max(1, highestOneBit(Math.min(sourceWidth / outSize.width, sourceHeight / outSize.height)))//将整型的缩放因子转换为2的次幂采样大小 + + if (this.getSampleSizeType(sourceWidth, sourceHeight, requestedWidth, requestedHeight, downsampType)=== + SampleSizeRounding.MEMORY && (scaleFactor < (1 / getScale(sourceWidth, sourceHeight, requestedWidth, requestedHeight, downsampType)))) { + scaleFactor = scaleFactor << 1; + } + return scaleFactor } - //实现 getSampleSizeRounding 方法 - getSampleSizeRounding(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number,downsampType:DownsampleStrategy|undefined - ): number { - if (downsampType===DownsampleStrategy.FIT_CENTER_QUALITY) { + getSampleSizeType(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number, + downsampType: DownsampleStrategy + ): number { + if (downsampType === DownsampleStrategy.FIT_CENTER_QUALITY) { return SampleSizeRounding.QUALITY; } else { return SampleSizeRounding.MEMORY; @@ -52,15 +58,19 @@ export class None implements BaseDownsampling { public getScaleFactor(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number): number { - //不进行任何下采样,缩放因子为 1 - return 1; + let outSize: Size = { + width: round(sourceWidth), + height:round(sourceHeight) + } + let scaleFactor = + Math.max(1, highestOneBit(Math.max(sourceWidth / outSize.width, sourceHeight / outSize.height))) + return scaleFactor } - //实现 getSampleSizeRounding 方法 - public getSampleSizeRounding(sourceWidth: number, sourceHeight: number, requestedWidth: number, + //实现 getSampleSizeType 方法 + public getSampleSizeType(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number): SampleSizeRounding { return SampleSizeRounding.QUALITY - } } @@ -77,10 +87,16 @@ export class CenterOutside implements BaseDownsampling { const widthPercentage = requestedWidth / sourceWidth; const heightPercentage = requestedHeight / sourceHeight; //返回宽度和高度比例中最大的值 - return Math.max(widthPercentage, heightPercentage); + let outSize: Size = { + width: round(Math.max(widthPercentage, heightPercentage) * sourceWidth), + height:round(Math.max(widthPercentage, heightPercentage) * sourceHeight) + } + let scaleFactor = Math.max(1, highestOneBit(Math.max(sourceWidth / outSize.width, sourceHeight / outSize.height))) + + return scaleFactor } - getSampleSizeRounding(sourceWidth: number, sourceHeight: number, requestedWidth: number, + getSampleSizeType(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number): SampleSizeRounding { return SampleSizeRounding.QUALITY; } @@ -93,7 +109,7 @@ export class AtMost implements BaseDownsampling { 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)); @@ -103,11 +119,19 @@ export class AtMost implements BaseDownsampling { } greaterOrEqualSampleSize = lesserOrEqualSampleSize << (lesserOrEqualSampleSize < maxIntegerFactor ? 1 : 0) //返回缩放因子 - return 1 / greaterOrEqualSampleSize + let outSize: Size = { + width: round((1 / greaterOrEqualSampleSize) * sourceWidth), + height:round((1 / greaterOrEqualSampleSize) * sourceHeight) + } + let scaleFactor = Math.max(1, highestOneBit(Math.min(sourceWidth / outSize.width, sourceHeight / outSize.height))) + if ((scaleFactor < greaterOrEqualSampleSize)) { + scaleFactor = scaleFactor << 1; + } + return scaleFactor } - getSampleSizeRounding(sourceWidth: number, sourceHeight: number, requestedWidth: number, + getSampleSizeType(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestHeight: number): SampleSizeRounding { //根据 AtMost 的逻辑,总是返回 MEMORY return SampleSizeRounding.MEMORY @@ -120,43 +144,44 @@ export class CenterInside implements BaseDownsampling { getName() { return "CenterInside" } - - getScaleFactor(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number,downsampType:DownsampleStrategy|undefined - ): number { - //获取 FIT_CENTER 的缩放因子 - const fitCenterScaleFactor: ESObject = - this.getScale(sourceWidth, sourceHeight, requestedWidth, requestedHeight, downsampType); - //返回不超过 1 的缩放因子,即尽量缩小图像以适应目标尺寸,但不会放大 - return Math.min(1, fitCenterScaleFactor); - - } - - getScale(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number,downsampType:DownsampleStrategy|undefined + getScaleFactor(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number, + downsampType: DownsampleStrategy ): number { - if (downsampType===DownsampleStrategy.FIT_CENTER_MEMORY) { - const widthPercentage = requestedWidth / sourceWidth - const heightPercentage = requestedHeight / sourceHeight - return Math.min(widthPercentage, heightPercentage) - } else { - const maxIntegerFactor = Math.max(sourceHeight / requestedHeight, sourceWidth / requestedWidth); - const a = maxIntegerFactor === 0 ? 1 : 1 / highestOneBit(maxIntegerFactor) - return a; + + let outSize: Size = { + width: round(Math.min(1, getScale(sourceWidth, sourceHeight, requestedWidth, requestedHeight, downsampType)) * sourceWidth), + height:round(Math.min(1, getScale(sourceWidth, sourceHeight, requestedWidth, requestedHeight, downsampType)) * sourceHeight) } + let scaleFactor = this.getSampleSizeType(sourceWidth, sourceHeight, requestedWidth, requestedHeight, + downsampType) == SampleSizeRounding.QUALITY ? + Math.max(sourceWidth / outSize.width, sourceHeight / outSize.height) : + Math.min(sourceWidth / outSize.width, sourceHeight / outSize.height) //重新计算宽高比 + + scaleFactor = Math.max(1, highestOneBit(scaleFactor))//将整型的缩放因子转换为2的次幂采样大小 + + if (this.getSampleSizeType(sourceWidth, sourceHeight, requestedWidth, requestedHeight, downsampType) + == SampleSizeRounding.MEMORY && (scaleFactor < (1 / Math.min(1, getScale(sourceWidth, sourceHeight, requestedWidth, requestedHeight, downsampType))))) { + scaleFactor = scaleFactor << 1; + } + return scaleFactor + } - getSampleSizeRounding(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number,downsampType:DownsampleStrategy|undefined - ): SampleSizeRounding { + getSampleSizeType(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number, + downsampType: DownsampleStrategy + ): SampleSizeRounding { //如果缩放因子为 1,表示没有缩放,优先选择质量 - if (this.getScaleFactor(sourceWidth, sourceHeight, requestedWidth, requestedHeight, downsampType) === 1) { + if (Math.min(1, getScale(sourceWidth, sourceHeight, requestedWidth, requestedHeight, downsampType)) === 1) { return SampleSizeRounding.QUALITY } //否则,使用 FIL_CENTER 的 SampleSizeRounding 值 - return this.getSampleSize(sourceWidth, sourceHeight, requestedWidth, requestedHeight,downsampType); + return this.getSampleSize(sourceWidth, sourceHeight, requestedWidth, requestedHeight, downsampType); } - getSampleSize(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number,downsampType:DownsampleStrategy|undefined - ): SampleSizeRounding { - if (downsampType===DownsampleStrategy.CENTER_INSIDE_MEMORY) { + getSampleSize(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number, + downsampType: DownsampleStrategy + ): SampleSizeRounding { + if (downsampType === DownsampleStrategy.CENTER_INSIDE_MEMORY) { return SampleSizeRounding.MEMORY; } else { return SampleSizeRounding.QUALITY; @@ -168,11 +193,11 @@ export enum DownsampleStrategy { //请求尺寸大于实际尺寸不进行放大 AT_MOST, //两边自适应内存优先 - FIT_CENTER_MEMORY , + FIT_CENTER_MEMORY, //两边自适应质量优先 FIT_CENTER_QUALITY, //按照宽高比的最大比进行适配内存优先 - CENTER_INSIDE_MEMORY , + CENTER_INSIDE_MEMORY, //按照宽高比的最大比进行适配质量优先 CENTER_INSIDE_QUALITY, //宽高进行等比缩放宽高里面最小的比例先放进去,然后再根据原图的缩放比去适配 From 1b812f84318fd140414437cd5df20ce0bf2825a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E5=8F=8C=E6=98=8E?= Date: Wed, 9 Oct 2024 08:40:06 +0000 Subject: [PATCH 04/12] update library/src/main/ets/downsampling/Downsampler.ets. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 田双明 --- .../src/main/ets/downsampling/Downsampler.ets | 61 ++++++++----------- 1 file changed, 27 insertions(+), 34 deletions(-) diff --git a/library/src/main/ets/downsampling/Downsampler.ets b/library/src/main/ets/downsampling/Downsampler.ets index a1bb180..1b7cdeb 100644 --- a/library/src/main/ets/downsampling/Downsampler.ets +++ b/library/src/main/ets/downsampling/Downsampler.ets @@ -20,12 +20,7 @@ import { FitCenter, None, } from './DownsampleStartegy'; -import { highestOneBit, SampleSizeRounding } from './DownsampleUtils'; - -export interface calculateScaleType { - targetWidth: number, - targetHeight: number -} +import { highestOneBit, round, SampleSizeRounding } from './DownsampleUtils'; export class Downsampler { @@ -36,43 +31,45 @@ export class Downsampler { requestWidth: number, //请求宽高 requestHeight: number, //请求宽高 downsampType: DownsampleStrategy, - ): calculateScaleType { + ): Size { if (sourceHeight <= 0 || sourceWidth <= 0) { - throw new Error(`Invalid width and height, sourceHeight:${sourceHeight}+ sourceWidth:${sourceWidth}`) + throw new Error(`Invalid width and height, sourceHeight:${sourceHeight}+ sourceWidth:${sourceWidth}`) } let downsampler = this.getDownsampler(downsampType); - let exactScaleFactor: number = - downsampler.getScaleFactor(sourceWidth, sourceHeight, requestWidth, requestHeight, downsampType); - let rounding: SampleSizeRounding = - downsampler.getSampleSizeRounding(sourceWidth, sourceHeight, requestWidth, requestHeight, downsampType); //采样类型 - //原始宽高和缩放系数的乘积 - let outSize:Size = { - width: this.round(exactScaleFactor * sourceWidth), - height: this.round(exactScaleFactor * sourceHeight) - } - let scaleFactor = - rounding == SampleSizeRounding.QUALITY ? Math.max(sourceWidth / outSize.width, sourceHeight / outSize.height) : - Math.min(sourceWidth / outSize.width, sourceHeight / outSize.height) //将整型的缩放因子转换为2的次幂采样大小 - scaleFactor = Math.max(1, highestOneBit(scaleFactor)) - if (rounding == 0 && (scaleFactor < (1 / exactScaleFactor))) { - scaleFactor = scaleFactor << 1; - } + let scaleFactor: number = + downsampler.getScaleFactor(sourceWidth, sourceHeight, requestWidth, requestHeight, downsampType);//缩放比 + // let rounding: SampleSizeRounding = + // downsampler.getSampleSizeType(sourceWidth, sourceHeight, requestWidth, requestHeight, downsampType); //采样类型 + //根据缩放比重新计算宽高 + // let outSize: Size = { + // width: round(exactScaleFactor * sourceWidth), + // height:round(exactScaleFactor * sourceHeight) + // } + // let scaleFactor = + // rounding == SampleSizeRounding.QUALITY ? Math.max(sourceWidth / exactScaleFactor.width, sourceHeight / exactScaleFactor.height) : + // Math.min(sourceWidth / exactScaleFactor.width, sourceHeight / exactScaleFactor.height) //重新计算宽高比 + + // scaleFactor = Math.max(1, highestOneBit(scaleFactor))//将整型的缩放因子转换为2的次幂采样大小 + // + // if (rounding == 0 && (scaleFactor < (1 / exactScaleFactor))) { + // scaleFactor = scaleFactor << 1; + // } //基于上一步得出的采样大小,根据不同的图片类型,计算采样后的图片尺寸 if (typeValue === "png") { return { - targetWidth: Math.floor(sourceWidth / scaleFactor), - targetHeight: Math.floor(sourceHeight / scaleFactor) + width: Math.floor(sourceWidth / scaleFactor), + height: Math.floor(sourceHeight / scaleFactor) } } else if (typeValue === "webp") { return { - targetWidth: Math.round(sourceWidth / scaleFactor), - targetHeight: Math.round(sourceHeight / scaleFactor) + width: Math.round(sourceWidth / scaleFactor), + height: Math.round(sourceHeight / scaleFactor) } } else { return { - targetWidth: sourceWidth / scaleFactor, - targetHeight: sourceHeight / scaleFactor + width: sourceWidth / scaleFactor, + height: sourceHeight / scaleFactor } } @@ -96,8 +93,4 @@ export class Downsampler { throw new Error('Unsupported downsampling strategy'); } } - - round(value: number): number { - return Math.floor(value + 0.5); - } } From d2cead637baef4e6d2390939605c412491b3ee58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E5=8F=8C=E6=98=8E?= Date: Wed, 9 Oct 2024 08:41:05 +0000 Subject: [PATCH 05/12] update library/src/main/ets/downsampling/DownsampleUtils.ets. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 田双明 --- .../main/ets/downsampling/DownsampleUtils.ets | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/library/src/main/ets/downsampling/DownsampleUtils.ets b/library/src/main/ets/downsampling/DownsampleUtils.ets index ef17f42..2a8af50 100644 --- a/library/src/main/ets/downsampling/DownsampleUtils.ets +++ b/library/src/main/ets/downsampling/DownsampleUtils.ets @@ -12,6 +12,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { DownsampleStrategy } from './DownsampleStartegy'; + export enum SampleSizeRounding { /** * Prefer to round the sample size up so that the image is downsampled to smaller than the @@ -26,6 +28,7 @@ export enum SampleSizeRounding { //(质量优先) QUALITY } + export function highestOneBit(i: number): number { i |= (i >> 1); i |= (i >> 2); @@ -33,4 +36,22 @@ export function highestOneBit(i: number): number { i |= (i >> 8); i |= (i >> 16); return i - (i >>> 1); +} + + +export function getScale(sourceWidth: number, sourceHeight: number, requestedWidth: number, requestedHeight: number, + downsampType: DownsampleStrategy +): number { + if (downsampType === DownsampleStrategy.FIT_CENTER_MEMORY) { + const widthPercentage = requestedWidth / sourceWidth + const heightPercentage = requestedHeight / sourceHeight + return Math.min(widthPercentage, heightPercentage) + } else { + const maxIntegerFactor = Math.max(sourceHeight / requestedHeight, sourceWidth / requestedWidth); + return maxIntegerFactor === 0 ? 1 : 1 / highestOneBit(maxIntegerFactor); + + } +} +export function round(value: number): number { + return Math.floor(value + 0.5); } \ No newline at end of file From ec27cb91fc70465a060ec41f940d7a9e64e3615a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E5=8F=8C=E6=98=8E?= Date: Wed, 9 Oct 2024 08:43:46 +0000 Subject: [PATCH 06/12] update library/src/main/ets/ImageKnifeLoader.ets. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 田双明 --- library/src/main/ets/ImageKnifeLoader.ets | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/src/main/ets/ImageKnifeLoader.ets b/library/src/main/ets/ImageKnifeLoader.ets index 0864b3c..14858a8 100644 --- a/library/src/main/ets/ImageKnifeLoader.ets +++ b/library/src/main/ets/ImageKnifeLoader.ets @@ -85,7 +85,7 @@ export class ImageKnifeLoader { decodingOptions = ImageKnifeLoader.downsamplerReqSize(typeValue, request, size, ImageKnifeRequestSource.SRC) } } catch (err) { - return ImageKnifeLoader.makeEmptyResult("image.createImageSource failed") + return return ImageKnifeLoader.makeEmptyResult(err) } await imageSource.createPixelMap(decodingOptions) @@ -132,7 +132,7 @@ export class ImageKnifeLoader { opts = ImageKnifeLoader.downsamplerReqSize(typeValue, request, size) } } catch (err) { - return ImageKnifeLoader.makeEmptyResult("image.createImageSource failed") + return ImageKnifeLoader.makeEmptyResult(err) } await imageSource.createPixelMap(opts) .then((pixelmap: PixelMap) => { From 2cd3465906ee4aedc756015fa6423841f01c7695 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E5=8F=8C=E6=98=8E?= Date: Wed, 9 Oct 2024 08:46:45 +0000 Subject: [PATCH 07/12] update entry/src/main/ets/common/CustomEngineKeyImpl.ets. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 田双明 --- entry/src/main/ets/common/CustomEngineKeyImpl.ets | 3 +++ 1 file changed, 3 insertions(+) diff --git a/entry/src/main/ets/common/CustomEngineKeyImpl.ets b/entry/src/main/ets/common/CustomEngineKeyImpl.ets index 06f2901..1473024 100644 --- a/entry/src/main/ets/common/CustomEngineKeyImpl.ets +++ b/entry/src/main/ets/common/CustomEngineKeyImpl.ets @@ -35,6 +35,9 @@ export class CustomEngineKeyImpl implements IEngineKey { if (imageKnifeOption.transformation) { key += "transformation=" + this.getTransformation(imageKnifeOption.transformation) + ";" } + if ((imageKnifeOption.downsampleOf !== DownsampleStrategy.NONE && imageKnifeOption.downsampleOf !== undefined)) { + key += "downsampleOf" + imageKnifeOption.downsampleOf +"width="+width+"height="+ height + } } return key } From 56bd1287e44de810c32b0764502b0317bd245ff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E5=8F=8C=E6=98=8E?= Date: Wed, 9 Oct 2024 11:06:15 +0000 Subject: [PATCH 08/12] update entry/src/main/ets/common/CustomEngineKeyImpl.ets. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 田双明 --- entry/src/main/ets/common/CustomEngineKeyImpl.ets | 1 + 1 file changed, 1 insertion(+) diff --git a/entry/src/main/ets/common/CustomEngineKeyImpl.ets b/entry/src/main/ets/common/CustomEngineKeyImpl.ets index 1473024..63437c3 100644 --- a/entry/src/main/ets/common/CustomEngineKeyImpl.ets +++ b/entry/src/main/ets/common/CustomEngineKeyImpl.ets @@ -12,6 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { DownsampleStrategy } from '@ohos/imageknife'; import { IEngineKey, ImageKnifeOption, PixelMapTransformation,SparkMD5 ,ImageKnifeRequestSource} from '@ohos/libraryimageknife'; //全局自定义key demo From f351c715412f968504bed461435fd428b8020e91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E5=8F=8C=E6=98=8E?= Date: Wed, 9 Oct 2024 11:08:13 +0000 Subject: [PATCH 09/12] update library/src/main/ets/ImageKnifeLoader.ets. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 田双明 --- library/src/main/ets/ImageKnifeLoader.ets | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/library/src/main/ets/ImageKnifeLoader.ets b/library/src/main/ets/ImageKnifeLoader.ets index 14858a8..082821d 100644 --- a/library/src/main/ets/ImageKnifeLoader.ets +++ b/library/src/main/ets/ImageKnifeLoader.ets @@ -85,7 +85,7 @@ export class ImageKnifeLoader { decodingOptions = ImageKnifeLoader.downsamplerReqSize(typeValue, request, size, ImageKnifeRequestSource.SRC) } } catch (err) { - return return ImageKnifeLoader.makeEmptyResult(err) + return ImageKnifeLoader.makeEmptyResult(err) } await imageSource.createPixelMap(decodingOptions) @@ -397,8 +397,8 @@ export class ImageKnifeLoader { return ({ editable: true, desiredSize: ({ - height: vp2px(reqSize.targetHeight), - width: vp2px(reqSize.targetWidth) + height: vp2px(reqSize.height), + width: vp2px(reqSize.width) } as Size) } as image.DecodingOptions) @@ -406,8 +406,8 @@ export class ImageKnifeLoader { return ({ editable: request.requestSource === SRC && request.transformation !== undefined ? true : false, desiredSize: ({ - width: reqSize.targetWidth, - height: reqSize.targetHeight + width: reqSize.width, + height: reqSize.height } as Size) } as image.DecodingOptions) } From b9365b49f45d7456bc3ed435d81fbb49af23f2ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E5=8F=8C=E6=98=8E?= Date: Wed, 9 Oct 2024 11:09:26 +0000 Subject: [PATCH 10/12] update entry/src/main/ets/pages/DownSamplePage.ets. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 田双明 --- entry/src/main/ets/pages/DownSamplePage.ets | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/entry/src/main/ets/pages/DownSamplePage.ets b/entry/src/main/ets/pages/DownSamplePage.ets index 9355a40..e4ff7ba 100644 --- a/entry/src/main/ets/pages/DownSamplePage.ets +++ b/entry/src/main/ets/pages/DownSamplePage.ets @@ -132,8 +132,8 @@ struct DownSamplePage { decodingOptions = { editable: true, desiredSize: { - width: reqSize.targetWidth, - height: reqSize.targetHeight + width: reqSize.width, + height: reqSize.height } } // 创建pixelMap From 618df4c3bbed5ffccfea2a40f58f557b48a72cb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E5=8F=8C=E6=98=8E?= Date: Wed, 9 Oct 2024 11:12:02 +0000 Subject: [PATCH 11/12] update library/src/main/ets/downsampling/BaseDownsampling.ets. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 田双明 --- library/src/main/ets/downsampling/BaseDownsampling.ets | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/src/main/ets/downsampling/BaseDownsampling.ets b/library/src/main/ets/downsampling/BaseDownsampling.ets index 5b49b68..28f34da 100644 --- a/library/src/main/ets/downsampling/BaseDownsampling.ets +++ b/library/src/main/ets/downsampling/BaseDownsampling.ets @@ -20,7 +20,7 @@ export interface BaseDownsampling { getName(): string getScaleFactor(sourceWidth: number, sourceHeight: number, requestWidth: number, requestHeight: number,downsampType?:DownsampleStrategy): number - - getSampleSizeRounding(sourceWidth: number, sourceHeight: number, requestWidth: number, + // + getSampleSizeType(sourceWidth: number, sourceHeight: number, requestWidth: number, requestHeight: number,downsampType?:DownsampleStrategy): SampleSizeRounding } \ No newline at end of file From c9957c458fdf09980c5ac1b691235cd7e1c00a94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E5=8F=8C=E6=98=8E?= Date: Wed, 9 Oct 2024 11:47:11 +0000 Subject: [PATCH 12/12] update library/src/main/ets/downsampling/DownsampleStartegy.ets. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 田双明 --- library/src/main/ets/downsampling/DownsampleStartegy.ets | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/library/src/main/ets/downsampling/DownsampleStartegy.ets b/library/src/main/ets/downsampling/DownsampleStartegy.ets index 50387f0..e42861b 100644 --- a/library/src/main/ets/downsampling/DownsampleStartegy.ets +++ b/library/src/main/ets/downsampling/DownsampleStartegy.ets @@ -152,13 +152,11 @@ export class CenterInside implements BaseDownsampling { width: round(Math.min(1, getScale(sourceWidth, sourceHeight, requestedWidth, requestedHeight, downsampType)) * sourceWidth), height:round(Math.min(1, getScale(sourceWidth, sourceHeight, requestedWidth, requestedHeight, downsampType)) * sourceHeight) } + //将整型的缩放因子转换为2的次幂采样大小 let scaleFactor = this.getSampleSizeType(sourceWidth, sourceHeight, requestedWidth, requestedHeight, downsampType) == SampleSizeRounding.QUALITY ? - Math.max(sourceWidth / outSize.width, sourceHeight / outSize.height) : - Math.min(sourceWidth / outSize.width, sourceHeight / outSize.height) //重新计算宽高比 - - scaleFactor = Math.max(1, highestOneBit(scaleFactor))//将整型的缩放因子转换为2的次幂采样大小 - + Math.max(1, highestOneBit(Math.max(sourceWidth / outSize.width, sourceHeight / outSize.height))) : + Math.max(1, highestOneBit(Math.min(sourceWidth / outSize.width, sourceHeight / outSize.height))) if (this.getSampleSizeType(sourceWidth, sourceHeight, requestedWidth, requestedHeight, downsampType) == SampleSizeRounding.MEMORY && (scaleFactor < (1 / Math.min(1, getScale(sourceWidth, sourceHeight, requestedWidth, requestedHeight, downsampType))))) { scaleFactor = scaleFactor << 1;