• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2024 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 imageEffect from 'libentry.so'
17import image from '@ohos.multimedia.image';
18import { ImageUtils } from '../utils/ImageUtils';
19import { fileUri } from '@kit.CoreFileKit';
20import fs from '@ohos.file.fs';
21
22@Entry
23@Component
24struct ImageEditPage {
25  private tag: string = '[Sample_ImageEdit]';
26  settingBtn: Resource = $r('app.media.ic_public_settings');
27  @Provide pixelMap: image.PixelMap | undefined = undefined;
28  @Provide displayPixelMap: image.PixelMap | undefined = undefined;
29  @State brightnessSetValue: number = 100;
30  @State brightnessSelect: boolean = true;
31  @State contrastSetValue: number = 0;
32  @State contrastSelect: boolean = false;
33  @State cropSetValue: number = 100;
34  @State cropSelect: boolean = false;
35  @State customSetValue: number = 0;
36  @State customSelect: boolean = false;
37  @State filterOptions: Array<Array<string | number>> = [];
38  @State filterInfo: string = "";
39
40  aboutToAppear(): void {
41    console.info(`${this.tag} aboutToAppear called`);
42    ImageUtils.getInstance().getPixelMap($r('app.media.ic_1080x1920')).then(pixelMap => {
43      this.pixelMap = pixelMap;
44    })
45    ImageUtils.getInstance().getPixelMap($r('app.media.ic_1080x1920')).then(pixelMap => {
46      this.displayPixelMap = pixelMap;
47    })
48    console.info(`${this.tag} aboutToAppear done`);
49  }
50
51  aboutToDisappear(): void {
52    console.info(`${this.tag} aboutToDisappear called`);
53    console.info(`${this.tag} aboutToDisappear done`);
54  }
55
56  build() {
57    Column() {
58      Row() {
59        Row() {
60          Text($r('app.string.image_edit'))
61            .fontColor(Color.White)
62            .fontSize('app.float.title_font_size')
63            .margin({ left: $r('app.float.home_page_title_margin') })
64        }
65
66        Blank()
67
68        Row({ space: 0 }) {
69          Button() {
70            Image(this.settingBtn)
71              .width($r('app.float.title_image_width'))
72              .height($r('app.float.title_image_height'))
73              .id('btn_setting')
74          }
75          .height('100%')
76          .type(ButtonType.Normal)
77          .aspectRatio(1)
78          .backgroundColor(Color.Transparent)
79          .onClick(() => {
80            if (this.dialogController != undefined) {
81              console.info(`${this.tag} to open setting dialog`);
82              this.dialogController.open();
83            }
84          })
85        }
86      }
87      .width('100%')
88      .height($r('app.float.home_page_title_height'))
89      .margin({ top: $r('app.float.home_page_title_margin') })
90
91      Column() {
92        Image(this.displayPixelMap)
93          .objectFit(ImageFit.None)
94      }
95      .clip(true)
96      .width('100%')
97      .height('85%')
98
99      Column() {
100        Row() {
101          Button("Reset").id("btn_reset").onClick(() => {
102            this.pixelInit();
103          }).width(100).margin({ left: 3, right: 3, top: 3, bottom: 6 })
104
105          Button("Apply").id("btn_apply").onClick(() => {
106            this.doApply();
107          }).width(100).margin({ left: 3, right: 3, top: 3, bottom: 6 })
108        }
109        .justifyContent(FlexAlign.Center)
110      }
111      .align(Alignment.End)
112      .width('100%')
113      .height('6%')
114      .margin({ top: $r('app.float.home_page_title_margin') })
115      .backgroundColor(Color.Black)
116    }
117    .width('100%')
118    .height('100%')
119    .backgroundColor(Color.Black)
120  }
121
122  private async doSavePixel(): Promise<void> {
123    let pixelMap = await ImageUtils.getInstance().getPixelMap($r('app.media.ic_1080x1920'));
124    const imagePackerApi = image.createImagePacker();
125    const packOption: image.PackingOption = {
126      format: 'image/jpeg',
127      quality: 100
128    };
129    let filePath = getContext().filesDir + "/test.jpg";
130    let uri = fileUri.getUriFromPath(filePath);
131    let imageData = await imagePackerApi.packing(pixelMap, packOption);
132    let file = fs.openSync(uri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
133    let writeLen = fs.writeSync(file.fd, imageData);
134    fs.closeSync(file);
135    console.info(`write data to file succeed and size is ${writeLen}`);
136  }
137
138  private confirmInfo() {
139    this.filterOptions = [];
140    if (this.brightnessSelect) {
141      let brightnessArray: (string | number)[] = [];
142      brightnessArray.push("Brightness", this.brightnessSetValue);
143      this.filterOptions.push(brightnessArray);
144    }
145    if (this.contrastSelect) {
146      let contrastArray: (string | number)[] = [];
147      contrastArray.push("Contrast", this.contrastSetValue);
148      this.filterOptions.push(contrastArray);
149    }
150    if (this.cropSelect) {
151      let cropArray: (string | number)[] = [];
152      cropArray.push("Crop", this.cropSetValue);
153      this.filterOptions.push(cropArray);
154    }
155    if (this.customSelect) {
156      let customArray: (string | number)[] = [];
157      customArray.push("CustomBrightness", this.customSetValue);
158      this.filterOptions.push(customArray);
159    }
160  }
161
162  private async doApply(): Promise<void> {
163    this.confirmInfo();
164    if (this.brightnessSelect || this.contrastSelect || this.cropSelect || this.customSelect) {
165      await this.doSavePixel();
166      let filePath = getContext().filesDir + "/test.jpg";
167      imageEffect.apply(filePath, [...this.filterOptions]);
168      this.displayPixelMap = await ImageUtils.getInstance().getPixelMapByFilePath(filePath);
169    }
170  }
171
172  private async pixelInit(): Promise<void> {
173    this.displayPixelMap?.release();
174    this.displayPixelMap = await ImageUtils.getInstance().getPixelMap($r('app.media.ic_1080x1920'))
175  }
176
177  dialogController: CustomDialogController = new CustomDialogController({
178    builder: CustomDialogExample({
179      cancel: this.onCancel,
180      confirm: this.onAccept,
181      filterOptions: $filterOptions,
182      brightnessSetValue: $brightnessSetValue,
183      brightnessSelect: $brightnessSelect,
184      contrastSetValue: $contrastSetValue,
185      contrastSelect: $contrastSelect,
186      cropSetValue: $cropSetValue,
187      cropSelect: $cropSelect,
188      customSetValue: $customSetValue,
189      customSelect: $customSelect,
190      filterInfo: $filterInfo,
191    }),
192    cancel: this.existApp,
193    autoCancel: true,
194  })
195
196  onCancel() {
197    console.info(`Callback when the cancel button is clicked`);
198  }
199
200  onAccept() {
201    console.info(`Callback when the confirm button is clicked`);
202  }
203
204  existApp() {
205    console.info(`click the callback in the blank area`);
206  }
207}
208
209@CustomDialog
210struct CustomDialogExample {
211  @Link brightnessSetValue: number
212  @Link brightnessSelect: boolean
213  @Link contrastSetValue: number
214  @Link contrastSelect: boolean
215  @Link cropSetValue: number
216  @Link cropSelect: boolean
217  @Link customSetValue: number
218  @Link customSelect: boolean
219  @Link filterOptions: Array<Array<string | number>>
220  @Link filterInfo: string;
221  controller: CustomDialogController;
222  cancel: () => void = () => {
223  }
224  confirm: () => void = () => {
225  }
226  @State formatList: Array<String> = ["Format:default", "Format:rgba_8888", "Format:nv21", "Format:nv12"]
227  @State handlePopup: boolean = false
228
229  @Builder
230  FilterInfoMenu() {
231    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Start, justifyContent: FlexAlign.Center }) {
232      Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Start }) {
233        Text(this.filterInfo).fontSize(16)
234      }
235    }
236    .width('60%').height('15%')
237    .opacity(0.8)
238    .id("filter_info_menu")
239  }
240
241  private async doLookFilterInfo(name: String): Promise<void> {
242    this.filterInfo = imageEffect.lookupFilterInfo(name);
243  }
244
245  build() {
246    Column() {
247      Column() {
248        Divider().height(2).color(0xcccccc)
249        Column() {
250          Text('Filter')
251            .width('100%')
252            .fontSize(18)
253            .margin({ bottom: 10 })
254
255          Row() {
256            Column() {
257              Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
258                Checkbox({ name: 'brightnessCheckbox', group: 'filterCheckboxGroup' })
259                  .selectedColor(0x39a2db)
260                  .select(this.brightnessSelect)
261                  .onChange((value: boolean) => {
262                    this.brightnessSelect = value;
263                  })
264                  .width(10)
265                  .height(14)
266                  .id("checkbox_brightness")
267                Text('Brightness').fontSize(10).width('18%')
268                Slider({
269                  value: this.brightnessSetValue,
270                  min: -100,
271                  max: 100,
272                  style: SliderStyle.OutSet
273                })
274                  .showTips(true, this.brightnessSetValue.toFixed())
275                  .onChange((value: number, mode: SliderChangeMode) => {
276                    this.brightnessSetValue = value
277                    console.info('value:' + value + 'mode:' + mode.toString())
278                  })
279                  .width('60%')
280                  .id("slider_brightness")
281                // toFixed(0)将滑动条返回值处理为整数精度
282                Column() {
283                  Text(this.brightnessSetValue.toFixed(0)).fontSize(12)
284                }.width('8%')
285
286                Column() {
287                  Image($r('app.media.ic_public_search'))
288                    .width('5%')
289                    .height('3.7%')
290                }.bindMenu(this.FilterInfoMenu, {
291                  onAppear: () => {
292                    this.doLookFilterInfo("Brightness");
293                  },
294                  onDisappear: () => {
295                    this.filterInfo = "";
296                  }
297                })
298                .margin({ left: 2 })
299                .id("btn_search_brightness")
300              }
301
302              Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
303                Checkbox({ name: 'contrastCheckbox', group: 'filterCheckboxGroup' })
304                  .selectedColor(0x39a2db)
305                  .select(this.contrastSelect)
306                  .onChange((value: boolean) => {
307                    this.contrastSelect = value;
308                  })
309                  .width(10)
310                  .height(14)
311                  .id("checkbox_contrast")
312                Text('Contrast').fontSize(10).width('18%')
313                Slider({
314                  value: this.contrastSetValue,
315                  min: -100,
316                  max: 100,
317                  style: SliderStyle.OutSet
318                })
319                  .showTips(true, this.contrastSetValue.toFixed())
320                  .onChange((value: number, mode: SliderChangeMode) => {
321                    this.contrastSetValue = value
322                    console.info('value:' + value + 'mode:' + mode.toString())
323                  })
324                  .width('60%')
325                  .id("slider_contrast")
326                // toFixed(0)将滑动条返回值处理为整数精度
327                Column() {
328                  Text(this.contrastSetValue.toFixed(0)).fontSize(12)
329                }.width('8%')
330
331                Column() {
332                  Image($r('app.media.ic_public_search'))
333                    .width('5%')
334                    .height('3.7%')
335                }.bindMenu(this.FilterInfoMenu, {
336                  onAppear: () => {
337                    this.doLookFilterInfo("Contrast");
338                  },
339                  onDisappear: () => {
340                    this.filterInfo = "";
341                  }
342                })
343                .margin({ left: 2 })
344                .id("btn_search_contrast")
345              }
346
347              Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
348                Checkbox({ name: 'cropCheckbox', group: 'filterCheckboxGroup' })
349                  .selectedColor(0x39a2db)
350                  .select(this.cropSelect)
351                  .onChange((value: boolean) => {
352                    this.cropSelect = value;
353                  })
354                  .width(10)
355                  .height(14)
356                  .id("checkbox_crop")
357                Text('Crop').fontSize(10).width('18%')
358                Slider({
359                  value: this.cropSetValue,
360                  min: 1,
361                  max: 100,
362                  style: SliderStyle.OutSet
363                })
364                  .showTips(true, this.cropSetValue.toFixed())
365                  .onChange((value: number, mode: SliderChangeMode) => {
366                    this.cropSetValue = value
367                    console.info('value:' + value + 'mode:' + mode.toString())
368                  })
369                  .width('60%')
370                  .id("slider_crop")
371                // toFixed(0)将滑动条返回值处理为整数精度
372                Column() {
373                  Text(this.cropSetValue.toFixed(0)).fontSize(12)
374                }.width('8%')
375
376                Column() {
377                  Image($r('app.media.ic_public_search'))
378                    .width('5%')
379                    .height('3.7%')
380                }.bindMenu(this.FilterInfoMenu, {
381                  onAppear: () => {
382                    this.doLookFilterInfo("Crop");
383                  },
384                  onDisappear: () => {
385                    this.filterInfo = "";
386                  }
387                })
388                .margin({ left: 2 })
389                .id("btn_search_crop")
390              }
391
392              Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
393                Checkbox({ name: 'customCheckbox', group: 'filterCheckboxGroup' })
394                  .selectedColor(0x39a2db)
395                  .select(this.customSelect)
396                  .onChange((value: boolean) => {
397                    this.customSelect = value;
398                  })
399                  .width(10)
400                  .height(14)
401                  .id("checkbox_custom")
402                Text('Custom').fontSize(10).width('18%')
403                Slider({
404                  value: this.customSetValue,
405                  min: -100,
406                  max: 100,
407                  style: SliderStyle.OutSet
408                })
409                  .showTips(true, this.customSetValue.toFixed())
410                  .onChange((value: number, mode: SliderChangeMode) => {
411                    this.customSetValue = value
412                    console.info('value:' + value + 'mode:' + mode.toString())
413                  })
414                  .width('60%')
415                  .id("slider_custom")
416                // toFixed(0)将滑动条返回值处理为整数精度
417                Column() {
418                  Text(this.customSetValue.toFixed(0)).fontSize(12)
419                }.width('8%')
420
421                Column() {
422                  Image($r('app.media.ic_public_search'))
423                    .width('5%')
424                    .height('3.7%')
425                }.bindMenu(this.FilterInfoMenu, {
426                  onAppear: () => {
427                    this.doLookFilterInfo("CustomBrightness");
428                  },
429                  onDisappear: () => {
430                    this.filterInfo = "";
431                  }
432                })
433                .margin({ left: 2 })
434                .id("btn_search_custom")
435              }
436            }
437            .width('100%')
438          }
439        }
440      }.margin({ bottom: 10 })
441
442      Column() {
443        Divider().height(2).color(0xCCCCCC);
444        Column() {
445          Text($r('app.string.look_up'))
446            .width('100%')
447            .fontSize(18)
448            .margin({ bottom: 10 })
449          Row() {
450            Column() {
451              Text($r('app.string.btn_search'))
452                .width('15%')
453                .fontSize(20)
454                .margin({ left: '35%' })
455            }
456
457            Column() {
458              Image($r('app.media.ic_public_arrow_right'))
459                .fillColor(Color.Black)
460                .width('10%')
461                .height('4%')
462            }
463          }
464          .id("btn_search")
465          .width('100%')
466          .justifyContent(FlexAlign.Start)
467          .bindMenu(this.LookupCategoryMenuBuilder)
468        }
469      }.margin({ bottom: 10 })
470
471      Divider().height(2).color(0xCCCCCC)
472
473      Flex({ justifyContent: FlexAlign.SpaceAround }) {
474        Button($r('app.string.btn_cancel'))
475          .onClick(() => {
476            this.controller.close()
477            this.cancel();
478          }).backgroundColor(0xffffff)
479          .fontColor(Color.Black)
480          .id("btn_dialog_cancel")
481        Button($r('app.string.btn_confirm'))
482          .onClick(() => {
483
484            this.controller.close()
485            this.confirm()
486          }).backgroundColor(0xffffff)
487          .fontColor(Color.Red)
488          .id("btn_dialog_confirm")
489      }
490    }.margin(24)
491  }
492
493  @Builder
494  LookupCategoryMenuBuilder() {
495    Menu() {
496      MenuItem({
497        content: "Format",
498        builder: (): void => this.FormatMenuBuilder()
499      })
500    }.id("menu_category")
501  }
502
503  @Builder
504  FormatMenuBuilder() {
505    Menu() {
506      ForEach(this.formatList, (item: string, index) => {
507        LookupFilterMenuItem({ item: item })
508          .id("menu_format")
509      })
510    }
511  }
512}
513
514@Component
515struct LookupFilterMenuItem {
516  @State item: string = "";
517
518  dialogController: CustomDialogController = new CustomDialogController({
519    builder: CustomDialogDetails({
520      item: $item
521    }),
522    offset: { dx: 0, dy: 220 },
523    autoCancel: true,
524  })
525
526  build() {
527    MenuItem({ content: this.item })
528      .onClick(() => {
529        if (this.dialogController != null) {
530          this.dialogController.open()
531        }
532      })
533  }
534}
535
536@CustomDialog
537struct CustomDialogDetails {
538  @Link item: string
539  controller?: CustomDialogController
540
541  build() {
542    Column() {
543      Text('Filters:\n' + imageEffect.lookupFilters(this.item))
544        .fontSize(16)
545    }
546  }
547}
548