zrender
Version:
A lightweight graphic library providing 2d draw for Apache ECharts
135 lines (118 loc) • 3.86 kB
text/typescript
import { PathStyleProps } from '../Path';
/**
* Sub-pixel optimize for canvas rendering, prevent from blur
* when rendering a thin vertical/horizontal line.
*/
const round = Math.round;
type LineShape = {
x1: number
y1: number
x2: number
y2: number
}
type RectShape = {
x: number
y: number
width: number
height: number
r?: number | number[]
}
/**
* Sub pixel optimize line for canvas
*
* @param outputShape The modification will be performed on `outputShape`.
* `outputShape` and `inputShape` can be the same object.
* `outputShape` object can be used repeatly, because all of
* the `x1`, `x2`, `y1`, `y2` will be assigned in this method.
*/
export function subPixelOptimizeLine(
outputShape: Partial<LineShape>,
inputShape: LineShape,
style: Pick<PathStyleProps, 'lineWidth'> // DO not optimize when lineWidth is 0
): LineShape {
if (!inputShape) {
return;
}
const x1 = inputShape.x1;
const x2 = inputShape.x2;
const y1 = inputShape.y1;
const y2 = inputShape.y2;
outputShape.x1 = x1;
outputShape.x2 = x2;
outputShape.y1 = y1;
outputShape.y2 = y2;
const lineWidth = style && style.lineWidth;
if (!lineWidth) {
return outputShape as LineShape;
}
if (round(x1 * 2) === round(x2 * 2)) {
outputShape.x1 = outputShape.x2 = subPixelOptimize(x1, lineWidth, true);
}
if (round(y1 * 2) === round(y2 * 2)) {
outputShape.y1 = outputShape.y2 = subPixelOptimize(y1, lineWidth, true);
}
return outputShape as LineShape;
}
/**
* Sub pixel optimize rect for canvas
*
* @param outputShape The modification will be performed on `outputShape`.
* `outputShape` and `inputShape` can be the same object.
* `outputShape` object can be used repeatly, because all of
* the `x`, `y`, `width`, `height` will be assigned in this method.
*/
export function subPixelOptimizeRect(
outputShape: Partial<RectShape>,
inputShape: RectShape,
style: Pick<PathStyleProps, 'lineWidth'> // DO not optimize when lineWidth is 0
): RectShape {
if (!inputShape) {
return;
}
const originX = inputShape.x;
const originY = inputShape.y;
const originWidth = inputShape.width;
const originHeight = inputShape.height;
outputShape.x = originX;
outputShape.y = originY;
outputShape.width = originWidth;
outputShape.height = originHeight;
const lineWidth = style && style.lineWidth;
if (!lineWidth) {
return outputShape as RectShape;
}
outputShape.x = subPixelOptimize(originX, lineWidth, true);
outputShape.y = subPixelOptimize(originY, lineWidth, true);
outputShape.width = Math.max(
subPixelOptimize(originX + originWidth, lineWidth, false) - outputShape.x,
originWidth === 0 ? 0 : 1
);
outputShape.height = Math.max(
subPixelOptimize(originY + originHeight, lineWidth, false) - outputShape.y,
originHeight === 0 ? 0 : 1
);
return outputShape as RectShape;
}
/**
* Sub pixel optimize for canvas
*
* @param position Coordinate, such as x, y
* @param lineWidth If `null`/`undefined`/`0`, do not optimize.
* @param positiveOrNegative Default false (negative).
* @return Optimized position.
*/
export function subPixelOptimize(
position: number,
lineWidth?: number,
positiveOrNegative?: boolean
) {
if (!lineWidth) {
return position;
}
// Assure that (position + lineWidth / 2) is near integer edge,
// otherwise line will be fuzzy in canvas.
const doubledPosition = round(position * 2);
return (doubledPosition + round(lineWidth)) % 2 === 0
? doubledPosition / 2
: (doubledPosition + (positiveOrNegative ? 1 : -1)) / 2;
}