• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2022-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 { LineSegment } from '../base/Line';
17import { Point } from '../base/Point';
18import { RectF } from '../base/Rect';
19import { Ratio } from '../base/Ratio';
20import { Constants, Log, ScreenManager } from '@ohos/common';
21import { CropAngle, CropRatioType } from './CropType';
22import { MathUtils } from './MathUtils';
23
24const TAG: string = 'editor_CropShow';
25
26export class CropShow {
27  private static readonly DEFAULT_MIN_SIDE_LENGTH: number = 90;
28  private static readonly DEFAULT_TOUCH_BOUND: number = 20;
29  private static readonly BASE_SCALE_VALUE: number = 1.0;
30  private limitRect: RectF = undefined;
31  private cropRect: RectF = undefined;
32  private imageRect: RectF = undefined;
33  private ratio: Ratio = undefined;
34  private screenMaxSide: number = 0;
35  private screenMinSide: number = 0;
36  private minSideLength: number = CropShow.DEFAULT_MIN_SIDE_LENGTH;
37  private touchBound: number = CropShow.DEFAULT_TOUCH_BOUND;
38  private rotationAngle: number = 0;
39  private horizontalAngle: number = 0;
40  private maxScaleFactorW: number = CropShow.BASE_SCALE_VALUE;
41  private maxScaleFactorH: number = CropShow.BASE_SCALE_VALUE;
42  private isFlipHorizontal: boolean = false;
43  private isFlipVertically: boolean = false;
44  private isLeft: boolean = false;
45  private isRight: boolean = false;
46  private isTop: boolean = false;
47  private isBottom: boolean = false;
48  private isHorizontalSide: boolean = false;
49  private isVerticalSide: boolean = false;
50
51  constructor() {
52    this.limitRect = new RectF();
53    this.imageRect = new RectF();
54    this.cropRect = new RectF();
55    this.ratio = new Ratio(-1, -1);
56
57    let screenWidth = Math.ceil(ScreenManager.getInstance().getWinWidth());
58    let screenHeight = Math.ceil(ScreenManager.getInstance().getWinHeight());
59    this.screenMaxSide = Math.max(screenWidth, screenHeight);
60    this.screenMinSide = Math.min(screenWidth, screenHeight);
61  }
62
63  init(limit: RectF, imageRatio: number): void {
64    this.limitRect.set(limit.left, limit.top, limit.right, limit.bottom);
65    MathUtils.computeMaxRectWithinLimit(this.imageRect, limit, imageRatio);
66    this.cropRect.set(this.imageRect.left, this.imageRect.top, this.imageRect.right, this.imageRect.bottom);
67    this.ratio.set(-1, -1);
68    this.rotationAngle = 0;
69    this.horizontalAngle = 0;
70    this.isFlipHorizontal = false;
71    this.isFlipVertically = false;
72  }
73
74  syncLimitRect(limit: RectF): void {
75    this.limitRect.set(limit.left, limit.top, limit.right, limit.bottom);
76    this.recalculateCropArea();
77  }
78
79  getCropRect(): RectF {
80    let crop = new RectF();
81    crop.set(this.cropRect.left, this.cropRect.top, this.cropRect.right, this.cropRect.bottom);
82    return crop;
83  }
84
85  getImageRect(): RectF {
86    let image = new RectF();
87    image.set(this.imageRect.left, this.imageRect.top, this.imageRect.right, this.imageRect.bottom);
88    return image;
89  }
90
91  setImageRect(image: RectF): void {
92    this.imageRect.set(image.left, image.top, image.right, image.bottom);
93  }
94
95  syncRotationAngle(angle: number): void {
96    this.rotationAngle = angle;
97    MathUtils.swapWidthHeight(this.cropRect);
98    this.swapCurrentRatio();
99    this.enlargeCropArea();
100  }
101
102  syncHorizontalAngle(angle: number): void {
103    this.horizontalAngle = angle;
104
105    let points = MathUtils.rectToPoints(this.cropRect);
106    let origin = this.getDisplayCenter();
107    let totalAngle = -(this.rotationAngle + this.horizontalAngle);
108    let rotated = MathUtils.rotatePoints(points, totalAngle, origin);
109    let scale = MathUtils.findSuitableScale(rotated, this.imageRect, origin);
110    MathUtils.scaleRectBasedOnPoint(this.imageRect, origin, scale);
111  }
112
113  setFlip(isFlipHorizontal: boolean, isFlipVertically: boolean): void {
114    this.isFlipHorizontal = isFlipHorizontal;
115    this.isFlipVertically = isFlipVertically;
116  }
117
118  setRatio(ratio: CropRatioType): void {
119    switch (ratio) {
120      case CropRatioType.RATIO_TYPE_FREE:
121        this.ratio.set(-1, -1);
122        break;
123      case CropRatioType.RATIO_TYPE_HORIZONTAL:
124        this.ratio.set(this.screenMaxSide, this.screenMinSide);
125        break;
126      case CropRatioType.RATIO_TYPE_VERTICAL:
127        this.ratio.set(this.screenMinSide, this.screenMaxSide);
128        break;
129      case CropRatioType.RATIO_TYPE_1_1:
130        this.ratio.set(1, 1);
131        break;
132      case CropRatioType.RATIO_TYPE_16_9:
133        this.ratio.set(16, 9);
134        break;
135      case CropRatioType.RATIO_TYPE_9_16:
136        this.ratio.set(9, 16);
137        break;
138      case CropRatioType.RATIO_TYPE_4_3:
139        this.ratio.set(4, 3);
140        break;
141      case CropRatioType.RATIO_TYPE_3_4:
142        this.ratio.set(3, 4);
143        break;
144      case CropRatioType.RATIO_TYPE_3_2:
145        this.ratio.set(3, 2);
146        break;
147      case CropRatioType.RATIO_TYPE_2_3:
148        this.ratio.set(2, 3);
149        break;
150      default:
151        Log.warn(TAG, 'setRatio: unknown ratio');
152        break;
153    }
154    if (this.ratio.isValid()) {
155      MathUtils.computeMaxRectWithinLimit(this.cropRect, this.limitRect, this.ratio.getRate());
156      let imageLines = this.getCurrentImageLines();
157      MathUtils.limitRectInRotated(this.cropRect, imageLines);
158      this.imageCropCompare();
159      this.enlargeCropArea();
160    }
161  }
162
163  setMaxScaleFactor(factorW: number, factorH: number): void {
164    this.maxScaleFactorW = factorW;
165    this.maxScaleFactorH = factorH;
166  }
167
168  couldEnlargeImage(): boolean {
169    return (this.couldEnlargeImageW() && this.couldEnlargeImageH());
170  }
171
172  enlargeCropArea(): void {
173    let newCrop: RectF = new RectF();
174    let cropRatio: number = this.cropRect.getWidth() / this.cropRect.getHeight();
175    // Recalculate crop Rect.
176    MathUtils.computeMaxRectWithinLimit(newCrop, this.limitRect, cropRatio);
177    let scale: number = newCrop.getWidth() / this.cropRect.getWidth();
178
179    // Scale Image based on the midpoint of the last crop.
180    let tX: number = this.isFlipHorizontal ? -1 : 1;
181    let tY: number = this.isFlipVertically ? -1 : 1;
182    let origin: Point = this.getDisplayCenter();
183    let preCenterX: number = this.cropRect.getCenterX() * tX + (
184      this.isFlipHorizontal ? Constants.NUMBER_2 * origin.x : 0
185    );
186    let preCenterY: number = this.cropRect.getCenterY() * tY + (
187      this.isFlipVertically ? Constants.NUMBER_2 * origin.y : 0
188    );
189    let preCenter: Point = new Point(preCenterX, preCenterY);
190    let angle: number = this.rotationAngle * tX * tY + this.horizontalAngle;
191    let rotated: Array<Point> = MathUtils.rotatePoints([preCenter], -angle, origin);
192
193    MathUtils.scaleRectBasedOnPoint(this.imageRect, rotated[0], scale);
194
195    // Rotate based on the new midpoint.
196    let offsetX: number = newCrop.getCenterX() - preCenter.x;
197    let offsetY: number = newCrop.getCenterY() - preCenter.y;
198    let alpha: number = MathUtils.formulaAngle(angle);
199    let x: number = Math.cos(alpha) * offsetX + Math.sin(alpha) * offsetY;
200    let y: number = -Math.sin(alpha) * offsetX + Math.cos(alpha) * offsetY;
201    this.imageRect.move(x, y);
202
203    this.cropRect.set(newCrop.left, newCrop.top, newCrop.right, newCrop.bottom);
204  }
205
206  recalculateCropArea(): void {
207    let newCrop: RectF = new RectF();
208    let cropRatio: number = this.cropRect.getWidth() / this.cropRect.getHeight();
209    // Recalculate cropRect scale.
210    MathUtils.computeMaxRectWithinLimit(newCrop, this.limitRect, cropRatio);
211    let scale: number = newCrop.getWidth() / this.cropRect.getWidth();
212    let originalCropCenter: Point = new Point(this.cropRect.getCenterX(), this.cropRect.getCenterY());
213    // Scales the imageRect using the scale of cropRect.
214    MathUtils.scaleRectBasedOnPoint(this.imageRect, originalCropCenter, scale);
215
216    // offset the imageRect using the offset of cropRect.
217    let imageOffsetX: number = newCrop.getCenterX() - this.cropRect.getCenterX();
218    let imageOffsetY: number = newCrop.getCenterY() - this.cropRect.getCenterY();
219    this.imageRect.move(imageOffsetX, imageOffsetY);
220
221    this.cropRect.set(newCrop.left, newCrop.top, newCrop.right, newCrop.bottom);
222  }
223
224  imageCropCompare(): void {
225    let imageRect = this.getImageRect();
226    let cropRect = this.getCropRect();
227    let imageRectWidth = imageRect.getWidth();
228    let imageRectHeight = imageRect.getHeight();
229    let cropRectWidth = cropRect.getWidth();
230    let cropRectHeight = cropRect.getHeight();
231    if (imageRectWidth < cropRectWidth) {
232      let scaleRatio = cropRectWidth / imageRectWidth;
233      this.imageRect.scale(scaleRatio);
234    }
235    if (imageRectHeight < cropRectHeight) {
236      let scaleRatio = cropRectHeight / imageRectHeight;
237      this.imageRect.scale(scaleRatio);
238    }
239
240  }
241
242  isCropRectTouch(x: number, y: number): boolean {
243    let w = this.touchBound;
244    let h = this.touchBound;
245    let crop = { ...this.cropRect };
246    let outer = new RectF();
247    outer.set(crop.left - w, crop.top - h, crop.right + w, crop.bottom + h);
248    let inner = new RectF();
249    inner.set(crop.left + w, crop.top + h, crop.right - w, crop.bottom - h);
250    if (outer.isInRect(x, y) && !inner.isInRect(x, y)) {
251      if (x <= inner.left) {
252        this.isLeft = true;
253      } else if (x >= inner.right) {
254        this.isRight = true;
255      }
256
257      if (y <= inner.top) {
258        this.isTop = true;
259      } else if (y >= inner.bottom) {
260        this.isBottom = true;
261      }
262
263      // convert side to conner, when fixed crop ratio
264      if (this.ratio.isValid()) {
265        this.fixSideToConner(x, y);
266      }
267      Log.debug(TAG, `isCropTouch: l[${this.isLeft}] r[${this.isRight}] t[${this.isTop}] b[${this.isBottom}]`);
268    }
269    return this.isLeft || this.isRight || this.isTop || this.isBottom;
270  }
271
272  getCurrentFlipImage(): RectF {
273    let center = this.getDisplayCenter();
274    let image = { ...this.imageRect };
275    let flipImage = new RectF();
276    flipImage.left = this.isFlipHorizontal ? (2 * center.x - image.right) : image.left;
277    flipImage.top = this.isFlipVertically ? (2 * center.y - image.bottom) : image.top;
278    flipImage.right = this.isFlipHorizontal ? (2 * center.x - image.left) : image.right;
279    flipImage.bottom = this.isFlipVertically ? (2 * center.y - image.top) : image.bottom;
280    return flipImage;
281  }
282
283  moveCropRect(offsetX: number, offsetY: number): void {
284    // crop rect in fixed mode
285    if (this.ratio.isValid()) {
286      this.moveInFixedMode(offsetX, offsetY);
287    } else {
288      this.moveInFreeMode(offsetX, offsetY);
289    }
290  }
291
292  endCropRectMove(): void {
293    this.isLeft = false;
294    this.isRight = false;
295    this.isTop = false;
296    this.isBottom = false;
297    this.isHorizontalSide = false;
298    this.isVerticalSide = false;
299  }
300
301  private swapCurrentRatio(): void {
302    let W = this.ratio.getW();
303    let H = this.ratio.getH();
304    this.ratio.set(H, W);
305  }
306
307  private getDisplayCenter(): Point {
308    return new Point(this.limitRect.getCenterX(), this.limitRect.getCenterY());
309  }
310
311  private couldEnlargeImageW(): boolean {
312    let scaleFactorW = this.imageRect.getWidth() / this.cropRect.getWidth();
313    return (scaleFactorW >= this.maxScaleFactorW ? false : true);
314  }
315
316  private couldEnlargeImageH(): boolean {
317    let scaleFactorH = this.imageRect.getHeight() / this.cropRect.getHeight();
318    return (scaleFactorH >= this.maxScaleFactorH ? false : true);
319  }
320
321  private fixSideToConner(x: number, y: number): void {
322    if ((this.isLeft || this.isRight) && !this.isTop && !this.isBottom) {
323      if (y < this.cropRect.getCenterY()) {
324        this.isTop = true;
325      } else {
326        this.isBottom = true;
327      }
328      this.isVerticalSide = true;
329    } else if ((this.isTop || this.isBottom) && !this.isLeft && !this.isRight) {
330      if (x < this.cropRect.getCenterX()) {
331        this.isLeft = true;
332      } else {
333        this.isRight = true;
334      }
335      this.isHorizontalSide = true;
336    }
337  }
338
339  private getCurrentRotatedImage(): RectF {
340    let flipImage = this.getCurrentFlipImage();
341    let points = MathUtils.rectToPoints(flipImage);
342    let origin = this.getDisplayCenter();
343    let rotated = MathUtils.rotatePoints(points, this.rotationAngle, origin);
344    let i = Math.abs(this.rotationAngle / CropAngle.ONE_QUARTER_CIRCLE_ANGLE);
345    let j = (i + 2) % rotated.length;
346    let image = new RectF();
347    image.set(rotated[i].x, rotated[i].y, rotated[j].x, rotated[j].y);
348    return image;
349  }
350
351  private getCurrentImageLines(): Array<LineSegment> {
352    let flipImage = this.getCurrentFlipImage();
353    let imagePoints = MathUtils.rectToPoints(flipImage);
354    let origin = this.getDisplayCenter();
355    let tX = this.isFlipHorizontal ? -1 : 1;
356    let tY = this.isFlipVertically ? -1 : 1;
357    let angle = this.rotationAngle * tX * tY + this.horizontalAngle;
358    let rotated = MathUtils.rotatePoints(imagePoints, angle, origin);
359
360    let imageLines = [];
361    for (let i = 0; i < rotated.length; i++) {
362      let j = (i + 1) % rotated.length;
363      imageLines.push(
364        new LineSegment(new Point(rotated[i].x, rotated[i].y), new Point(rotated[j].x, rotated[j].y)));
365    }
366    return imageLines;
367  }
368
369  private moveInFixedMode(offsetX: number, offsetY: number): void {
370    let x = offsetX;
371    let y = offsetY;
372    if (this.isHorizontalSide) {
373      x = 0;
374    } else if (this.isVerticalSide) {
375      y = 0;
376    }
377    let offsetHypot = Math.hypot(x, y);
378
379    if (this.isLeft && this.isTop) {
380      // left top conner move
381      let isEnlarge = offsetX < 0 || offsetY < 0;
382      if (isEnlarge || this.couldEnlargeImage()) {
383        this.fixLeftTopInFixedMode(offsetHypot, isEnlarge);
384      }
385    } else if (this.isLeft && this.isBottom) {
386      // left bottom conner move
387      let isEnlarge = offsetX < 0 || offsetY > 0;
388      if (isEnlarge || this.couldEnlargeImage()) {
389        this.fixLeftBottomInFixedMode(offsetHypot, isEnlarge);
390      }
391    } else if (this.isRight && this.isTop) {
392      // right top conner move
393      let isEnlarge = offsetX > 0 || offsetY < 0;
394      if (isEnlarge || this.couldEnlargeImage()) {
395        this.fixRightTopInFixedMode(offsetHypot, isEnlarge);
396      }
397    } else if (this.isRight && this.isBottom) {
398      // right bottom conner move
399      let isEnlarge = offsetX > 0 || offsetY > 0;
400      if (isEnlarge || this.couldEnlargeImage()) {
401        this.fixRightBottomInFixedMode(offsetHypot, isEnlarge);
402      }
403    }
404  }
405
406  private fixLeftTopInFixedMode(offsetHypot: number, isEnlarge: boolean): void {
407    let crop = this.getCropRect();
408    let rate = this.ratio.getRate();
409    let rect = new RectF();
410    if (isEnlarge) {
411      let limit = { ...this.limitRect };
412      let size = MathUtils.getMaxFixedRectSize(rate, crop.right - limit.left, crop.bottom - limit.top);
413      rect.set(crop.right - size[0], crop.bottom - size[1], crop.right, crop.bottom);
414      let imageLines = this.getCurrentImageLines();
415      MathUtils.limitRectInRotatedBasedOnPoint(2, rect, imageLines);
416    } else {
417      let size = MathUtils.getMinFixedRectSize(rate, this.minSideLength);
418      rect.set(crop.right - size[0], crop.bottom - size[1], crop.right, crop.bottom);
419    }
420    let rectHypot = Math.hypot(rect.getWidth(), rect.getHeight());
421    let cropHypot = Math.hypot(crop.getWidth(), crop.getHeight());
422    let limitHypot = (rectHypot - cropHypot) * (isEnlarge ? 1 : -1);
423    let finalOffsetHypot = Math.min(offsetHypot, Math.max(limitHypot, 0));
424    let tX = isEnlarge ? -1 : 1;
425    let tY = isEnlarge ? -1 : 1;
426    let ratioHypot = Math.hypot(this.ratio.getW(), this.ratio.getH());
427    this.cropRect.left += finalOffsetHypot * tX * this.ratio.getW() / ratioHypot;
428    this.cropRect.top += finalOffsetHypot * tY * this.ratio.getH() / ratioHypot;
429  }
430
431  private fixLeftBottomInFixedMode(offsetHypot: number, isEnlarge: boolean): void {
432    let crop = this.getCropRect();
433    let rate = this.ratio.getRate();
434    let rect = new RectF();
435    if (isEnlarge) {
436      let limit = { ...this.limitRect };
437      let size = MathUtils.getMaxFixedRectSize(rate, crop.right - limit.left, limit.bottom - crop.top);
438      rect.set(crop.right - size[0], crop.top, crop.right, crop.top + size[1]);
439      let imageLines = this.getCurrentImageLines();
440      MathUtils.limitRectInRotatedBasedOnPoint(1, rect, imageLines);
441    } else {
442      let size = MathUtils.getMinFixedRectSize(rate, this.minSideLength);
443      rect.set(crop.right - size[0], crop.top, crop.right, crop.top + size[1]);
444    }
445    let rectHypot = Math.hypot(rect.getWidth(), rect.getHeight());
446    let cropHypot = Math.hypot(crop.getWidth(), crop.getHeight());
447    let limitHypot = (rectHypot - cropHypot) * (isEnlarge ? 1 : -1);
448    let finalOffsetHypot = Math.min(offsetHypot, Math.max(limitHypot, 0));
449    let tX = isEnlarge ? -1 : 1;
450    let tY = isEnlarge ? 1 : -1;
451    let ratioHypot = Math.hypot(this.ratio.getW(), this.ratio.getH());
452    this.cropRect.left += finalOffsetHypot * tX * this.ratio.getW() / ratioHypot;
453    this.cropRect.bottom += finalOffsetHypot * tY * this.ratio.getH() / ratioHypot;
454  }
455
456  private fixRightTopInFixedMode(offsetHypot: number, isEnlarge: boolean): void {
457    let crop = this.getCropRect();
458    let rate = this.ratio.getRate();
459    let rect = new RectF();
460    if (isEnlarge) {
461      let limit = { ...this.limitRect };
462      let size = MathUtils.getMaxFixedRectSize(rate, limit.right - crop.left, crop.bottom - limit.top);
463      rect.set(crop.left, crop.bottom - size[1], crop.left + size[0], crop.bottom);
464      let imageLines = this.getCurrentImageLines();
465      MathUtils.limitRectInRotatedBasedOnPoint(3, rect, imageLines);
466    } else {
467      let size = MathUtils.getMinFixedRectSize(rate, this.minSideLength);
468      rect.set(crop.left, crop.bottom - size[1], crop.left + size[0], crop.bottom);
469    }
470    let rectHypot = Math.hypot(rect.getWidth(), rect.getHeight());
471    let cropHypot = Math.hypot(crop.getWidth(), crop.getHeight());
472    let limitHypot = (rectHypot - cropHypot) * (isEnlarge ? 1 : -1);
473    let finalOffsetHypot = Math.min(offsetHypot, Math.max(limitHypot, 0));
474    let tX = isEnlarge ? 1 : -1;
475    let tY = isEnlarge ? -1 : 1;
476    let ratioHypot = Math.hypot(this.ratio.getW(), this.ratio.getH());
477    this.cropRect.right += finalOffsetHypot * tX * this.ratio.getW() / ratioHypot;
478    this.cropRect.top += finalOffsetHypot * tY * this.ratio.getH() / ratioHypot;
479  }
480
481  private fixRightBottomInFixedMode(offsetHypot: number, isEnlarge: boolean): void {
482    let crop = this.getCropRect();
483    let rate = this.ratio.getRate();
484    let rect = new RectF();
485    if (isEnlarge) {
486      let limit = { ...this.limitRect };
487      let size = MathUtils.getMaxFixedRectSize(rate, limit.right - crop.left, limit.bottom - crop.top);
488      rect.set(crop.left, crop.top, crop.left + size[0], crop.top + size[1]);
489      let imageLines = this.getCurrentImageLines();
490      MathUtils.limitRectInRotatedBasedOnPoint(0, rect, imageLines);
491    } else {
492      let size = MathUtils.getMinFixedRectSize(rate, this.minSideLength);
493      rect.set(crop.left, crop.top, crop.left + size[0], crop.top + size[1]);
494    }
495    let rectHypot = Math.hypot(rect.getWidth(), rect.getHeight());
496    let cropHypot = Math.hypot(crop.getWidth(), crop.getHeight());
497    let limitHypot = (rectHypot - cropHypot) * (isEnlarge ? 1 : -1);
498    let finalOffsetHypot = Math.min(offsetHypot, Math.max(limitHypot, 0));
499    let tX = isEnlarge ? 1 : -1;
500    let tY = isEnlarge ? 1 : -1;
501    let ratioHypot = Math.hypot(this.ratio.getW(), this.ratio.getH());
502    this.cropRect.right += finalOffsetHypot * tX * this.ratio.getW() / ratioHypot;
503    this.cropRect.bottom += finalOffsetHypot * tY * this.ratio.getH() / ratioHypot;
504  }
505
506  private moveInFreeMode(offsetX: number, offsetY: number): void {
507    let crop = this.getCropRect();
508    let limit = { ...this.limitRect };
509    let image = this.getCurrentRotatedImage();
510    let minLength = this.minSideLength;
511    let imageLines = this.getCurrentImageLines();
512    if (this.isLeft) {
513      if (offsetX < 0 || this.couldEnlargeImageW()) {
514        let left = Math.min(crop.left + offsetX, crop.right - minLength);
515        left = Math.max(left, image.left, limit.left);
516        this.cropRect.left = this.fixLeftInFreeMode(left, crop, imageLines);
517        crop.left = this.cropRect.left;
518      }
519    } else if (this.isRight) {
520      if (offsetX > 0 || this.couldEnlargeImageW()) {
521        let right = Math.max(crop.right + offsetX, crop.left + minLength);
522        right = Math.min(right, image.right, limit.right);
523        this.cropRect.right = this.fixRightInFreeMode(right, crop, imageLines);
524        crop.right = this.cropRect.right;
525      }
526    }
527    if (this.isTop) {
528      if (offsetY < 0 || this.couldEnlargeImageH()) {
529        let top = Math.min(crop.top + offsetY, crop.bottom - minLength);
530        top = Math.max(top, image.top, limit.top);
531        this.cropRect.top = this.fixTopInFreeMode(top, crop, imageLines);
532      }
533    } else if (this.isBottom) {
534      if (offsetY > 0 || this.couldEnlargeImageH()) {
535        let bottom = Math.max(crop.bottom + offsetY, crop.top + minLength);
536        bottom = Math.min(bottom, image.bottom, limit.bottom);
537        this.cropRect.bottom = this.fixBottomInFreeMode(bottom, crop, imageLines);
538      }
539    }
540  }
541
542  private fixLeftInFreeMode(left: number, crop: RectF, imageLines: Array<LineSegment>): number {
543    let leftLine = new LineSegment(new Point(left, crop.top), new Point(left, crop.bottom));
544    let adjacentLines = [];
545    adjacentLines.push(new LineSegment(new Point(left, crop.top), new Point(crop.right, crop.top)));
546    adjacentLines.push(new LineSegment(new Point(left, crop.bottom), new Point(crop.right, crop.bottom)));
547    let fixedLeft = left;
548    for (let imageLine of imageLines) {
549      if (MathUtils.hasIntersection(imageLine, leftLine)) {
550        let result = this.tryToFindFixedSide(adjacentLines, imageLine, left, true, true);
551        fixedLeft = Math.max(fixedLeft, result);
552      }
553    }
554    return fixedLeft;
555  }
556
557  private fixRightInFreeMode(right: number, crop: RectF, imageLines: Array<LineSegment>): number {
558    let rightLine = new LineSegment(new Point(right, crop.top), new Point(right, crop.bottom));
559    let adjacentLines = [];
560    adjacentLines.push(new LineSegment(new Point(crop.left, crop.top), new Point(right, crop.top)));
561    adjacentLines.push(new LineSegment(new Point(crop.left, crop.bottom), new Point(right, crop.bottom)));
562    let fixedRight = right;
563    for (let imageLine of imageLines) {
564      if (MathUtils.hasIntersection(imageLine, rightLine)) {
565        let result = this.tryToFindFixedSide(adjacentLines, imageLine, right, true, false);
566        fixedRight = Math.min(fixedRight, result);
567      }
568    }
569    return fixedRight;
570  }
571
572  private fixTopInFreeMode(top: number, crop: RectF, imageLines: Array<LineSegment>): number {
573    let topLine = new LineSegment(new Point(crop.left, top), new Point(crop.right, top));
574    let adjacentLines = [];
575    adjacentLines.push(new LineSegment(new Point(crop.left, top), new Point(crop.left, crop.bottom)));
576    adjacentLines.push(new LineSegment(new Point(crop.right, top), new Point(crop.right, crop.bottom)));
577    let fixedTop = top;
578    for (let imageLine of imageLines) {
579      if (MathUtils.hasIntersection(imageLine, topLine)) {
580        let result = this.tryToFindFixedSide(adjacentLines, imageLine, top, false, true);
581        fixedTop = Math.max(fixedTop, result);
582      }
583    }
584    return fixedTop;
585  }
586
587  private fixBottomInFreeMode(bottom: number, crop: RectF, imageLines: Array<LineSegment>): number {
588    let bottomLine = new LineSegment(new Point(crop.left, bottom), new Point(crop.right, bottom));
589    let adjacentLines = [];
590    adjacentLines.push(new LineSegment(new Point(crop.left, crop.top), new Point(crop.left, bottom)));
591    adjacentLines.push(new LineSegment(new Point(crop.right, crop.top), new Point(crop.right, bottom)));
592    let fixedBottom = bottom;
593    for (let imageLine of imageLines) {
594      if (MathUtils.hasIntersection(imageLine, bottomLine)) {
595        let result = this.tryToFindFixedSide(adjacentLines, imageLine, bottom, false, false);
596        fixedBottom = Math.min(fixedBottom, result);
597      }
598    }
599    return fixedBottom;
600  }
601
602  private tryToFindFixedSide(adjacentLines: Array<LineSegment>, imageLine: LineSegment,
603                             side: number, isCompareX: boolean, isCompareMax: boolean): number {
604    let fixedSide = side;
605    let compareFunc = isCompareMax ? Math.max : Math.min;
606    for (let adjacentLine of adjacentLines) {
607      if (MathUtils.hasIntersection(imageLine, adjacentLine)) {
608        let intersection = MathUtils.getIntersection(imageLine, adjacentLine);
609        if (intersection == undefined) {
610          continue;
611        }
612        let compare = isCompareX ? intersection.x : intersection.y;
613        fixedSide = compareFunc(side, compare);
614      }
615    }
616    return fixedSide;
617  }
618}