@ohmi/react-native-image-crop-picker
Version:
Select single or multiple images, with cropping option
1,042 lines (990 loc) • 42.9 kB
text/typescript
// Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved
// Use of this source code is governed by a MIT license that can be
// found in the LICENSE file.
import type { TurboModuleContext } from '@rnoh/react-native-openharmony/ts';
import { ImageCropPicker } from "./generated/turboModules/ImageCropPicker";
import { TurboModule } from '@rnoh/react-native-openharmony/ts'
import Logger from './Logger';
import type Want from '@ohos.app.ability.Want';
import image from '@ohos.multimedia.image';
import media from '@ohos.multimedia.media';
import util from '@ohos.util';
import uri from '@ohos.uri';
import picker from '@ohos.multimedia.cameraPicker';
import photoAccessHelper from '@ohos.file.photoAccessHelper';
import camera from '@ohos.multimedia.camera';
import { BusinessError } from '@ohos.base';
import fs, { Filter } from '@ohos.file.fs';
import { JSON } from '@kit.ArkTS';
import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
import { PermissionRequestResult } from '@ohos.abilityAccessCtrl';
import { window } from '@kit.ArkUI';
export type MediaType = 'photo' | 'video' | 'any';
export type OrderType = 'asc' | 'desc' | 'none';
export type ErrorCode = 'camera_unavailable' | 'permission' | 'others';
const MaxNumber = 5;
const MinNumber = 1;
const ImageQuality = 1;
const TAG: string = 'ImageCropPickerTurboModule';
const WANT_PARAM_URI_SELECT_SINGLE: string = 'singleselect';
const WANT_PARAM_URI_SELECT_MULTIPLE: string = 'multipleselect';
const ENTER_GALLERY_ACTION: string = "ohos.want.action.photoPicker";
const filePrefix = 'file://'
const atManager = abilityAccessCtrl.createAtManager();
const MINIMUM_VALUE = 1;
let avMetadataExtractor: media.AVMetadataExtractor;
export type SmartAlbums = | 'Regular' | 'SyncedEvent' | 'SyncedFaces';
export type CompressVideoPresets = | 'LowQuality' | 'MediumQuality' | 'HighestQuality' | 'Passthrough';
export type CropperOptions = ImageOptions & {
path: string;
}
export type Options = AnyOptions | VideoOptions | ImageOptions;
export type AnyOptions = Omit<ImageOptions, 'mediaType'> & Omit<VideoOptions, 'mediaType'> & {
mediaType?: 'any';
};
export type VideoOptions = CommonOptions & {
mediaType: 'video';
compressVideoPreset?: CompressVideoPresets;
};
export type ImageOptions = CommonOptions & {
mediaType: MediaType;
width?: number;
height?: number;
includeBase64?: boolean;
includeExif?: boolean;
forceJpg?: boolean;
cropping?: boolean;
avoidEmptySpaceAroundImage?: boolean;
cropperActiveWidgetColor?: string;
cropperStatusBarColor?: string;
cropperToolbarColor?: string;
cropperToolbarWidgetColor?: string;
cropperToolbarTitle?: string;
freeStyleCropEnabled?: boolean;
cropperTintColor?: string;
cropperCircleOverlay?: boolean;
cropperCancelText?: string;
cropperCancelColor?: string;
cropperChooseText?: string;
cropperChooseColor?: string;
cropperRotateButtonsHidden?: boolean
showCropGuidelines?: boolean;
showCropFrame?: boolean;
enableRotationGesture?: boolean;
disableCropperColorSetters?: boolean;
compressImageMaxWidth?: number;
compressImageMaxHeight?: number;
compressImageQuality?: number;
}
export interface CommonOptions {
multiple?: boolean;
minFiles?: number;
maxFiles?: number;
waitAnimationEnd?: boolean;
smartAlbums?: SmartAlbums[];
useFrontCamera?: boolean;
loadingLabelText?: string;
showsSelectedCount?: boolean;
sortOrder?: 'none' | 'asc' | 'desc';
hideBottomControls?: boolean;
compressImageQuality?: number;
mediaType: MediaType;
width?: number;
height?: number;
includeBase64?: boolean;
includeExif?: boolean;
forceJpg?: boolean;
cropping?: boolean;
avoidEmptySpaceAroundImage?: boolean;
cropperActiveWidgetColor?: string;
cropperStatusBarColor?: string;
cropperToolbarColor?: string;
cropperToolbarWidgetColor?: string;
cropperToolbarTitle?: string;
freeStyleCropEnabled?: boolean;
cropperTintColor?: string;
cropperCircleOverlay?: boolean;
cropperCancelText?: string;
cropperCancelColor?: string;
cropperChooseText?: string;
cropperChooseColor?: string;
cropperRotateButtonsHidden?: boolean
showCropGuidelines?: boolean;
showCropFrame?: boolean;
enableRotationGesture?: boolean;
disableCropperColorSetters?: boolean;
compressImageMaxWidth?: number;
compressImageMaxHeight?: number;
writeTempFile?: boolean;
}
export interface Image extends ImageVideoCommon {
data?: string | null;
cropRect?: CropRect | null;
}
export interface Video extends ImageVideoCommon {
duration: number | null;
}
export interface CropRect {
x: number;
y: number;
width: number;
height: number;
}
export interface ImageOrVideo {
data?: string | null;
width?: number | null;
height?: number | null;
size?: number | null;
cropRect?: CropRect | null;
filename?: string | null;
path?: string | null;
exif?: Exif | null;
mime?: string | null;
sourceURL?: string | null;
creationDate?: string | null;
modificationDate?: string | null;
localIdentifier?: string | null;
duration?: string | null;
}
export interface ImageVideoCommon {
path: string;
size: number;
width: number;
height: number;
mime: string;
exif?: Exif;
localIdentifier?: string;
sourceURL?: string;
filename?: string;
creationDate?: string;
modificationDate?: string;
}
export interface Exif {
BitsPerSample?: string;
Orientation?: string;
ImageLength?: string;
ImageWidth?: string;
GPSLatitude?: string;
GPSLongitude?: string;
GPSLatitudeRef?: string;
GPSLongitudeRef?: string;
DateTimeOriginal?: string;
ExposureTime?: string;
SceneType?: string;
ISOSpeedRatings?: string;
FNumber?: string;
}
export class FilePath {
path?: string;
}
export interface VideoImageInfo {
data?: string | null;
width?: number | null;
height?: number | null;
size?: number | null;
cropRect?: CropRect | null;
filename?: string | null;
path?: string | null;
exif?: Exif | null;
mime?: string | null;
sourceURL?: string | null;
creationDate?: number | null;
modificationDate?: number | null;
localIdentifier?: string | null;
duration?: string | null;
}
export interface FilePathResult {
result?: FilePath[];
}
export class AbilityResult {
resultCode: number;
want?: Want;
}
export class ImageCropPickerTurboModule extends TurboModule implements ImageCropPicker.Spec {
constructor(protected ctx: TurboModuleContext) {
super(ctx);
}
isNullOrUndefined(value: any): boolean {
return value === null || value === undefined || value === '';
}
async openPicker(options?: Options): Promise<Video[] | Video | ImageOrVideo[] | ImageOrVideo | Image[] | Image> {
Logger.info(`${TAG} into openPicker request ${JSON.stringify(options)}`);
let cropping = this.isNullOrUndefined(options?.cropping) ? false : options?.cropping;
let minFiles = this.isNullOrUndefined(options?.minFiles) ? MinNumber : options.minFiles;
let maxFiles = this.isNullOrUndefined(options?.maxFiles) ? MaxNumber : options.maxFiles;
let isShowSerialNum = this.isNullOrUndefined(options?.showsSelectedCount) ? true : options?.showsSelectedCount;
if (options.multiple && minFiles > maxFiles) {
return new Promise(async (res, rej) => {
rej('minFiles is error')
})
}
let quality = options.compressImageQuality;
if (!this.isNullOrUndefined(quality) && !(quality >= 0 && quality <= 1)) {
return new Promise(async (res, rej) => {
rej('quality is error')
})
}
let multiple = this.isNullOrUndefined(options.multiple) ? false : options.multiple;
let mediaType = options.mediaType == 'photo' ? photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE
: (options.mediaType == 'video' ? photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE : photoAccessHelper.PhotoViewMIMETypes.IMAGE_VIDEO_TYPE);
let writeTempFile = this.isNullOrUndefined(options.writeTempFile) ? true : options.writeTempFile;
let qualityNumber = this.isNullOrUndefined(options.compressImageQuality) ? ImageQuality : options.compressImageQuality;
let forceJpg = this.isNullOrUndefined(options.forceJpg) ? false : options.forceJpg;
try {
let photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
photoSelectOptions.MIMEType = mediaType;
photoSelectOptions.maxSelectNumber = multiple ? maxFiles : 1;
photoSelectOptions.isSearchSupported = false;
let photoPicker = new photoAccessHelper.PhotoViewPicker();
let result: photoAccessHelper.PhotoSelectResult = await photoPicker.select(photoSelectOptions);
let sourceFilePaths: Array<string> = result.photoUris as Array<string>;
if (sourceFilePaths.length < MINIMUM_VALUE) {
return new Promise(async (res, rej) => {
rej('sourceFilePaths is empty')
})
}
let tempFilePaths = null;
Logger.info(`${TAG} into openPicker tempFilePaths ${JSON.stringify(sourceFilePaths)}`);
if (qualityNumber !== 1 || forceJpg) {
Logger.info(`${TAG} qualityNumber = ${qualityNumber} forceJpg = ${forceJpg}`);
tempFilePaths = await this.compressPictures(qualityNumber * 100, forceJpg, sourceFilePaths);
} else {
tempFilePaths = writeTempFile ? this.getTempFilePaths(sourceFilePaths) : null;
}
let tempFilePath = null;
const isImg = tempFilePaths ? this.isImage(tempFilePaths[0]) : false;
if (!multiple && isImg && cropping) {
const imgCropPath = await this.intoCropper(tempFilePaths[0], options);
if (!this.isNullOrUndefined(imgCropPath)) {
tempFilePath = imgCropPath;
Logger.info(`${TAG} into openCamera imgCropPath = ${imgCropPath}`);
}
let exifInfo;
if (options?.includeExif) {
try {
let exifFile = fs.openSync(tempFilePath, fs.OpenMode.READ_ONLY);
let exifImageIS = image.createImageSource(exifFile.fd);
exifInfo = await this.getImageExif(exifImageIS);
} catch (err) {
Logger.error(`${TAG} into getPickerResult err : ${JSON.stringify(err)}`);
}
}
let imgResult: Image = {
data: null,
cropRect: null,
path: null,
size: 0,
width: 0,
height: 0,
mime: '',
exif: null,
localIdentifier: '',
sourceURL: '',
filename: '',
creationDate: null,
modificationDate: null
};
await this.getFileInfo(options?.includeBase64, tempFilePath, null, exifInfo).then((imageInfo) => {
imgResult.path = filePrefix + imgCropPath;
imgResult.exif = imageInfo.exif;
imgResult.data = imageInfo.data;
imgResult.size = imageInfo.size;
imgResult.width = imageInfo.width;
imgResult.height = imageInfo.height;
imgResult.filename = imageInfo.filename;
imgResult.mime = imageInfo.mime;
imgResult.localIdentifier = imageInfo.localIdentifier;
imgResult.cropRect = imageInfo.cropRect;
imgResult.creationDate = imageInfo.creationDate + '';
imgResult.modificationDate = imageInfo.modificationDate + '';
})
return imgResult;
}
return this.getPickerResult(options, sourceFilePaths, tempFilePaths);
} catch (error) {
Logger.error(`${TAG} PhotoViewPicker failed err: ${JSON.stringify(error)}`);
return new Promise(async (res, rej) => {
rej('PhotoViewPicker is failed')
})
}
}
getTempFilePaths(images: Array<string>): Array<string> {
let resultImages: Array<string> = new Array<string>();
Logger.info(`${TAG} getTempFilePaths images = ${images}`);
for (let srcPath of images) {
Logger.info(`${TAG} getTempFilePaths img srcPath = ${srcPath}`);
let i = srcPath.lastIndexOf('.');
let imageType = '';
if (i != -1) {
imageType = srcPath.substring(i + 1);
Logger.info(`${TAG} getTempFilePaths img imageType = ${imageType}`);
}
let file = fs.openSync(srcPath, fs.OpenMode.CREATE);
let dstPath = this.ctx.uiAbilityContext.tempDir + '/rn_image_crop_picker_lib_temp_' + util.generateRandomUUID(true) + '.' + imageType;
try {
fs.copyFileSync(file.fd, dstPath, 0);
resultImages.push(dstPath);
Logger.info(`${TAG} getTempFilePaths suc dstPath = ${dstPath}`);
} catch (err) {
Logger.info(`${TAG}, getTempFilePaths fail err = ${JSON.stringify(err)}`);
}
fs.closeSync(file);
}
return resultImages;
}
async getPickerResult(options: Options, sourceFilePaths: Array<string>, tempFilePaths: Array<string>): Promise<ImageOrVideo[] | ImageOrVideo> {
Logger.info(`${TAG}, into openPickerResult :`);
let resultsList: ImageOrVideo[] = [];
let includeExif = this.isNullOrUndefined(options?.includeExif) ? false : options?.includeExif;
let images = this.isNullOrUndefined(tempFilePaths) ? sourceFilePaths : tempFilePaths;
Logger.info(`${TAG} into openPickerResult : images = ${images}`);
let includeBase64 = this.isNullOrUndefined(options.includeBase64) ? false : options.includeBase64;
let results;
for (let j = 0; j < images.length; j++) {
results = {
duration: null,
data: null,
cropRect: null,
path: null,
size: 0,
width: 0,
height: 0,
mime: '',
exif: null,
localIdentifier: '',
sourceURL: '',
filename: '',
creationDate: null,
modificationDate: null
};
let value = images[j];
if (this.isNullOrUndefined(value)) {
return;
}
let imageType;
let i = value.lastIndexOf('/')
let fileName = value.substring(i + 1)
i = value.lastIndexOf('.')
if (i != -1) {
imageType = value.substring(i + 1)
}
results.sourceURL = sourceFilePaths[j];
results.filename = fileName;
let exifInfo;
// /\.(jpeg)$/i.test(sourceFilePaths[j])
if (includeExif) {
try {
let exifFile = fs.openSync(sourceFilePaths[j], fs.OpenMode.READ_ONLY);
let exifImageIS = image.createImageSource(exifFile.fd);
exifInfo = await this.getImageExif(exifImageIS);
} catch (err) {
Logger.error(`${TAG} into getPickerResult err : ${JSON.stringify(err)}`);
}
}
results.exif = exifInfo;
let file = fs.openSync(value, fs.OpenMode.READ_ONLY)
Logger.info(`${TAG} into openSync : file.fd = ${file.fd} file.path = ${file.path} file.name = ${file.name}`);
let stat = fs.statSync(file.fd);
let length = stat.size;
results.size = length;
results.creationDate = stat.ctime + '';
results.modificationDate = stat.mtime + '';
results.path = this.isNullOrUndefined(tempFilePaths) ? null : filePrefix + value;
if (this.isImage(value)) {
results.data = includeBase64 ? this.imageToBase64(value) : null;
results.mime = 'image/' + imageType;
Logger.info(`${TAG} into openPickerResult value : ${value}`);
let imageIS = image.createImageSource(file.fd)
let imagePM = await imageIS.createPixelMap()
Logger.info(`${TAG} end createImageSource : imageIS = ${imageIS} imagePM = ${imagePM}`);
let imgInfo = await imagePM.getImageInfo();
results.height = imgInfo.size.height;
results.width = imgInfo.size.width;
imagePM.release().then(() => {
imagePM = undefined;
})
imageIS.release().then(() => {
imageIS = undefined;
})
results.duration = null;
} else {
Logger.info(`${TAG} into getPickerResult video start`);
results.data = null;
results.mime = 'video/' + imageType;
let url = 'fd://' + file.fd;
Logger.info(`${TAG} start avPlayer url = ${url}`);
avMetadataExtractor = await media.createAVMetadataExtractor();
avMetadataExtractor.fdSrc = { fd: file.fd, offset: 0, length: length };
try {
const res = await avMetadataExtractor.fetchMetadata();
results.duration = res.duration;
results.width = Number(res.videoWidth);
results.height = Number(res.videoHeight);
} catch (error) {
Logger.error(`${TAG} get video info suc error = ${JSON.stringify(error)}`);
}
}
resultsList.push(results);
fs.closeSync(file);
}
return options.multiple ? resultsList : results;
}
async openCamera(options?: Options): Promise<Video[] | Video | ImageOrVideo[] | ImageOrVideo | Image[] | Image> {
Logger.info(`${TAG} into openCamera request = ${JSON.stringify(options)}`);
let cropping = this.isNullOrUndefined(options?.cropping) ? false : options?.cropping;
let quality = options.compressImageQuality;
if (!this.isNullOrUndefined(quality) && !(quality >= 0 && quality <= 1)) {
return new Promise(async (res, rej) => {
rej('quality is error')
})
}
let isImg = false;
let imgResult: Image = {
data: null,
cropRect: null,
path: null,
size: 0,
width: 0,
height: 0,
mime: '',
exif: null,
localIdentifier: '',
sourceURL: '',
filename: '',
creationDate: null,
modificationDate: null
};
let videoResult: Video = {
duration: null,
path: null,
size: 0,
width: 0,
height: 0,
mime: '',
exif: null,
localIdentifier: '',
sourceURL: '',
filename: '',
creationDate: null,
modificationDate: null
};
let useFrontCamera = this.isNullOrUndefined(options.useFrontCamera) ? false : options.useFrontCamera;
let writeTempFile = this.isNullOrUndefined(options?.writeTempFile) ? true : options?.writeTempFile;
let includeBase64 = this.isNullOrUndefined(options.includeBase64) ? false : options.includeBase64;
let qualityNumber = this.isNullOrUndefined(options.compressImageQuality) ? ImageQuality : options.compressImageQuality;
let forceJpg = this.isNullOrUndefined(options.forceJpg) ? false : options.forceJpg;
let mediaType = options.mediaType == 'photo' ? [picker.PickerMediaType.PHOTO] : (options.mediaType == 'video'
? [picker.PickerMediaType.VIDEO] : [picker.PickerMediaType.PHOTO, picker.PickerMediaType.VIDEO]);
return new Promise(async (res, rej) => {
try {
let mContext = await this.ctx.uiAbilityContext;
let pickerProfile: picker.PickerProfile = {
cameraPosition: useFrontCamera ? camera.CameraPosition.CAMERA_POSITION_FRONT : camera.CameraPosition.CAMERA_POSITION_BACK,
};
let pickerResult: picker.PickerResult = await picker.pick(mContext, mediaType, pickerProfile);
Logger.info(`${TAG} into openCamera results = ${JSON.stringify(pickerResult)}`);
let imgOrVideoPath = pickerResult.resultUri;
isImg = this.isImage(imgOrVideoPath);
if (isImg) {
let file = fs.openSync(imgOrVideoPath, fs.OpenMode.READ_ONLY);
try {
let dstPath = this.ctx.uiAbilityContext.tempDir + '/rn_image_crop_picker_lib_temp_' + util.generateRandomUUID(true) + '.jpeg';
fs.copyFileSync(file.fd, dstPath, 0);
imgOrVideoPath = dstPath;
Logger.info(`${TAG} into openCamera suc dstPath = ${dstPath}`);
} catch (err) {
Logger.error(`${TAG} into openCamera fail err = ${JSON.stringify(err)}`);
}
fs.closeSync(file);
}
let tempFilePaths = null;
let sourceFilePaths: Array<string> = [imgOrVideoPath];
if (qualityNumber !== 1 || forceJpg) {
Logger.info(`${TAG} into openCamera qualityNumber = ${qualityNumber} forceJpg = ${forceJpg}`);
tempFilePaths = await this.compressPictures(qualityNumber * 100, forceJpg, sourceFilePaths);
} else {
tempFilePaths = writeTempFile ? this.getTempFilePaths(sourceFilePaths) : null;
}
let tempFilePath = this.isNullOrUndefined(tempFilePaths) ? null : tempFilePaths[0];
let includeExif = this.isNullOrUndefined(options?.includeExif) ? false : options?.includeExif;
if (isImg) {
let exifInfo;
// /\.(jpeg)$/i.test(imgOrVideoPath)
if (includeExif) {
try {
let exifFile = fs.openSync(imgOrVideoPath, fs.OpenMode.READ_ONLY);
let exifImageIS = image.createImageSource(exifFile.fd);
exifInfo = await this.getImageExif(exifImageIS);
} catch (err) {
Logger.error(`${TAG} into openCamera err = ${JSON.stringify(err)}`);
}
}
let imgCropPath = cropping ? await this.intoCropper(imgOrVideoPath, options) : '';
if (!this.isNullOrUndefined(imgCropPath)) {
tempFilePath = imgCropPath;
Logger.info(`${TAG} into openCamera imgCropPath = ${imgCropPath}`);
}
this.getFileInfo(includeBase64, imgOrVideoPath, tempFilePath, exifInfo).then((imageInfo) => {
imgResult.sourceURL = imgOrVideoPath;
imgResult.path = this.isNullOrUndefined(tempFilePath) ? filePrefix + imgOrVideoPath : filePrefix + tempFilePath;
imgResult.exif = imageInfo.exif;
imgResult.data = imageInfo.data;
imgResult.size = imageInfo.size;
imgResult.width = imageInfo.width;
imgResult.height = imageInfo.height;
imgResult.filename = imageInfo.filename;
imgResult.mime = imageInfo.mime;
imgResult.localIdentifier = imageInfo.localIdentifier;
imgResult.cropRect = imageInfo.cropRect;
imgResult.creationDate = imageInfo.creationDate + '';
imgResult.modificationDate = imageInfo.modificationDate + '';
res(imgResult);
})
} else {
this.getFileInfo(false, imgOrVideoPath, tempFilePath, null).then((imageInfo) => {
videoResult.sourceURL = imgOrVideoPath;
videoResult.path = this.isNullOrUndefined(tempFilePath) ? imgOrVideoPath : filePrefix + tempFilePath;
videoResult.exif = imageInfo.exif;
videoResult.size = imageInfo.size;
videoResult.width = imageInfo.width;
videoResult.height = imageInfo.height;
videoResult.filename = imageInfo.filename;
videoResult.mime = imageInfo.mime;
videoResult.localIdentifier = imageInfo.localIdentifier;
videoResult.creationDate = imageInfo.creationDate + '';
videoResult.modificationDate = imageInfo.modificationDate + '';
videoResult.duration = Number(imageInfo.duration);
res(videoResult);
})
}
} catch (error) {
let err = error as BusinessError;
rej(JSON.stringify(err));
}
})
};
imageToBase64(filePath: string): string {
Logger.info(`${TAG} into imageToBase64 filePath = ${filePath}`);
let uriPath = new uri.URI(filePath);
let dstPath = filePath;
if (uriPath.host === 'media') {
let i = filePath.lastIndexOf('.');
let imageType;
if (i != -1) {
imageType = filePath.substring(i + 1);
}
let file = fs.openSync(filePath, fs.OpenMode.CREATE);
try {
dstPath = this.ctx.uiAbilityContext.tempDir + '/rn_image_crop_picker_lib_temp_' + util.generateRandomUUID(true) + '.' + imageType;
fs.copyFileSync(file.fd, dstPath, 0);
Logger.info(`${TAG} into imageToBase64 suc dstPath = ${dstPath}`);
} catch (err) {
Logger.error(`${TAG} into imageToBase64 fail err = ${JSON.stringify(err)}`);
}
fs.closeSync(file);
}
let base64Data;
try {
let file = fs.openSync(dstPath, fs.OpenMode.READ_ONLY);
let stat = fs.lstatSync(dstPath);
Logger.info(`${TAG} into imageToBase64 stat.size = ${stat.size}`);
let buf = new ArrayBuffer(stat.size);
fs.readSync(file.fd, buf);
let unit8Array: Uint8Array = new Uint8Array(buf);
let base64Helper = new util.Base64Helper();
base64Data = base64Helper.encodeToStringSync(unit8Array, util.Type.BASIC);
fs.closeSync(file);
Logger.info(`${TAG} into imageToBase64 base64Data = ${base64Data}`);
} catch (err) {
Logger.error(`${TAG} into imageToBase64 err = ${JSON.stringify(err)}`);
}
return base64Data;
}
isImage(filePath: string): boolean {
Logger.info(`${TAG} into isImage fileName = ${filePath}`);
const imageExtensionsRegex = /\.(jpg|jpeg|png|gif|bmp|webp)$/i;
return imageExtensionsRegex.test(filePath);
}
async compressPictures(quality: number, forceJpg: boolean, sourceURL: Array<string>): Promise<Array<string>> {
Logger.info(`${TAG} into compressPictures sourceURL = ${sourceURL} quality = ${quality} forceJpg = ${forceJpg}`);
let imageType: string = 'jpg';
let resultImages: Array<string> = new Array<string>();
quality = (quality > 100) ? 100 : quality;
for (let srcPath of sourceURL) {
if (this.isImage(srcPath)) {
Logger.info(`${TAG} into compressPictures img srcPath = ${srcPath}`);
if (forceJpg) {
imageType = 'jpg'
} else {
let i = srcPath.lastIndexOf('.');
if (i != -1) {
imageType = srcPath.substring(i + 1);
}
}
Logger.info(`${TAG} into compressPictures imageType = ${imageType}`);
let files = fs.openSync(srcPath, fs.OpenMode.READ_ONLY)
let imageISs = image.createImageSource(files.fd);
let imagePMs = await imageISs.createPixelMap();
let imagePackerApi = await image.createImagePacker();
let options: image.PackingOption = {
format: 'image/jpeg',
quality: quality,
};
try {
let packerData = await imagePackerApi.packing(imagePMs, options);
Logger.info(`${TAG} into compressPictures data = ${JSON.stringify(packerData)}`);
let dstPath = this.ctx.uiAbilityContext.tempDir + '/rn_image_crop_picker_lib_temp_' + util.generateRandomUUID(true) + '.' + imageType;
let newFile = fs.openSync(dstPath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE);
Logger.info(`${TAG} into compressPictures newFile id = ${newFile.fd}`);
const number = fs.writeSync(newFile.fd, packerData);
Logger.info(`${TAG} into compressPictures write data to file succeed size = ${number}`);
resultImages.push(dstPath);
fs.closeSync(files);
} catch (err) {
Logger.error(`${TAG} into compressPictures write data to file failed err = ${JSON.stringify(err)}`);
}
} else {
Logger.info(`${TAG} into compressPictures video srcPath = ${srcPath}`);
resultImages.push(srcPath);
}
}
return resultImages;
}
async getListFile(): Promise<FilePathResult> {
let filePathResult: FilePathResult = { result: [] };
let filesDir = this.ctx.uiAbilityContext.tempDir;
class ListFileOption {
public recursion: boolean = false;
public listNum: number = 0;
public filter: Filter = {};
}
let option = new ListFileOption();
option.filter.suffix = ['.jpg'];
option.filter.displayName = ['*'];
option.filter.fileSizeOver = 0;
option.filter.lastModifiedAfter = new Date(0).getTime();
Logger.info(`${TAG} into getListFile filesDir = ${filesDir}`);
let files = fs.listFileSync(filesDir, option);
for (let i = 0; i < files.length; i++) {
let listFilePath: FilePath = {}
listFilePath.path = files[i];
filePathResult.result.push(listFilePath);
}
Logger.info(`${TAG} into getListFile arrayList = ${JSON.stringify(filePathResult)}`);
return filePathResult;
}
async cleanSingle(path: string): Promise<void> {
Logger.info(`${TAG} into cleanSingle path = ${path}`);
let filePath = path.trim();
fs.access(filePath, (err) => {
if (err) {
Logger.info(`${TAG} cleanSingle access error data = ${err.data}`);
} else {
fs.unlink(filePath).then(() => {
Logger.info(`${TAG} cleanSingle file succeed`);
}).catch((err: BusinessError) => {
Logger.error(`${TAG} cleanSingle err = ${JSON.stringify(err)}`);
});
}
});
}
async clean(): Promise<void> {
let dirPath = this.ctx.uiAbilityContext.tempDir;
fs.rmdir(dirPath).then(() => {
Logger.info(`${TAG} clean rmdir succeed`);
}).catch((err: BusinessError) => {
Logger.error(`${TAG} clean rmdir err = ${JSON.stringify(err)}`);
});
}
async openCropper(options?: CropperOptions): Promise<Image> {
Logger.info(`${TAG} into openCropper = ${JSON.stringify(options)}`);
let path = options?.path;
if (this.isNullOrUndefined(path?.trim())) {
return new Promise(async (res, rej) => {
rej('path is empty')
})
}
let quality = options.compressImageQuality;
if (!this.isNullOrUndefined(quality) && !(quality >= 0 && quality <= 1)) {
return new Promise(async (res, rej) => {
rej('quality is error')
})
}
let result: Image = {
data: null,
cropRect: null,
path: null,
size: 0,
width: 0,
height: 0,
mime: '',
exif: null,
localIdentifier: '',
sourceURL: '',
filename: '',
creationDate: null,
modificationDate: null
};
let includeExif = this.isNullOrUndefined(options?.includeExif) ? false : options?.includeExif;
let writeTempFile = this.isNullOrUndefined(options?.writeTempFile) ? true : options?.writeTempFile;
let qualityNumber = this.isNullOrUndefined(options.compressImageQuality) ? ImageQuality : options.compressImageQuality;
let forceJpg = this.isNullOrUndefined(options.forceJpg) ? false : options.forceJpg;
let imgPath = await this.intoCropper(path, options);
Logger.info(`${TAG} into openCropper imgPath = ${imgPath}`);
if (this.isNullOrUndefined(imgPath)) {
return new Promise(async (res, rej) => {
rej('imgPath is null')
})
}
let tempFilePaths = null;
let sourceFilePaths: Array<string> = [imgPath];
if (qualityNumber !== 1 || forceJpg) {
Logger.info(`${TAG} into openCropper qualityNumber = ${qualityNumber} forceJpg = ${forceJpg}`);
tempFilePaths = await this.compressPictures(qualityNumber * 100, forceJpg, sourceFilePaths);
} else {
tempFilePaths = writeTempFile ? this.getTempFilePaths(sourceFilePaths) : null;
}
let tempFilePath = this.isNullOrUndefined(tempFilePaths) ? null : tempFilePaths[0];
let exifInfo;
// /\.(jpeg)$/i.test(path)
if (includeExif) {
try {
let exifFile = fs.openSync(path, fs.OpenMode.READ_ONLY);
let exifImageIS = image.createImageSource(exifFile.fd);
exifInfo = await this.getImageExif(exifImageIS);
} catch (err) {
Logger.error(`${TAG} into openCamera err = ${JSON.stringify(err)}`);
}
}
let originalImgPath = options?.compressImageQuality ? tempFilePath : imgPath;
return new Promise(async (res, rej) => {
this.getFileInfo(options?.includeBase64, originalImgPath, tempFilePath, exifInfo).then((imageInfo) => {
result.path = this.isNullOrUndefined(tempFilePath) ? filePrefix + imgPath : filePrefix + tempFilePath;
result.exif = imageInfo.exif;
result.sourceURL = options?.path;
result.data = imageInfo.data;
result.size = imageInfo.size;
result.width = imageInfo.width;
result.height = imageInfo.height;
result.filename = imageInfo.filename;
result.mime = imageInfo.mime;
result.localIdentifier = imageInfo.localIdentifier;
result.cropRect = imageInfo.cropRect;
result.creationDate = imageInfo.creationDate + '';
result.modificationDate = imageInfo.modificationDate + '';
res(result);
})
})
}
async getFileInfo(includeBase64: boolean, filePath: string, compressOrTempFilePath: string, exifInfo: Exif): Promise<VideoImageInfo> {
let videoImageInfo: VideoImageInfo = { duration: null };
let imageType;
let i = this.isNullOrUndefined(compressOrTempFilePath) ? filePath.lastIndexOf('/') : compressOrTempFilePath.lastIndexOf('/')
let fileName = this.isNullOrUndefined(compressOrTempFilePath) ? filePath.substring(i + 1) : compressOrTempFilePath.substring(i + 1)
i = filePath.lastIndexOf('.')
if (i != -1) {
imageType = filePath.substring(i + 1)
}
videoImageInfo.path = this.isNullOrUndefined(compressOrTempFilePath) ? filePrefix + filePath : compressOrTempFilePath + filePath;
videoImageInfo.filename = fileName;
videoImageInfo.mime = 'image/' + imageType;
let file = fs.openSync(filePath, fs.OpenMode.READ_ONLY)
let stat = fs.statSync(file.fd);
let length = stat.size;
videoImageInfo.size = length;
videoImageInfo.creationDate = stat.ctime;
videoImageInfo.modificationDate = stat.mtime;
if (this.isImage(filePath)) {
let imageIS = image.createImageSource(file.fd)
let imagePM = await imageIS.createPixelMap()
let imgInfo = await imagePM.getImageInfo();
videoImageInfo.data = includeBase64 ? this.imageToBase64(compressOrTempFilePath || filePath) : null;
videoImageInfo.height = imgInfo.size.height;
videoImageInfo.width = imgInfo.size.width;
videoImageInfo.exif = exifInfo;
imagePM.release().then(() => {
imagePM = undefined;
})
imageIS.release().then(() => {
imageIS = undefined;
})
} else {
videoImageInfo.mime = 'video/' + imageType;
let url = 'fd://' + file.fd;
Logger.info(`${TAG} start avPlayer url = ${url}`);
avMetadataExtractor = await media.createAVMetadataExtractor();
avMetadataExtractor.fdSrc = { fd: file.fd, offset: 0, length: length };
try {
const res = await avMetadataExtractor.fetchMetadata();
videoImageInfo.duration = res.duration;
videoImageInfo.width = Number(res.videoWidth);
videoImageInfo.height = Number(res.videoHeight);
} catch (error) {
Logger.error(`${TAG} get video info suc error = ${JSON.stringify(error)}`);
}
}
videoImageInfo.cropRect = AppStorage.get('cropRect') as CropRect;
fs.close(file.fd);
return videoImageInfo;
}
async intoCropper(filePath: string, options?: Options | CropperOptions): Promise<string> {
Logger.info(`${TAG} into intoCropper : filePath = ${filePath}`);
let imagePath: string;
const bundleName = this.ctx.uiAbilityContext.abilityInfo.bundleName;
AppStorage.setOrCreate('filePath', filePath);
return new Promise((res, rej) => {
const initWidth: number = this.isNullOrUndefined(options?.width) ? 0 : options?.width;
const initHeight: number = this.isNullOrUndefined(options?.height) ? 0 : options?.height;
const enableRotationGesture: boolean = this.isNullOrUndefined(options?.enableRotationGesture) ? false : options?.enableRotationGesture;
const title: string = this.isNullOrUndefined(options?.cropperToolbarTitle) ? '编辑图片' : options?.cropperToolbarTitle;
const chooseText: string = this.isNullOrUndefined(options?.cropperChooseText) ? 'Choose' : options?.cropperChooseText;
const chooseTextColor: string = this.isNullOrUndefined(options?.cropperChooseColor) ? '#FFCC00' : options?.cropperChooseColor;
const cancelText: string = this.isNullOrUndefined(options?.cropperCancelText) ? 'Cancel' : options?.cropperCancelText;
const cancelTextColor: string = this.isNullOrUndefined(options?.cropperCancelColor) ? '#0000FF' : options?.cropperCancelColor;
const showCropGuidelines: boolean = this.isNullOrUndefined(options?.showCropGuidelines) ? true : options?.showCropGuidelines;
const showCropFrame: boolean = this.isNullOrUndefined(options?.showCropFrame) ? true : options?.showCropFrame;
const freeStyleCropEnabled: boolean = this.isNullOrUndefined(options?.freeStyleCropEnabled) ? false : options?.freeStyleCropEnabled;
const cropperRotate: string = options?.cropperRotateButtonsHidden + '';
AppStorage.setOrCreate('initWidth', initWidth);
AppStorage.setOrCreate('initHeight', initHeight);
AppStorage.setOrCreate('enableRotationGesture', enableRotationGesture);
AppStorage.setOrCreate('textTitle', title);
AppStorage.setOrCreate('chooseText', chooseText);
AppStorage.setOrCreate('chooseTextColor', chooseTextColor);
AppStorage.setOrCreate('cancelText', cancelText);
AppStorage.setOrCreate('cancelTextColor', cancelTextColor);
AppStorage.setOrCreate('cropperRotate', cropperRotate);
AppStorage.setOrCreate('showCropGuidelines', showCropGuidelines);
AppStorage.setOrCreate('showCropFrame', showCropFrame);
AppStorage.setOrCreate('freeStyleCropEnabled', freeStyleCropEnabled);
try {
let want: Want = {
"bundleName": bundleName,
"abilityName": "ImageEditAbility",
}
this.ctx.uiAbilityContext.startAbilityForResult(want, (error, data) => {
imagePath = AppStorage.get('cropImagePath') as string;
AppStorage.setOrCreate('cropImagePath', '')
Logger.info(`${TAG} into intoCropper startAbility suc imagePath = ${imagePath}`);
res(imagePath);
});
} catch (err) {
Logger.error(`${TAG} into intoCropper startAbility err = ${JSON.stringify(err)}`);
res('')
}
});
}
async getImageExif(imageSource: image.ImageSource): Promise<Exif> {
Logger.info(`${TAG} into getImageExif:`);
let bitsPerSample;
let orientation;
let imageLength;
let imageWidth;
let gpsLatitude;
let gpsLongitude;
let gpsLatitudeRef;
let gpsLongitudeRef;
let dateTimeOriginal;
let exposureTime;
let sceneType;
let isoSpeedRatings;
let fNumber;
try {
bitsPerSample = await imageSource.getImageProperty(image.PropertyKey.BITS_PER_SAMPLE);
} catch (err) {
Logger.info(`${TAG} into getImageExif bitsPerSample err = ${JSON.stringify(err)}`);
}
try {
orientation = await imageSource.getImageProperty(image.PropertyKey.ORIENTATION);
} catch (err) {
Logger.info(`${TAG} into getImageExif orientation err = ${JSON.stringify(err)}`);
}
try {
imageLength = await imageSource.getImageProperty(image.PropertyKey.IMAGE_LENGTH);
} catch (err) {
Logger.info(`${TAG} into getImageExif imageLength err = ${JSON.stringify(err)}`);
}
try {
imageWidth = await imageSource.getImageProperty(image.PropertyKey.IMAGE_WIDTH);
} catch (err) {
Logger.info(`${TAG} into getImageExif imageWidth err = ${JSON.stringify(err)}`);
}
try {
gpsLatitude = await imageSource.getImageProperty(image.PropertyKey.GPS_LATITUDE);
} catch (err) {
Logger.info(`${TAG} into getImageExif gpsLatitude err = ${JSON.stringify(err)}`);
}
try {
gpsLongitude = await imageSource.getImageProperty(image.PropertyKey.GPS_LONGITUDE);
} catch (err) {
Logger.info(`${TAG} into getImageExif gpsLongitude err = ${JSON.stringify(err)}`);
}
try {
gpsLatitudeRef = await imageSource.getImageProperty(image.PropertyKey.GPS_LATITUDE_REF);
} catch (err) {
Logger.info(`${TAG} into getImageExif gpsLatitudeRef err = ${JSON.stringify(err)}`);
}
try {
gpsLongitudeRef = await imageSource.getImageProperty(image.PropertyKey.GPS_LONGITUDE_REF);
} catch (err) {
Logger.info(`${TAG} into getImageExif gpsLongitudeRef err = ${JSON.stringify(err)}`);
}
try {
dateTimeOriginal = await imageSource.getImageProperty(image.PropertyKey.DATE_TIME_ORIGINAL);
} catch (err) {
Logger.info(`${TAG} into getImageExif dateTimeOriginal err = ${JSON.stringify(err)}`);
}
try {
exposureTime = await imageSource.getImageProperty(image.PropertyKey.EXPOSURE_TIME);
} catch (err) {
Logger.info(`${TAG} into getImageExif exposureTime err = ${JSON.stringify(err)}`);
}
try {
sceneType = await imageSource.getImageProperty(image.PropertyKey.SCENE_TYPE);
} catch (err) {
Logger.info(`${TAG} into getImageExif sceneType err = ${JSON.stringify(err)}`);
}
try {
isoSpeedRatings = await imageSource.getImageProperty(image.PropertyKey.ISO_SPEED_RATINGS);
} catch (err) {
Logger.info(`${TAG} into getImageExif isoSpeedRatings err = ${JSON.stringify(err)}`);
}
try {
fNumber = await imageSource.getImageProperty(image.PropertyKey.F_NUMBER);
} catch (err) {
Logger.info(`${TAG} into getImageExif fNumber err = ${JSON.stringify(err)}`);
}
return {
BitsPerSample: bitsPerSample,
Orientation: orientation,
ImageLength: imageLength,
ImageWidth: imageWidth,
GPSLatitude: gpsLatitude,
GPSLongitude: gpsLongitude,
GPSLatitudeRef: gpsLatitudeRef,
GPSLongitudeRef: gpsLongitudeRef,
DateTimeOriginal: dateTimeOriginal,
ExposureTime: exposureTime,
SceneType: sceneType,
ISOSpeedRatings: isoSpeedRatings,
FNumber: fNumber,
}
}
grantPermission(): Promise<boolean> {
Logger.info(`${TAG} into grantPermission:`);
const permissions: Array<Permissions> = [
'ohos.permission.READ_MEDIA',
'ohos.permission.WRITE_MEDIA',
'ohos.permission.MEDIA_LOCATION',
];
return new Promise((res, rej) => {
atManager.requestPermissionsFromUser(this.ctx.uiAbilityContext, permissions)
.then((data: PermissionRequestResult) => {
res(data?.authResults[0] === 0)
}).catch((err) => {
res(false)
Logger.info(`${TAG} grantPermission err = ${JSON.stringify(err)}`);
})
});
}
}