所有shape都转为run编码
转为run编码后再运算可以节约大量内存
subtractIntervals 函数的逻辑:目前的实现假设了所有的 subIntervals 都会与 intervals 完全重叠,这可能导致计算不准确。应该将 subIntervals 从 intervals 中去除时,考虑到可能的部分重叠。
差集计算:subtractIntervals 函数需要更准确地处理部分重叠和完整重叠的情况。
效果图
关键代码
/**
* 绘制矩形
* @param {CanvasRenderingContext2D} ctx - Canvas 2D 渲染上下文
* @param {number[]} rect - 矩形 [x, y, width, height]
* @param {string} color - 颜色
*/
function drawRect(ctx, rect, color) {
ctx.fillStyle = color;
ctx.fillRect(rect[0], rect[1], rect[2], rect[3]);
}
/**
* 绘制 RLE 区域
* @param {CanvasRenderingContext2D} ctx - Canvas 2D 渲染上下文
* @param {Object[]} rle - RLE 区域数组
* @param {string} color - 颜色
*/
function drawRLE(ctx, rle, color) {
ctx.fillStyle = color;
rle.forEach(({ row, intervals }) => {
intervals.forEach(({ startCol, runLength }) => {
ctx.fillRect(startCol, row, runLength, 1);
});
});
}
/**
* 将矩形转换为 RLE 编码
* @param {number[]} rect - 矩形 [x, y, width, height]
* @returns {Object[]} - RLE 区域数组
*/
function rectangleToRLE(rect) {
const [x, y, w, h] = rect;
const rle = [];
for (let i = y; i < y + h; i++) {
rle.push({
row: i,
intervals: [{ startCol: x, runLength: w }]
});
}
return rle;
}
/**
* 计算两个 RLE 区域的交集
* @param {Object[]} rle1 - 第一个 RLE 区域数组
* @param {Object[]} rle2 - 第二个 RLE 区域数组
* @returns {Object[]} - 交集 RLE 区域数组
*/
function intersectRLE(rle1, rle2) {
const intersected = [];
const rleMap = (rle) => {
const map = new Map();
rle.forEach(({ row, intervals }) => {
if (!map.has(row)) {
map.set(row, []);
}
map.get(row).push(...intervals);
});
return map;
};
const rleMap1 = rleMap(rle1);
const rleMap2 = rleMap(rle2);
const allRows = new Set([...rleMap1.keys(), ...rleMap2.keys()]);
allRows.forEach(row => {
const intervals1 = rleMap1.get(row) || [];
const intervals2 = rleMap2.get(row) || [];
const rowIntersected = intersectIntervals(intervals1, intervals2);
if (rowIntersected.length > 0) {
intersected.push({ row, intervals: rowIntersected });
}
});
return intersected;
}
/**
* 计算两个区间数组的交集
* @param {Object[]} intervals1 - 第一个区间数组
* @param {Object[]} intervals2 - 第二个区间数组
* @returns {Object[]} - 交集区间数组
*/
function intersectIntervals(intervals1, intervals2) {
const result = [];
intervals1.forEach(({ startCol: start1, runLength: length1 }) => {
intervals2.forEach(({ startCol: start2, runLength: length2 }) => {
const start = Math.max(start1, start2);
const end = Math.min(start1 + length1, start2 + length2);
if (start < end) {
result.push({ startCol: start, runLength: end - start });
}
});
});
return result;
}
/**
* 计算两个 RLE 区域的并集
* @param {Object[]} rle1 - 第一个 RLE 区域数组
* @param {Object[]} rle2 - 第二个 RLE 区域数组
* @returns {Object[]} - 并集 RLE 区域数组
*/
function unionRLE(rle1, rle2) {
const union = [];
const rleMap = (rle) => {
const map = new Map();
rle.forEach(({ row, intervals }) => {
if (!map.has(row)) {
map.set(row, []);
}
map.get(row).push(...intervals);
});
return map;
};
const rleMap1 = rleMap(rle1);
const rleMap2 = rleMap(rle2);
const allRows = new Set([...rleMap1.keys(), ...rleMap2.keys()]);
allRows.forEach(row => {
const intervals1 = rleMap1.get(row) || [];
const intervals2 = rleMap2.get(row) || [];
const rowUnion = unionIntervals(intervals1, intervals2);
if (rowUnion.length > 0) {
union.push({ row, intervals: rowUnion });
}
});
return union;
}
/**
* 计算两个区间数组的并集
* @param {Object[]} intervals1 - 第一个区间数组
* @param {Object[]} intervals2 - 第二个区间数组
* @returns {Object[]} - 并集区间数组
*/
function unionIntervals(intervals1, intervals2) {
const result = [];
const allIntervals = [...intervals1, ...intervals2];
allIntervals.sort((a, b) => a.startCol - b.startCol);
let currentStart = -Infinity;
let currentEnd = -Infinity;
allIntervals.forEach(({ startCol, runLength }) => {
const end = startCol + runLength;
if (startCol > currentEnd) {
if (currentEnd > currentStart) {
result.push({ startCol: currentStart, runLength: currentEnd - currentStart });
}
currentStart = startCol;
currentEnd = end;
} else {
currentEnd = Math.max(currentEnd, end);
}
});
if (currentEnd > currentStart) {
result.push({ startCol: currentStart, runLength: currentEnd - currentStart });
}
return result;
}
/**
* 计算一个 RLE 区域相对于另一个区域的差集
* @param {Object[]} rle1 - 第一个 RLE 区域数组
* @param {Object[]} rle2 - 第二个 RLE 区域数组
* @returns {Object[]} - 差集 RLE 区域数组
*/
function subtractRLE(rle1, rle2) {
const difference = [];
const rleMap = (rle) => {
const map = new Map();
rle.forEach(({ row, intervals }) => {
if (!map.has(row)) {
map.set(row, []);
}
map.get(row).push(...intervals);
});
return map;
};
const rleMap1 = rleMap(rle1);
const rleMap2 = rleMap(rle2);
const allRows = new Set([...rleMap1.keys(), ...rleMap2.keys()]);
allRows.forEach(row => {
const intervals1 = rleMap1.get(row) || [];
const intervals2 = rleMap2.get(row) || [];
const rowDifference = subtractIntervals(intervals1, intervals2);
if (rowDifference.length > 0) {
difference.push({ row, intervals: rowDifference });
}
});
return difference;
}
/**
* 计算两个区间数组的差集
* @param {Object[]} intervals - 原始区间数组
* @param {Object[]} subIntervals - 子区间数组
* @returns {Object[]} - 差集区间数组
*/
function subtractIntervals(intervals, subIntervals) {
const result = [];
intervals.forEach(({ startCol, runLength }) => {
let currentStart = startCol;
let currentLength = runLength;
subIntervals.forEach(({ startCol: subStart, runLength: subLength }) => {
if (subStart >= currentStart + currentLength || subStart + subLength <= currentStart) {
return;
}
const overlapStart = Math.max(currentStart, subStart);
const overlapEnd = Math.min(currentStart + currentLength, subStart + subLength);
if (overlapStart > currentStart) {
result.push({ startCol: currentStart, runLength: overlapStart - currentStart });
}
if (overlapEnd < currentStart + currentLength) {
result.push({ startCol: overlapEnd, runLength: (currentStart + currentLength) - overlapEnd });
}
let currentStartBack=currentStart;
currentStart = Math.max(currentStart, overlapEnd);
currentLength = (currentStartBack + currentLength) - currentStart;
});
if (currentLength > 0) {
result.push({ startCol: currentStart, runLength: currentLength });
}
});
return result;
}
/**
* 绘制 RLE 运算结果
* @param {CanvasRenderingContext2D} ctxIntersect - 交集 Canvas 渲染上下文
* @param {CanvasRenderingContext2D} ctxUnion - 并集 Canvas 渲染上下文
* @param {CanvasRenderingContext2D} ctxDifference - 差集 Canvas 渲染上下文
* @param {number[]} rect1 - 第一个矩形 [x, y, width, height]
* @param {number[]} rect2 - 第二个矩形 [x, y, width, height]
*/
function drawResults(ctxIntersect, ctxUnion, ctxDifference, rect1, rect2) {
const rle1 = rectangleToRLE(rect1);
const rle2 = rectangleToRLE(rect2);
const intersectedRLE = intersectRLE(rle1, rle2);
const unionRLE1 = unionRLE(rle1, rle2);
const differenceRLE = subtractRLE(rle1, rle2);
// 清空 Canvas
ctxIntersect.clearRect(0, 0, ctxIntersect.canvas.width, ctxIntersect.canvas.height);
ctxUnion.clearRect(0, 0, ctxUnion.canvas.width, ctxUnion.canvas.height);
ctxDifference.clearRect(0, 0, ctxDifference.canvas.width, ctxDifference.canvas.height);
// 绘制交集结果
drawRLE(ctxIntersect, intersectedRLE, 'rgba(255, 255, 0, 0.5)'); // Yellow for intersection
// 绘制并集结果
drawRLE(ctxUnion, unionRLE1, 'rgba(0, 255, 0, 0.5)'); // Green for union
// 绘制差集结果
drawRLE(ctxDifference, differenceRLE, 'rgba(255, 0, 0, 0.5)'); // Red for difference
}
const canvasIntersect = document.getElementById('canvasIntersect');
const canvasUnion = document.getElementById('canvasUnion');
const canvasDifference = document.getElementById('canvasDifference');
const ctxIntersect = canvasIntersect.getContext('2d');
const ctxUnion = canvasUnion.getContext('2d');
const ctxDifference = canvasDifference.getContext('2d');
const rect1 = [0, 0, 200, 200]; // x, y, width, height
const rect2 = [50, 50, 150, 150]; // x, y, width, height
drawResults(ctxIntersect, ctxUnion, ctxDifference, rect1, rect2);