Низкоуровневая графика в FreeBASIC

Это перевод статьи (может быть немного вольный). Автор статьи: angros47. Со стороны переводчика (то есть меня) сделаны несколько правок в коде для лучшей совместимости с 64-х bit компилятором. 

FreeBasic предлагает много команд высокого уровня (GET, PUT, DRAW ...) для управления графикой: эти команды, во всяком случае, универсальны, и поэтому они не оптимизированы для конкретной задачи: скорость была принесена в жертву универсальности.

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

Графическая страница хранится в памяти компьютера в виде последовательности битов (как и все остальное). В FreeBasic для каждого пикселя может использоваться различный объем памяти: 1 байт (это означает 256 возможных цветов для каждого пикселя) или 4 байта (один байт для уровня синего, один для зеленого и один для красного; четвертый байт альфа, или уровень прозрачности).

Используя 1 байт для каждого пикселя, можно использовать 256 цветов: но какие это цвета? Вы можете определить их все, используя команду PALETTE: каждый из 256 цветов будет указывать на правильный цвет в палитре; это то, что подразумевается, когда мы говорим об индексированных цветах.

Используя 4 байта для каждого пикселя, мы можем получить 256 уровней синего, 256 уровней зеленого, 256 уровней красного, а это означает 16777216 разных цветов! Наши глаза не могут даже различить их всех ... но подождите: мы использовали три байта (24 бита), зачем нам четвертый байт (еще 8 бит, то есть 24 + 8 = 32 бита)? Поскольку процессор использует 32-битные регистры, он может работать быстрее с 32-битными порциями данных, чем с 24-битными порциями данных. Мы вернемся к этому позже.

 

Теперь давайте начнем!

Для начала нам нужно включить графический режим:

Screenres 800, 600, 32


Также нам нужен доступ к видеопамяти; для этого нам нужен указатель: компьютерная память (включая видеопамять) организована в ячейках: каждая ячейка имеет номер, называемый адресом (если вы его знаете, вы можете читать или записывать в / из определенной ячейки); указатель - это переменная, содержащая адрес.

Поскольку мы говорим о байтах, мы будем использовать (без знака) байтовый указатель:

Dim As Ubyte Ptr target


В данном случае мы создали пустую переменную target , теперь нам надо записать туда адрес начала видеопамяти. Мы можем узнать это, используя ключевое слово screenPtr:

target = Screenptr

Помните, что для доступа к ячейке памяти (в данном случае к значению пикселя) указателя мы можем использовать префикс «*»:
*target = 255


... чтобы изменить адрес (например, перейти к следующему адресу), нам нужно увеличить или уменьшить сам указатель:

target = target + 1

или

target += 1


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

Screenlock

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

Screenunlock


Полная программа

Screenres 800,600,32
Dim As Ubyte Ptr target = Screenptr
Screenlock
For i As Long = 0 To 99999
   *target = 255
   target += 1
Next i
Screenunlock
Sleep


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

Но мы хотим сделать экран полностью белым: сколько байтов нам нужно изменить?

Ну, экран (в нашем примере) имеет размер 800 * 600 пикселей, и каждый пиксель требует 4 байта (помните? Один для синего, один для зеленого, один для красного, один для уровня альфа).

Давай попробуем снова:

Screenres 800,600,32
Dim As Ubyte Ptr target = Screenptr
Screenlock
For i As Long = 0 To 800*600*4-1
   *target = 255
   target += 1
Next i
Screenunlock
Sleep

Теперь это полностью белый экран! Если мы заменим «255» на более низкое значение, мы получим более темный экран: значение 128 приведет к серому экрану, значение 64 к более темно-серому, а значение 0 приведет к черному экрану.
Но теперь давайте попробуем повозиться с отдельными цветами: для этого нам понадобится управлять байтами. Итак, давайте изменим цикл FOR, удалив «* 4», и увеличим указатель на один пиксель за раз (4 байта):

Screenres 800,600,32
Dim As Ubyte Ptr target = Screenptr
Screenlock
For i As Long = 0 To 800*600-1
   *target = 255
   target += 4
Next i
Screenunlock
Sleep


Что произошло? Экран синий!
Фактически, мы устанавливаем только первый байт каждого фрагмента (затем, увеличивая указатель на 4, мы переходим к следующему фрагменту). И первый байт фрагмента - это синий уровень.
Давайте попробуем также поиграться с другими байтами:

Screenres 800,600,32
Dim As Ubyte Ptr target = Screenptr
Screenlock
For i As Long = 0 To 800*600-1
   *target = 255    '  Blue level.
   *(target+1) = 255    ' Green level.
   *(target+2) = 255    ' Red level.
   target += 4
Next i
Screenunlock
Sleep

Мы снова получили белый экран, но давайте теперь попробуем изменить цветовые уровни!


Чтение и преобразование уровней цвета

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

Screenres 800,600,32
Dim As Ubyte Ptr target = Screenptr
Screenlock
For i As Long = 0 To 800*600-1
   *target = *target    '  Blue level.
   *(target+1) = *(target+1)   ' Green level.
   *(target+2) = *(target+2)   ' Red level.
   target += 4
Next i
Screenunlock
Sleep


Это не повлияет на изображение (каждое значение цвета просто заменяется на ... само себя!). Но попробуйте что-то вроде:

*target = (*target) / 2 ' Blue level


..после рисования чего-либо на экране: уровень синего будет понижен: изображение будет более темным и будет иметь желтоватый оттенок.

Этот пример обеспечит простой эффект затухания:

Screenres 800,600,32
Print String (7500,"#")
Dim As Ubyte Ptr target
Screenlock
For a As Long = 0 To 7
   target = Screenptr
   For i As Long = 0 To 800*600-1
      *target = (*(target)+1)/2-1         ' Blue level.
      *(target+1) = (*(target+1)+1)/2-1   ' Green level.
      *(target+2) = (*(target+2)+1)/2-1   ' Red level.
      target += 4
   Next i
   Screenunlock
   Sleep
Next a
Sleep

Попробуйте применить фильтр на изображение: вы можете удалить один цвет или установить этот цвет на максимум ...

 

Обработка пикселей вместо одного цвета

Хорошо, теперь давайте скажем, что мы не хотим возиться с отдельными цветами, а вместо этого будем обрабатывать пиксели: поскольку пиксель состоит из 4 байтов, вам придется обрабатывать блок из 4 байтов. Типы переменных Long и Unsigned Long (uLong) используют ровно 4 байта!

Итак, нам нужно использовать:

Dim As Ulong Ptr target = Screenptr


Теперь указатель позволит получить доступ к целому пиксельному фрагменту (и, если мы увеличим указатель, мы перейдем к следующему фрагменту). Сначала базовый код (который пока ничего не делает):

Screenres 800,600,32
Dim As Ulong Ptr target = Screenptr
Screenlock
For i As Long = 0 To 800*600-1
   *target = *target
   target += 1
Next i
Screenunlock
Sleep


Мы также можем использовать целые числа (без знака) для перемещения пикселей: фактически, мы можем скопировать пиксель в другой, используя *target = *(target + x)

Как мы это реализуем? Вот пример (плавная прокрутка):

Screenres 800,600,32
Print String(7500, "!")
Dim As Ulong Ptr target, source
Screenlock
For a As Long = 0 To 99
   target = Screenptr
   source = target + 1
   For i As Long = 0 To 800*600-1
      *target = *source
      If i <> 800*600-2 Then source +=1 Else source -= (800*600-1)
      target +=1
   Next i
   Screenunlock
   Sleep 10
Next a
Sleep


Экран будет прокручиваться влево. Если вместо добавления 1 к target (следующий столбец) мы добавим 800 (целая строка), то перейдем к следующей строке:

Screenres 800,600,32
Print String(7500, "!")
Dim As Ulong Ptr target, source
Screenlock
For a As Long = 0 To 99
   target = Screenptr
   source = target + 800
   For i As Long = 0 To 800*600-1
      *target = *source
      If i <> 800*600-801 Then source +=1 Else source -= (800*600-800)
      target +=1
   Next i
   Screenunlock
   Sleep 10
Next a
Sleep


Экран будет прокручиваться вверх: обратите внимание, что в обоих примерах, чтобы избежать чтения вне видеобуфера, мы должны перенести значение источника, чтобы оно оставалось меньше (screenPtr + 800 * 600).

Мы также можем прокручивать в противоположных направлениях, но мы должны идти назад (уменьшая указатель, а не увеличивая его): в противном случае мы перезапишем пиксели, которые еще не скопированы.

Конечно, движение назад означает также, что нам нужно начинать с последнего пикселя: так, ScreenPointer плюс количество видео байтов, которое равно числу пикселей, умноженному на количество байтов на пиксель (то есть 4)

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

Screenres 800,600,32
Print String(7500,"a")
Dim As Ulong Ptr target1=ScreenPtr
Dim As Ulong Ptr target2=ScreenPtr+(4*800*600)
Dim tmp As Ulong
Screenlock
For i As Long = 0 To 800*600/2 - 1
   tmp=*target1
   *target1=*target2
   *target2=tmp
   target1+=1
   target2-=1
Next i
Screenunlock
Sleep

На этом наша небольшая учебная статья завершена, теперь получайте удовольствие от быстрой графики!