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 { Constants, Log, MediaItem } from '@ohos/common';
17import { PhotoEditCrop } from './crop/PhotoEditCrop';
18import { PhotoEditBase } from './base/PhotoEditBase';
19import { PhotoEditMode } from './base/PhotoEditType';
20import { PixelMapWrapper } from './base/PixelMapWrapper';
21import { ImageFilterStack } from './ImageFilterStack';
22import { Loader } from './Loader';
23import { Save } from './Save';
24import image from '@ohos.multimedia.image';
25
26const TAG: string = 'editor_PhotoEditorManager';
27
28export class PhotoEditorManager {
29  private currentMode: PhotoEditMode = PhotoEditMode.EDIT_MODE_MAIN;
30  private origin: PixelMapWrapper = undefined;
31  private item: MediaItem = undefined;
32  private editors: Array<PhotoEditBase> = undefined;
33  private historyManager: ImageFilterStack = undefined;
34  private albumUri: string = '';
35
36  private constructor() {
37    this.historyManager = new ImageFilterStack();
38    this.editors = [];
39    this.editors[PhotoEditMode.EDIT_MODE_CROP] = new PhotoEditCrop();
40  }
41
42  static getInstance(): PhotoEditorManager {
43    if (AppStorage.get(Constants.PHOTO_EDITOR_MANAGER) == null) {
44      AppStorage.setOrCreate(Constants.PHOTO_EDITOR_MANAGER, new PhotoEditorManager());
45    }
46    return AppStorage.get(Constants.PHOTO_EDITOR_MANAGER);
47  }
48
49  initialize(item: MediaItem, albumUri: string, mode: PhotoEditMode, errCallback?: Function): void {
50    Log.info(TAG, `initialize mode[${mode}], item[${item.uri}] albumUri = ${albumUri}`);
51    this.item = item;
52    this.albumUri = albumUri;
53    Loader.loadPixelMapWrapper(item, true).then((pixelMap) => {
54      if (pixelMap) {
55        this.origin = pixelMap;
56        (this.historyManager as ImageFilterStack).setOriginPixelMap(this.origin);
57        this.switchMode(mode);
58      } else {
59        Log.error(TAG, 'initialize loadPixelMapWrapper failed');
60        errCallback && errCallback();
61      }
62    });
63  }
64
65  clear(): void {
66    Log.debug(TAG, 'clear');
67    this.item = undefined;
68    if (this.origin) {
69      this.origin.release();
70      this.origin = undefined;
71    }
72    this.historyManager.setOriginPixelMap(undefined);
73    this.historyManager.releaseAll();
74    if (this.editors[this.currentMode]) {
75      this.editors[this.currentMode].clearCanvas();
76    }
77    this.currentMode = PhotoEditMode.EDIT_MODE_MAIN;
78  }
79
80  getPhotoEditBaseInstance(mode: PhotoEditMode): PhotoEditBase {
81    return this.editors[mode];
82  }
83
84  getLastestPixelMap(): PixelMapWrapper {
85    let position = this.historyManager.getPosition();
86    Log.debug(TAG, `getLastestPixelMap position = ${position}`);
87    if (position < 0) {
88      return this.origin;
89    } else {
90      return this.historyManager.at(position).getCache();
91    }
92  }
93
94  switchMode(mode: PhotoEditMode): PhotoEditMode {
95    Log.info(TAG, `switchMode: currentMode[${this.currentMode}] mode[${mode}]`);
96    if (this.currentMode == mode) {
97      return mode;
98    }
99
100    // exit current edit mode
101    if (this.editors[this.currentMode] != undefined) {
102      const filter = this.editors[this.currentMode].exit();
103      if (filter != undefined) {
104        // save cache
105        if (!filter.isCached()) {
106          const prePixelMap = this.getLastestPixelMap();
107          filter.setCache(filter.render(prePixelMap));
108        }
109        this.historyManager.push(filter);
110      }
111    }
112
113    // entry next edit mode
114    let input = this.getLastestPixelMap();
115    if (input && this.editors[mode] != undefined) {
116      this.editors[mode].entry(input);
117      // update current edit mode
118      this.currentMode = mode;
119    }
120    return this.currentMode;
121  }
122
123  isRedoValid(): boolean {
124    return this.historyManager.isRedoValid();
125  }
126
127  isUndoValid(): boolean {
128    return this.historyManager.isUndoValid();
129  }
130
131  redo(): boolean {
132    if (this.isRedoValid() && this.editors[this.currentMode] != undefined) {
133      let newPixel = this.historyManager.doRedo();
134      this.editors[this.currentMode].entry(newPixel);
135      return true;
136    }
137
138    return false;
139  }
140
141  undo(): boolean {
142    if (this.isUndoValid() && this.editors[this.currentMode] != undefined) {
143      let newPixel = this.historyManager.doUndo();
144      this.editors[this.currentMode].entry(newPixel);
145      return true;
146    }
147
148    return false;
149  }
150
151  save(isReplace: boolean, callback: Function): void {
152    if (this.editors[this.currentMode] != undefined) {
153      Log.info(TAG, `save enter isReplace = ${isReplace}`);
154      AppStorage.setOrCreate('isReplace', isReplace);
155      AppStorage.setOrCreate('isEditFunc', true);
156      const filter = this.editors[this.currentMode].exit();
157      if (filter != undefined) {
158        this.historyManager.push(filter);
159      }
160      Save.save(this.item as MediaItem, this.albumUri, this.historyManager as ImageFilterStack, isReplace, callback);
161    } else {
162      Log.warn(TAG, `editor is undefined, currentMode = ${this.currentMode}`);
163    }
164  }
165}