FBgfx изображения и буфер шрифта
 
Создание и понимание ваших FBgfx изображений и буферов шрифтов

FBgfx буфер изображения
Создание буфера
Формат буфера
Получение пикселов
FBgfx заголовок шрифта
Детали заголовка
Создание буфера шрифта
Добавление символов шрифта
Трюки и советы
Окраска ваших пользовательских шрифтов
ScrPtr vs ImgBuf

Скачать сопроводительные файлы учебника: FreeBASIC Font Tutorial.7z

FBgfx буфер изображения


FBgfx имеет новый тип данных в .17 и выше. Этот тип называется IMAGE. Вы можете использовать его, включив заголовок FBgfx в вашей программе (#include "fbgfx.bi") , а доступ к пространству имен для FBgfx, через FB.IMAGE. Когда мы будем создавать буферы в этом учебнике, мы будем использовать тип fb.Image Ptr. Используется указатель, потому что это динамическая память, размер , которой мы можем изменить.

Чтобы использовать изображение в библиотеке FBgfx, вы должны создать его с помощью буфера изображения. Буфер - это область выделенной памяти (создаваемой, доступной) для вашего изображения. Вам нужно освободить память буфера (сделать память доступной для других программ) когда он больше не требуется, в частности в конце вашей программы. FBgfx имеет свой собственный внутренний формат точек, а также заголовок изображения, создаваемый в начале каждого буфера. Заголовок изображения содержит информацию об изображении, такую как ширина, высота, глубина и т.д., с другой стороны буфер пикселей содержит фактические цвета для каждого индивидуального пиксела в формате RGB (красный, синий, зеленый).

Создание буфера

Размер создаваемого буфера будет варьироваться в зависимости от глубины экрана. Байт на пиксель (bytes-per-pixel) по сути это количество байт, необходимых для хранения отдельных пикселей. Таким образом для 32-битной глубины экрана будет нужно 4 байта на пиксель (8 бит в байте). Вам не нужно беспокоиться об этом, однако,  используя настройки созданного буфера fb.Image Ptr , очень легко получить информацию об нашем буфере. Вам просто необходимо знать эту информацию, чтобы понять, какой размер буфера используется, для получения сведений об использовании памяти.

На самом деле создать буфер очень просто. Нужно просто объявить указатель fb.Image Ptr, и вызвать ImageCreate (Example1.bas):

#include "fbgfx.bi"

  '' ширина/высота нашего изображения
Const ImgW = 64
Const ImgH = 64

  '' Экран должен быть создан до вызова imagecreate
ScreenRes 640, 480, 32

  '' Создание нашего буфера
Dim As FB.Image Ptr myBuf = ImageCreate(ImgW, ImgH)

  '' Печатаем адрес нашего буфера.
Print "Buffer created at: " & myBuf
Sleep

  '' Уничтожаем наш буфер.  Всегда нужно уничтожать созданные буферы
ImageDestroy( myBuf )
Print "Our buffer was destroyed."
Sleep


Разберем код

#include "fbgfx.bi"


Включаем файл заголовка, который содержит определения для типов fb.Image.

  '' ширина/высота нашего изображения
Const ImgW = 64
Const ImgH = 64


Создаем константы, которые будут использоваться для определения размера нашего изображения. FBgfx не знает о них. Мы должны передать их в ImageCreate , когда мы ее используем.

  '' Экран должен быть создан до вызова imagecreate
ScreenRes 640, 480, 32


Создаем наш экран FBgfx. Функции ImageCreate необходимо заранее знать нашу битовую глубину. Однако FBgfx ImageCreate теперь имеет дополнительный параметр, позволяющий установить глубину для себя.

  '' Создание нашего буфера
Dim As FB.Image Ptr myBuf = ImageCreate(ImgW, ImgH)


Это в первую очередь создает указатель типа fb.Image. Это просто место в памяти, пока еще ничем не заполненное. На самом деле, он сейчас равен нулю и не может быть использован.

При вызове ImageCreate возвращается адрес в области памяти и закрепляется за нашим указателем fb.Image. Размер этого буфера зависит от глубины бит , а также от ширины и высоты изображения, которую мы установили ранее. ImageCreate также может принимать цвет заливки и глубину в третьем и четвертом аргументе, соответственно; Если они не указаны, образ будет создан с прозрачным цветом и текущей глубиной цвета экрана.

Мы сейчас выделили пространство в памяти. Это достаточное место для хранения изображения ImgW x ImgH  вместе с данными. FBgfx записывает эти данные в пределах своего типа fb.Image. Мы должны уничтожить его позднее для правильного управления памятью.

  '' Печатаем адрес нашего буфера.
Print "Buffer created at: " & myBuf
Sleep


Здесь мы печатаем адрес myBuf. Если он не равен 0, мы можем предположить, что ImageCreate отработал правильно.

  '' Уничтожаем наш буфер.  Всегда нужно уничтожать созданные буферы
ImageDestroy( myBuf )
Print "Our buffer was destroyed."
Sleep


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

Формат буфера

Теперь, когда мы знаем, как создавать буфер, мы можем узнать больше информации о его внутреннем устройстве. Вы можете открыть файл заголовка fbgfx.bi и найти в нем тип fb.Image, который покажет какие данные в нем содержатся.

На самом деле нам не нужно много знать о самом формате. При использовании нашего fb.Image Ptr , область в памяти после Buf + SizeOf(fb.Image) принадлежит пикселям, до этого всё принадлежит заголовку. Используя заголовок, очень легко добраться до любой информации о нашем буфере.

FB.IMAGE тип данных

  '' Заголовок буфера изображения, новый стиль (включает в себя старый заголовок)
Type IMAGE Field = 1
    Union
        old As _OLD_HEADER
        Type As UInteger
    End Union
    bpp As Integer
    Width As UInteger
    height As UInteger
    pitch As UInteger
    _reserved(1 To 12) As UByte
End Type


Эту же информацию можно найти в fbgfx.bi. Как вы можете видеть, этот тип данных сохраняет различную информацию о вашем буфере: ширина, высота, шаг (кол-во байт в строке) и битовую глубину (байт на пиксель) . В Union включен тип заголовка и старый заголовок. Новый формат заголовка имеет 7 полей. Старый формат заголовка не используется в диалекте по умолчанию в новых версиях FB, поэтому мы не собираемся охватывать его здесь.

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

Получение пикселей

В первой секции нашего буфера содержится информация заголовка. Добавьте размер fb.Image к нашему адресу, и остальная часть нашего буфера будет содержать пиксели (Example2.bas).

  '' Мы должны включить эту запись, чтобы использовать наш тип данных FB.IMAGE.
#include "fbgfx.bi"


Не забудьте включить наш тип данных fb.Image!

  '' Этот очень важно.
  '' Прежде всего мы приводим наш буфер к UBYTE PTR, чтобы получить точное кол-во байт наших пикселей.
  '' После расчетов, мы приводим обратно к UInteger PTR, просто для того, чтобы избежать предупреждений от компилятора
Dim As UInteger Ptr myPix = Cast( UInteger Ptr, ( Cast( UByte Ptr, myBuf ) + SizeOf(FB.Image) ) )


Хорошо. Мы должны убедиться, что мы получаем точный адрес наших пикселей. Integer содержит 4 байта. 3 из них используются для нашего RGB, а дополнительный обычно используется для альфа когда это нужно (некоторые люди очень изобретательны и используют альфа-байт - или канал - для хранения каких-то других видов данных). Если мы ошибемся хотя бы на один байт, то ваш красный цвет может стать зеленым, а ваш синий красным! Поэтому мы должны приводить указатель к UByte Ptr в первую очередь.

Вы наверное заметили, что мы просто добавили sizeof(fb.Image) к нашему адресу. Это еще одна выгода от использования fb.Image! Если добавить его размер к началу буфера, мы просто пропустим все адреса памяти, связанные с заголовком и это будет началом наших пиксельных данных.

Наконец, мы преобразовали все это к UInteger Ptr, в основном для обеспечения безопасности. Мы находимся в режиме 32 битовой глубины, так что нам нужно 4 байта на пиксель. UInteger как раз таким и является.

Вот небольшой результат, если вы до сих пор не понимаете, как это работает. Вот наш буфер: |FB.IMAGE ЗАГОЛОВОК|ПИКСЕЛЫ|

Если то, что содержится в первом разделе нашего буфера является FB.IMAGE ЗАГОЛОВОК, то очевидно что мы можем получить наш адрес для пикселей, просто добавив размер типа данных fb.Image к нашему оригинальному адресу.

Хотя одна проблема! Если мы просто добавим этот размер в нашему адресу буфера, мы в конечном итоге получим странные результаты. Это потому, что наш тип данных длиной не один байт. Мы должны привести указатель сначала к UByte Ptr, а затем уже добавлять адрес.

И наконец мы наш полученный адрес преобразовываем обратно к UInteger Ptr.Конечно мы можем прямо без преобразования присвоить, но тогда мы получим предупреждение от компилятора.


  '' Распечатать информацию, хранящуюся в нашем буфере.
Print "Image Width: " & myBuf->Width
Print "Image Height: " & myBuf->Height
Print "Image Bit Depth: " & myBuf->BPP
Print "Image Pitch: " & myBuf->Pitch
Print ""


Это то, что я говорил ранее. FB будет относиться к вашему указателю, как будто это fb.Image Ptr, поэтому можно получить доступ к данным в заголовке напрямую. Поскольку у нас теперь есть размер изображения, а также адрес пикселей, мы могли бы редактировать и манипулировать ими, как будто они являются указателем на буфер нашего экрана! Смотрите пример ScrPtr vs ImgBuf.bas.

FBGfx заголовок шрифта


Детали заголовка

Первая строка из буфера изображения, который будет использоваться в качестве шрифта, содержит информацию заголовка для вашего шрифта, на побайтной основе (напомним, что первая строка пикселей - это первые байты, хранящиеся в row->column).

Самый первый байт говорит нам, какую версию заголовка мы используем. В настоящее время поддерживается только 0. Второй байт говорит нам о первом символе в нашем шрифте, и третий байт говорит нам о последнем.

0; байт; Версия заголовка
1; байт; Первый поддерживаемый символ
2; байт; Последний поддерживаемый символ
C 3 байта по (3 + LastChar - FirstChar) байт; Ширина каждого символа в нашем шрифта.

Создание буфера заголовка

Если у вас шрифт поддерживает символы  с 37 по 200 то заголовок будет:

0 для версии заголовка. Только эта версия поддерживается.
37 для первого поддерживаемого символа.
200 для последнего поддерживаемого символа.
94 байт, содержащие значения ширины для каждого символа.

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

Вот пример (Example3.bas), который создает буфер шрифта. Он только создает его и присваивает данные заголовка, а не актуальный шрифт:

  '' Первый поддерживаемый символ
Const FirstChar = 32
  '' Последний поддерживаемый символ
Const LastChar = 190
  '' Общее кол-во символов.
Const NumChar = (LastChar - FirstChar) + 1


Эти константы помогут нам. Это делает код чище и быстрее.


  '' Создаем буфер шрифта достаточный для размещения 96 символов, с шириной 8.
  '' Не забываем сделать наш буфер высотой больше на единицу.
Dim As FB.Image Ptr myFont = ImageCreate( ( NumChar * 8 ), 8 + 1 )


Создание нашего буфера шрифта. Помните, мы должны добавить горизонтальное пространство для каждого символа в шрифте (8 пикселей в ширину). Мы также должны добавить дополнительную строку для информационного заголовка нашего шрифта.


  '' Информация заголовка шрифта.
  '' Преобразовываем к uByte ptr для обеспечения безопасности и согласованности
Dim As UByte Ptr myHeader = Cast(UByte Ptr, myFont )


Получаем адрес, по которому мы можем перемещаться побайтово.


  '' Назначение информации в заголовок шрифта.
  '' Версия заголовка
myHeader[0] = 0
  '' Первый поддерживаемый символ
myHeader[1] = FirstChar
  '' Последний поддерживаемый символ
myHeader[2] = LastChar


Назначаем данные заголовка, описанные выше, в первых трех байтах. Версия заголовка, первый поддерживаемый символ и последний поддерживаемый символ.


  '' Назначаем ширину для каждого символа в нашем шрифте.
For DoVar As Integer = 0 To NumChar - 1
    '' Пропускаем заголовок, если вы помните
  myHeader[3 + DoVar] = 8  
Next


Каждый символ в нашем шрифте может иметь свою собственную ширину, поэтому мы должны назначить это. 3 + это пропуск информации заголовка. ##DoVar ## начинается с 0, поэтому в первый раз, она будет с индексом 3. В нашем примере для всех символов мы даем ширину 8.


  '' Помните, что надо удалять наш буфер, когда он больше не требуется.
ImageDestroy( myFont )


Просто напоминаю вам :D

Добавление символов шрифта

Это довольно просто. Мы будем использовать шрифт по умолчанию FreeBASIC , чтобы организовать наш буфер. Не забудьте при рисовании, начинать с 1 столбца, а не с 0, так как нулевой столбец зарезервирован для данных заголовка. Рисуем символы и придаем им нужный цвет. Внимание, вы не можете изменить цвета после создания шрифта. То есть создав шрифт с определенными цветами, он с такими цветами и будет,  и изменить его цвета как у встроенного не получится. В разделе трюки и советы можно найти способ обойти это.

Вот модифицированный код (Example4.bas), где мы будем добавлять шрифт, рисуя с помощью FreeBASIC шрифта по умолчанию в наш буфер.

  '' НОВЫЙ!!!
  '' Наш текущий символ шрифта.
Dim As UByte CurChar


Просто, чтобы иметь быстрый индекс текущего символа ASCII , который мы рисуем в наш шрифт.


Draw String myFont, ( DoVar * 8, 1 ), Chr(CurChar), RGB(Rnd * 255, Rnd * 255, Rnd * 255)


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


Print Chr(CurChar);


Просто для ясности, чтобы увидеть символы, которые мы рисуем в буфер.


  '' Используем наш буфер шрифта для рисования текста!
Draw String (0, 80), "Hello!", , myFont
Draw String (0, 88), "HOW ARE ya DOIN Today?!  YA DOIN FINE?!", , myFont
Sleep


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

Трюки и советы


Окраска ваших пользовательских шрифтов

Вы уже поняли, что после того , как шрифт создан, изменить его цвет невозможно встроенной функцией Draw String , однако мы можем обойти это способом (CustFontCol.bas), хотя данный способ не является быстрым.

Мы можем создать объект font, который имеет функцию для возвращения буфера шрифта. Код перерисовывает буфер шрифта каждый раз, когда мы изменяем цвет и возвращает буфер шрифта, сохраняя в объекте. Это может в теории работать быстрее, если бы мы знали заранее нужный диапазон символов для перерисовки.

#include "fbgfx.bi"

Type Font
    '' Наш буфер шрифта.
  Buf     As FB.Image Ptr
    '' Заголовок шрифта.
  Hdr     As UByte Ptr
  
    '' Текущий цвет шрифта.
  Col     As UInteger
  
    '' Создание нашего буфера шрифта.
  Declare Sub Make( ByVal _Col_ As UInteger = RGB(255, 255, 255) )
    '' Изменение цвета шрифта и редактирование буфера шрифта.
    '' Возвращает новый шрифт.
  Declare Function myFont( ByVal _Col_ As UInteger = RGB(255, 255, 255) ) As FB.Image Ptr
  
    '' Создает\удаляет наш шрифт.
    '' Если вы хотите задать цвет по умолчанию.
  Declare Constructor( ByVal _Col_ As UInteger = RGB(255, 255, 255) )
  Declare Destructor()
End Type

  '' Создание буфера нашего шрифта.
Constructor Font( ByVal _Col_ As UInteger = RGB(255, 255, 255) )
  This.Make( _Col_ )
End Constructor

  '' Удаление буфера шрифта.
Destructor Font()
  ImageDestroy( Buf )
End Destructor

  '' Назначение шрифта FBgfx в наш буфер шрифта.
Sub Font.Make( ByVal _Col_ As UInteger = RGB(255, 255, 255) )
    '' Нет изображения буфера данных.  Создаем его.
  If This.Buf = 0 Then
  
      '' Нет экрана, еще не создан.
    If ScreenPtr = 0 Then Exit Sub
    
      '' Поддержка 256 символов, с шириной 8.
      '' Добавление дополнительной строки для заголовка шрифта.
    This.Buf = ImageCreate( 256 * 8, 9 )
    
      '' Получение адреса заголовка шрифта,
      '' Это то же самое, как получение нашего адреса пиксельных данных
      '' Кроме этого, мы будем использовать ubyte.
    This.Hdr = Cast(UByte Ptr, This.Buf) + SizeOf(FB.Image)
    
      '' Назначаем информацию о версии.
    This.Hdr[0] = 0
      '' Первый поддерживаемый символ
    This.Hdr[1] = 0
      '' Последний поддерживаемый символ
    This.Hdr[2] = 255
  Else
    If This.Col = _Col_ Then Exit Sub
    
  End If
  
    '' Рисуем наш шрифт.
  For DoVar As Integer = 0 To 255
      '' Устанавливаем информацию о ширине символов шрифта.
    This.Hdr[3 + DoVar] = 8
    
    Draw String This.Buf, (DoVar * 8, 1), Chr(DoVar), _Col_
  Next
  
    '' Запоминаем цвет нашего шрифта.
  This.Col = _Col_
End Sub

  '' Получаем буфер для нашего шрифта.
  '' Пересоздание шрифта, если цвета разные.
Function Font.myFont( ByVal _Col_ As UInteger = RGB(255, 255, 255) ) As FB.Image Ptr
    '' Если наши цвета совпадают, просто вернуть текущий буфер.
  If _Col_ = Col Then
    Return Buf
  End If
  
    '' Создаем шрифт с новым цветом.
  This.Make( _Col_ )
    '' Возвращаем наш буфер.
  Return This.Buf
End Function


  '' Основной код!
ScreenRes 640, 480, 32

  '' Создание нашего шрифта.
Dim As Font myFont = RGB(255, 255, 255)

  '' Нарисуем строку с помощью наших пользовательских шрифтов.
Draw String (0,0), "Hello.  I am the custom font.",, myFont.myFont()
  '' Новый цвет!
Draw String (0,8), "Hello.  I am the custom font.",, myFont.myFont(RGB(255, 0, 0))
Sleep

  '' Тест скорости.  Оказывается, это довольно медленно.
Scope
  Randomize Timer
    '' Наш таймер, засекаем время.
  Dim As Double T = Timer
  
    '' Создаем 500 шрифтов.
  For DoVar As Integer = 0 To 499
    myFont.Make( RGB(Rnd * 255, Rnd * 255, Rnd * 255) )
  Next
  
    '' Все сделали. Распечатаем важные данные.
  Locate 3, 1
  Print "Time to Re-Draw font 499 times: " & ( Timer - T )
  Print "Time per Re-Draw: " & ( Timer - T ) / 500
  Sleep
End Scope


ScrPtr vs ImgBuf

Сравнение между рисованием в буфер пикселей изображения и рисованием в буфер экрана(ScrPtr vs ImgBuf.bas):

#include "fbgfx.bi"


ScreenRes 640, 480, 32


  '' Создаем буфер с размером нашего экрана.
Dim As FB.IMAGE Ptr myBuf = ImageCreate( 640, 480 )

  '' Получаем адрес буфера нашего экрана.
Dim As UInteger Ptr myScrPix = ScreenPtr
  '' Получаем адрес буфера наших пикселей.
Dim As UInteger Ptr myBufPix = Cast( UInteger Ptr, Cast( UByte Ptr, myBuf ) + SizeOf(FB.IMAGE) )


  '' Блокировка нашей страницы. Заполним всю страницу белым цветом.
ScreenLock

  '' Если неизвестно разрешение экрана, используйте ScreenInfo
  '' это более безопасно
  
  '' Примечание: этот код не учитывает выравнивание пикселей.
  '' В реальных программах вы должны использовать ScreenInfo , чтобы получить pitch (кол-во пикселей в строке), и подсчитывать
  '' исходя из этого параметра.
  For xVar As Integer = 0 To 639
    For yVar As Integer = 0 To 479
      myScrPix[ ( yVar * 640 ) + xVar ] = RGB(255, 255, 255)
    Next
  Next

ScreenUnlock
Sleep


  '' Зарисуем наш буфер изображения красным цветом.
For xVar As Integer = 0 To myBuf->Width - 1
  For yVar As Integer = 0 To myBuf->Height - 1
    myBufPix[ ( yVar * (myBuf->Pitch \ SizeOf(*myBufPix)) ) + xVar ] = RGB(255, 0, 0)
  Next
Next

  '' Положим буфер на экране.
Put (0,0), myBuf, PSet
Sleep


/'
  ScreenPtr:
 1) Получение адреса буфера экрана
    (Помните, что FBgfx использует макетный буфер, он переворачивается автоматически)

 2) Блокировка страницы
 3) Рисование по адресу экрана
 4) Разблокировка страницы, чтобы показать буфер
  
  Image Buffer (буфер изображения):
 1) Создание буфера изображения
 2) Получение адреса пиксельных данных изображения
 3) Рисование пикселов изображения
 (Можно использовать информацию буфера для помощи)

 4) Положим изображение, где хотим
    (еще один большой плюс!)
    
  О рисовании:
 cast(ubyte ptr, mybuff) + Y * Pitch + X * Bpp
 
 Каждый Y содержит PITCH количество байт. Для того чтобы достичь нового Y, вам
нужно пропустить всю строку.

 Нужно сделать арифметику указателей безопасной в случаях, когда тип данных указателя
 не один байт, так что вы можете найти его проще, используя тип указателя в
 соответствии с вашей глубиной бит.
 В этих случаях вы должны разделить PITCH и BPP на размер типа указателя.
 Удобно то, что в этом случае PITCH всегда должен быть кратным размеру типа пикселя.
 И, очевидно, так будет BPP, который будет просто отменой до 1

 
'/