Есть ли ошибка в TCanvas Delphi? - PullRequest
5 голосов
/ 22 мая 2011

Я просто собираюсь выбросить это здесь, чтобы получить некоторую обратную связь, то, что я называю ", не забывайте считать ноль " (спасибо Андреасу Рейбранду за ссылку. Оказалось, это называется "отключено одна проблема ") при работе с пикселями. Что я имею в виду, не забывая считать ноль? Хорошо, если вы реализуете процедуру, которая должна вычислять количество пикселей, участвующих в прямоугольной операции (например, FillRect или CopyRect), вы должны помнить, что ноль (0,0) - это пиксель. Но правило рассматривать ноль как пиксель, а не число без значения, кажется, вступает в игру только с кординатами, включающими значения <= 0. Возьмите этот пример: </p>

mRect:=Rect(0,0,10,10);
mRectWidth:=mRect.right-mRect.left; // returns 10 - 0 = 10

Видишь проблему? прямоугольник фактически определяет в терминах операций с пикселями область, растягивающуюся от позиции 0,0 до позиции 10, 10. На самом деле длина составляет 11 шагов, а не десять (для x: = от 0 до 10 на самом деле 11 шагов). Чтобы восполнить потерянный пиксель (ноль не имеет массы и исчезает, когда вы перемещаете его в положительное или отрицательное пространство. Кажется, я помню, что теорема Пифагора о Боге), большинство людей просто добавляют 1 к конечному результату, например:

function getRectWidth(const aRect:TRect):Integer;
Begin
  result:=(aRect.right-aRect.left) +1;
End;

Теперь это работает, на самом деле это работает настолько хорошо, что 90% всех графических библиотек используют это в качестве своего метода для вычисления ширины и высоты прямоугольника. Но, как и у могущественного героя, у него есть слабое место, а именно то, что пустые прямоугольники возвращаются как имеющие массу 1 (он также может создавать всевозможные смешные АВ, если вы используете его с блиттером).

mRect:=Rect(0,0,0,0);
mRectWidth:=(mRect.right-mRect.left) + 1;

Что примерно равно 0 - 0 = 0: +1 = 1, что означает, что пиксель будет визуализирован, если вы не высматриваете мертвую точку. Что меня озадачивает, так это то, что Delphi XE, похоже, имеет проблему отсечения (?) Или, по крайней мере, противоречие в терминах. Потому что вы фактически теряете один пиксель внизу и в крайнем правом положении, если вы рисуете на нем. Разве ClientRect не должен возвращать полную область рисования от первого пикселя до последнего? - все же, если вы попробуете это:

mRect:=getClientRect;
MoveTo(mRect.left,mRect.Bottom);
LineTo(mRect.right,mRect.bottom);

Вы ничего не увидите! Потому что Delphi обрезает последний пиксель (по ошибке?). Просто кажется любопытным, что когда вы спрашиваете о клиенте правильно, вам придется вручную его настраивать?

Я кодировал свои собственные графические библиотеки с нуля (для быстрого доступа к DIB и рендеринга за пределами экрана, ничего общего с этим конкретным случаем), поэтому я давно работал над этими методами. Когда дело доходит до кодирования, всегда есть что-то новое, но никто не может сказать мне, что в этом материале нет слепого пятна на работе.

Когда я сравнил, как VCL работает с другими библиотеками, особенно с теми, что написаны на C #, я также заметил, что многим из них понравился я, и убедился, что клиентское право - это полная область региона, с которой вы можете работать. И они также взяли высоту за «слепое пятно», когда блеют за пределами области обрезки и работают с перекрывающимися прямоугольниками.

Корпус слепого пятна

Допустим, вы копируете прямоугольник из одного растрового изображения в другое. Цель вашего блита - Rect (-10, -10,10,10). Чтобы правильно «обрезать» цель здесь, чтобы вы не получили нарушения прав доступа для записи вне буфера памяти, вам нужно вычислить расстояние между X1 / Y1 и вашим клипрэтом (здесь принято равным 0,0, Width-1 , высота-1). * * один тысяча двадцать пять

Это дает вам смещение, которое должно быть добавлено к целевому прямоугольнику и исходному прямоугольнику. В противном случае вы будете писать вне буфера, а также читать из неправильного места в исходном буфере.

Теперь все зависит от того, как вы реализуете этот курс. Но есть множество библиотек, которые не учитывают ноль. Слепое пятно возникает, когда X1 и X2 имеют одинаковое значение, но x1 отрицательно. Потому что люди обычно пишут: mOffset: = x2 - abs (x1). Который в нашем случае становится 10-10 = 0. И пока cliprect установлен в 0,0, он будет работать просто отлично. Но в тот момент, когда ваш клипрэт переместится в положительное пространство - вы будете отключены на один пиксель. И если вы автоматически включите значения в ваш getRectWidth (например, mWidth: = aRect.right-aRect.left +1) - вы будете отключены на 2 пикселя в зависимости от исходного прямоугольника (я знаю, это большая скучная вещь).

Под C # на Mac, с использованием GTK #, а также родных привязок MonoMac - clientrect является абсолютным. Это означает, что вы можете рисовать в mRect.bottom или mRect.right и иметь видимые результаты. Поэтому мне было любопытно, что мой любимый язык и инструментарий, Delphi, мы всегда должны вручную корректировать клиентские права каждого владельца или пользовательского элемента управления при работе с ним.

Ответы [ 2 ]

13 голосов
/ 22 мая 2011

Так работает GDI, а Delphi TCanvas просто отражает базовый фреймворк.

Например, рассмотрим LineTo():

The *Функция 1010 * LineTo рисует линию от текущей позиции до, но не включая указанную точку.

Или FillRect():

Функция FillRect заполняет прямоугольник с помощью указанной кисти.Эта функция включает в себя левую и верхнюю границы, но исключает правую и нижнюю границы прямоугольника.

или Rectangle():

нарисованный прямоугольник исключает нижний и правый края.

И т. д. и т. п.

Рассмотрим теперь функцию API GetWindowRect().

Извлекает размеры ограничивающего прямоугольника указанного окна.Размеры даны в координатах экрана относительно левого верхнего угла экрана.

Значения bottom и right в возвращенных значениях RECT находятся на 1 пиксель за границейокна.Таким образом, ширина окна на самом деле составляет width = right-left, а также для высоты.Я предполагаю, что соглашение было выбрано так, чтобы это равенство выполнялось.

Поведение, о котором вы сообщаете, не является ошибкой в ​​коде Delphi TCanvas - код работает правильно и точно так, как задумано.

На сегодняшний день лучший подход для разработчиков, работающих с пользовательским интерфейсом Windows, состоит в том, чтобы следовать тем же соглашениям.Попытка принять ваши собственные соглашения просто приведет к путанице и ошибкам.

8 голосов
/ 22 мая 2011

Мне очень жаль ваших усилий по написанию полной библиотеки для этого феномена , но вы совершенно не правы.

Например, ваш код должен выглядеть так:

mRect := getClientRect;
MoveTo(mRect.left, mRect.Bottom - 1);
LineTo(mRect.right - 1, mRect.bottom - 1);

Всегда учитывайте, что такие подпрограммы, как FillRect(), ничего не делают на X = Rect.Right и Y = Rect.Bottom.Все они тянутся до Right - 1 и Bottom - 1.Так и должно быть: для кнопки с Left = 10 и Width = 10 самый правый пиксель находится на X = 19, а не X = 20.

Возможно, это сбивает с толку, но вы можете легко визуализировать этона бумажном квадратном блоке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...