降采样适配3.x

Signed-off-by: tsm <2418639820@qq.com>
This commit is contained in:
tsm 2024-05-22 15:28:20 +08:00
parent 8c15816eee
commit 3ff328d762
16 changed files with 662 additions and 42 deletions

View File

@ -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%')
}
}

View File

@ -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',

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 KiB

View File

@ -14,8 +14,9 @@
"pages/TestHeader",
"pages/ImageTransformation",
"pages/ObjectFitPage",
"pages/TestWriteCacheStage",
"pages/LoadStatePage",
"pages/TestRemoveCache"
"pages/TestRemoveCache",
"pages/TestWriteCacheStage",
"pages/DownSamplePage"
]
}

View File

@ -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" */

View File

@ -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';
}

View File

@ -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'

View File

@ -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<RequestJobResult
}
}
if (resBuf == undefined) {
return {
pixelMap: undefined,
@ -418,7 +425,6 @@ async function requestJob(request: RequestJobRequest): Promise<RequestJobResult
let typeValue = fileTypeUtil.getFileType(resBuf);
if (typeValue === 'gif' || typeValue === 'webp') {
let base64Help = new util.Base64Helper()
let base64str = "data:image/" + typeValue + ";base64," + base64Help.encodeToStringSync(new Uint8Array(resBuf))
return {
pixelMap: base64str,
@ -426,10 +432,19 @@ async function requestJob(request: RequestJobRequest): Promise<RequestJobResult
fileKey: fileKey
};
}
let autoResizes = request.autoResize
let imageSource: image.ImageSource = image.createImageSource(resBuf);
let decodingOptions: image.DecodingOptions = {
let imageInfo = await imageSource.getImageInfo()
let a = new Downsampler().calculateScaling(typeValue, imageInfo.size.width, imageInfo.size.height, request.targetWidth,
request.targetHeight, request.downsampType,autoResizes)
let decodingOptions: image.DecodingOptions = request.downsampType === DownsampleStrategy.NONE ? {
editable: true,
} : {
editable: true,
desiredSize: {
width: a.targetWidth,
height: a.targetHeight
}
}
let resPixelmap: PixelMap | undefined = undefined

View File

@ -13,14 +13,16 @@
* limitations under the License.
*/
import taskpool from '@ohos.taskpool';
import common from '@ohos.app.ability.common'
import common from '@ohos.app.ability.common';
import { CacheStrategy } from './model/ImageKnifeData';
import { PixelMapTransformation } from './transform/PixelMapTransformation';
import { DownsampleStrategy } from './downsampling/DownsampleStartegy';
export interface HeaderOptions {
key: string;
value: Object;
}
@Observed
export class ImageKnifeOption {
// 主图资源
@ -29,38 +31,31 @@ export class ImageKnifeOption {
placeholderSrc?: string | PixelMap | Resource;
// 失败占位图
errorholderSrc?: string | PixelMap | Resource;
headerOption?: Array<HeaderOptions>;
// 自定义缓存关键字
signature?: string;
// 主图填充效果
objectFit?: ImageFit
// 占位图填充效果
placeholderObjectFit?: ImageFit
// 错误图填充效果
errorholderObjectFit?: ImageFit
customGetImage?: (context: Context, src: string | PixelMap | Resource) => Promise<ArrayBuffer | undefined>
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() {
}

View File

@ -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<string,Object> = new Map<string,Object>()
headers: Map<string, Object> = new Map<string, Object>()
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;
}

View File

@ -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

View File

@ -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
}

View File

@ -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,
}

View File

@ -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)
}
}

View File

@ -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);
}

View File

@ -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
}