a13faea8e44644be2ad2c56520f8f1e0245cd4b8d3414361c96544a282e66dd016208a8a04312209cca7d66509119345327581a3b5bab4ecda2de73bdd5310 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. import { PathStyleProps } from '../Path';
  2. /**
  3. * Sub-pixel optimize for canvas rendering, prevent from blur
  4. * when rendering a thin vertical/horizontal line.
  5. */
  6. const round = Math.round;
  7. type LineShape = {
  8. x1: number
  9. y1: number
  10. x2: number
  11. y2: number
  12. }
  13. type RectShape = {
  14. x: number
  15. y: number
  16. width: number
  17. height: number
  18. r?: number | number[]
  19. }
  20. /**
  21. * Sub pixel optimize line for canvas
  22. *
  23. * @param outputShape The modification will be performed on `outputShape`.
  24. * `outputShape` and `inputShape` can be the same object.
  25. * `outputShape` object can be used repeatly, because all of
  26. * the `x1`, `x2`, `y1`, `y2` will be assigned in this method.
  27. */
  28. export function subPixelOptimizeLine(
  29. outputShape: Partial<LineShape>,
  30. inputShape: LineShape,
  31. style: Pick<PathStyleProps, 'lineWidth'> // DO not optimize when lineWidth is 0
  32. ): LineShape {
  33. if (!inputShape) {
  34. return;
  35. }
  36. const x1 = inputShape.x1;
  37. const x2 = inputShape.x2;
  38. const y1 = inputShape.y1;
  39. const y2 = inputShape.y2;
  40. outputShape.x1 = x1;
  41. outputShape.x2 = x2;
  42. outputShape.y1 = y1;
  43. outputShape.y2 = y2;
  44. const lineWidth = style && style.lineWidth;
  45. if (!lineWidth) {
  46. return outputShape as LineShape;
  47. }
  48. if (round(x1 * 2) === round(x2 * 2)) {
  49. outputShape.x1 = outputShape.x2 = subPixelOptimize(x1, lineWidth, true);
  50. }
  51. if (round(y1 * 2) === round(y2 * 2)) {
  52. outputShape.y1 = outputShape.y2 = subPixelOptimize(y1, lineWidth, true);
  53. }
  54. return outputShape as LineShape;
  55. }
  56. /**
  57. * Sub pixel optimize rect for canvas
  58. *
  59. * @param outputShape The modification will be performed on `outputShape`.
  60. * `outputShape` and `inputShape` can be the same object.
  61. * `outputShape` object can be used repeatly, because all of
  62. * the `x`, `y`, `width`, `height` will be assigned in this method.
  63. */
  64. export function subPixelOptimizeRect(
  65. outputShape: Partial<RectShape>,
  66. inputShape: RectShape,
  67. style: Pick<PathStyleProps, 'lineWidth'> // DO not optimize when lineWidth is 0
  68. ): RectShape {
  69. if (!inputShape) {
  70. return;
  71. }
  72. const originX = inputShape.x;
  73. const originY = inputShape.y;
  74. const originWidth = inputShape.width;
  75. const originHeight = inputShape.height;
  76. outputShape.x = originX;
  77. outputShape.y = originY;
  78. outputShape.width = originWidth;
  79. outputShape.height = originHeight;
  80. const lineWidth = style && style.lineWidth;
  81. if (!lineWidth) {
  82. return outputShape as RectShape;
  83. }
  84. outputShape.x = subPixelOptimize(originX, lineWidth, true);
  85. outputShape.y = subPixelOptimize(originY, lineWidth, true);
  86. outputShape.width = Math.max(
  87. subPixelOptimize(originX + originWidth, lineWidth, false) - outputShape.x,
  88. originWidth === 0 ? 0 : 1
  89. );
  90. outputShape.height = Math.max(
  91. subPixelOptimize(originY + originHeight, lineWidth, false) - outputShape.y,
  92. originHeight === 0 ? 0 : 1
  93. );
  94. return outputShape as RectShape;
  95. }
  96. /**
  97. * Sub pixel optimize for canvas
  98. *
  99. * @param position Coordinate, such as x, y
  100. * @param lineWidth If `null`/`undefined`/`0`, do not optimize.
  101. * @param positiveOrNegative Default false (negative).
  102. * @return Optimized position.
  103. */
  104. export function subPixelOptimize(
  105. position: number,
  106. lineWidth?: number,
  107. positiveOrNegative?: boolean
  108. ) {
  109. if (!lineWidth) {
  110. return position;
  111. }
  112. // Assure that (position + lineWidth / 2) is near integer edge,
  113. // otherwise line will be fuzzy in canvas.
  114. const doubledPosition = round(position * 2);
  115. return (doubledPosition + round(lineWidth)) % 2 === 0
  116. ? doubledPosition / 2
  117. : (doubledPosition + (positiveOrNegative ? 1 : -1)) / 2;
  118. }