Войти
ПодсказкиСтатьи

Простейшее рисование текста для любого графического API

Автор:

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

Возможно этот способ уже описан где-то на сайте. Пишем текст через WinAPI функцией TextOut, копируем изображение в свой буфер, и обновляем GPU текстуру.
Думаю, ФПС не просядет даже если на экране будет множество меняющихся символов (конечно, если продумать рисование более детально, рисовать всё в один буфер только 1 раз за кадр).

Возможно некоторые вещи лишние. Код взят из генератора шрифта.
Аналогичным образом должно работать на других ОС, раз эти генераторы кросплатформенные.

class TextMaker
{
  wchar_t m_buffer[1024];
  HDC dc;
  HFONT font;
  HBITMAP bmp;
  HDC bmpdc;
  HBRUSH brush;
  HPEN pen;
  char *row;
  int rowsize;
  BITMAP b;
  WORD cClrBits;
  PBITMAPINFO pbmi;
  LPBYTE lpBits;
  PBITMAPINFOHEADER pbih;
  TextMaker() {}
public:
  TextMaker(const v4f& rect, int fontSize, const wchar_t* fontName)
  {
    int textureWidth = rect.z - rect.x;
    int textureHeight = rect.w - rect.y;

    rowsize = 0;
    row = nullptr;
    dc = CreateDC(L"DISPLAY", L"DISPLAY", 0, 0);
    font = CreateFontW(
      -MulDiv(fontSize, GetDeviceCaps(dc, LOGPIXELSY), 72), 0,
      0, 0,
      FW_SEMIBOLD,
      0, 0, 0, ANSI_CHARSET, 0, 0,
      ANTIALIASED_QUALITY,
      0, fontName);
    
    bmp = CreateCompatibleBitmap(dc, textureWidth, textureHeight);
    bmpdc = CreateCompatibleDC(dc);
    LOGBRUSH lbrush;
    lbrush.lbHatch = 0;
    lbrush.lbStyle = BS_SOLID;
    lbrush.lbColor = RGB(0, 0, 0);
    brush = CreateBrushIndirect(&lbrush);
    pen = CreatePen(PS_NULL, 0, 0);

    GetObject(bmp, sizeof(BITMAP), (LPSTR)&b);
    cClrBits = (WORD)(b.bmPlanes * b.bmBitsPixel);

    pbmi = (PBITMAPINFO)LocalAlloc(LPTR,
      sizeof(BITMAPINFOHEADER));
    pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    pbmi->bmiHeader.biWidth = b.bmWidth;
    pbmi->bmiHeader.biHeight = b.bmHeight;
    pbmi->bmiHeader.biPlanes = b.bmPlanes;
    pbmi->bmiHeader.biBitCount = b.bmBitsPixel;
    pbmi->bmiHeader.biCompression = BI_RGB;
    pbmi->bmiHeader.biSizeImage = ((pbmi->bmiHeader.biWidth * cClrBits + 31) & ~31) / 8
      * pbmi->bmiHeader.biHeight;
    pbmi->bmiHeader.biClrImportant = 0;

    pbih = (PBITMAPINFOHEADER)pbmi;


// yyImage - простая структура для хранения изображения. на основе неё создаются GPU текстуры
// Важен WinAPI код, мой код лишь как пример как делать дальше
    m_image = yyCreate<yyImage>();
    m_image->m_width = textureWidth;
    m_image->m_height = textureHeight;
    m_image->m_format = yyImageFormat::R8G8B8A8;
    m_image->m_bits = 32;
    m_image->m_pitch = 4 * textureWidth;
    m_image->m_dataSize = m_image->m_pitch * m_image->m_height;
    m_image->m_data = (unsigned char*)yyMemAlloc(m_image->m_dataSize);

    m_textureGPU = g_videoDriver->CreateTexture(m_image, true, false); // просто создание GPU текстуры
    m_pictureBox = yyGUICreatePictureBox(rect, m_textureGPU, 1); // это прямоугольник который создаётся по координатам rect и на который натягивается GPU текстура
  }

  ~TextMaker() {
    LocalFree(pbmi);
    GlobalFree(lpBits);
    if(row)
      delete[] row;
    if (m_image)
      yyDestroy(m_image);
    DeleteDC(bmpdc);
    DeleteObject(brush);
    DeleteObject(pen);
    DeleteObject(bmp);
    DeleteObject(font);
    DeleteDC(dc);
  }

  void set(const wchar_t* format, ...)
  {
    va_list arg;
    va_start(arg, format);
    _vsnwprintf(m_buffer, 1024, format, arg);
    va_end(arg);

    auto len = wcslen(m_buffer);
    if (len)
    {
      SelectObject(dc, font);
      SetTextAlign(dc, TA_LEFT | TA_TOP | TA_NOUPDATECP);

      SelectObject(bmpdc, bmp);
      SelectObject(bmpdc, pen);
      SelectObject(bmpdc, brush);
      SelectObject(bmpdc, font);

      SetTextColor(bmpdc, RGB(255, 255, 255));
      Rectangle(bmpdc, 0, 0, m_image->m_width, m_image->m_height);
      SetBkMode(bmpdc, TRANSPARENT);

      TextOutW(bmpdc, 10, 30, m_buffer, len);

      if (!row)
      {
        lpBits = (LPBYTE)GlobalAlloc(GMEM_FIXED, pbih->biSizeImage);
        rowsize = ((pbmi->bmiHeader.biWidth * cClrBits + 31) & ~31) / 8;
        row = new char[rowsize];
      }
      auto r = GetDIBits(dc, bmp, 0, (WORD)pbih->biHeight, lpBits, pbmi, DIB_RGB_COLORS);

      for (int i = 0; i < (pbih->biHeight / 2); ++i)
      {
        memcpy(row, lpBits + (rowsize * i), rowsize);
        memcpy(lpBits + (rowsize * i), lpBits + ((pbih->biHeight - 1 - i) * rowsize), rowsize);
        memcpy(lpBits + ((pbih->biHeight - 1 - i) * rowsize), row, rowsize);
      }

      auto imageData = m_image->m_data;
      if (cClrBits > 24)
      {
        for (LPBYTE m = lpBits; m < lpBits + pbih->biSizeImage; m += 4)
        {
          *imageData = m[0]; ++imageData;  // R
          *imageData = m[1]; ++imageData;
          *imageData = m[2]; ++imageData;

          unsigned char A = 0;

          if (m[0] > 0)
          {
            A = 255 - (255 - m[0]);
          }

          *imageData = A; ++imageData; // A
        }
      }
    }

// возможно правильнее делать map\unmap. но я не делал так как не реализовал map\unmap 
    g_videoDriver->UnloadTexture(m_textureGPU);
    g_videoDriver->LoadTexture(m_textureGPU);
  }

  yyImage* m_image;
  yyResource * m_textureGPU;
  yyGUIPictureBox* m_pictureBox;
};

TextMaker textTime(v4f(10, 10, 310, 110), 16, L"Arial");
TextMaker textFPS(v4f(10,40, 310,140), 16, L"Arial");
...
textTime.set(L"Время: %f ", timeAll);
if (fpsTimeCounter >= 1.f)
{
  fpsTimeCounter = 0.f;
  textFPS.set(L"ФПС: %f ", fpsCounter);
  fpsCounter = 0.f;
}

Запустить видео по клику - Как делать игрыЗапустить видео по клику - Как делать игры

#графика, #текст

30 декабря 2020