У меня есть приложение Winforms, которое использует Graphics.DrawImage
для растягивания и рисования растрового изображения, и мне нужна помощь, чтобы точно понять, как исходные пиксели отображаются на место назначения.
В идеале я хочу написать такую функцию, как:
Point MapPixel(Point p, Size src, Size dst)
, который берет координату пикселя на исходном изображении и возвращает координату «левого верхнего» пикселя, соответствующего ему в масштабированном пункте назначения.
Для ясности, вот тривиальный пример, где растровое изображение 2x2 масштабируется до 4x4:
![Zoom example](https://i.stack.imgur.com/nlhC2.png)
Стрелка показывает, как вводится точка (1,0) в MapPixel:
MapPixel(new Point(1, 0), new Size(2, 2), new Size(4, 4))
должен дать результат (2,0).
Сделать так, чтобы MapPixel работал для приведенного выше примера, просто с помощью логики:
double scaleX = (double)dst.Width / (double)src.Width;
x_dst = (int)Math.Round((double)x_src * scaleX);
Однако я заметил, что эта наивная реализация выдает ошибки из-за округления, когда dst.Width
не является кратным src.Width
. В этом случае DrawImage нужно выбрать некоторые пиксели, чтобы рисовать больше, чем другие, чтобы привести изображение в соответствие, и у меня возникают проблемы с дублированием его логики.
Следующий код демонстрирует проблему, масштабируя растровое изображение 2x1 до нескольких значений ширины:
Bitmap src = new Bitmap(2, 1);
src.SetPixel(0, 0, Color.Red);
src.SetPixel(1, 0, Color.Blue);
Bitmap[] dst = {
new Bitmap(3, 1),
new Bitmap(5, 1),
new Bitmap(7, 1),
new Bitmap(9, 1)};
// Draw stretched images
foreach (Bitmap b in dst) {
using (Graphics g = Graphics.FromImage(b)) {
g.InterpolationMode = InterpolationMode.NearestNeighbor;
g.PixelOffsetMode = PixelOffsetMode.Half;
g.DrawImage(src, 0, 0, b.Width, b.Height);
}
}
Вот как выглядят исходное src
изображение и выходные dst
изображения, а также некоторые цифры, показывающие, как MapPixel необходимо отобразить синий пиксель:
![Scaling examples](https://i.stack.imgur.com/1QYEk.png)
Не могу понять, как DrawImage решает, какой пиксель увеличить. Кажется, иногда округляется, а иногда - вниз. Мне все равно, какой он выберет, но мне нужно, чтобы он был предсказуемым, чтобы моя функция работала правильно.
Я попытался изменить мой пример MapPixel выше, чтобы использовать MidpointRounding.AwayFromZero
, и даже заменил Math.Round
на функцию, которая округляет до ближайшего нечетного числа (немного улучшает результаты, но все еще не идеально). Я также попытался позволить классу Graphics обрабатывать масштабирование - то есть я установил ScaleTransform
, вызвал DrawImageUnscaled
, а затем попытался использовать TransformPoints
для преобразования координат. Интересно, что результаты метода TransformPoints не всегда согласуются с тем, что делают DrawImage и DrawImageUnscaled.
Я также пытался найти подсказки в GDI +, но пока не нашел ничего полезного.
Я не хочу прибегать к рисованию каждого пикселя по отдельности, просто чтобы сохранить предсказуемость места его посадки.
Если вам интересно, причина, по которой я использую InterpolationMode.NearestNeighbor
, состоит в том, чтобы избежать сглаживания (мне нужно поддерживать точность отдельных пикселей), а PixelOffsetMode.Half
включено, потому что если его там нет, то DrawImage панорамирует мое растровое изображение на полпикселя.
Еще пара примеров проблемных точек: x = 7 при масштабировании от 4 до 13 пикселей и x = 8 при масштабировании от 4 до 17 пикселей.
При желании я могу опубликовать полный код модульного теста, который позволит вам зайти и проверить функцию MapPixel. Пока что единственный способ достичь 100% точности - это уродливый хак, который генерирует исходное изображение «подсказка», в котором каждому пикселю присваивается уникальный цвет. Он отображает координаты, проверяя, какого цвета это изображение подсказки, а затем ищет этот цвет в уменьшенной версии изображения подсказки. Оптимизация возможна (например, изображения подсказок имеют ширину или высоту в один пиксель, а наивная логика выше используется, чтобы угадать приблизительный ответ и оттуда работать наружу), но все равно это уродливо.
Буду признателен, если кто-нибудь сможет пролить свет на сантехнику позади DrawImage и помочь мне придумать более простую (но все же точную) реализацию для MapPixel.