1/*
2 * Copyright (c) 2022-2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import {
17  BigDataConstants,
18  BrowserDataFactory,
19  Log,
20  MediaItem,
21  ReportToBigDataUtil,
22  UserFileManagerAccess
23} from '@ohos/common';
24import { ImageFilterStack } from './ImageFilterStack';
25import image from '@ohos.multimedia.image';
26import fileIO from '@ohos.fileio';
27import { DateUtil } from './utils/DateUtil';
28import { Loader } from './Loader';
29import userFileManager from '@ohos.filemanagement.userFileManager';
30import type { Album, FileType } from '@ohos/common/src/main/ets/default/access/UserFileManagerAccess';
31
32const TAG: string = 'editor_Save';
33
34export class Save {
35  private static readonly QUALITY_95: number = 95;
36  private static readonly PERCENT_100: number = 100;
37
38  constructor() {
39  }
40
41  public static async save(item: MediaItem, albumUri: string, optStack: ImageFilterStack, isReplace: Boolean,
42                           callback: Function): Promise<void> {
43    Log.info(TAG, `${JSON.stringify(item)} ${isReplace}`);
44
45    // Get quality of image first, making sure it's succeed even in replace mode
46    let finalQuality: number = await this.getImageQuality(item);
47    Log.debug(TAG, "save: final quality = " + finalQuality);
48    let options = {
49      format: 'image/jpeg',
50      quality: finalQuality
51    };
52
53    try {
54      let wrapper = await Loader.loadPixelMapWrapper(item);
55      wrapper = optStack.apply(wrapper);
56      Log.debug(TAG, 'Edit and restore operation execution end.');
57
58      let fileAsset = await this.createFileAsset(item.uri, albumUri, isReplace);
59      Log.info(TAG, `create fileAsset succeed: [${fileAsset}]`);
60      let fd = await UserFileManagerAccess.getInstance().openAsset('RW', fileAsset);
61      if (fd < 0) {
62        Log.error(TAG, 'open asset failed.');
63        return;
64      }
65
66      let packer = image.createImagePacker();
67      let buffer = await packer.packing(wrapper.pixelMap, options);
68      Log.info(TAG, 'Format pixelMap data to jpg data end.');
69
70      await fileIO.write(fd, buffer);
71      Log.info(TAG, 'write jpg file end.');
72      try {
73        let imageSourceApi: image.ImageSource = image.createImageSource(fd);
74        await new Promise(async (resolve, reject) => {
75          imageSourceApi.modifyImageProperty('CompressedBitsPerPixel', String(finalQuality / Save.PERCENT_100));
76          resolve(Boolean(true));
77        });
78      } catch (e) {
79        Log.error(TAG, 'sth wrong when set CompressedBitsPerPixel value is ' + (finalQuality / Save.PERCENT_100) +
80          ', error is ' + JSON.stringify(e));
81      }
82      let newUri = fileAsset.uri;
83      Log.info(TAG, `New saved file: ${newUri}`);
84      callback && callback(newUri);
85      await UserFileManagerAccess.getInstance().closeAsset(fd, fileAsset);
86      wrapper && wrapper.release();
87      await packer.release();
88
89    } catch (e) {
90      Log.error(TAG, `save catch error: ${JSON.stringify(e)}`);
91      let msg = {
92        'CatchError': JSON.stringify(e)
93      }
94      ReportToBigDataUtil.errEventReport(BigDataConstants.PHOTO_EDIT_SAVE_ERROR_ID, msg);
95      callback && callback(undefined);
96    }
97  }
98
99  private static async createFileAsset(uri: string, albumUri: string,  isReplace: Boolean) {
100    let dataImpl = BrowserDataFactory.getFeature(BrowserDataFactory.TYPE_PHOTO);
101    let fileAsset = await dataImpl.getDataByUri(uri);
102    let batchUris: Array<string> = new Array();
103    batchUris.push(uri)
104    if (!fileAsset) {
105      Log.error(TAG, 'get file error');
106      return null;
107    }
108    let title = DateUtil.nameByDate(isReplace, fileAsset.displayName);
109    if (null == title) {
110      Log.error(TAG, `create picture name failed.`);
111      return null;
112    }
113    let displayName = title + '.jpg';
114    Log.debug(TAG, `file displayname = ${displayName}`);
115    let favorite: boolean = false;
116    if (isReplace) {
117      favorite = Boolean(await fileAsset.get(userFileManager.ImageVideoKey.FAVORITE.toString()));
118      await UserFileManagerAccess.getInstance().deleteToTrash(batchUris);
119      Log.debug(TAG, `trash picture file uri ${uri} end.`);
120    }
121    fileAsset = await UserFileManagerAccess.getInstance().createAsset(fileAsset.mediaType, displayName);
122    await fileAsset.favorite(favorite);
123    let album: Album = await UserFileManagerAccess.getInstance().getAlbumByUri(albumUri);
124    let isSystemAlbum = UserFileManagerAccess.getInstance().isSystemAlbum(album);
125    if (!isSystemAlbum) {
126      await UserFileManagerAccess.getInstance().addFileToAlbum(albumUri, fileAsset);
127    }
128    return fileAsset;
129  }
130
131  private static async getImageQuality(item: MediaItem): Promise<number> {
132    let quality = undefined;
133    try {
134      quality = await Loader.getCompressedBitsPerPixel(item);
135    } catch (e) {
136      Log.error(TAG, "sth wrong with getCompressedBitsPerPixel" + e);
137    }
138    let finalQuality: number = quality !== undefined ? quality * Save.PERCENT_100 : Save.QUALITY_95
139    Log.debug(TAG, "save: final quality = " + finalQuality);
140    return finalQuality;
141  }
142}