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