mh-two-thousand-and-two
2024-03-25 b8c93990f3fa5e50a8aca16bdc9c2758168aa0fd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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;
}