服务器返回给端侧一张大图和多组图像裁剪坐标,页面根据图像坐标裁剪出小图进行展示。该行为需要将一张大图裁剪为多张小图,使用常规方案(使用PixelMap图像变换拷贝图片后裁剪)处理10张小图约耗时2S,页面会长时间保持loading状态。
由于PixelMap.crop方法操作PixelMap自身而不是返回副本,一张图裁剪为多张小图时,需要将原图拷贝后再剪切的方式处理,大量的复制拷贝增加了处理耗时,参考PixelMap深拷贝案例。实现逻辑如下图所示:

实现一张图片的高效裁剪,可以使用PixelMap.readPixels,传入area参数仅读取裁剪范围,再使用writePixels写入新图片。此时仅复制了必须的裁剪结果数据,降低大量复制消耗并且可以省略复制后的裁剪步骤,操作示意图及步骤如下:

static async cropImage(pixelMap: image.PixelMap, x: number, y: number, width: number,
height: number): Promise<PixelMap> {
let region: image.Region = { x: x, y: y, size: { height: height, width: width } };
const area: image.PositionArea = {
pixels: new ArrayBuffer(pixelMap.getPixelBytesNumber()),
offset: 0,
stride: pixelMap.getBytesNumberPerRow(),
region: region
};
pixelMap.readPixelsSync(area);
const opts: image.InitializationOptions = {
editable: true,
// readPixelsSync使用的是BGRA_8888,转换时设置RGBA_8888
pixelFormat: image.PixelMapFormat.RGBA_8888,
size: { height: height, width: width }
};
let snapshot = image.createPixelMapSync(opts);
const snapshotArea: image.PositionArea = {
pixels: area.pixels,
offset: 0,
stride: area.stride,
region: {
size: {
width: width,
height: height
},
x: 0,
y: 0
}
};
snapshot.writePixelsSync(snapshotArea);
return snapshot;
} import { image } from '@kit.ImageKit';
import { BusinessError, systemDateTime } from '@kit.BasicServicesKit';
import fs from '@ohos.file.fs';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
const MAX_PICK_COUNT = 1;
// 图片选择类
export class PickerUtil {
static async photoSelect(): Promise<image.PixelMap | null> {
return PickerUtil.photoSelectWithLimit(MAX_PICK_COUNT);
}
static async photoSelectWithLimit(count: number): Promise<image.PixelMap | null> {
try {
let photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
photoSelectOptions.maxSelectNumber = count;
photoSelectOptions.isEditSupported = false;
let photoPicker = new photoAccessHelper.PhotoViewPicker();
return await photoPicker.select(photoSelectOptions)
.then(async (photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
return PickerUtil.readImageToCache(photoSelectResult.photoUris[0]);
})
.catch((err: BusinessError) => {
console.error(`PhotoViewPicker.select failed with err: ${err.code}, ${err.message}`);
return null;
});
} catch (error) {
let err: BusinessError = error as BusinessError;
console.error(`PhotoViewPicker failed with err: ${err.code}, ${err.message}`);
return null;
}
}
// 压缩图片
private static async readImageToCache(path: string): Promise<image.PixelMap> {
let readFile = fs.openSync(path, fs.OpenMode.READ_ONLY);
try {
let imageSource: image.ImageSource = image.createImageSource(readFile.fd);
let decodingOptions: image.DecodingOptions = {
editable: false, desiredPixelFormat: 3,
};
return await imageSource.createPixelMap(decodingOptions);
} finally {
fs.closeSync(readFile);
}
}
}
// 图片裁剪类
export class ImageUtil {
// 像素读取方式实现裁剪
static async cropImage(pixelMap: image.PixelMap, x: number, y: number, width: number,
height: number): Promise<PixelMap> {
let region: image.Region = { x: x, y: y, size: { height: height, width: width } };
const area: image.PositionArea = {
pixels: new ArrayBuffer(pixelMap.getPixelBytesNumber()),
offset: 0,
stride: pixelMap.getBytesNumberPerRow(),
region: region
};
pixelMap.readPixelsSync(area);
const opts: image.InitializationOptions = {
editable: true,
// readPixelsSync使用的是BGRA_8888,转换时设置RGBA_8888
pixelFormat: image.PixelMapFormat.RGBA_8888,
size: { height: height, width: width }
};
let snapshot = image.createPixelMapSync(opts);
const snapshotArea: image.PositionArea = {
pixels: area.pixels,
offset: 0,
stride: area.stride,
region: {
size: {
width: width,
height: height
},
x: 0,
y: 0
}
};
snapshot.writePixelsSync(snapshotArea);
return snapshot;
}
// copy方式实现裁剪
static async cropImageWithCopy(pixelMap: image.PixelMap, x: number, y: number, width: number,
height: number): Promise<PixelMap> {
let region: image.Region = { x: x, y: y, size: { height: height, width: width } };
let copy = await ImageUtil.copyPixelMap(pixelMap);
await copy.crop(region);
return copy;
}
static async copyPixelMap(pixelMap: PixelMap): Promise<PixelMap> {
const imageInfo: image.ImageInfo = await pixelMap.getImageInfo();
const buffer: ArrayBuffer = new ArrayBuffer(pixelMap.getPixelBytesNumber());
await pixelMap.readPixelsToBuffer(buffer);
const opts: image.InitializationOptions = {
editable: true,
srcPixelFormat: imageInfo.pixelFormat,
pixelFormat: imageInfo.pixelFormat,
size: { height: imageInfo.size.height, width: imageInfo.size.width }
};
return image.createPixelMap(buffer, opts);
}
}
@Entry
@ComponentV2
struct Index {
@Local copyImages: image.PixelMap[] = [];
@Local copyTime: number = 0;
@Local cropImages: image.PixelMap[] = [];
@Local cropTime: number = 0;
@Local cropX: number = 0;
@Local cropY: number = 0;
@Local cropWidth: number = 100;
@Local cropHeight: number = 100;
async doTest() {
try {
this.copyImages = [];
this.cropImages = [];
let pixel = await PickerUtil.photoSelect();
if (pixel === null) {
return;
}
let subWidth = 100;
if (this.cropWidth > 0) {
subWidth = this.cropWidth;
}
let subHeight = 100;
if (this.cropHeight > 0) {
subHeight = this.cropHeight;
}
let x = 0;
if (this.cropX > 0) {
x = this.cropX;
}
let y = 0;
if (this.cropY > 0) {
y = this.cropY;
}
let count = 10;
let copyImages: image.PixelMap[] = [];
let cropImages: image.PixelMap[] = [];
let start = systemDateTime.getTime();
for (let i = 0; i < count; i++) {
copyImages.push(await ImageUtil.cropImageWithCopy(pixel, x + i * 100, y + i * 100, subWidth, subHeight));
}
this.copyTime = systemDateTime.getTime() - start;
start = systemDateTime.getTime();
for (let i = 0; i < count; i++) {
cropImages.push(await ImageUtil.cropImage(pixel, x + i * 100, y + i * 100, subWidth, subHeight));
}
this.cropTime = systemDateTime.getTime() - start;
this.copyImages = copyImages;
this.cropImages = cropImages;
} catch (e) {
console.error('error:', e);
}
}
build() {
RelativeContainer() {
Scroll() {
Column() {
Text(`拷贝裁剪耗时${this.copyTime}`)
Grid() {
ForEach(this.copyImages, (image: image.PixelMap) => {
GridItem() {
Image(image)
.width(80)
}
})
}
Text(`直接裁剪耗时${this.cropTime}`)
Grid() {
ForEach(this.cropImages, (image: image.PixelMap) => {
GridItem() {
Image(image)
.width(80)
}
})
}
Button('选择图片开始测试')
.onClick(() => {
this.doTest();
})
Column() {
TextInput({ placeholder: '左上角坐标 x,y逗号分割' })
.onChange(value => {
let sub = value?.split(',');
if (sub && sub.length > 1) {
this.cropX = Number(sub[0]).valueOf();
this.cropY = Number(sub[1]).valueOf();
console.info('this.cropX:', this.cropX, 'this.cropY:', this.cropY);
}
})
TextInput({ placeholder: '裁剪宽度' })
.onChange(value => {
this.cropWidth = Number(value).valueOf();
console.info('this.cropWidth:', this.cropWidth);
})
TextInput({ placeholder: '裁剪高度' })
.onChange(value => {
this.cropHeight = Number(value).valueOf();
console.info('this.cropHeight:', this.cropHeight);
})
}
}
}
}
.height('100%')
.width('100%')
}
}