Низкоуровневая графика в 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
На этом наша небольшая учебная статья завершена, теперь получайте удовольствие от быстрой графики!