• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2022 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
16enum Type {
17  brightness,
18  saturate,
19  contrast
20}
21
22import { NavigationBar } from '../../common/components/navigationBar'
23import prompt from '@ohos.prompt';
24
25@Entry
26@Component
27struct CanvasExample {
28  private settings: RenderingContextSettings = new RenderingContextSettings(true)
29  private ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
30  private img: ImageBitmap = new ImageBitmap('/common/test5.jpg')
31  @State visibility1: string = 'Visible'
32  @State visibility2: string = 'None'
33  @State brightnessValue: number = 10
34  @State oldBrightnessValue: number = 10
35  @State saturateValue: number = 10
36  @State oldSaturateValue: number = 10
37  @State contrastValue: number = 10
38  @State oldContrastValue: number = 10
39  @State typeProperties: Type = Type.brightness
40  @State clipOffsetx: number = 100
41  @State clipOffsety: number = 100
42  @State clipWidth: number = 100
43  @State clipHeight: number = 100
44  @State dWidth: number = 0
45  @State dHeight: number = 0
46  @State cropBoxLeftOne: number = 0
47  @State cropBoxLeftTwo: number = 0
48  @State cropBoxLeftThr: number = 0
49  @State cropBoxLeftFou: number = 0
50  @State cropBoxTopOne: number = 0
51  @State cropBoxTopTwo: number = 0
52  @State cropBoxTopThr: number = 0
53  @State cropBoxTopFou: number = 0
54  private brightnessImgData: any = null
55  private contrastImgData: any = null
56  private saturationImgData: any = null
57  @State clipState: string = 'original'
58  @State sliderChangeMode: SliderChangeMode = SliderChangeMode.Begin
59  @State adjustValue: number = 0
60  @State sliderNumber: string = ''
61  @State scaleX: number = 0
62  @State scaleY: number = 0
63  // 亮度
64  adjustBrightness(value) {
65    let imageData = this.ctx.getImageData
66    (this.clipOffsetx, this.clipOffsety, this.clipWidth * this.scaleX, this.clipHeight * this.scaleY)
67    this.ctx.putImageData((this.changeBrightness(imageData, value)), this.clipOffsetx, this.clipOffsety)
68  }
69
70  changeBrightness(imageData, value) {
71    let data = imageData.data
72    for (let i = 0; i < data.length; i += 4) {
73      const hsv = this.rgb2hsv([data[i], data[i + 1], data[i+2]])
74      hsv[2] *= value
75      const rgb = this.hsv2rgb([...hsv])
76      data[i] = rgb[0]
77      data[i+1] = rgb[1]
78      data[i+2] = rgb[2]
79    }
80    console.info('image:')
81    return imageData
82  }
83  // 对比度
84  adjustContrast(value) {
85    let imageData = this.ctx.getImageData
86    (this.clipOffsetx, this.clipOffsety, this.clipWidth * this.scaleX, this.clipHeight * this.scaleY)
87    this.ctx.putImageData((this.changeContrast(imageData, value)), this.clipOffsetx, this.clipOffsety)
88  }
89
90  changeContrast(imageData, value) {
91    const data = imageData.data
92    for (let i = 0;i < data.length; i += 4) {
93      const hsv = this.rgb2hsv([data[i], data[i+1], data[i+2]])
94      hsv[0] *= value
95      const rgb = this.hsv2rgb([...hsv])
96      data[i] = rgb[0]
97      data[i+1] = rgb[1]
98      data[i+2] = rgb[2]
99    }
100    return imageData
101  }
102  // 饱和度
103  adjustSaturation(value) {
104    let imageData = this.ctx.getImageData
105    (this.clipOffsetx, this.clipOffsety, this.clipWidth * this.scaleX, this.clipHeight * this.scaleY)
106    this.ctx.putImageData((this.changeSaturation(imageData, value)), this.clipOffsetx, this.clipOffsety)
107  }
108
109  changeSaturation(imageData, value) {
110    const data = imageData.data
111    for (let i = 0;i < data.length; i += 4) {
112      const hsv = this.rgb2hsv([data[i], data[i+1], data[i+2]])
113      hsv[1] *= value
114      const rgb = this.hsv2rgb([...hsv])
115      data[i] = rgb[0]
116      data[i+1] = rgb[1]
117      data[i+2] = rgb[2]
118    }
119    return imageData
120  }
121  // RGB转HSV
122  rgb2hsv(arr) {
123    let rr
124    let gg
125    let bb
126    const r = arr[0] / 255
127    const g = arr[1] / 255
128    const b = arr[2] / 255
129    let h
130    let s
131    const v = Math.max(r, g, b)
132    const diff = v - Math.min(r, g, b)
133    const diffc = function (c) {
134      return (v - c) / 6 / diff + 1 / 2
135    }
136    if (diff === 0) {
137      h = s = 0
138    } else {
139      s = diff / v
140      rr = diffc(r)
141      gg = diffc(g)
142      bb = diffc(b)
143      if (r === v) {
144        h = bb - gg
145      } else if (g === v) {
146        h = 1 / 3 + rr - bb
147      } else if (b === v) {
148        h = 2 / 3 + gg - rr
149      }
150      if (h < 0) {
151        h += 1
152      } else if (h > 1) {
153        h -= 1
154      }
155    }
156    return [Math.round(h * 360), Math.round(s * 100), Math.round(v * 100)]
157  }
158  // HSV转RGB
159  hsv2rgb(hsv) {
160    let _l = hsv[0]
161    let _m = hsv[1]
162    let _n = hsv[2]
163    let newR
164    let newG
165    let newB
166    if (_m === 0) {
167      _l = _m = _n = Math.round(255 * _n / 100)
168      newR = _l
169      newG = _m
170      newB = _n
171    } else {
172      _m = _m / 100
173      _n = _n / 100
174      const p = Math.floor(_l / 60) % 6
175      const f = _l / 60 - p
176      const a = _n * (1 - _m)
177      const b = _n * (1 - _m * f)
178      const c = _n * (1 - _m * (1 - f))
179      switch (p) {
180        case 0:
181          newR = _n;
182          newG = c;
183          newB = a;
184          break;
185        case 1:
186          newR = b;
187          newG = _n;
188          newB = a;
189          break;
190        case 2:
191          newR = a;
192          newG = _n;
193          newB = c;
194          break;
195        case 3:
196          newR = a;
197          newG = b;
198          newB = _n;
199          break;
200        case 4:
201          newR = c;
202          newG = a;
203          newB = _n;
204          break;
205        case 5:
206          newR = _n;
207          newG = a;
208          newB = b;
209          break;
210      }
211      newR = Math.round(255 * newR)
212      newG = Math.round(255 * newG)
213      newB = Math.round(255 * newB)
214    }
215    return [newR, newG, newB]
216  }
217
218  build() {
219    Column(){
220      NavigationBar({ title: 'Canvas' })
221
222      Column() {
223        Column(){
224          Column() {
225            Canvas(this.ctx)
226              .width(300)
227              .height(300)
228              .onAppear(() => {
229                this.dWidth = 300
230                this.dHeight = 250
231                // 绘制原始图片
232                this.ctx.drawImage(this.img, 0, 0, 300, 250)
233              })
234          }
235        }.width('100%').justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)
236      }
237      .width('100%')
238      .constraintSize({ minHeight: 218, maxHeight: 402 })
239      .padding({ left: 12, right: 12, top: 22, bottom: 22 })
240
241      Column() {
242        Row({ space: 10 }) {
243          Button('原图')
244            .onClick(() => {
245              this.clipState = 'original'
246              // 原图裁剪大小与原图一致
247              this.clipWidth = this.dWidth
248              this.clipHeight = this.dHeight
249              this.clipOffsetx = (this.dWidth - this.clipWidth) / 2
250              this.clipOffsety = (this.dHeight - this.clipHeight) / 2
251              this.ctx.save()
252            })
253            .fontSize('12fp')
254            .borderRadius(14)
255            .fontWeight(FontWeight.Medium)
256          // 1:1裁剪
257          Button('1:1')
258            .onClick(() => {
259              this.clipState = 'sameProportion'
260              this.clipWidth = this.dHeight
261              this.clipHeight = this.dHeight
262              this.cropBoxLeftTwo = (this.dHeight - this.clipHeight) / 2
263              this.cropBoxTopTwo = (this.dHeight - this.clipHeight) / 2
264              this.clipOffsetx = this.dHeight - this.clipWidth
265              this.clipOffsety = this.dHeight - this.clipHeight
266              this.scaleX = this.dWidth / this.clipWidth
267              this.scaleY = this.scaleX
268            })
269            .fontSize('12fp')
270            .borderRadius(14)
271            .fontWeight(FontWeight.Medium)
272          // 16:9裁剪
273          Button('16:9')
274            .onClick(() => {
275              this.clipState = 'HighProportion'
276              this.clipHeight = (this.dWidth * 9 / 19)
277              this.clipWidth = this.dWidth
278              this.cropBoxLeftThr = (this.dWidth - this.clipWidth) / 2
279              this.cropBoxTopThr = (this.dHeight - this.clipHeight) / 2
280              this.clipOffsetx = (this.dWidth - this.clipWidth) / 2
281              this.clipOffsety = (300 - this.clipHeight) / 2
282              this.scaleX = this.dWidth / this.clipWidth
283              this.scaleY = this.scaleX
284            })
285            .fontSize('12fp')
286            .borderRadius(14)
287            .fontWeight(FontWeight.Medium)
288          // 9:16裁剪
289          Button('9:16')
290            .onClick(() => {
291              this.clipState = 'LowProportion'
292              this.clipWidth = (this.dHeight * 9 / 16)
293              this.clipHeight = this.dHeight
294              this.cropBoxLeftFou = (300 - this.dHeight * 9 / 16) / 2
295              this.cropBoxTopFou = (this.dHeight - this.clipHeight) / 2
296              this.clipOffsetx = (300 - this.clipWidth) / 2
297              this.clipOffsety = this.cropBoxTopFou
298              this.scaleX = this.dHeight / this.clipHeight
299              this.scaleY = this.dWidth / this.clipHeight
300            })
301            .fontSize('12fp')
302            .borderRadius(14)
303            .fontWeight(FontWeight.Medium)
304        }
305        .padding({ bottom: 5, top: 10 })
306        .visibility(Visibility[this.visibility1])
307
308        Row() {
309          Button('确认剪切')
310            .fontSize(18)
311            .onClick(() => {
312              if (this.clipState == 'original') {
313                this.ctx.save()
314              }
315              // 1:1剪切
316              if (this.clipState == 'sameProportion') {
317                let imageData = this.ctx.getImageData
318                (this.cropBoxLeftOne, this.cropBoxTopOne, this.clipWidth, this.clipHeight)
319                this.ctx.clearRect(0, 0, 300, 300)
320                this.ctx.scale(this.scaleX, this.scaleY)
321                this.ctx.putImageData(imageData, 0, 0)
322                this.brightnessImgData = this.ctx.getImageData
323                (this.clipOffsetx, this.clipOffsety, this.clipWidth * this.scaleX, this.clipHeight * this.scaleY)
324                this.contrastImgData = this.ctx.getImageData
325                (this.clipOffsetx, this.clipOffsety, this.clipWidth * this.scaleX, this.clipHeight * this.scaleY)
326                this.saturationImgData = this.ctx.getImageData
327                (this.clipOffsetx, this.clipOffsety, this.clipWidth * this.scaleX, this.clipHeight * this.scaleY)
328                this.ctx.save()
329              }
330              // 16:9剪切
331              if (this.clipState == 'HighProportion') {
332                let imageData = this.ctx.getImageData
333                (this.cropBoxLeftThr, this.cropBoxTopThr, this.clipWidth, this.clipHeight)
334                this.ctx.clearRect(0, 0, 300, 300)
335                this.ctx.scale(this.scaleX, this.scaleY)
336                this.ctx.putImageData(imageData, this.clipOffsetx, this.clipOffsety)
337                this.brightnessImgData = this.ctx.getImageData
338                (this.clipOffsetx, this.clipOffsety, this.clipWidth * this.scaleX, this.clipHeight * this.scaleY)
339                this.contrastImgData = this.ctx.getImageData
340                (this.clipOffsetx, this.clipOffsety, this.clipWidth * this.scaleX, this.clipHeight * this.scaleY)
341                this.saturationImgData = this.ctx.getImageData
342                (this.clipOffsetx, this.clipOffsety, this.clipWidth * this.scaleX, this.clipHeight * this.scaleY)
343                this.ctx.save()
344              }
345              // 9:16剪切
346              if (this.clipState == 'LowProportion') {
347                let dx = (300 - 250 * 9 / 16) / 2
348                let imageData = this.ctx.getImageData
349                (dx, this.cropBoxTopFou, this.clipWidth, this.clipHeight)
350                this.ctx.clearRect(0, 0, 300, 300)
351                this.ctx.scale(this.scaleX, this.scaleY)
352                this.ctx.putImageData(imageData, this.clipOffsetx, this.clipOffsety)
353                this.brightnessImgData = this.ctx.getImageData
354                (this.clipOffsetx, this.clipOffsety, this.clipWidth * this.scaleX, this.clipHeight * this.scaleY)
355                this.contrastImgData = this.ctx.getImageData
356                (this.clipOffsetx, this.clipOffsety, this.clipWidth * this.scaleX, this.clipHeight * this.scaleY)
357                this.saturationImgData = this.ctx.getImageData
358                (this.clipOffsetx, this.clipOffsety, this.clipWidth * this.scaleX, this.clipHeight * this.scaleY)
359                this.ctx.save()
360              }
361            })
362            .fontSize('12fp')
363            .borderRadius(14)
364            .fontWeight(FontWeight.Medium)
365            .visibility(Visibility[this.visibility1])
366        }
367        .margin({ top: 5, bottom: 5 })
368
369        // 判断调节中选中亮度,对比度,饱和度三者中的哪一个
370        if (this.typeProperties == Type.brightness) {
371          Slider({
372            value: this.brightnessValue,
373            min: 0,
374            max: 10,
375            step: 1.0,
376            style: SliderStyle.OutSet
377          })
378            .onChange((value: number, mode: SliderChangeMode) => {
379              this.sliderNumber = mode.toString()
380              if (this.sliderNumber === '2') {
381                this.brightnessValue = value
382                if (value == 10) {
383                  this.ctx.restore()
384                  this.ctx.putImageData(this.brightnessImgData, this.clipOffsetx, this.clipOffsety)
385                } else {
386                  const adjust = value / this.oldBrightnessValue
387                  this.adjustValue = adjust
388                  this.adjustBrightness(this.adjustValue)
389                }
390              }
391            })
392            .visibility(Visibility[this.visibility2])
393        } else if (this.typeProperties == Type.contrast) {
394          Slider({
395            value: this.contrastValue,
396            min: 0,
397            max: 10,
398            step: 1.0,
399            style: SliderStyle.OutSet
400          })
401            .onChange((value: number, mode: SliderChangeMode) => {
402              this.sliderNumber = mode.toString()
403              if (this.sliderNumber === '2') {
404                this.contrastValue = value
405                if (value == 10) {
406                  this.ctx.restore()
407                  this.ctx.putImageData(this.contrastImgData, this.clipOffsetx, this.clipOffsety)
408                } else {
409                  const adjust = value / this.oldContrastValue
410                  this.adjustValue = adjust
411                  this.adjustContrast(this.adjustValue)
412                }
413              }
414            })
415        } else {
416          Slider({
417            value: this.saturateValue,
418            min: 0,
419            max: 10,
420            step: 1.0,
421            style: SliderStyle.OutSet
422          })
423            .onChange((value: number, mode: SliderChangeMode) => {
424              this.sliderNumber = mode.toString()
425              if (this.sliderNumber === '2') {
426                this.saturateValue = value
427                if (value == 10) {
428                  this.ctx.restore()
429                  this.ctx.putImageData(this.saturationImgData, this.clipOffsetx, this.clipOffsety)
430                } else {
431                  const adjust = value / this.oldSaturateValue
432                  this.adjustValue = adjust
433                  this.adjustSaturation(this.adjustValue)
434                }
435              }
436            })
437        }
438
439        Row({ space: 10 }) {
440          Button('亮度')
441            .onClick(() => {
442              this.typeProperties = Type.brightness
443            })
444            .fontSize('12fp')
445            .borderRadius(14)
446            .fontWeight(FontWeight.Medium)
447          Button('对比度')
448            .onClick(() => {
449              this.typeProperties = Type.contrast
450            })
451            .fontSize('12fp')
452            .borderRadius(14)
453            .fontWeight(FontWeight.Medium)
454          Button('饱和度')
455            .onClick(() => {
456              this.typeProperties = Type.saturate
457            })
458            .fontSize('12fp')
459            .borderRadius(14)
460            .fontWeight(FontWeight.Medium)
461        }
462        .padding({ bottom: 5, top: 5 })
463        .visibility(Visibility[this.visibility2])
464
465        Row({ space: 6 }) {
466          Button('裁剪')
467            .fontSize(18)
468            .onClick(() => {
469              this.visibility1 = 'Visible'
470              this.visibility2 = 'None'
471              this.clipState = 'original'
472              // 绘制图片 赋值裁剪框宽高
473              this.clipWidth = this.dWidth
474              this.clipHeight = this.dHeight
475              this.clipOffsetx = 0
476              this.clipOffsety = 0
477              // 绘制裁剪框
478              this.ctx.fillStyle = '#0344ee'
479              this.ctx.lineWidth = 5
480              this.cropBoxLeftOne = (this.dWidth - this.clipWidth) / 2
481              this.cropBoxTopOne = (this.dHeight - this.clipHeight) / 2
482            })
483            .fontSize('12fp')
484            .borderRadius(14)
485            .fontWeight(FontWeight.Medium)
486          Button('调节')
487            .fontSize(18)
488            .onClick(() => {
489              this.visibility1 = 'None'
490              this.visibility2 = 'Visible'
491              this.typeProperties = Type.brightness
492              this.ctx.restore()
493            })
494            .fontSize('12fp')
495            .borderRadius(14)
496            .fontWeight(FontWeight.Medium)
497        }
498        .padding({ bottom: 5, top: 5 })
499      }
500      .padding(10)
501      .borderRadius(20)
502      .backgroundColor('#FFFFFF')
503      .margin({ top: 30 })
504    }
505    .alignItems(HorizontalAlign.Center)
506    .justifyContent(FlexAlign.Start)
507    .width('100%')
508    .height('100%')
509    .backgroundColor('#F1F1F5')
510    .padding({ left: '3%', right: '3%' })
511  }
512
513  pageTransition() {
514    PageTransitionEnter({ duration: 370, curve: Curve.Friction })
515      .slide(SlideEffect.Bottom)
516      .opacity(0.0)
517
518    PageTransitionExit({ duration: 370, curve: Curve.Friction })
519      .slide(SlideEffect.Bottom)
520      .opacity(0.0)
521  }
522}