diff --git a/src/components/piecescanvas.tsx b/src/components/piecescanvas.tsx index 7e9cfef..e4589c6 100644 --- a/src/components/piecescanvas.tsx +++ b/src/components/piecescanvas.tsx @@ -21,8 +21,10 @@ import React, { useEffect, useMemo, useRef } from "react"; import { useResizeDetector } from "react-resize-detector"; import type { Torrent } from "../rpc/torrent"; +const toDevicePixels = (cssPixels: number) => cssPixels * window.devicePixelRatio; + export function PiecesCanvas(props: { torrent: Torrent }) { - const { width, height, ref } = useResizeDetector({ + const { width: cssWidth, height: cssHeight, ref } = useResizeDetector({ refreshMode: "throttle", refreshRate: 1000, }); @@ -56,28 +58,34 @@ export function PiecesCanvas(props: { torrent: Torrent }) { }, [props.torrent]); const [pieceSize, rows, cols] = useMemo(() => { - if (width === undefined || height === undefined) return [5, 1, 1]; - - const check = (size: number) => { - const cols = Math.floor(width / size); - const rows = Math.ceil(props.torrent.pieceCount / cols); - if (rows * size < height) return [rows, cols]; - else return [-1, -1]; - }; - - let right = 20; - let left = 0.0; - let mid = 10; - let rows = 1; - - while (right - left > 0.05) { - [rows] = check(mid); - if (rows < 0) right = mid; - else left = mid; - mid = (right + left) * 0.5; + if (cssWidth === undefined || cssHeight === undefined) return [5, 1, 1]; + + const canvasWidth = Math.floor(toDevicePixels(cssWidth)); + const canvasHeight = Math.floor(toDevicePixels(cssHeight)); + const pieceCount = props.torrent.pieceCount; + const maxPieceSize = toDevicePixels(20); + const minColumns = Math.ceil(canvasWidth / maxPieceSize); + + if (pieceCount < minColumns && canvasHeight >= maxPieceSize) return [maxPieceSize, 1, pieceCount]; + + /** + * The following code is based on https://math.stackexchange.com/a/2570649 + */ + + const ratio = canvasWidth / canvasHeight; + let cols = Math.max( + Math.ceil(Math.sqrt(pieceCount * ratio)), + minColumns, + ); + let rows = Math.ceil(pieceCount / cols); + + while (cols < rows * ratio) { + cols++; + rows = Math.ceil(pieceCount / cols); } - return [left, ...check(left)]; - }, [props.torrent.pieceCount, width, height]); + + return [canvasWidth / cols, rows, cols]; + }, [props.torrent.pieceCount, cssWidth, cssHeight]); const pieces = useMemo(() => { const bstr = window.atob(props.torrent.pieces); @@ -95,7 +103,7 @@ export function PiecesCanvas(props: { torrent: Torrent }) { const remainder = rows * cols - props.torrent.pieceCount; ctx.beginPath(); - ctx.lineWidth = pieceSize > 5 ? 1 : 0.5; + ctx.lineWidth = toDevicePixels(pieceSize > toDevicePixels(5) ? 1 : 0.5); ctx.strokeStyle = "rgba(0, 0, 0, 0.5)"; for (let i = 0; i < rows; i++) { ctx.moveTo(0, i * pieceSize); @@ -112,7 +120,7 @@ export function PiecesCanvas(props: { torrent: Torrent }) { ctx.lineTo(i * pieceSize, (rows - 1) * pieceSize); } ctx.stroke(); - }, [gridRef, rows, cols, width, height, props.torrent.pieceCount, pieceSize]); + }, [gridRef, rows, cols, cssWidth, cssHeight, props.torrent.pieceCount, pieceSize]); useEffect(() => { const canvas = piecesRef.current as HTMLCanvasElement; @@ -134,17 +142,17 @@ export function PiecesCanvas(props: { torrent: Torrent }) { } if (index >= props.torrent.pieceCount) break; } - }, [piecesRef, rows, cols, pieceSize, pieces, wantedPieces, props.torrent.pieceCount]); + }, [piecesRef, rows, cols, cssWidth, cssHeight, pieceSize, pieces, wantedPieces, props.torrent.pieceCount]); - const dw = Math.floor(window.devicePixelRatio * (width ?? 1)); - const dh = Math.floor(window.devicePixelRatio * (height ?? 1)); + const canvasWidth = Math.floor(toDevicePixels(cssWidth ?? 1)); + const canvasHeight = Math.floor(toDevicePixels(cssHeight ?? 1)); const style: CSSProperties = { - width: width ?? 1, height: height ?? 1, position: "absolute", top: 0, left: 0, + width: cssWidth ?? 1, height: cssHeight ?? 1, position: "absolute", top: 0, left: 0, }; return (
- - + +
); }