Система.Drawing-плохой рендеринг текста с помощью DrawString поверх прозрачных пикселей

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

вот несколько скриншотов:

текст, нарисованный поверх прозрачного пиксели:

текст, нарисованный поверх полупрозрачных пикселей:

текст, нарисованный на непрозрачных пикселях:

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

    g.SmoothingMode = SmoothingMode.HighQuality;
    g.DrawString("Press the spacebar", Font, Brushes.Black, textLeft, textTop);

4 ответов


опция, которую я использовал для решения этой проблемы, была:

Graphics graphics = new Graphics();
graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SingleBitPerPixelGridFit;

есть некоторые другие полезные опции в TextRenderingHint

надеюсь, что это помогает


на это есть очень простой ответ...

g.TextRenderingHint = Drawing.Text.TextRenderingHint.AntiAliasGridFit

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

Спасибо за чтение этого поста.


первый вывод-это то, что вы получаете, когда рисуете черный текст на черном фоне, возможно, цвет.Прозрачный. Второй был нарисован на почти черном фоне. 3rd был нарисован на том же фоне, с которым он отображается.

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

обратите внимание, что SmoothingMode не влияет на вывод текста. Это будет выглядеть немного менее плохо, если вы используете более низкое качество TextRenderingHint и цвет фона, который сероватый с Альфой нуля. Только TextRenderingHint.SingleBitPerPixelGridFit позволяет избежать всех проблем сглаживания.

получить идеальное решение для этого очень сложно. Стеклянный эффект Vista в строке заголовка окна использует очень тонкий затенение, чтобы придать тексту четко определенный цвет фона. Вам понадобится инструмент ZoomIt SysInternals, чтобы действительно увидеть его. Функция DrawThemeTextEx () с ненулевым iGlowSize.


если вы ищете что-то, что сохраняет сглаживание немного лучше, чем GDI+ делает по умолчанию, вы можете вызвать Graphics.Clear С помощью цветового ключа, а затем вручную удалите артефакты цветности, которые приводят. (См.почему DrawString выглядит так дерьмово? и уродливый текст проблема.)

вот как я в конечном итоге решил аналогичную проблему:

static Bitmap TextToBitmap(string text, Font font, Color foregroundColor)
{
  SizeF textSize;

  using ( var g = Graphics.FromHwndInternal(IntPtr.Zero) )
    textSize = g.MeasureString(text, font);

  var image = new Bitmap((int)Math.Ceiling(textSize.Width), (int)Math.Ceiling(textSize.Height));
  var brush = new SolidBrush(foregroundColor);

  using ( var g = Graphics.FromImage(image) )
  {
    g.Clear(Color.Magenta);
    g.SmoothingMode = SmoothingMode.AntiAlias;
    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
    g.PixelOffsetMode = PixelOffsetMode.HighQuality;
    g.DrawString(text, font, brush, 0, 0);
    g.Flush();
  }

  image.MakeTransparent(Color.Magenta);

  // The image now has a transparent background, but around each letter are antialiasing artifacts still keyed to magenta.  We need to remove those.
  RemoveChroma(image, foregroundColor, Color.Magenta);
  return image;
}

static unsafe void RemoveChroma(Bitmap image, Color foregroundColor, Color chroma)
{
  if (image == null) throw new ArgumentNullException("image");
  BitmapData data = null;

  try
  {
    data = image.LockBits(new Rectangle(Point.Empty, image.Size), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);

    for ( int y = data.Height - 1; y >= 0; --y )
    {
      int* row = (int*)(data.Scan0 + (y * data.Stride));
      for ( int x = data.Width - 1; x >= 0; --x )
      {
        if ( row[x] == 0 ) continue;
        Color pixel = Color.FromArgb(row[x]);

        if ( (pixel != foregroundColor) &&
             ((pixel.B >= foregroundColor.B) && (pixel.B <= chroma.B)) &&
             ((pixel.G >= foregroundColor.G) && (pixel.G <= chroma.G)) &&
             ((pixel.R >= foregroundColor.R) && (pixel.R <= chroma.R)) )
        {
          row[x] = Color.FromArgb(
            255 - ((int)
              ((Math.Abs(pixel.B - foregroundColor.B) +
                Math.Abs(pixel.G - foregroundColor.G) +
                Math.Abs(pixel.R - foregroundColor.R)) / 3)),
            foregroundColor).ToArgb();
        }
      }
    }
  }
  finally
  {
    if (data != null) image.UnlockBits(data);
  }
}

жаль, что GDI / GDI+ не делает этого уже, но это было бы разумно, не так ли? :)

если вы не можете использовать unsafe контекст, вы можете легко использовать ту же логику с Bitmap.GetPixel и Bitmap.SetPixel, хотя это будет значительно медленнее.