blob: 9fcb3ee287e8fbbc4b5bdbee244bf6d0c7f34f78 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2018 Ericsson
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License 2.0 which
* accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.tracecompass.internal.tmf.ui.util;
import org.eclipse.swt.graphics.Rectangle;
/**
* Cohen Sutherland line clipping algorithm, developed in 1967, public domain,
* clean room implemented.
*
* @see <a href=
* "https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm">Cohen
* Sutherland algorithm (Wikipedia)</a>
*
* @author Matthew Khouzam
*
*/
public final class LineClipper {
/**
* Quadrant bitmasks for Cohen Sutherland
*/
private static final int DOWN = 1 << 3;
private static final int UP = 1 << 2;
private static final int RIGHT = 1 << 1;
private static final int LEFT = 1 << 0;
private static final int INSIDE = 0;
private LineClipper() {
// do nothing
}
/**
* Computer graphics algorithm used for line clipping, workaround for Cairo bug
*
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=470115
*
* https://bugs.freedesktop.org/show_bug.cgi?id=103840
*
* @param bounds
* the clipping rectangle
* @param x0
* point 0 x
* @param y0
* point 0 y
* @param x1
* point 1 x
* @param y1
* point 1 y
* @return the clipped rectangle of the arrow
*/
public static Rectangle clip(Rectangle bounds, int x0, int y0, int x1, int y1) {
int maxLoops = 3;
int quadrant0 = computeQuadrant(x0, y0, bounds);
int quadrant1 = computeQuadrant(x1, y1, bounds);
if (quadrant0 == 0 && quadrant1 == 0) {
return new Rectangle(x0, y0, x1 - x0, y1 - y0);
}
if (guaranteedOut(quadrant0, quadrant1) || bounds.width == 0 || bounds.height == 0) {
return null;
}
long deltaY = (long) y1 - (long) y0;
long deltaX = (long) x1 - (long) x0;
double slope = deltaY / (double) deltaX;
double antiSlope = deltaX / (double) deltaY;
int newX0 = x0;
int newY0 = y0;
int newX1 = x1;
int newY1 = y1;
// log n iterations
for (int i = 0; i < maxLoops && quadrant0 != 0; i++) {
if (guaranteedOut(quadrant0, quadrant1)) {
return null;
}
if ((quadrant0 & UP) != 0) {
newX0 = (int) (newX0 + antiSlope * (bounds.y + bounds.height - newY0));
newY0 = bounds.height + bounds.y;
} else if ((quadrant0 & DOWN) != 0) {
newX0 = (int) (newX0 + antiSlope * (bounds.y - newY0));
newY0 = bounds.y;
} else if ((quadrant0 & RIGHT) != 0) {
newY0 = (int) (newY0 + slope * (bounds.x + bounds.width - newX0));
newX0 = bounds.x + bounds.width;
} else {
newY0 = (int) (newY0 + slope * (bounds.x - newX0));
newX0 = bounds.x;
}
quadrant0 = computeQuadrant(newX0, newY0, bounds);
}
// ditto
for (int i = 0; i < maxLoops && quadrant1 != 0; i++) {
if (guaranteedOut(quadrant0, quadrant1)) {
return null;
}
if ((quadrant1 & UP) != 0) {
newX1 = (int) (newX1 + antiSlope * (bounds.y + bounds.height - newY1));
newY1 = bounds.height + bounds.y;
} else if ((quadrant1 & DOWN) != 0) {
newX1 = (int) (newX1 + antiSlope * (bounds.y - newY1));
newY1 = bounds.y;
} else if ((quadrant1 & RIGHT) != 0) {
newY1 = (int) (newY1 + slope * (bounds.x + bounds.width - newX1));
newX1 = bounds.x + bounds.width;
} else {
newY1 = (int) (newY1 + slope * (bounds.x - newX1));
newX1 = bounds.x;
}
quadrant1 = computeQuadrant(newX1, newY1, bounds);
}
return (guaranteedOut(quadrant0, quadrant1)) ? null : new Rectangle(newX0, newY0, newX1 - newX0, newY1 - newY0);
}
private static boolean guaranteedOut(int quadrant0, int quadrant1) {
return (quadrant0 & quadrant1) != 0;
}
/**
* Cohen Sutherland compute algorithm
* <table>
* <tr>
* <th>0101</th>
* <th>0100</th>
* <th>0110</th>
* </tr>
* <tr>
* <th>0001</th>
* <th>0000</th>
* <th>0010</th>
* </tr>
* <tr>
* <th>1001</th>
* <th>1000</th>
* <th>1010</th>
* </tr>
* </table>
*
* @param x
* point x
* @param y
* point y
* @param bounds
* bounds rectangle
* @return a quadrant
*/
private static int computeQuadrant(int x, int y, Rectangle bounds) {
int retCode = INSIDE;
if (x < bounds.x) {
retCode |= LEFT;
} else if (x > (bounds.x + bounds.width)) {
retCode |= RIGHT;
}
if (y < bounds.y) {
retCode |= DOWN;
} else if (y > (bounds.y + bounds.height)) {
retCode |= UP;
}
return retCode;
}
}