export interface IXy { x: number y: number } export interface IRltb { right: number left: number top: number bottom: number } export interface IXywh { x: number y: number w: number h: number } export interface IBox { x: number y: number width: number height: number } // 矩形,可以是 IXywh 或者 IRltb IBox 形式 export type IRect = IRltb | IXywh | IBox /** * 矩形描述转换 * 把 {right, left, top, bottom} 转化为 {x, y , w, h} * @param rltb * @return {IXywh} */ export function rltb2xywh(rltb: IRltb) { let xyhw: IXywh = {} xyhw.x = rltb.left xyhw.y = rltb.top xyhw.h = rltb.bottom - rltb.top xyhw.w = rltb.right - rltb.left return xyhw } /** * 矩形描述转换 * 把 {x, y , w, h} 转化为 {right, left, top, bottom} * @param xywh * @return {IRltb} */ export function xywh2rltb(xywh: IXywh) { let rltb: IRltb = {} rltb.left = xywh.x rltb.top = xywh.y rltb.right = xywh.x + xywh.w rltb.bottom = xywh.y + xywh.h return rltb } /** * 给 Rltb 添加内边距 * @param rltb * @param padding 内边距,支持类似 CSS 的数组形式 * * @example * paddingRltb(xywh, 5) * paddingRltb(xywh, [3,4,5,10]) * * @return {IRltb} */ export function paddingRltb(rltb: IRltb, padding: number | number[]) { if (typeof padding === "number") { rltb.left -= padding rltb.top -= padding rltb.right += padding rltb.bottom += padding } else if (padding.length && padding.length == 4) { rltb.left -= padding[0] rltb.top -= padding[1] rltb.right += padding[2] rltb.bottom += padding[3] } return rltb } /** * 给 Xywh 添加内边距 * @param xywh * @param padding 内边距,支持类似 CSS 的数组形式 * * @example * paddingXywh(xywh, 5) * paddingXywh(xywh, [3,4,5,10]) */ export function paddingXywh(xywh: IXywh, padding: number | number[]) { let rltb = xywh2rltb(xywh) let reRltb = paddingRltb(rltb, padding) return rltb2xywh(reRltb) } /** * 给 Rect 添加内边距 * @param rect * @param padding 内边距,支持类似 CSS 的数组形式 * * @example * paddingRect(xywh, 5) * paddingRect(rltb, [3,4,5,10]) */ export function paddingRect(rect: IRect, padding: number | number[]) { if (rectIsRltb(rect)) { return paddingRltb(rect, padding) } else { return paddingXywh(rect, padding) } } /** * 矩形对象是否是 IXywh 的描述形式 * @param rect */ export function rectIsXywh(rect: IRect) { return ( (rect).x !== undefined && (rect).y !== undefined && (rect).h !== undefined && (rect).w !== undefined ) } /** * 矩形对象是否是 IRltb 的描述形式 * @param rect */ export function rectIsRltb(rect: IRect) { return ( (rect).top !== undefined && (rect).bottom !== undefined && (rect).left !== undefined && (rect).right !== undefined ) } /** * 计算多个 rltb 矩形的边界 * * getRltbsRange([xywh]) * @returns {IRltb} * @param rltbs */ export function getRltbsRange(rltbs: IRltb[]) { let rangeRltb: IRltb = { left: 0, right: 0, top: 0, bottom: 0 } // 空 rltbs 数组,直接返回 if (rltbs.length <= 0) return rangeRltb // 从第一个 rltb 初始化 rangeRltb.left = rltbs[0].left rangeRltb.top = rltbs[0].top rangeRltb.right = rltbs[0].right rangeRltb.bottom = rltbs[0].bottom for (let i = 1, len = rltbs.length; i < len; i++) { let rltb = rltbs[i] if (rltb.left < rangeRltb.left) rangeRltb.left = rltb.left if (rltb.top < rangeRltb.top) rangeRltb.top = rltb.top if (rltb.right > rangeRltb.right) rangeRltb.right = rltb.right if (rltb.bottom > rangeRltb.bottom) rangeRltb.bottom = rltb.bottom } return rangeRltb } /** * 计算多个 xywh 矩形的边界 * * getXywhsRange([xywh]) * @param xywhs * @returns {IRltb} */ export function getXywhsRange(xywhs: IXywh[]) { let rltbs = xywhs.map((xywh) => xywh2rltb(xywh)) return getRltbsRange(rltbs) } /** * 整体移动多个 xywh 到某点,保留原 xywhs 相对位置。 * 会改变 xywhs 里每个 xywh 对象的 x,y 值。 * @param xywhs * @param xy * @return {IXywh[]} */ export function moveXywhs(xywhs: IXywh[], xy: IXy) { let range = getXywhsRange(xywhs) let offsetX = xy.x - range.left let offsetY = xy.y - range.top for (let i = 0, len = xywhs.length; i < len; i++) { let xywh = xywhs[i] xywh.x = xywh.x + offsetX xywh.y = xywh.y + offsetY } return xywhs } /** * 判断 2 个 rltb 是否有重叠 * @param rltbA * @param rltbB * @returns {boolean} */ export function rltbHasOverlap(rltbA: IRltb, rltbB: IRltb) { let hasOverlap = !!( rltbA.left <= rltbB.right && rltbA.right >= rltbB.left && rltbA.top <= rltbB.bottom && rltbA.bottom >= rltbB.top ) return hasOverlap } /** * 判断 2 个 xywh 是否有重叠 * @param xywhA * @param xywhB * @return {boolean} */ export function xywhHasOverlap(xywhA: IXywh, xywhB: IXywh) { let rltbA = xywh2rltb(xywhA) let rltbB = xywh2rltb(xywhB) return rltbHasOverlap(rltbA, rltbB) } /** 找出目标区域与多个区域中重叠的区域 */ export function xywhOverlapXywhs(xywhSrc: IXywh, xywhs: IXywh[]) { let overlapXywhs: IXywh[] = [] xywhs.forEach((xywh) => { if (xywhHasOverlap(xywhSrc, xywh)) overlapXywhs.push(xywh) }) return overlapXywhs } /** * 判断点是否在区域内 * @param point * @param rltb */ export function rltbHasPoint(point: IXy, rltb: IRltb) { return point.x > rltb.left && point.x < rltb.right && point.y > rltb.top && point.y < rltb.bottom } /** * 判断点是否在区域内 * @param point * @param rltb */ export function xywhHasPoint(point: IXy, xywh: IXywh) { let rltb = xywh2rltb(xywh) return rltbHasPoint(point, rltb) } /** * 判断区域B是否在区域A内 */ export function xywhHasXywh(xywhA: IXywh, xywhB: IXywh): boolean { let rltbA = xywh2rltb(xywhA) let rltbB = xywh2rltb(xywhB) return ( rltbB.top >= rltbA.top && rltbB.top <= rltbA.bottom && rltbB.bottom <= rltbA.bottom && rltbB.bottom >= rltbA.top && rltbB.left <= rltbA.right && rltbB.left >= rltbA.left && rltbB.right <= rltbA.right && rltbB.left >= rltbA.left ) } /** * rect 转换到 box * @param rect */ export function rect2Box(rect: IRect) { let box: IBox = {} if (rectIsRltb(rect)) { let xywh = rltb2xywh(rect) box.height = xywh.h box.width = xywh.w box.x = xywh.x box.y = xywh.y } else { box.height = (rect).h box.width = (rect).w box.x = (rect).x box.y = (rect).y } return box } export function box2xywh(box: IBox) { return { x: box.x, y: box.y, w: box.width, h: box.height, } }