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
16import { LineSegment } from '../base/Line';
17import { Point } from '../base/Point';
18import { RectF } from '../base/Rect';
19import { CropAngle } from './CropType';
20
21export abstract class MathUtils {
22  private static readonly EQUALITY_THRESHOLD = 0.0001;
23
24  static roundOutRect(rect: RectF): void {
25    let copy = { ...rect };
26    rect.set(Math.round(copy.left), Math.round(copy.top), Math.round(copy.right), Math.round(copy.bottom));
27  }
28
29  static normalizeRect(rect: RectF, width: number, height: number): void {
30    rect.left /= width;
31    rect.right /= width;
32    rect.top /= height;
33    rect.bottom /= height;
34  }
35
36  static revertRect(rect: RectF, width: number, height: number): void {
37    rect.left *= width;
38    rect.right *= width;
39    rect.top *= height;
40    rect.bottom *= height;
41  }
42
43  static rectToPoints(rect: RectF): Array<Point> {
44    let points = [];
45    points.push(new Point(rect.left, rect.top));
46    points.push(new Point(rect.right, rect.top));
47    points.push(new Point(rect.right, rect.bottom));
48    points.push(new Point(rect.left, rect.bottom));
49    return points;
50  }
51
52  static swapWidthHeight(rect: RectF): void {
53    let centerX = rect.getCenterX();
54    let centerY = rect.getCenterY();
55    let halfWidth = rect.getWidth() / 2;
56    let halfHeight = rect.getHeight() / 2;
57    rect.left = centerX - halfHeight;
58    rect.right = centerX + halfHeight;
59    rect.top = centerY - halfWidth;
60    rect.bottom = centerY + halfWidth;
61  }
62
63  static rotatePoints(inputs: Array<Point>, angle: number, origin: Point): Array<Point> {
64    let alpha = MathUtils.formulaAngle(angle);
65    let outputs = [];
66    for (let input of inputs) {
67      let dx = input.x - origin.x;
68      let dy = input.y - origin.y;
69      let output = new Point(origin.x, origin.y);
70      output.x += Math.cos(alpha) * dx - Math.sin(alpha) * dy;
71      output.y += Math.sin(alpha) * dx + Math.cos(alpha) * dy;
72      outputs.push(output);
73    }
74    return outputs;
75  }
76
77  static computeMaxRectWithinLimit(rect: RectF, limit: RectF, rate: number): void {
78    let limitWidth = limit.getWidth();
79    let limitHeight = limit.getHeight();
80    let width = limitWidth;
81    let height = limitHeight;
82    if (rate > (limitWidth / limitHeight)) {
83      height = width / rate;
84      rect.left = limit.left;
85      rect.top = limit.top + (limitHeight - height) / 2;
86    } else {
87      width = height * rate;
88      rect.left = limit.left + (limitWidth - width) / 2;
89      rect.top = limit.top;
90    }
91    rect.right = rect.left + width;
92    rect.bottom = rect.top + height;
93  }
94
95  static scaleRectBasedOnPoint(rect: RectF, p: Point, scale: number): void {
96    let operate = { ...rect };
97    operate.left = (rect.left - p.x) * scale + p.x;
98    operate.right = (rect.right - p.x) * scale + p.x;
99    operate.top = (rect.top - p.y) * scale + p.y;
100    operate.bottom = (rect.bottom - p.y) * scale + p.y;
101    rect.set(operate.left, operate.top, operate.right, operate.bottom);
102  }
103
104  static hasIntersection(line1: LineSegment, line2: LineSegment): boolean {
105    let p1 = line1.start;
106    let p2 = line1.end;
107    let p3 = line2.start;
108    let p4 = line2.end;
109    if (Math.max(p1.x, p2.x) < Math.min(p3.x, p4.x) || Math.max(p1.y, p2.y) < Math.min(p3.y, p4.y)
110    || Math.max(p3.x, p4.x) < Math.min(p1.x, p2.x) || Math.max(p3.y, p4.y) < Math.min(p1.y, p2.y)) {
111      return false;
112    }
113
114    if ((((p1.x - p3.x) * (p4.y - p3.y) - (p1.y - p3.y) * (p4.x - p3.x))
115    * ((p2.x - p3.x) * (p4.y - p3.y) - (p2.y - p3.y) * (p4.x - p3.x))) >= 0
116    || (((p3.x - p1.x) * (p2.y - p1.y) - (p3.y - p1.y) * (p2.x - p1.x))
117    * ((p4.x - p1.x) * (p2.y - p1.y) - (p4.y - p1.y) * (p2.x - p1.x))) >= 0) {
118      return false;
119    }
120    return true;
121  }
122
123  static getIntersection(line1: LineSegment, line2: LineSegment): Point {
124    let A1 = line1.start.y - line1.end.y;
125    let B1 = line1.end.x - line1.start.x;
126    let C1 = A1 * line1.start.x + B1 * line1.start.y;
127
128    let A2 = line2.start.y - line2.end.y;
129    let B2 = line2.end.x - line2.start.x;
130    let C2 = A2 * line2.start.x + B2 * line2.start.y;
131
132    let k = A1 * B2 - A2 * B1;
133    if (Math.abs(k) < MathUtils.EQUALITY_THRESHOLD) {
134      return undefined;
135    }
136
137    let a = B2 / k;
138    let b = -B1 / k;
139    let c = -A2 / k;
140    let d = A1 / k;
141
142    let x = a * C1 + b * C2;
143    let y = c * C1 + d * C2;
144    return new Point(x, y);
145  }
146
147  static findSuitableScale(points: Array<Point>, rect: RectF, origin: Point): number {
148    let scale = 1;
149    let temp = 1;
150    for (let point of points) {
151      if (point.x < rect.left) {
152        temp = (origin.x - point.x) / (origin.x - rect.left);
153        scale = Math.max(scale, temp);
154      }
155      if (point.x > rect.right) {
156        temp = (point.x - origin.x) / (rect.right - origin.x);
157        scale = Math.max(scale, temp);
158      }
159      if (point.y < rect.top) {
160        temp = (origin.y - point.y) / (origin.y - rect.top);
161        scale = Math.max(scale, temp);
162      }
163      if (point.y > rect.bottom) {
164        temp = (point.y - origin.y) / (rect.bottom - origin.y);
165        scale = Math.max(scale, temp);
166      }
167    }
168    return scale;
169  }
170
171  static fixImageMove(rotated: Array<Point>, flipImage: RectF): Array<number> {
172    let offsetX = 0;
173    let offsetY = 0;
174    for (let point of rotated) {
175      if (point.x < flipImage.left) {
176        offsetX = Math.min(offsetX, point.x - flipImage.left);
177      } else if (point.x > flipImage.right) {
178        offsetX = Math.max(offsetX, point.x - flipImage.right);
179      }
180      if (point.y < flipImage.top) {
181        offsetY = Math.min(offsetY, point.y - flipImage.top);
182      } else if (point.y > flipImage.bottom) {
183        offsetY = Math.max(offsetY, point.y - flipImage.bottom);
184      }
185    }
186    return [offsetX, offsetY];
187  }
188
189  static isOddRotation(angle: number): boolean {
190    if (angle == -CropAngle.ONE_QUARTER_CIRCLE_ANGLE || angle == -CropAngle.THREE_QUARTER_CIRCLE_ANGLE) {
191      return true;
192    }
193    return false;
194  }
195
196  static limitCornerIfLineIntersect(outerLine: LineSegment, diagonal: LineSegment, rect: RectF): void {
197    let origin = new Point(rect.getCenterX(), rect.getCenterY());
198    if (MathUtils.hasIntersection(outerLine, diagonal)) {
199      let intersection = MathUtils.getIntersection(outerLine, diagonal);
200      if (intersection == undefined) {
201        return;
202      }
203      if (intersection.x < origin.x) {
204        rect.left = intersection.x;
205      } else {
206        rect.right = intersection.x;
207      }
208      if (intersection.y < origin.y) {
209        rect.top = intersection.y;
210      } else {
211        rect.bottom = intersection.y;
212      }
213    }
214  }
215
216  static limitRectInRotated(rect: RectF, outerLines: Array<LineSegment>): void {
217    let copy = new RectF();
218    copy.set(rect.left, rect.top, rect.right, rect.bottom);
219    let diagonal1 = new LineSegment(new Point(copy.left, copy.top), new Point(copy.right, copy.bottom));
220    for (let line of outerLines) {
221      MathUtils.limitCornerIfLineIntersect(line, diagonal1, copy);
222    }
223
224    let diagonal2 = new LineSegment(new Point(copy.left, copy.bottom), new Point(copy.right, copy.top));
225    for (let line of outerLines) {
226      MathUtils.limitCornerIfLineIntersect(line, diagonal2, copy);
227    }
228    rect.set(copy.left, copy.top, copy.right, copy.bottom);
229  }
230
231  static limitRectInRotatedBasedOnPoint(baseIndex: number, rect: RectF, rotatedLines: Array<LineSegment>): void {
232    let points = MathUtils.rectToPoints(rect);
233    let base = points[baseIndex];
234    points.splice(baseIndex, 1);
235    let scale = 1;
236    for (let point of points) {
237      let line = new LineSegment(base, point);
238      for (let rotatedLine of rotatedLines) {
239        if (MathUtils.hasIntersection(line, rotatedLine)) {
240          let p = MathUtils.getIntersection(line, rotatedLine);
241          if (p == undefined) {
242            continue;
243          }
244          let tempScale
245            = Math.hypot(p.x - base.x, p.y - base.y) / Math.hypot(point.x - base.x, point.y - base.y);
246          scale = Math.min(scale, (tempScale > MathUtils.EQUALITY_THRESHOLD) ? tempScale : 1);
247        }
248      }
249    }
250    MathUtils.scaleRectBasedOnPoint(rect, base, scale);
251  }
252
253  static getMaxFixedRectSize(rate: number, maxWidth: number, maxHeight: number): Array<number> {
254    let width = 0;
255    let height = 0;
256    if (rate > (maxWidth / maxHeight)) {
257      width = maxWidth;
258      height = width / rate;
259    } else {
260      height = maxHeight;
261      width = height * rate;
262    }
263    return [width, height];
264  }
265
266  static getMinFixedRectSize(rate: number, minLength: number): Array<number> {
267    let width = minLength;
268    let height = minLength;
269    if (rate > 1) {
270      width = height * rate;
271    } else {
272      height = width / rate;
273    }
274    return [width, height];
275  }
276
277  static areRectsSame(rect1: RectF, rect2: RectF): boolean {
278    if (rect1.left == rect2.left && rect1.top == rect2.top
279    && rect1.right == rect2.right && rect1.bottom == rect2.bottom) {
280      return true;
281    }
282    return false;
283  }
284
285  static formulaAngle(angle: number): number {
286    return angle * Math.PI / CropAngle.HALF_CIRCLE_ANGLE;
287  }
288}