Fix blur on high DPI screens

This commit is contained in:
Pyro Nicampt committed 2025-03-21 12:53:50 -05:00
1 parent ebdbb1e979
commit 1c4093481e
4 files changed
+37 -35

No files matched your search

+1 -1
View File
@@ -15,7 +15,7 @@
</head> </head>
<body> <body>
<div id="mapContainer"> <div id="mapContainer">
<canvas id="mapCanvas"></canvas> <canvas id="mapCanvas" style="width:100%;height:100%"></canvas>
<div id="tooltip" class="panel" style="display:none"></div> <div id="tooltip" class="panel" style="display:none"></div>
</div> </div>
+31 -30
View File
@@ -250,21 +250,22 @@ async function loadPoiData(file){
let railTrack = {}; let railTrack = {};
let curve = {}; let curve = {};
function redrawMap(){ function redrawMap(){
mapCanvas.width = mapContainer.clientWidth; MapData.view.pixelRatio = window.devicePixelRatio;
mapCanvas.height = mapContainer.clientHeight; mapCanvas.width = mapContainer.clientWidth * MapData.view.pixelRatio;
zoomLevelDisplay.innerHTML = `Zoom: ${MapData.view.scale.toFixed(3)}x / ${(1/MapData.view.scale).toFixed(2)} m/px`; mapCanvas.height = mapContainer.clientHeight * MapData.view.pixelRatio;
zoomLevelDisplay.innerHTML = `Zoom: ${(MapData.view.scale*MapData.view.pixelRatio).toFixed(3)}x / ${(1/(MapData.view.scale*MapData.view.pixelRatio)).toFixed(2)} m/px`;
if(MapData.layers.terrain){ if(MapData.layers.terrain){
mapctx.drawImage( mapctx.drawImage(
mapTerrain, mapTerrain,
MapData.view.convertX(0), MapData.view.convertX(0),
MapData.view.convertY(16384), MapData.view.convertY(16384),
16384 * MapData.view.scale, 16384 * MapData.view.scale * MapData.view.pixelRatio,
16384 * MapData.view.scale 16384 * MapData.view.scale * MapData.view.pixelRatio
); );
} }
// Game gauge is 1.5 meter // Game gauge is 1.5 meter
const trackWidth = Math.min(Math.max(6, MapData.view.scale * 1.5), MapData.view.scale * 100); const trackWidth = Math.min(Math.max(6, MapData.view.scale * 1.5), MapData.view.scale * 100) * MapData.view.pixelRatio;
mapctx.lineWidth = trackWidth; mapctx.lineWidth = trackWidth;
for(const railTrack of MapData.railTracks){ for(const railTrack of MapData.railTracks){
@@ -308,7 +309,7 @@ function redrawMap(){
spriteSize = 30; spriteSize = 30;
mapctx.translate(markerX, markerY); mapctx.translate(markerX, markerY);
mapctx.rotate(marker.rotation); mapctx.rotate(marker.rotation);
mapctx.translate(-0.5*spriteSize, -0.5*spriteSize); mapctx.translate(-0.5*spriteSize * MapData.view.pixelRatio, -0.5*spriteSize * MapData.view.pixelRatio);
mapctx.drawImage( mapctx.drawImage(
Utils.gradeArrows, Utils.gradeArrows,
curSprite.x, curSprite.x,
@@ -317,8 +318,8 @@ function redrawMap(){
curSprite.height, curSprite.height,
0, 0,
0, 0,
spriteSize, spriteSize * MapData.view.pixelRatio,
spriteSize spriteSize * MapData.view.pixelRatio
); );
mapctx.resetTransform(); mapctx.resetTransform();
curSprite = null; curSprite = null;
@@ -332,7 +333,7 @@ function redrawMap(){
case 'service': case 'service':
if(!(MapData.layers.services && MapData.layers.poi)) break; if(!(MapData.layers.services && MapData.layers.poi)) break;
marker.visible = true; marker.visible = true;
spriteSize = 30; spriteSize = 30 * MapData.view.pixelRatio;
if(marker.serviceTypes.length == 0) marker.serviceTypes.push(''); if(marker.serviceTypes.length == 0) marker.serviceTypes.push('');
if(marker.serviceTypes.length > 1){ if(marker.serviceTypes.length > 1){
mapctx.fillStyle = '#239a96'; mapctx.fillStyle = '#239a96';
@@ -355,8 +356,8 @@ function redrawMap(){
} }
if(!marker.tooltipHitzone){ if(!marker.tooltipHitzone){
marker.tooltipHitzone = { marker.tooltipHitzone = {
width: spriteSize * marker.serviceTypes.length, width: spriteSize/MapData.view.pixelRatio * marker.serviceTypes.length,
height: spriteSize height: spriteSize/MapData.view.pixelRatio
}; };
} }
curSprite = null; curSprite = null;
@@ -383,20 +384,20 @@ function redrawMap(){
mapctx.fillStyle = marker.color; mapctx.fillStyle = marker.color;
mapctx.strokeStyle = '#000'; mapctx.strokeStyle = '#000';
mapctx.lineJoin = 'round'; mapctx.lineJoin = 'round';
mapctx.lineWidth = 20*MapData.view.scale; mapctx.lineWidth = 20*MapData.view.scale*MapData.view.pixelRatio;
mapctx.font = `bold ${400*MapData.view.scale}px "Noto Sans"`; mapctx.font = `bold ${400*MapData.view.scale*MapData.view.pixelRatio}px "Noto Sans"`;
textMeasure = mapctx.measureText(marker.shorthand); textMeasure = mapctx.measureText(marker.shorthand);
mapctx.strokeText(marker.shorthand, markerX - textMeasure.width * 0.5, markerY); mapctx.strokeText(marker.shorthand, markerX - textMeasure.width * 0.5, markerY);
mapctx.fillText(marker.shorthand, markerX - textMeasure.width * 0.5, markerY); mapctx.fillText(marker.shorthand, markerX - textMeasure.width * 0.5, markerY);
if(!marker.tooltipHitzone) marker.tooltipHitzone = {}; if(!marker.tooltipHitzone) marker.tooltipHitzone = {};
marker.tooltipHitzone.width = textMeasure.width; marker.tooltipHitzone.width = textMeasure.width/MapData.view.pixelRatio;
marker.tooltipHitzone.height = textMeasure.actualBoundingBoxAscent; marker.tooltipHitzone.height = textMeasure.actualBoundingBoxAscent/MapData.view.pixelRatio;
marker.tooltipHitzone.offsetY = marker.tooltipHitzone.height * 0.5; marker.tooltipHitzone.offsetY = marker.tooltipHitzone.height * 0.5;
if(MapData.view.scale < 0.14) break; if(MapData.view.scale < 0.14) break;
mapctx.font = `bold ${70*MapData.view.scale}px "Noto Sans"`; mapctx.font = `bold ${70*MapData.view.scale*MapData.view.pixelRatio}px "Noto Sans"`;
textMeasure = mapctx.measureText(marker.nickname ?? marker.name); textMeasure = mapctx.measureText(marker.nickname ?? marker.name);
mapctx.strokeText(marker.nickname ?? marker.name, markerX - textMeasure.width * 0.5, markerY+80*MapData.view.scale); mapctx.strokeText(marker.nickname ?? marker.name, markerX - textMeasure.width * 0.5, markerY+80*MapData.view.scale*MapData.view.pixelRatio);
mapctx.fillText(marker.nickname ?? marker.name, markerX - textMeasure.width * 0.5, markerY+80*MapData.view.scale); mapctx.fillText(marker.nickname ?? marker.name, markerX - textMeasure.width * 0.5, markerY+80*MapData.view.scale*MapData.view.pixelRatio);
break; break;
case 'yardSiding': case 'yardSiding':
case 'yard': case 'yard':
@@ -405,21 +406,21 @@ function redrawMap(){
mapctx.fillStyle = '#fff'; mapctx.fillStyle = '#fff';
mapctx.strokeStyle = '#000'; mapctx.strokeStyle = '#000';
mapctx.lineJoin = 'round'; mapctx.lineJoin = 'round';
mapctx.lineWidth = 2; mapctx.lineWidth = 2*MapData.view.pixelRatio;
mapctx.font = `14px "Noto Sans Mono"`; mapctx.font = `${14*MapData.view.pixelRatio}px "Noto Sans Mono"`;
textMeasure = mapctx.measureText(marker.name); textMeasure = mapctx.measureText(marker.name);
tempMeasure.width = textMeasure.actualBoundingBoxLeft + textMeasure.actualBoundingBoxRight; tempMeasure.width = textMeasure.actualBoundingBoxLeft + textMeasure.actualBoundingBoxRight;
tempMeasure.height = textMeasure.fontBoundingBoxAscent + textMeasure.fontBoundingBoxDescent; tempMeasure.height = textMeasure.fontBoundingBoxAscent + textMeasure.fontBoundingBoxDescent;
if(!marker.tooltipHitzone){ if(!marker.tooltipHitzone){
marker.tooltipHitzone = { marker.tooltipHitzone = {
width: tempMeasure.width + 20, width: (tempMeasure.width + 20)/MapData.view.pixelRatio,
height: tempMeasure.height + 8, height: (tempMeasure.height + 8)/MapData.view.pixelRatio,
offsetY: -tempMeasure.height * 0.5 + 3, offsetY: (-tempMeasure.height * 0.5 + 3)/MapData.view.pixelRatio,
}; };
} }
mapctx.beginPath(); mapctx.beginPath();
mapctx.moveTo(markerX, markerY); mapctx.moveTo(markerX, markerY);
mapctx.roundRect(markerX - 0.5*tempMeasure.width - 4, markerY - textMeasure.fontBoundingBoxDescent + 1, tempMeasure.width + 12, tempMeasure.height - 4, 3); mapctx.roundRect(markerX - 0.5*tempMeasure.width - 4*MapData.view.pixelRatio, markerY - textMeasure.fontBoundingBoxDescent + 1*MapData.view.pixelRatio, tempMeasure.width + 12*MapData.view.pixelRatio, tempMeasure.height - 4*MapData.view.pixelRatio, 3*MapData.view.pixelRatio);
mapctx.fill(); mapctx.fill();
mapctx.stroke(); mapctx.stroke();
mapctx.fillStyle = '#000'; mapctx.fillStyle = '#000';
@@ -438,7 +439,7 @@ function redrawMap(){
marker.tooltipHitzone.radius = marker.radius * MapData.view.scale; marker.tooltipHitzone.radius = marker.radius * MapData.view.scale;
mapctx.beginPath(); mapctx.beginPath();
mapctx.moveTo(markerX, markerY); mapctx.moveTo(markerX, markerY);
mapctx.arc(markerX, markerY, marker.radius*MapData.view.scale, 0, Math.PI*2); mapctx.arc(markerX, markerY, marker.radius*MapData.view.scale*MapData.view.pixelRatio, 0, Math.PI*2);
mapctx.fillStyle = '#894b35'; mapctx.fillStyle = '#894b35';
mapctx.globalAlpha = 0.65; mapctx.globalAlpha = 0.65;
mapctx.fill(); mapctx.fill();
@@ -458,10 +459,10 @@ function redrawMap(){
curSprite.y, curSprite.y,
curSprite.width, curSprite.width,
curSprite.height, curSprite.height,
markerX-(0.5*spriteSize), markerX-(0.5*spriteSize)*MapData.view.pixelRatio,
markerY-(0.5*spriteSize), markerY-(0.5*spriteSize)*MapData.view.pixelRatio,
spriteSize, spriteSize*MapData.view.pixelRatio,
spriteSize spriteSize*MapData.view.pixelRatio
); );
} }
} }
+4 -3
View File
@@ -46,8 +46,9 @@ export let view = {
const mapContainer = document.getElementById('mapContainer'); const mapContainer = document.getElementById('mapContainer');
let isLandscape = mapContainer.clientWidth > mapContainer.clientHeight; let isLandscape = mapContainer.clientWidth > mapContainer.clientHeight;
view.scale = Math.min(mapContainer.clientWidth/(matrix.width + Config.defaultMapPadding*2), mapContainer.clientHeight/(matrix.height + Config.defaultMapPadding*2)); view.scale = Math.min(mapContainer.clientWidth/(matrix.width + Config.defaultMapPadding*2), mapContainer.clientHeight/(matrix.height + Config.defaultMapPadding*2));
view.pixelRatio = window.devicePixelRatio;
if(isLandscape){ if(isLandscape){
view.x = mapContainer.clientWidth*0.6 - (matrix.width * 0.5 + matrix.minX)*view.scale; view.x = (mapContainer.clientWidth*0.6 - (matrix.width * 0.5 + matrix.minX)*view.scale);
view.y = (matrix.minY - Config.defaultMapPadding) * view.scale; view.y = (matrix.minY - Config.defaultMapPadding) * view.scale;
}else{ }else{
view.x = -(matrix.minX - Config.defaultMapPadding) * view.scale; view.x = -(matrix.minX - Config.defaultMapPadding) * view.scale;
@@ -56,10 +57,10 @@ export let view = {
view.dirty = false; view.dirty = false;
view.convertX = x => { view.convertX = x => {
return x * view.scale + view.x; return x * view.scale * view.pixelRatio + view.x * view.pixelRatio;
} }
view.convertY = y => { view.convertY = y => {
return (matrix.minY+matrix.maxY-y) * view.scale - view.y; return (matrix.minY+matrix.maxY-y) * view.scale * view.pixelRatio - view.y * view.pixelRatio;
} }
view.unconvertX = x => { view.unconvertX = x => {
+1 -1
View File
@@ -16,7 +16,7 @@ export const gradeIncrements = [
0.001 0.001
]; ];
const gradeArrowDimensions = 64; const gradeArrowDimensions = 64 * window.devicePixelRatio;
export const gradeArrows = new OffscreenCanvas(11*gradeArrowDimensions, 2*gradeArrowDimensions); export const gradeArrows = new OffscreenCanvas(11*gradeArrowDimensions, 2*gradeArrowDimensions);
export function generateGradeArrows(){ export function generateGradeArrows(){