среда, 17 августа 2011 г.

Подводные камни вывода сглаженного текста с прозрачным фоном

Это перевод Pitfalls of transparent rendering of anti-aliased fonts. Автор: Реймонд Чен.

Windows предоставляет несколько технологий рендеринга монохромного текста на цветных экранах, пользуясь преимуществами характеристик экрана для предоставления гладкого результата. Эти техники включают в себя сглаживание (grayscale anti-aliasing), а также и более продвинутую технику - ClearType. Обе техники читают фоновые пиксели, чтобы определить, что рисовать. Это означает, что рендеринг текста требует дополнительного внимания.

Если вы рисуете текст вместе с фоном (обрамляющим прямоугольником), то тут нет никаких проблем, потому что вы выводите текст вместе с фоном, так что вы получаете согласованный результат, вне зависимости от предыдущего состояния холста (canvas). Но если вы рисуете текст поверх существующего фона, то вы должны убедиться, что на фоне находится именно ожидаемое вами изображение.

К примеру, наиболее часто люди лажаются, выводя текст многократно. Я видел программы, которые рисуют всё более тёмный текст; чем дольше вы её используете - тем темнее становится текст. Давайте посмотрим на то, как это происходит, и как этого можно избежать. Создайте пустое VCL приложение с таким кодом:
type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormPaint(Sender: TObject);
  private
    FAntialias: TFont;
    FClearType: TFont;
    procedure MultiPaint(const X, Y, N: Integer);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
 FAntialias := TFont.Create;
 FClearType := TFont.Create;

 FAntialias.Handle := CreateFont(-20, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, DEFAULT_PITCH, 'Tahoma');
 FClearType.Handle := CreateFont(-20, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH, 'Tahoma');
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(FClearType);
  FreeAndNil(FAntialias);
end;

procedure TForm1.FormPaint(Sender: TObject);
var
  SavedFont: TFont;
  SavedBrushStyle: TBrushStyle;
begin
  SavedFont := TFont.Create;
  try
    SavedBrushStyle := Canvas.Brush.Style;
    SavedFont.Assign(Canvas.Font);
    try
      Canvas.Brush.Style := bsClear;

      Canvas.Font := FAntialias;
      MultiPaint(10,  0, 1);
      MultiPaint(10, 20, 2);
      MultiPaint(10, 40, 3);
      MultiPaint(10, 60, 4);

      Canvas.Font := FClearType;
      MultiPaint(10,100, 1);
      MultiPaint(10,120, 2);
      MultiPaint(10,140, 3);
      MultiPaint(10,160, 4);
    finally
      Canvas.Font.Assign(SavedFont);
      Canvas.Brush.Style := SavedBrushStyle;
    end;
  finally
    FreeAndNil(SavedFont);
  end;
end;

procedure TForm1.MultiPaint(const X, Y, N: Integer);
var
  Str: String;
  I: Integer;
begin
  for I := 0 to N - 1 do
    Canvas.TextOut(X, Y, 'Съешь еще этих мягких французских булок, да выпей чаю.');
end;
Эта программа создаёт два шрифта: один вида anti-aliased, а другой - ClearType (я понятия не имею, почему люди утверждают, что не существует безопасного способа включить ClearType в одном окне. Мы только что это сделали).

Запустите эту программу и внимательно посмотрите на результаты. Заметьте, что каждый следующий ряд рисуется темнее предыдущего в обоих наборах. Чем больше мы делаем повторов вывода текста, тем темнее становится текст. В частности, заметьте, что повторный вывод текста с anti-aliased шрифтом делает результат всё уродливее и уродливее!


Что же пошло не так?

Когда мы рисуем текст первый раз, фон заполнен сплошным цветом окна. Но когда текст рисуется поверх повторно, то на фоновом изображении уже есть ранее написанный текст. Когда алгоритм решает что "Этот пиксель следует рисовать на 50% темнее", то по факту получается на 75% темнее, потому что пиксель затемняется дважды. А если вы рисуете три раза, то итоговое затемнение будет 88%.

Когда вы рисуете текст ровно один раз, он рисуется поверх планируемого вами фонового изображения. Это позволяет движкам anti-aliasing сглаживания и ClearType выполнять их работу очень аккуратно.

Те программы, которые рисуют чрезмерно тёмный текст, попались на эту проблему "печати поверх". Когда программа решает, что ей нужно обновить какое-то окно (к примеру, сместили фокус, так что фокусное выделение надо перерисовать - добавить или убрать), она "экономит время", не выполняя стирание фона, а просто рисует изображение поверх старого (текст, но с или без прямоугольника выделения). К сожалению, если вы не стираете фон, то текст будет выводиться поверх старого, приводя к затемнению.

Решение заключается в рисовании текста на правильном фоне. Если вы не знаете, что за фон сейчас на экране - то вам нужно стереть его, чтобы быть уверенным в известном его состоянии. В противном случае вы будете миксировать пиксели текста с неизвестным содержимым, что может приводить к нежелательным (и уродливым) результатам.

Если у вас орлиные глаза, то вы могли заметить и другой случай, когда люди делают эту же ошибку: когда текст в элементе управления (скажем, check box) становится темнее и темнее с каждым проходом через него по Tab. Это происходит из-за того, что люди не обращают внимание на флаги, передаваемые в записи TDrawItemStruct, передаваемой с сообщением WM_DRAWITEM. К примеру, некоторые люди просто рисуют элемент целиком, даже хотя оконный менеджер указывает флаг ODA_FOCUS, указывающий на необходимость рисования или стирания только прямоугольника фокуса. Это не будет проблемой, если рисование элемента включает в себя стирание фона. Но если вместо этого вы не стираете фон, предполагая, что его стёрло сообщение WM_ERASEBKGND, то в итоге вы повторите проблему рисования текста многократно в случае, когда вас просят исправить только прямоугольник фокуса. В этом случае элемент управления не стирается; всё, что от вам требуется - перерисовать прямоугольник фокуса. Если вы при этом ещё и рисуете текст, то вы делаете то же, что и функция MultiPaint из нашего примера выше: рисуете текст поверх текста, приводя к всё более тёмному тексту.

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

Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.