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}