• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2023-2024 Hunan OpenValley Digital Industry Development 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 */
15import router from '@ohos.router';
16import image from '@ohos.multimedia.image';
17import effectKit from '@ohos.effectKit';
18import { logger } from '../util/Logger';
19import { copyPixelMap } from '../util/CopyObj';
20import { adjustDatas, cropTaskDatas, markDatas } from '../model/AdjustModels';
21import { CropTasks, TaskData, Tasks } from '../model/AdjustData';
22import { ScalePhotoPage } from '../pages/ScalePhotoPage';
23import MaterialEdit from './MaterialEdit';
24import InputTextDialog from './InputTextDialog';
25import LoadingDialog from './LoadingDialog';
26import PixelMapQueue from '../feature/PixelMapQueue';
27import { readImage } from '../util/MediaUtil';
28import { FontColorData, MaterialData } from '../model/MaterialData';
29import { fontColors, stickers } from '../model/MaterialModels';
30import { getContainSize, Size } from '../util/ImageUtil';
31import { savePixelMap } from '../util/FileUtil';
32import { BusinessError } from '@ohos.base';
33import { ColorSpacePage } from './ColorSpacePage';
34
35const TAG: string = '[Sample_EditImage]';
36
37@Component
38export struct EditImage {
39  @State mediaUris: string = (router.getParams() as Record<string, Object>)['mediaUris'] as string;
40  @State pixelMap: PixelMap | undefined | null = undefined;
41  @State isPixelMapChange: boolean = false;
42  @State adjustMarkJudg: boolean = false;
43  @State currentTask: number = Tasks.ADJUST;
44  @State currentCropTask: number = CropTasks.NONE;
45  @State tempPixelMap: image.PixelMap | undefined | null = undefined;
46  @State cancelOkText: Resource | null = null;
47  @State brightnessValue: number = 0;
48  @State outSetValueOne: number = 0;
49  @State cropFlag: boolean = false;
50  @State originBM: PixelMap | null = null;
51  @State cropIndex: number = 0;
52  @State canClick: boolean = true;
53  @State loading: boolean = false;
54  @State isCancelQuit: boolean = false;
55  // 选择资源素材index
56  @State resourceIndex: number = 0;
57  @State isTextMaterial: boolean = true;
58  selectedResource: Resource | undefined = $r('app.color.material_font_color_one');
59  scroller: Scroller = new Scroller();
60  brightnessOriginBM: PixelMap | null | undefined = null;
61  @State resolution: string = '';
62  @State inputValue: string = '';
63  // 分辨率的坐标
64  @State dpiX: number = 0;
65  @State dpiY: number = 0;
66  // 分辨率
67  @State dpi: string = '';
68  // 图片父容器大小
69  containerArea: Area | null = null;
70  isBrightChanging: boolean = false;
71  private pixelMapQueue: PixelMapQueue = new PixelMapQueue();
72
73  onOpen(): void {
74    if (this.loading) {
75      return;
76    }
77    this.loading = true;
78    this.loadingController.open();
79  }
80
81  onClose(): void {
82    this.loading = false;
83    this.loadingController.close();
84  }
85
86
87  /**
88   * 输入文字素材弹窗
89   */
90  dialogController: CustomDialogController | undefined = new CustomDialogController({
91    builder: InputTextDialog({
92      cancel: (): void => this.onCancel(),
93      confirm: (): void => this.onAccept(),
94      inputValue: $inputValue
95    }),
96    cancel: (): void => this.existApp(),
97    autoCancel: true,
98    alignment: DialogAlignment.Bottom,
99    offset: { dx: 0, dy: -20 },
100    gridCount: 4,
101    customStyle: false
102  });
103
104  /**
105   * 加载弹窗
106   */
107  loadingController: CustomDialogController = new CustomDialogController({ builder: LoadingDialog() });
108
109  onCancel(): void {
110    console.info('Callback when the first button is clicked');
111  }
112
113  onAccept(): void {
114    if (this.inputValue !== null && this.inputValue.length > 0) {
115      this.currentTask = Tasks.TEXT;
116    }
117    this.onSelectItem(markDatas[0], true);
118  }
119
120  async onEditCancel(): Promise<void> {
121    if (this.currentTask === Tasks.CROP || this.currentTask === Tasks.SCALE ||
122    this.currentTask === Tasks.ROTATE || this.currentTask === Tasks.TONING || this.currentTask === Tasks.COLORSPACE ||
123      this.currentTask === Tasks.HDR) {
124      this.currentTask = Tasks.ADJUST;
125      this.cropIndex = 0;
126    }
127    if (this.currentTask === Tasks.TEXT || this.currentTask === Tasks.STICKER) {
128      this.currentTask = Tasks.MARK;
129      this.cropIndex = 0;
130    }
131    if (this.currentTask === Tasks.CROP) {
132      this.cropIndex = 0;
133    }
134    this.isCancelQuit = true;
135    this.outSetValueOne = 0;
136    if (this.originBM !== null && this.originBM !== undefined) {
137      this.pixelMap = await copyPixelMap(this.originBM); // 拷贝
138    }
139    // 移出一个缓存
140    const pm: image.PixelMap | undefined = this.pixelMapQueue.pop();
141    this.releasePm(pm);
142  }
143
144  existApp(): void {
145    console.info('Click the callback in the blank area')
146  }
147
148  async aboutToAppear(): Promise<void> {
149    this.pixelMap = await readImage(this.mediaUris);
150    if (this.pixelMap !== null) {
151      this.originBM = await copyPixelMap(this.pixelMap);
152    }
153  }
154
155  aboutToDisappear(): void {
156    this.dialogController = undefined
157  }
158
159  /**
160   * 选择子菜单
161   * @param item
162   * @param hasInputText
163   */
164  async onSelectItem(item: TaskData, hasInputText: boolean = false): Promise<void> {
165    if (hasInputText || item.task !== Tasks.TEXT) {
166      // 只有hasInputText,才是图片素材模式,其他都是贴纸
167      this.isTextMaterial = hasInputText;
168      this.resourceIndex = 0;
169      this.isCancelQuit = false;
170      if (item.task !== undefined) {
171        this.currentTask = item.task;
172      }
173
174      if (item.text !== undefined) {
175        this.cancelOkText = item.text;
176      }
177
178      if (this.pixelMap) {
179        this.originBM = await copyPixelMap(this.pixelMap);
180      }
181      // 保存到队列
182      if (this.originBM !== null && this.originBM !== undefined) {
183        this.pixelMapQueue.push(this.originBM);
184      }
185    } else if (item.task === Tasks.TEXT) {
186      this.dialogController?.open();
187    }
188  }
189
190  /**
191   * 撤销功能
192   */
193  async repeal(): Promise<void> {
194    const pm: image.PixelMap | undefined = this.pixelMapQueue.pop();
195    if (pm !== null && pm !== undefined) {
196      this.pixelMap = await copyPixelMap(pm);
197    }
198  }
199
200  /**
201   * 刷新图层显示
202   */
203  flushPage(): void {
204    this.tempPixelMap = this.pixelMap;
205    this.pixelMap = null;
206    this.pixelMap = this.tempPixelMap;
207  }
208
209  /**
210   * 亮度调节
211   */
212  async brightChange(): Promise<void> {
213    let headFilter: effectKit.Filter = effectKit.createEffect(this.brightnessOriginBM);
214    if (headFilter !== null && !this.isBrightChanging) {
215      this.isBrightChanging = true;
216      headFilter.brightness(this.brightnessValue);
217      this.pixelMap = await headFilter.getEffectPixelMap();
218      this.flushPage();
219      this.isBrightChanging = false;
220    }
221  }
222
223  /**
224   * 图片裁剪
225   * @param proportion
226   */
227  async cropImage(proportion: number): Promise<void> {
228    if (!this.pixelMap) {
229      return;
230    }
231    let imageInfo: image.ImageInfo = await this.pixelMap.getImageInfo();
232    if (!imageInfo) {
233      return;
234    }
235    let imageHeight: number = imageInfo.size.height;
236    let imageWith: number = imageInfo.size.width;
237    logger.info(TAG, `imageInfo = ${JSON.stringify(imageInfo)}`);
238    if (proportion === 1) {
239      if (imageHeight > imageWith) {
240        imageHeight = imageWith;
241      } else {
242        imageWith = imageHeight;
243      }
244      logger.info(TAG, `imageHeight = ${JSON.stringify(imageHeight)},imageWith = ${JSON.stringify(imageWith)}`);
245    }
246    try {
247      await this.pixelMap.crop({
248        size: { height: imageHeight / proportion, width: imageWith },
249        x: 0,
250        y: 0
251      });
252      this.isPixelMapChange = !this.isPixelMapChange;
253    } catch (error) {
254      logger.info(TAG, `crop error = ${JSON.stringify(error)}`);
255    }
256    this.flushPage();
257  }
258
259  /**
260   * 图片解码
261   * @param decodingType 解码类型(0:AUTO,1:SDR,2:HDR)
262   * @returns
263   */
264  async decodingImage(decodingType:number):Promise<void>{
265    logger.info(TAG, 'decodingImage start');
266    let decodingOptions: image.DecodingOptions = {
267      editable: true,
268      desiredDynamicRange: decodingType
269    };
270    logger.debug(TAG, `decoding Type = ${decodingType}`);
271    this.pixelMap = await readImage(this.mediaUris, decodingOptions);
272    if (!this.pixelMap) {
273      logger.info(TAG, `decodingImage no pixelMap`);
274      return;
275    }
276    let imageInfo: image.ImageInfo = await this.pixelMap.getImageInfo();
277    if (!imageInfo) {
278      logger.info(TAG, `decodingImage no imageInfo`);
279      return;
280    }
281    logger.debug(TAG, `imageInfo = ${JSON.stringify(imageInfo)}`);
282    logger.debug(TAG, `imageInfo.isHdr  = ${imageInfo.isHdr}`);
283    this.flushPage();
284    logger.info(TAG, 'decodingImage end');
285  }
286
287  /**
288   * 底部一级菜单
289   */
290  @Builder
291  getFirstLvMenu() {
292    Row() {
293      Column() {
294        Image($r('app.media.ic_adjust'))
295          .width($r('app.float.size_30'))
296          .height($r('app.float.size_30'))
297        Text($r('app.string.edit_image_adjust'))
298          .fontColor(Color.White)
299          .fontSize($r('app.float.size_16'))
300      }
301      .justifyContent(FlexAlign.Center)
302      .height('100%')
303      .width('40%')
304      .margin({ left: '10%' })
305      .backgroundColor(this.adjustMarkJudg ? Color.Black : $r('app.color.edit_image_adjust_selected'))
306      .onClick(async () => {
307        this.adjustMarkJudg = false;
308        this.currentTask = Tasks.ADJUST;
309      })
310
311      Column() {
312        Image($r('app.media.ic_mark'))
313          .width($r('app.float.size_30'))
314          .height($r('app.float.size_30'))
315        Text($r('app.string.edit_image_mark'))
316          .fontColor(Color.White)
317          .fontSize($r('app.float.size_16'))
318      }
319      .justifyContent(FlexAlign.Center)
320      .onClick(() => {
321        this.adjustMarkJudg = true;
322        this.currentTask = Tasks.MARK;
323      })
324      .backgroundColor(this.adjustMarkJudg ? $r('app.color.edit_image_adjust_selected') : Color.Black)
325      .height('100%')
326      .width('40%')
327      .margin({ right: '10%' })
328    }
329    .height('9%')
330    .width('100%')
331  }
332
333  releasePm(pm: PixelMap | undefined): void {
334    if (!pm) {
335      return
336    }
337
338    pm.release().catch((err: BusinessError) => {
339      logger.error('pm release异常:' + JSON.stringify(err));
340    });
341  }
342
343  @Builder
344  CancelOrOk(text: string | Resource) {
345    Row() {
346      Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
347        Image($r('app.media.ic_cancel'))
348          .width($r('app.float.size_30'))
349          .height($r('app.float.size_30'))
350          .onClick(async () => {
351            await this.onEditCancel();
352          })
353          .margin({ left: '10%' })
354          .id('Cancel')
355
356        Text(text)
357          .fontColor(Color.White)
358          .fontSize($r('app.float.size_18'))
359          // 根据【修改hdr设置】文字长度,设置宽度为120
360          .width(this.currentTask === Tasks.HDR ? $r('app.float.size_120') : $r('app.float.size_40'))
361          .height($r('app.float.size_30'))
362
363        Image($r('app.media.ic_ok'))
364          .width($r('app.float.size_30'))
365          .height($r('app.float.size_30'))
366          .margin({ right: '10%' })
367          .onClick(() => {
368            if (this.currentTask === Tasks.CROP || this.currentTask === Tasks.SCALE ||
369            this.currentTask === Tasks.ROTATE || this.currentTask === Tasks.TONING || this.currentTask === Tasks.COLORSPACE ||
370              this.currentTask === Tasks.HDR) {
371              this.currentTask = Tasks.ADJUST;
372              this.cropIndex = 0;
373            }
374            if (this.currentTask === Tasks.TEXT || this.currentTask === Tasks.STICKER) {
375              this.currentTask = Tasks.MARK;
376              this.cropIndex = 0;
377            }
378            this.outSetValueOne = 0;
379          })
380          .id('Ok')
381      }
382    }
383    .backgroundColor($r('app.color.edit_image_public_background'))
384    .height('9%')
385    .width('100%')
386  }
387
388  @Builder
389  getMarkTool() {
390    Row() {
391      List() {
392        ForEach(markDatas, (item: TaskData, index) => {
393          ListItem() {
394            Column() {
395              Image(item.image)
396                .width($r('app.float.size_30'))
397                .height($r('app.float.size_30'))
398              Text(item.text)
399                .fontColor(Color.White)
400                .fontSize($r('app.float.size_15'))
401                .margin({ top: $r('app.float.size_5') })
402            }
403            .justifyContent(FlexAlign.Center)
404            .height('100%')
405            .width('40%')
406            .margin(index === 0 ? { left: '10%' } : { right: '10%' })
407            .onClick(async () => {
408              this.onSelectItem(item);
409            })
410          }
411        })
412      }
413      .listDirection(Axis.Horizontal) // 排列方向
414      .width('100%')
415      .height('14%')
416    }
417    .backgroundColor($r('app.color.edit_image_public_background'))
418    .width('100%')
419    .justifyContent(FlexAlign.SpaceAround)
420    .alignItems(VerticalAlign.Center)
421  }
422
423  @Builder
424  getCropTool() {
425    Row() {
426      List() {
427        ForEach(cropTaskDatas, (item: TaskData, index: number) => {
428          ListItem() {
429            Column() {
430              Image(item.image)
431                .width($r('app.float.size_30'))
432                .height($r('app.float.size_30'))
433              Text(item.text)
434                .fontColor(Color.White)
435                .fontSize($r('app.float.size_14'))
436                .margin({ top: $r('app.float.size_5') })
437            }
438            .backgroundColor(this.cropIndex === index ? $r('app.color.edit_image_public_background') : $r('app.color.edit_image_crop_select'))
439            .justifyContent(FlexAlign.Center)
440            .height('100%')
441            .width('25%')
442            .onClick(async () => {
443              if (item.task !== undefined) {
444                this.currentCropTask = item.task;
445              }
446              this.pixelMap = await copyPixelMap(this.originBM !); // 拷贝
447              this.flushPage();
448              this.cropIndex = index;
449              if (this.currentCropTask === CropTasks.ORIGIN) { // 原图
450                this.pixelMap = await readImage(this.mediaUris);
451              } else if (this.currentCropTask === CropTasks.ONE_ONE) {
452                await this.cropImage(1);
453                console.log(TAG + 'CropTasks  this.cropImage(1)' + this.currentCropTask)
454              } else if (this.currentCropTask === CropTasks.THREE_FOUR) {
455                await this.cropImage(4 / 3);
456                console.log(TAG + 'CropTasks  cropImage(4 / 3)==' + this.currentTask)
457              } else if (this.currentCropTask === CropTasks.NINE_SIXTH) {
458                await this.cropImage(16 / 9);
459                console.log(TAG + 'CropTasks  (16 / 9)==' + this.currentTask);
460              }
461            })
462          }
463        })
464      }
465      .listDirection(Axis.Horizontal) // 排列方向
466      .height('14%')
467    }
468    .width('100%')
469  }
470
471  @Builder
472  getAdjustTool() {
473    Row() {
474      List() {
475        ForEach(adjustDatas, (item: TaskData, index) => {
476          ListItem() {
477            Column() {
478              Image(item.image)
479                .width($r('app.float.size_30'))
480                .height($r('app.float.size_30'))
481              Text(item.text)
482                .fontColor(Color.White)
483                .fontSize($r('app.float.size_15'))
484                .margin({ top: $r('app.float.size_5') })
485            }
486            .justifyContent(FlexAlign.Center)
487            .height('100%')
488            .width('25%')
489            .onClick(async () => {
490              if (item.task !== undefined) {
491                this.currentTask = item.task;
492              }
493              if (this.currentTask === Tasks.SCALE) {
494                this.computeDpiPosition();
495              }
496              if (item.task === Tasks.TONING) {
497                this.brightnessOriginBM = this.pixelMap;
498              }
499              if (item.text !== undefined) {
500                this.cancelOkText = item.text
501              }
502              this.originBM = await copyPixelMap(this.pixelMap !) // 拷贝
503              if (item.task === Tasks.ROTATE) {
504                this.pixelMap = await copyPixelMap(this.originBM);
505              }
506              // 保存到队列
507              this.pixelMapQueue.push(this.originBM);
508            })
509          }
510        })
511      }
512      .listDirection(Axis.Horizontal) // 排列方向
513      .height('14%')
514    }
515    .backgroundColor($r('app.color.edit_image_public_background'))
516    .width('100%')
517    .justifyContent(FlexAlign.SpaceAround)
518    .alignItems(VerticalAlign.Center)
519  }
520
521  @Builder
522  getScaleTool() {
523    ScalePhotoPage({ pixelMap: $pixelMap, dpi: $dpi })
524      .backgroundColor($r('app.color.edit_image_public_background'))
525      .height('14%')
526      .width('100%')
527      .padding({ top: $r('app.float.size_10') })
528  }
529
530  @Builder
531  getColorSpaceTool() {
532    ColorSpacePage({ pixelMap: $pixelMap, dpi: $dpi })
533      .backgroundColor($r('app.color.edit_image_public_background'))
534      .height('14%')
535      .width('100%')
536      .padding({ top: $r('app.float.size_10') })
537  }
538
539  @Builder
540  getRotateTool() {
541    Row() {
542      Column() {
543        Image($r('app.media.ic_rotateto90'))
544          .width($r('app.float.size_30'))
545          .height($r('app.float.size_30'))
546        Text($r('app.string.edit_image_rotate_90'))
547          .margin({ top: $r('app.float.size_5') })
548          .fontSize($r('app.float.size_14'))
549          .fontColor(Color.White)
550      }
551      .id('90')
552      .onClick(async () => {
553        if (this.canClick) {
554          this.canClick = false;
555          this.isPixelMapChange = true;
556          await this.pixelMap?.rotate(90);
557          setTimeout(() => {
558            this.canClick = true;
559            this.isPixelMapChange = false;
560            this.flushPage();
561          }, 300)
562        }
563      })
564
565      Column() {
566        Image($r('app.media.ic_rotate'))
567          .width($r('app.float.size_30'))
568          .height($r('app.float.size_30'))
569        Text($r('app.string.edit_image_rotate_down_90'))
570          .margin({ top: $r('app.float.size_5') })
571          .fontColor(Color.White)
572          .fontSize($r('app.float.size_14'))
573      }
574      .id('-90')
575      .onClick(async () => {
576        if (this.canClick) {
577          this.canClick = false;
578          this.isPixelMapChange = true;
579          await this.pixelMap?.rotate(-90);
580          setTimeout(() => {
581            this.canClick = true;
582            this.isPixelMapChange = false;
583            this.flushPage();
584          }, 300);
585        }
586      })
587    }
588    .justifyContent(FlexAlign.SpaceAround)
589    .backgroundColor($r('app.color.edit_image_public_background'))
590    .height('14%')
591    .width('100%')
592  }
593
594  @Builder
595  getToningTool() {
596    Row() {
597      Row() {
598        Slider({
599          value: this.outSetValueOne,
600          min: 0,
601          max: 30,
602          style: SliderStyle.OutSet,
603        })
604          .id('Slider')
605          .trackThickness($r('app.float.size_5'))
606          .trackColor($r('app.color.edit_image_slider_trackColor'))
607          .selectedColor($r('app.color.edit_image_slider_selected'))
608          .onChange(async (value: number, mode: SliderChangeMode) => {
609            this.outSetValueOne = value;
610            this.brightnessValue = Number((value / 100).toFixed(2));
611            await this.brightChange();
612          })
613      }
614      .height('14%')
615      .width('96%')
616    }
617    .justifyContent(FlexAlign.Center)
618    .width('100%')
619    .backgroundColor($r('app.color.edit_image_public_background'))
620  }
621
622  @Builder
623  getDecodingTool(){
624    Row() {
625      Column() {
626        Image($r('app.media.ic_rotate'))
627          .width($r('app.float.size_30'))
628          .height($r('app.float.size_30'))
629        Text($r('app.string.edit_image_decoding_auto'))
630          .margin({ top: $r('app.float.size_5') })
631          .fontSize($r('app.float.size_14'))
632          .fontColor(Color.White)
633      }
634      .id('auto')
635      .onClick(async () => {
636        if (this.canClick) {
637          this.canClick = false;
638          await this.decodingImage(image.DecodingDynamicRange.AUTO);
639          setTimeout(() => {
640            this.canClick = true;
641          }, 300)
642        }
643      })
644
645      Column() {
646        Image($r('app.media.ic_rotateto90'))
647          .width($r('app.float.size_30'))
648          .height($r('app.float.size_30'))
649        Text($r('app.string.edit_image_decoding_sdr'))
650          .margin({ top: $r('app.float.size_5') })
651          .fontColor(Color.White)
652          .fontSize($r('app.float.size_14'))
653      }
654      .id('sdr')
655      .onClick(async () => {
656        if (this.canClick) {
657          this.canClick = false;
658          await this.decodingImage(image.DecodingDynamicRange.SDR);
659          setTimeout(() => {
660            this.canClick = true;
661          }, 300)
662        }
663      })
664
665      Column() {
666        Image($r('app.media.ic_rotateto90'))
667          .width($r('app.float.size_30'))
668          .height($r('app.float.size_30'))
669        Text($r('app.string.edit_image_decoding_hdr'))
670          .margin({ top: $r('app.float.size_5') })
671          .fontColor(Color.White)
672          .fontSize($r('app.float.size_14'))
673      }
674      .id('hdr')
675      .onClick(async () => {
676        if (this.canClick) {
677          this.canClick = false;
678          await this.decodingImage(image.DecodingDynamicRange.HDR);
679          setTimeout(() => {
680            this.canClick = true;
681          }, 300)
682        }
683      })
684    }
685    .justifyContent(FlexAlign.SpaceAround)
686    .backgroundColor($r('app.color.edit_image_public_background'))
687    .height('14%')
688    .width('100%')
689  }
690
691  @Builder
692  getMaterialTool(materials: MaterialData[]) {
693    this.TextOrStickerScroll(materials)
694  }
695
696  async onSave(): Promise<void> {
697    if (this.loading) {
698      return;
699    }
700    this.loading = true;
701    this.loadingController.open();
702
703    if (this.pixelMap !== undefined && this.pixelMap !== null) {
704      const uri: string = await savePixelMap(getContext(this), this.pixelMap);
705      logger.debug('保存图片地址为:' + uri);
706      router.pushUrl({
707        url: 'pages/Index',
708        params: { isShowCamera: true }
709      });
710      this.onClose();
711    }
712
713  }
714
715  @Builder
716  TextOrStickerScroll(materials: MaterialData[]) {
717    Row() {
718      Scroll() {
719        List({ scroller: this.scroller }) {
720          ForEach(materials, (item: MaterialData, index: number) => {
721            ListItem() {
722              Column() {
723                if (item instanceof FontColorData) {
724                  Text(item.getResource())
725                    .visibility(Visibility.Hidden)
726                    .width($r('app.float.size_40'))
727                    .height($r('app.float.size_40'))
728                } else {
729                  Image(item.getResource())
730                    .width($r('app.float.size_40'))
731                    .height($r('app.float.size_40'))
732                }
733              }
734              .justifyContent(FlexAlign.Center)
735              .borderRadius($r('app.float.size_10'))
736              .border(this.resourceIndex === index ?
737                {
738                  width: $r('app.float.size_3'),
739                  color: $r('app.color.edit_image_mark_scroll_selected'),
740                  radius: $r('app.float.size_10')
741                } : { width: $r('app.float.size_0'), color: $r('app.color.edit_image_mark_scroll') })
742              .backgroundColor(this.currentTask === Tasks.STICKER ? $r('app.color.edit_image_mark_scroll') : item.getResource())
743              .width($r('app.float.size_45'))
744              .height($r('app.float.size_45'))
745              .onClick(() => {
746                this.resourceIndex = index;
747                this.selectedResource = item.getResource();
748              })
749            }
750            .height('100%')
751            .width('17%')
752          })
753        }
754        .listDirection(Axis.Horizontal) // 排列方向
755        .height('14%')
756      }
757      .padding({ left: $r('app.float.size_30'), right: $r('app.float.size_30') })
758      .scrollBar(BarState.Off)
759      .scrollable(ScrollDirection.Horizontal)
760    }
761    .alignItems(VerticalAlign.Center)
762    .backgroundColor($r('app.color.edit_image_public_background'))
763    .width('100%')
764  }
765
766  @Builder
767  getTopBar() {
768    Row() {
769      Image($r("app.media.ic_public_back"))
770        .fillColor(Color.White)
771        .width($r('app.float.size_32'))
772        .height($r('app.float.size_32'))
773        .onClick(() => {
774          router.back();
775        })
776      Blank()
777      // 非编辑模式,展示撤回和保存功能
778      if (this.currentTask === Tasks.ADJUST || this.currentTask === Tasks.MARK || this.currentTask === Tasks.HDR) {
779        Row({ space: 24 }) {
780          Image($r('app.media.ic_public_tosmall'))
781            .height($r('app.float.size_32'))
782            .width($r('app.float.size_32'))
783            .id('Repeal')
784            .onClick(async () => {
785              this.repeal();
786            })
787          Image($r('app.media.ic_public_save'))
788            .height($r('app.float.size_32'))
789            .width($r('app.float.size_32'))
790            .id('Save')
791            .onClick(() => {
792              this.onSave();
793            })
794        }.margin({ right: $r('app.float.size_10') })
795      }
796    }
797    .width('100%')
798    .padding({ left: $r('app.float.size_14') })
799    .margin({ top: $r('app.float.size_20') });
800  }
801
802  async computeDpiPosition(): Promise<void> {
803    if (this.containerArea === null || this.pixelMap === null || this.pixelMap === undefined) {
804      return;
805    }
806    let imageInfo: image.ImageInfo = await this.pixelMap.getImageInfo()
807    if (!imageInfo) {
808      return;
809    }
810    const imageHeight: number = imageInfo.size.height;
811    const imageWith: number = imageInfo.size.width;
812    this.dpi = imageWith + "*" + imageHeight;
813    // 计算容器宽高
814    const containerHeight: number = Number(this.containerArea.height);
815    const containerWidth: number = Number(this.containerArea.width);
816    const size: Size = getContainSize(containerWidth * 0.9, containerHeight * 0.9, imageWith, imageHeight);
817    // 计算左上角的坐标
818    const x: number = containerWidth / 2 + size.width / 2;
819    const y: number = containerHeight / 2 - size.height / 2;
820    // 最终坐标还要除去控件宽高
821    this.dpiX = x - 100;
822    this.dpiY = y;
823  }
824
825  getResolutionText(): string {
826    const tip: string = getContext(this).resourceManager.getStringSync($r('app.string.edit_image_resolution'));
827    return tip + this.dpi;
828  }
829
830  build() {
831    Column() {
832      // 顶部功能区
833      this.getTopBar()
834      // 中间操作区
835      if (this.currentTask === Tasks.TEXT || this.currentTask === Tasks.STICKER) {
836        Stack() {
837          MaterialEdit({
838            pixelMap: $pixelMap,
839            text: this.inputValue,
840            isCancelQuit: this.isCancelQuit,
841            selectedMaterialIndex: this.resourceIndex,
842            isTextMaterial: this.isTextMaterial,
843            onCancel: (): Promise<void> => this.onEditCancel(),
844            onOpen: (): void => this.onOpen(),
845            onClose: (): void => this.onClose()
846          }).width('90%')
847            .height('90%')
848        }.layoutWeight(1)
849      } else {
850        Stack({ alignContent: Alignment.Center }) {
851          if (this.isPixelMapChange) {
852            Image(this.pixelMap)
853              .objectFit(ImageFit.Contain)
854              .width('90%')
855              .height('90%')
856              .dynamicRangeMode(DynamicRangeMode.HIGH)
857              .backgroundColor($r('app.color.edit_image_stack_image'))
858              .alignRules(
859                {
860                  middle: { anchor: '__container__', align: HorizontalAlign.Center },
861                  center: { anchor: '__container__', align: VerticalAlign.Center }
862                }
863              )
864              .id('image')
865          } else {
866            Image(this.pixelMap)
867              .objectFit(ImageFit.Contain)
868              .width('90%')
869              .height('90%')
870              .dynamicRangeMode(DynamicRangeMode.HIGH)
871              .backgroundColor($r('app.color.edit_image_stack_image'))
872              .alignRules(
873                {
874                  middle: { anchor: '__container__', align: HorizontalAlign.Center },
875                  center: { anchor: '__container__', align: VerticalAlign.Center }
876                }
877              )
878              .id('image')
879          }
880
881          if (this.currentTask === Tasks.SCALE) {
882            Stack() {
883              Text(this.getResolutionText())
884                .fontSize($r('app.float.size_14'))
885                .fontColor(Color.White)
886            }
887            .position({ x: this.dpiX, y: this.dpiY })
888            .backgroundColor($r('app.color.edit_image_stack_resolution'))
889            .id('dpi')
890            .padding($r('app.float.size_10'))
891            .width($r('app.float.size_100'))
892            .height($r('app.float.size_80'))
893            .border({ radius: $r('app.float.size_20') })
894          }
895        }.layoutWeight(1)
896        .onAreaChange((oldValue: Area, newValue: Area) => {
897          this.containerArea = newValue;
898        })
899      }
900      // 底部菜单栏
901      Column() {
902        if (this.currentTask === Tasks.MARK) {
903          this.getMarkTool();
904        } else if (this.currentTask === Tasks.ADJUST) {
905          this.getAdjustTool();
906        } else if (this.currentTask === Tasks.CROP) {
907          this.getCropTool();
908        } else if (this.currentTask === Tasks.SCALE) {
909          this.getScaleTool();
910        } else if (this.currentTask === Tasks.ROTATE) {
911          this.getRotateTool();
912        } else if (this.currentTask === Tasks.TONING) {
913          this.getToningTool();
914        } else if (this.currentTask === Tasks.COLORSPACE) {
915          this.getColorSpaceTool();
916        } else if (this.currentTask === Tasks.TEXT) {
917          this.getMaterialTool(fontColors);
918        } else if (this.currentTask === Tasks.STICKER) {
919          this.getMaterialTool(stickers);
920        } else if (this.currentTask === Tasks.HDR) {
921          this.getDecodingTool();
922        }
923        if (this.currentTask === Tasks.MARK || this.currentTask === Tasks.ADJUST) {
924          this.getFirstLvMenu();
925        } else {
926          this.CancelOrOk(this.cancelOkText !);
927        }
928      }
929      .width('100%')
930    }
931    .backgroundColor(Color.Black)
932    .width('100%')
933    .height('100%')
934  }
935}