Учет различий в размерах холста при рисовании на изображении с сохраненными координатами

#javascript #html #canvas #screen-size

#javascript #HTML #холст #размер экрана

Вопрос:

Я изо всех сил пытаюсь найти метод / стратегию для обработки рисования с сохраненными координатами и различий в размерах холста на различных устройствах и размерах экрана для моего веб-приложения.

В принципе, я хочу отобразить изображение на холсте. Пользователь отметит две точки на области изображения, и приложение запишет, где были размещены эти маркеры. Идея заключается в том, что пользователь будет использовать приложение каждый нечетный день, имея возможность видеть, где было нарисовано X количество предыдущих точек, и добавлять две новые в область, указанную в местах, еще не отмеченных предыдущими маркерами. В настоящее время холст настроен на высоту = window.innerHeight и ширину = window.innerWidth/2.

Моей первоначальной мыслью было записывать координаты каждой пары точек и извлекать их по мере необходимости, чтобы их можно было перерисовать. Но эти координаты не совпадают, если размер холста меняется, как было обнаружено, когда я тестировал веб-страницу на разных устройствах. Как я могу записать предыдущие координаты и использовать их для обозначения одной и той же области изображения независимо от размеров холста?

Ответ №1:

Используйте проценты! Пример:

Итак, допустим, на устройстве 1 размер холста равен 150x200 ,
пользователь ставит маркер на пиксель 25×30. Вы можете выполнить некоторую математику, чтобы получить процент.
И затем вы СОХРАНЯЕТЕ этот процент, а не местоположение,
Пример:

 let userX = 25; //where the user placed a marker
let canvasWidth = 150;
//Use a calculator to verify :D
let percent = 100 / (canvasWidth / userX); //16.666%
  

И теперь, когда у вас есть percent , вы можете установить местоположение маркера на основе этого процента.
Пример:

 let markerX = (canvasWidth * percent) / 100; //24.999
canvasWidth = 400; //Lets change the canvas size!
markerX = (canvasWidth * percent) / 100; //66.664;
  

И вуаля: D просто возьмите размер холста, и вы сможете определять местоположение маркера каждый раз.

Комментарии:

1. Это было полезно, однако проблема изменения соотношения сторон по-прежнему приводит к тому, что маркеры отличаются от того места, где они должны быть нарисованы.

2. @RamsayM нашел какое-либо решение по этому поводу?

3. Мое решение по-прежнему актуально, при любом соотношении сторон и размере окна будут работать проценты. Если пользователь помещает маркер в середину экрана, компьютер знает, что он нарисован на 50% Y. Итак, когда вы отображаете холст, вы просто ставите этот маркер на 50% Y

Ответ №2:

Виртуальный холст

Вы должны определить виртуальный холст. Это идеальный холст с предопределенным размером, все координаты указаны относительно этого холста. Центром этого виртуального холста является координата 0,0

Когда вводится координата, она преобразуется в виртуальные координаты и сохраняется. При визуализации они преобразуются в координаты экрана устройства.

Разные устройства имеют разное соотношение сторон, даже одно устройство может быть наклонено, что изменяет аспект. Это означает, что виртуальный холст точно не поместится на всех устройствах. Лучшее, что вы можете сделать, это убедиться, что весь виртуальный холст виден, не растягивая его в направлениях x или y. это называется масштабированием по размеру.

Масштабировать по размеру

Для рендеринга на холсте устройства вам необходимо масштабировать координаты так, чтобы весь виртуальный холст мог поместиться. Вы используете преобразование холста для применения масштабирования.

Для создания матрицы масштаба устройства

 const vWidth = 1920;  // virtual canvas size
const vHeight = 1080;

function scaleToFitMatrix(dWidth, dHeight) {
    const scale = Math.min(dWidth / vWidth, dHeight / vHeight);
    return [scale, 0, 0, scale, dWidth / 2, dHeight / 2];
}

const scaleMatrix = scaleToFitMatrix(innerWidth, innerHeight);
  

Положение масштаба, а не пиксели

Точка определяется как позиция на виртуальном холсте. Однако преобразование также изменит ширину линий и размеры объектов, которые вы не хотели бы использовать на устройствах с очень низким или высоким разрешением.

Чтобы сохранить тот же размер пикселей, но при этом отображать объекты в размерах в пикселях, вы используете обратный масштаб и сбрасываете преобразование непосредственно перед обводкой следующим образом (прямоугольник в 4 пикселя с центром по точке)

 const point = {x : 0, y : 0}; // center of virtual canvas
const point1 = {x : -vWidth / 2, y : -vHeight / 2}; // top left of virtual canvas
const point2 = {x : vWidth / 2, y : vHeight / 2}; // bottom right of virtual canvas

function drawPoint(ctx, matrix, vX, vY, pW, pH) { // vX, vY virtual coordinate
     const invScale = 1 / matrix[0]; // to scale to pixel size
     ctx.setTransform(...matrix); 
     ctx.lineWidth = 1; // width of line
     ctx.beginPath();
     ctx.rect(vX - pW * 0.5 * invScale, vY - pH * 0.5 * invScale, pW * invScale, pH * invScale);
     ctx.setTransform(1,0,0,1,0,0); // reset transform for line width to be correct
     ctx.fill();
     ctx.stroke();
}
const ctx = canvas.getContext("2d");
drawPoint(ctx, scaleMatrix, point.x, point.y, 4, 4);
  

Преобразование с помощью процессора

Чтобы преобразовать точку из координат устройства в виртуальные координаты, вам нужно применить обратную матрицу к этой точке. Например, вы получаете координаты pageX, страницы с помощью мыши, преобразуете с использованием матрицы масштаба следующим образом

 function pointToVirtual(matrix, point) {
    point.x = (point.x - matrix[4]) / matrix[0];
    point.y = (point.y - matrix[5]) / matrix[3];
    return point;
}
  

Для преобразования из виртуального в устройство

 function virtualToPoint(matrix, point) {
    point.x = (point.x * matrix[0])   matrix[4];
    point.y = (point.y * matrix[3])   matrix[5];
    return point;
}
  

Проверьте границы

Может быть область выше / ниже или слева / справа от холста, которая находится за пределами координат виртуального холста. Чтобы проверить, находится ли внутри виртуального холста, вызовите следующее

 function isInVritual(vPoint) {
     return ! (vPoint.x < -vWidth / 2 || 
         vPoint.y < -vHeight / 2 ||
         vPoint.x >= vWidth / 2 ||
         vPoint.y >= vHeight / 2);
}
const dPoint = {x: page.x, y: page.y};  // coordinate in device coords
if (isInVirtual(pointToVirtual(scaleMatrix,dPoint))) {
     console.log("Point inside");
} else {
     console.log("Point out of bounds.");
}
  

Дополнительные баллы

  • Вышесказанное предполагает, что холст выровнен по экрану.
  • Некоторые устройства будут увеличены (pinch scaled). Вам нужно будет проверить масштаб устройства в пикселях для получения наилучших результатов.
  • Лучше всего установить размер виртуального холста на максимальное разрешение экрана, которое вы ожидаете.
  • Всегда работайте в виртуальных координатах, преобразуйте в координаты устройства только тогда, когда вам нужно выполнить рендеринг.

Комментарии:

1. Когда вы упоминаете о предполагаемом выравнивании холста по экрану, вы имеете в виду, что холст занимает весь экран, т. е. window.innerWidth amp; window.innerHeight?

2. @Ramrod506 выравнивание означает, что оси x и y виртуального холста параллельны сторонам устройства (вы не поворачивали виртуальный холст). Это потому, что я использовал несколько математических сокращений при вычислениях матрицы. Холст может быть любого размера. я выбрал innerWidth и innerHeight для удобства подойдет любой размер (кроме <= 0, конечно)

3. Имеет смысл. Есть какие-нибудь советы о том, как бы вы разместили маркер круга вместо квадрата / прямоугольника, учитывая ширину и высоту пикселя?

4. @Ramrod506 Просто замените ctx.rect на ctx.arc и масштабируйте радиус, используя invScale