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}