1/* 2 * Copyright (c) 2023-2023 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 FileModel from '../../Model/FileModel'; 17import { Log, MediaSize } from '@ohos/common'; 18import { Constants, PageDirection, ColorCode } from '@ohos/common'; 19import { GlobalThisHelper, GlobalThisStorageKey } from '@ohos/common'; 20import CheckEmptyUtils from '@ohos/common'; 21import image from '@ohos.multimedia.image'; 22import { BusinessError } from '@ohos.base'; 23 24const canvasScale = 2 25const to300DPI = 300/72 26const TAG = 'PreviewComponent' 27 28@Component 29export struct PreviewComponent { 30 31 @Consume('ImageCount') imageCount: number 32 @Consume('CurrentIndex') currentIndex: number 33 @Consume('IsGlobalDisable') isGlobalDisable: boolean 34 @Consume('PreviewCompHeight')@Watch('onHeightLoad') previewCompHeight: number//预览区组件高度 35 36 @Consume('IsBorderless')isBorderless: boolean 37 @Consume('PrintRange')@Watch('onPrintRangeChange') printRange: Array<number> 38 @Link @Watch('onPageDirectionChange') pageDirection:number 39 @Link @Watch('onMediaSizeChange') mediaSize:MediaSize 40 @Link @Watch('handleImage') imageSources: Array<FileModel> 41 42 @State currentImage: FileModel | undefined = undefined; 43 @State isPreviewFailed: boolean = false; 44 @State @Watch('loadCurrentPix')currentPixelMap: PixelMap | undefined = undefined; 45 @State imageOrientation: number = PageDirection.VERTICAL; 46 @State canvasWidth: number = 313 / canvasScale; 47 @State canvasHeight: number = 444 / canvasScale; 48 @State canvasRatio: number = 313 / 444;//width/height 49 @State offCanvasWidth: number = this.canvasWidth * to300DPI * canvasScale; 50 @State offCanvasHeight: number = this.canvasHeight * to300DPI * canvasScale; 51 @State imageWidth: number = Constants.NUMBER_0; 52 @State imageHeight: number = Constants.NUMBER_0; 53 @State xPosition: number = Constants.NUMBER_0; 54 @State yPosition: number = Constants.NUMBER_0; 55 @State firstPageBackgroundColor: Resource = $r('app.color.effect_color_none'); 56 @State previousBackgroundColor: Resource = $r('app.color.effect_color_none'); 57 @State nextBackgroundColor: Resource = $r('app.color.effect_color_none'); 58 @State lastPageBackgroundColor: Resource = $r('app.color.effect_color_none'); 59 @State originalIndex: number = 1; 60 private readonly maxWidth = 304 61 private readonly maxHeight = 261 62 @Link colorMode:number 63 64 private rate: number = 1.5 65 66 build() { 67 Column() { 68 Row() { 69 Image($r('app.media.ic_firstPage')).width(32).height(32).key('PreviewComponent_Image_firstPage') 70 .enabled(this.currentIndex !== 1 && !this.isGlobalDisable) 71 .opacity((this.currentIndex !== 1 && !this.isGlobalDisable) ? Constants.NUMBER_1 : $r('app.float.disable_opacity')) 72 .onClick(() => { 73 this.currentIndex = 1; 74 this.parseImageSize(false); 75 }) 76 .borderRadius($r('app.float.radius_m')) 77 .backgroundColor(this.firstPageBackgroundColor) 78 .responseRegion({x:0,y:'-50%',width:'100%',height:'100%'}) 79 .onHover((isHover: boolean) => { 80 if (isHover) { 81 this.firstPageBackgroundColor = $r('app.color.effect_color_hover') 82 } else { 83 this.firstPageBackgroundColor = $r('app.color.effect_color_none') 84 } 85 }) 86 .onTouch((event: TouchEvent) => { 87 if (event.type === TouchType.Down) { 88 this.firstPageBackgroundColor = $r('app.color.effect_color_press') 89 } 90 if (event.type === TouchType.Up) { 91 this.firstPageBackgroundColor = $r('app.color.effect_color_none') 92 } 93 94 }) 95 Image($r('app.media.ic_previous')).width(32).height(32).key('PreviewComponent_Image_previous') 96 .enabled(this.currentIndex !== 1 && !this.isGlobalDisable) 97 .opacity((this.currentIndex !== 1 && !this.isGlobalDisable) ? Constants.NUMBER_1 : $r('app.float.disable_opacity')) 98 .onClick(() => { 99 Log.info(TAG,'this.currentIndex --:'+this.currentIndex) 100 if(this.currentIndex === 1) { 101 return 102 } 103 this.currentIndex -= 1; 104 this.parseImageSize(false); 105 }) 106 .responseRegion({x:0,y:'-50%',width:'100%',height:'100%'}) 107 .borderRadius($r('app.float.radius_m')) 108 .backgroundColor(this.previousBackgroundColor) 109 .onHover((isHover: boolean) => { 110 if (isHover) { 111 this.previousBackgroundColor = $r('app.color.effect_color_hover') 112 } else { 113 this.previousBackgroundColor = $r('app.color.effect_color_none') 114 } 115 }) 116 .onTouch((event: TouchEvent) => { 117 if (event.type === TouchType.Down) { 118 this.previousBackgroundColor = $r('app.color.effect_color_press') 119 } 120 if (event.type === TouchType.Up) { 121 this.previousBackgroundColor = $r('app.color.effect_color_none') 122 } 123 124 }) 125 Text(this.currentIndex+'/'+this.imageCount).key('PreviewComponent_Text_imageCount') 126 .fontSize($r('app.float.font_size_body1')) 127 .width($r('app.float.preview_pages_comp_width')) 128 .height($r('app.float.params_comp_height')).textAlign(TextAlign.Center) 129 .margin({left:$r('app.float.preview_pages_comp_margin_left'),right:$r('app.float.preview_pages_comp_margin_right')}) 130 Image($r('app.media.ic_next')).key('PreviewComponent_Image_next') 131 .width(32) 132 .height(32) 133 .enabled(this.currentIndex !== this.imageCount && !this.isGlobalDisable) 134 .opacity((this.currentIndex !== this.imageCount && !this.isGlobalDisable) ? Constants.NUMBER_1 : $r('app.float.disable_opacity')) 135 .responseRegion({x:0,y:'-50%',width:'100%',height:'100%'}) 136 .onClick(() => { 137 Log.info(TAG,'this.currentIndex --:'+this.currentIndex) 138 if(this.currentIndex === this.imageCount) { 139 return; 140 } 141 this.currentIndex += 1; 142 this.parseImageSize(false); 143 }) 144 .borderRadius($r('app.float.radius_m')) 145 .backgroundColor(this.nextBackgroundColor) 146 .onHover((isHover: boolean) => { 147 if (isHover) { 148 this.nextBackgroundColor = $r('app.color.effect_color_hover') 149 } else { 150 this.nextBackgroundColor = $r('app.color.effect_color_none') 151 } 152 }) 153 .onTouch((event: TouchEvent) => { 154 if (event.type === TouchType.Down) { 155 this.nextBackgroundColor = $r('app.color.effect_color_press') 156 } 157 if (event.type === TouchType.Up) { 158 this.nextBackgroundColor = $r('app.color.effect_color_none') 159 } 160 161 }) 162 Image($r('app.media.ic_lastPage')).key('PreviewComponent_Image_lastPage') 163 .width(32) 164 .height(32) 165 .enabled(this.currentIndex !== this.imageCount && !this.isGlobalDisable) 166 .opacity((this.currentIndex !== this.imageCount && !this.isGlobalDisable) ? Constants.NUMBER_1 : $r('app.float.disable_opacity')) 167 .onClick(() => { 168 this.currentIndex = this.imageCount; 169 this.parseImageSize(false) 170 }) 171 .responseRegion({x:0,y:'-50%',width:'100%',height:'100%'}) 172 .borderRadius($r('app.float.radius_m')) 173 .backgroundColor(this.lastPageBackgroundColor) 174 .onHover((isHover: boolean) => { 175 if (isHover) { 176 this.lastPageBackgroundColor = $r('app.color.effect_color_hover') 177 } else { 178 this.lastPageBackgroundColor = $r('app.color.effect_color_none') 179 } 180 }) 181 .onTouch((event: TouchEvent) => { 182 if (event.type === TouchType.Down) { 183 this.lastPageBackgroundColor = $r('app.color.effect_color_press') 184 } 185 if (event.type === TouchType.Up) { 186 this.lastPageBackgroundColor = $r('app.color.effect_color_none') 187 } 188 189 }) 190 }.height(48) 191 .margin({top:8, bottom : 8}) 192 Row() { 193 if (this.currentPixelMap) { 194 Image(this.currentPixelMap).key('PreviewComponent_Image_currentPixelMap') 195 .width(this.canvasWidth).height(this.canvasHeight) 196 .backgroundColor($r('app.color.white')) 197 .objectFit(this.isBorderless?ImageFit.Cover:ImageFit.Contain) 198 .renderMode(this.colorMode === ColorCode.COLOR ? ImageRenderMode.Original : ImageRenderMode.Template) 199 .shadow(ShadowStyle.OUTER_DEFAULT_MD) 200 }else{ 201 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { 202 Column(){ 203 if (CheckEmptyUtils.isEmptyArr(this.imageSources)) { 204 Image($r('app.media.ic_gallery_list_damage')).fillColor('#66000000').width(36).height(36).margin({bottom:8}) 205 Text($r('app.string.preview_failed')).fontColor('#66000000').fontSize($r('app.float.font_size_body2')) 206 }else{ 207 LoadingProgress().width(36).height(36).margin({bottom:8}) 208 Text( $r('app.string.preview_loading')).fontColor('#66000000').fontSize($r('app.float.font_size_body2')) 209 } 210 } 211 }.width(this.canvasWidth).height(this.canvasHeight).backgroundColor($r('app.color.white')) 212 .shadow({ radius: $r('app.float.radius_m'), color: Color.Gray, offsetX: Constants.NUMBER_0, offsetY: Constants.NUMBER_0 }) 213 } 214 } 215 .width(480) 216 .height(this.previewCompHeight-80) 217 .justifyContent(FlexAlign.Center) 218 .alignItems(VerticalAlign.Center) 219 .margin({bottom : $r('app.float.preview_comp_margin')}) 220 } 221 .width($r('app.float.preview_comp_width')) 222 .height(this.previewCompHeight) 223 .alignItems(HorizontalAlign.Center) 224 .backgroundColor($r('app.color.preview_background_color')) 225 } 226 227 aboutToAppear() { 228 if(this.imageSources !== undefined){ 229 this.handleImage() 230 }else{ 231 this.checkCanvasWidth() 232 } 233 } 234 235 aboutToDisappear() { 236 } 237 238 parseImageSize(isRendered: boolean) { 239 this.originalIndex = this.printRange[this.currentIndex - 1] 240 this.currentImage = this.imageSources[this.originalIndex - 1]; 241 if (CheckEmptyUtils.isEmpty(this.currentImage)){ 242 return; 243 } 244 if (!isRendered) { 245 this.fdToPixelMap(this.currentImage!.fd); 246 } 247 let width = this.currentImage!.width 248 let height = this.currentImage!.height 249 if(width > height) { 250 this.imageOrientation = PageDirection.LANDSCAPE //图片横向 251 } else { 252 this.imageOrientation = PageDirection.VERTICAL //图片竖向 253 } 254 this.updateCanvasSize() 255 } 256 257 updateCanvasSize() { 258 if (this.pageDirection === PageDirection.AUTO) { 259 if (this.imageOrientation === PageDirection.LANDSCAPE) {//图片横向 260 this.setWidthMax() 261 } else if (this.imageOrientation === PageDirection.VERTICAL) {//图片竖向 262 this.setHeightMax() 263 } 264 } else if (this.pageDirection === PageDirection.LANDSCAPE) { 265 //横向 266 this.setWidthMax() 267 } else if (this.pageDirection === PageDirection.VERTICAL) { 268 //竖向 269 this.setHeightMax() 270 } 271 } 272 273 setWidthMax(){ 274 this.canvasWidth = this.maxWidth 275 this.canvasHeight = this.canvasWidth * this.canvasRatio 276 } 277 278 setHeightMax(){ 279 this.canvasHeight = this.previewCompHeight-80 280 this.canvasWidth = this.canvasHeight * this.canvasRatio 281 } 282 283 284 /** 285 * 纸张尺寸修改事件 286 */ 287 onMediaSizeChange() { 288 let pixelSize = this.mediaSize.getPixelMediaSize() 289 this.canvasWidth = pixelSize.width / this.rate 290 this.canvasHeight = pixelSize.height / this.rate; 291 this.canvasRatio = pixelSize.width / pixelSize.height 292 this.checkCanvasWidth() 293 this.parseImageSize(true) 294 } 295 296 /** 297 * 纸张方向修改 298 */ 299 onPageDirectionChange() { 300 Log.info(TAG, 'onPageDirectionChange enter'); 301 this.parseImageSize(true) 302 } 303 /** 304 * 打印范围修改 305 */ 306 onPrintRangeChange() { 307 Log.info(TAG, 'onPrintRangeChange enter',JSON.stringify(this.printRange)); 308 if (this.printRange.length === 0 || this.imageSources === undefined) { 309 return; 310 } 311 if (this.currentIndex>this.printRange.length) { 312 Log.info(TAG, 'parseImageSize this.printRange.length: ', JSON.stringify(this.printRange.length)) 313 this.currentIndex = this.printRange.length 314 } 315 let newIndex = this.printRange[this.currentIndex - 1] 316 Log.info(TAG,'newIndex: '+newIndex+' originalIndex: '+this.originalIndex) 317 if (newIndex === this.originalIndex) { 318 this.parseImageSize(true) 319 } else { 320 this.parseImageSize(false) 321 } 322 } 323 324 /** 325 *界面高度调整 326 */ 327 onHeightLoad(){ 328 Log.info(TAG,'onHeightLoad:'+this.previewCompHeight) 329 if (this.imageSources === undefined ){ 330 return; 331 } 332 if(this.previewCompHeight){ 333 this.handleImage(); 334 } 335 } 336 337 338 /** 339 * 调整画布的方向 340 */ 341 swapCanvasWidthAndHeight() { 342 let temp = this.canvasWidth; 343 this.canvasWidth = this.canvasHeight 344 this.canvasHeight = temp 345 this.checkCanvasWidth() 346 } 347 /** 348 * 画布宽度是否超过最大值 349 */ 350 checkCanvasWidth(){ 351 let ratio: number = 1; 352 if(this.canvasWidth > Constants.CANVAS_MAX_WIDTH){ 353 ratio = this.canvasHeight/this.canvasWidth 354 this.canvasWidth = Constants.CANVAS_MAX_WIDTH 355 this.canvasHeight = this.canvasWidth * ratio 356 } 357 if(this.canvasHeight > Constants.CANVAS_MAX_HEIGHT){ 358 ratio = this.canvasWidth/this.canvasHeight 359 this.canvasHeight = Constants.CANVAS_MAX_HEIGHT 360 this.canvasWidth = this.canvasHeight * ratio 361 } 362 } 363 /** 364 * fd生成pixelMap 365 * @param fd 366 */ 367 fdToPixelMap(fd:number){ 368 Log.info(TAG, 'fd is: ' + fd); 369 Log.debug(TAG, 'this.currentImage: ' + JSON.stringify(this.currentImage)); 370 if (this.currentImage === undefined) { 371 Log.error(TAG, 'currentImage is undefined'); 372 return; 373 } 374 if (CheckEmptyUtils.isEmpty(this.currentImage!.imageSource)) { 375 this.currentImage!.imageSource = image.createImageSource(fd); 376 if (this.currentImage!.imageSource === undefined) { 377 Log.error(TAG, 'createImageSource error'); 378 return; 379 } 380 } 381 let info = this.currentImage!.imageSource!.getImageInfo(); 382 if (CheckEmptyUtils.isEmpty(info)) { 383 Log.error(TAG, 'createImageSource error: invalid imageSource'); 384 return; 385 } 386 let decodingOptions: image.DecodingOptions | undefined = undefined; 387 let width = this.currentImage!.width 388 let height = this.currentImage!.height 389 let ratio = width/height 390 if(width*height>=Constants.MAX_PIXELMAP){//超过像素上限pixelmap无法显示在image组件里,需要resize 391 height = Math.floor(Math.sqrt(Constants.MAX_PIXELMAP/ratio)) 392 Log.info(TAG,'decodingOptions width: '+height*ratio+' height: '+height) 393 decodingOptions = { 394 desiredSize:{width:height*ratio,height:height} 395 } 396 } 397 this.currentImage!.imageSource!.createPixelMap(decodingOptions).then(pixelmap => { 398 Log.info(TAG,'Succeeded in creating pixelmap object through image decoding parameters.'); 399 if(this.currentPixelMap!==undefined){ 400 this.currentPixelMap!.release().then(()=>{ 401 Log.info(TAG,'currentPixelMap release success'); 402 this.currentPixelMap = pixelmap 403 GlobalThisHelper.createValue<PixelMap>(this.currentPixelMap, GlobalThisStorageKey.KEY_CURRENT_PIXELMAP); 404 }) 405 }else{ 406 this.currentPixelMap = pixelmap 407 GlobalThisHelper.createValue<PixelMap>(this.currentPixelMap, GlobalThisStorageKey.KEY_CURRENT_PIXELMAP); 408 } 409 410 }).catch((error: BusinessError) => { 411 Log.info(TAG,'Failed to create pixelmap object through image decoding parameters.'+error); 412 }) 413 } 414 415 handleImage(){ 416 Log.info(TAG,'handleImage'+this.imageSources.length) 417 this.checkCanvasWidth() 418 this.parseImageSize(false); 419 } 420 421 loadCurrentPix(){ 422 if (this.currentPixelMap === undefined) { 423 Log.info(TAG,'loadCurrentPix load failed') 424 }else{ 425 Log.info(TAG,'loadCurrentPix load success') 426 } 427 } 428} 429