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}