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}