Skip to content

Commit

Permalink
fix: no change canvas when zoom in
Browse files Browse the repository at this point in the history
  • Loading branch information
iFwu committed Oct 10, 2024
1 parent c5d42fe commit d92b226
Show file tree
Hide file tree
Showing 27 changed files with 754 additions and 211 deletions.
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pnpm test
7 changes: 7 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100
}
16 changes: 14 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
"build": "tsc -b && vite build",
"preview": "vite preview",
"predeploy": "npm run build",
"deploy": "gh-pages -d dist"
"deploy": "gh-pages -d dist",
"format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,css,scss,md}\"",
"format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,css,scss,md}\"",
"prepare": "husky",
"test": "echo \"No tests specified\" && exit 0"
},
"dependencies": {
"@techstark/opencv-js": "^4.10.0-release.1",
Expand All @@ -18,12 +22,20 @@
},
"devDependencies": {
"@preact/preset-vite": "^2.9.1",
"husky": "^9.1.6",
"lint-staged": "^15.2.0",
"prettier": "^3.3.3",
"typescript": "^5.6.2",
"vite": "^5.4.8",
"vite-plugin-cdn2": "^1.1.0",
"vite-plugin-markdown": "^2.2.0"
},
"packageManager": "[email protected]",
"homepage": "https://ifwu.github.io/xiangqi-analysis/",
"license": "MIT"
"license": "MIT",
"lint-staged": {
"*.{ts,tsx,js,jsx,json,css,scss,md}": [
"prettier --write"
]
}
}
420 changes: 420 additions & 0 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

15 changes: 6 additions & 9 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ export function App() {
}>();

const { templates, isLoading: isOpenCVLoading } = useOpenCV();
const { bestMove, isCalculating, error, fetchBestMove, setBestMove, isEngineReady } = useChessEngine();
const { bestMove, isCalculating, error, fetchBestMove, setBestMove, isEngineReady } =
useChessEngine();
const { depth, setDepth } = useDepth();
const isLoading = isOpenCVLoading || !isEngineReady;

Expand Down Expand Up @@ -121,11 +122,7 @@ export function App() {
}
}

const overlayCanvas = createOverlayImage(
img,
adjustedChessboardRect,
detectedPieces
);
const overlayCanvas = createOverlayImage(img, adjustedChessboardRect, detectedPieces);
setOverlayImageSrc(overlayCanvas.toDataURL());

const pieceLayout: string[][] = Array(10)
Expand Down Expand Up @@ -201,11 +198,11 @@ export function App() {
</main>
<footer>
<p>
© 2024 象棋棋盘识别与分析系统 |
Powered by <a href="https://github.com/official-pikafish/Pikafish">Pikafish</a>&nbsp;|&nbsp;
© 2024 象棋棋盘识别与分析系统 | Powered by{' '}
<a href="https://github.com/official-pikafish/Pikafish">Pikafish</a>&nbsp;|&nbsp;
<a href="https://github.com/iFwu/xiangqi-analysis">GitHub 源码仓库</a>
</p>
</footer>
</>
);
}
}
49 changes: 36 additions & 13 deletions src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ h2 {
}

/* 头部和底部 */
header, footer {
header,
footer {
width: 100%;
background-color: var(--header-footer-bg);
color: var(--header-footer-color);
Expand Down Expand Up @@ -147,7 +148,9 @@ button {
background-color: var(--primary-color);
color: #fff;
cursor: pointer;
transition: background-color 0.3s, opacity 0.3s;
transition:
background-color 0.3s,
opacity 0.3s;
display: inline-block; /* 确保按钮可以被居中 */
}

Expand All @@ -162,7 +165,7 @@ button:disabled {
}

/* 输入框样式 */
input[type="file"],
input[type='file'],
.fen-container input {
display: block;
margin: 1rem 0;
Expand Down Expand Up @@ -298,11 +301,21 @@ input[type="file"],
width: 100%;
}

.solution-section { order: 1; }
.upload-section { order: 0; }
.board-result-section { order: 2; }
.fen-section { order: 3; }
.depth-control-section { order: 4; }
.solution-section {
order: 1;
}
.upload-section {
order: 0;
}
.board-result-section {
order: 2;
}
.fen-section {
order: 3;
}
.depth-control-section {
order: 4;
}
}

@media (min-width: 769px) {
Expand Down Expand Up @@ -444,7 +457,9 @@ dialog button:hover {
border: none;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.3s, transform 0.1s;
transition:
background-color 0.3s,
transform 0.1s;
flex: 1;
min-width: 120px;
}
Expand Down Expand Up @@ -608,12 +623,20 @@ dialog button:hover {
}

/* 可以根据需要调整不同级别标题的大小 */
.changelog-content h1 { font-size: 1.2rem; }
.changelog-content h2 { font-size: 1.1rem; }
.changelog-content h3 { font-size: 1rem; }
.changelog-content h1 {
font-size: 1.2rem;
}
.changelog-content h2 {
font-size: 1.1rem;
}
.changelog-content h3 {
font-size: 1rem;
}
.changelog-content h4,
.changelog-content h5,
.changelog-content h6 { font-size: 0.9rem; }
.changelog-content h6 {
font-size: 0.9rem;
}

@font-face {
font-family: 'LiSu';
Expand Down
2 changes: 1 addition & 1 deletion src/chessEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,4 @@ export class ChessEngine {
}
});
}
}
}
85 changes: 59 additions & 26 deletions src/chessboard/chessboardDetection.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import cv from "@techstark/opencv-js";
import cv from '@techstark/opencv-js';
import { kmeans } from 'ml-kmeans';

export function detectAndExtractChessboard(imgElement: HTMLImageElement, expandRatioW: number = 0.055, expandRatioH: number = 0.055): { gridCells: ImageData[], chessboardRect: { x: number, y: number, width: number, height: number } } {
export function detectAndExtractChessboard(
imgElement: HTMLImageElement,
expandRatioW: number = 0.055,
expandRatioH: number = 0.055
): {
gridCells: ImageData[];
chessboardRect: { x: number; y: number; width: number; height: number };
} {
const img = cv.imread(imgElement);
const gray = new cv.Mat();
cv.cvtColor(img, gray, cv.COLOR_RGBA2GRAY);
Expand All @@ -21,7 +28,7 @@ export function detectAndExtractChessboard(imgElement: HTMLImageElement, expandR
cv.findContours(finalEdges, contours, hierarchy, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE);

if (contours.size() === 0) {
console.log("未检测到任何轮廓。");
console.log('未检测到任何轮廓。');
return { gridCells: [], chessboardRect: { x: NaN, y: NaN, width: NaN, height: NaN } };
}

Expand All @@ -41,22 +48,36 @@ export function detectAndExtractChessboard(imgElement: HTMLImageElement, expandR

const { gridCells, expandedRect } = segmentChessboard(croppedRegion, expandRatioW, expandRatioH);

img.delete(); gray.delete(); blurred.delete(); edges.delete();
kernel.delete(); dilatedEdges.delete(); finalEdges.delete();
contours.delete(); hierarchy.delete(); croppedRegion.delete();
img.delete();
gray.delete();
blurred.delete();
edges.delete();
kernel.delete();
dilatedEdges.delete();
finalEdges.delete();
contours.delete();
hierarchy.delete();
croppedRegion.delete();

return {
gridCells,
chessboardRect: {
x: rect.x + expandedRect.x,
y: rect.y + expandedRect.y,
width: expandedRect.width,
height: expandedRect.height
}
height: expandedRect.height,
},
};
}

function segmentChessboard(croppedRegion: cv.Mat, expandRatioW: number, expandRatioH: number): { gridCells: ImageData[], expandedRect: { x: number, y: number, width: number, height: number } } {
function segmentChessboard(
croppedRegion: cv.Mat,
expandRatioW: number,
expandRatioH: number
): {
gridCells: ImageData[];
expandedRect: { x: number; y: number; width: number; height: number };
} {
const grayCropped = new cv.Mat();
cv.cvtColor(croppedRegion, grayCropped, cv.COLOR_RGBA2GRAY);
const blurCropped = new cv.Mat();
Expand All @@ -68,10 +89,10 @@ function segmentChessboard(croppedRegion: cv.Mat, expandRatioW: number, expandRa
cv.HoughLinesP(edgesCropped, lines, 1, Math.PI / 180, 80, 100, 50);

if (lines.rows === 0) {
console.log("无法检测到棋盘线条。");
console.log('无法检测到棋盘线条。');
return {
gridCells: [],
expandedRect: { x: 0, y: 0, width: 0, height: 0 }
expandedRect: { x: 0, y: 0, width: 0, height: 0 },
};
}

Expand All @@ -80,7 +101,7 @@ function segmentChessboard(croppedRegion: cv.Mat, expandRatioW: number, expandRa

for (let i = 0; i < lines.rows; i++) {
const [x1, y1, x2, y2] = lines.data32S.slice(i * 4, (i + 1) * 4);
const angle = Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI;
const angle = (Math.atan2(y2 - y1, x2 - x1) * 180) / Math.PI;
if (Math.abs(angle) < 10) {
horizontalLines.push([x1, y1, x2, y2]);
} else if (Math.abs(angle) > 80) {
Expand All @@ -89,21 +110,29 @@ function segmentChessboard(croppedRegion: cv.Mat, expandRatioW: number, expandRa
}

if (horizontalLines.length < 10 || verticalLines.length < 9) {
console.log("线条不足,无法进行 KMeans 聚类。");
console.log('线条不足,无法进行 KMeans 聚类。');
return {
gridCells: [],
expandedRect: { x: 0, y: 0, width: 0, height: 0 }
expandedRect: { x: 0, y: 0, width: 0, height: 0 },
};
}

const horizontalYPositions = horizontalLines.flatMap(line => [line[1], line[3]]);
const verticalXPositions = verticalLines.flatMap(line => [line[0], line[2]]);
const horizontalYPositions = horizontalLines.flatMap((line) => [line[1], line[3]]);
const verticalXPositions = verticalLines.flatMap((line) => [line[0], line[2]]);

const resultH = kmeans(horizontalYPositions.map(y => [y]), 10, { maxIterations: 100 });
const resultV = kmeans(verticalXPositions.map(x => [x]), 9, { maxIterations: 100 });
const resultH = kmeans(
horizontalYPositions.map((y) => [y]),
10,
{ maxIterations: 100 }
);
const resultV = kmeans(
verticalXPositions.map((x) => [x]),
9,
{ maxIterations: 100 }
);

const horizontalClusterCenters = resultH.centroids.map(c => c[0]).sort((a, b) => a - b);
const verticalClusterCenters = resultV.centroids.map(c => c[0]).sort((a, b) => a - b);
const horizontalClusterCenters = resultH.centroids.map((c) => c[0]).sort((a, b) => a - b);
const verticalClusterCenters = resultV.centroids.map((c) => c[0]).sort((a, b) => a - b);

const minY = Math.floor(horizontalClusterCenters[0]);
const maxY = Math.ceil(horizontalClusterCenters[horizontalClusterCenters.length - 1]);
Expand Down Expand Up @@ -138,7 +167,7 @@ function segmentChessboard(croppedRegion: cv.Mat, expandRatioW: number, expandRa
const x1 = j * gridWidth;
const x2 = (j + 1) * gridWidth;
const gridCell = expandedChessboardRegion.roi(new cv.Rect(x1, y1, x2 - x1, y2 - y1));

const canvas = document.createElement('canvas');
canvas.width = gridCell.cols;
canvas.height = gridCell.rows;
Expand All @@ -147,16 +176,20 @@ function segmentChessboard(croppedRegion: cv.Mat, expandRatioW: number, expandRa
if (ctx) {
gridCells.push(ctx.getImageData(0, 0, canvas.width, canvas.height));
}

gridCell.delete();
}
}

grayCropped.delete(); blurCropped.delete(); edgesCropped.delete();
lines.delete(); chessboardRegion.delete(); expandedChessboardRegion.delete();
grayCropped.delete();
blurCropped.delete();
edgesCropped.delete();
lines.delete();
chessboardRegion.delete();
expandedChessboardRegion.delete();

return {
gridCells,
expandedRect: { x: newX, y: newY, width: newW, height: newH }
expandedRect: { x: newX, y: newY, width: newW, height: newH },
};
}
}
25 changes: 14 additions & 11 deletions src/chessboard/fenGeneration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,27 @@ import { PieceColor, PieceType } from './types';

function mapPieceTypeToFEN(pieceType: PieceType, color: PieceColor): string {
const mapping: Record<PieceType, string> = {
'k': color === 'red' ? 'K' : 'k',
'a': color === 'red' ? 'A' : 'a',
'b': color === 'red' ? 'B' : 'b',
'n': color === 'red' ? 'N' : 'n',
'r': color === 'red' ? 'R' : 'r',
'c': color === 'red' ? 'C' : 'c',
'p': color === 'red' ? 'P' : 'p',
"none": ""
k: color === 'red' ? 'K' : 'k',
a: color === 'red' ? 'A' : 'a',
b: color === 'red' ? 'B' : 'b',
n: color === 'red' ? 'N' : 'n',
r: color === 'red' ? 'R' : 'r',
c: color === 'red' ? 'C' : 'c',
p: color === 'red' ? 'P' : 'p',
none: '',
};
return mapping[pieceType];
}

export function generateFenFromPieces(pieceLayout: string[][], nextMoveColor: PieceColor = 'red'): string {
export function generateFenFromPieces(
pieceLayout: string[][],
nextMoveColor: PieceColor = 'red'
): string {
const fenRows: string[] = [];

for (const row of pieceLayout) {
let emptyCount = 0;
let fenRow = "";
let fenRow = '';

for (const piece of row) {
if (piece === 'none') {
Expand Down Expand Up @@ -48,4 +51,4 @@ export function generateFenFromPieces(pieceLayout: string[][], nextMoveColor: Pi
console.log(`Generated FEN: ${fullFenString}`);

return fullFenString;
}
}
Loading

0 comments on commit d92b226

Please sign in to comment.