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