Основы. Чертим прямые.
Итак, наша цель познакомиться компилятором FreeBasic и написать программу построения графиков. Для начала вспомним школьный курс математики:
Представим, что нам надо нарисовать этот график в тетрадке в клетку. Здесь все просто, начало системы координат находится в нижнем левом углу, точка с наибольшими координатами в правом верхнем. При построении, графика функции Y = N*X, многократно увеличивая значение Х на 1, мы можем по вычисленным координатам построить прямую линию, которая будет чертиться слева направо и снизу вверх.
Если N=1, то при равных размерах единиц измерения по оси X и по оси Y, линия будет строиться под углом в 45 градусов. В стандартной тетради в клетку размер страницы равен обычно 20х16 сантиметров или 200х160 миллиметров. Тетрадь разлинована каждые 5 мм. Примем единицу измерения равной 1 клетке и развернем тетрадь широкой стороной к себе, и тогда максимальное значение по оси X будет равно 40, а по оси Y = 32 клеткам. При построении графика функции Y = X, мы увидим, что максимальное значение величины X не должно быть больше максимального значения оси Y, иначе наш график выйдет за пределы тетради, то есть и вычислять такие параметры в нашем случае нет никакого смысла.
X 1 2 3 4 5 6 … 32 33 34
Y 1 2 3 4 5 6 … 32 33 34
Экран современного жидкокристаллического монитора точно так же состоит из определенного количества маленьких точек (видеопикселей), по ширине и высоте. Нам только необходимо выбрать оптимальный масштаб для отображения графической информации.
Стандартные размеры экранов могут быть 800х600, 1280х720 или 1920х1080 видеопикселей, возможны и другие величины. (Будем считать, что размер пикселя вашего монитора одинаков по высоте и ширине, хотя обычно это не так). Соответственно, приняв условие, что 1 пиксель равен 1 мм, мы можем строить на экране монитора точно такие же графики функций, как и на листе «миллиметровки», изображения будут идентичны.
Но надо учитывать, что кроме оптимального разрешения для монитора (когда 1 точка изображения = 1 видеопикселю, будем считать это 100% масштабом), монитор может работать и в других разрешениях. Например, жидкокристаллический монитор с размером экрана 1920х1080 пикселей можно переключить в режим 800х600 и тогда изображение будет чуть размытым, из-за того, что монитору придется делать интерполяцию (сжатие) изображение с частичной потерей качества.
Вообще имеется два способа работы программ с изображением:
- Оконный режим. Программа выводит текстовую или графическую информацию в специально выделенное место на экране монитора. При этом используется программное обеспечение операционной системы (ОС).
- Полноэкранный режим. Программа забирает себе все ресурсы компьютера и выводит изображение на экран монитора без ОС, используя специальное программное обеспечение для 2D или 3D графики. Для ОС семейства Windows это обычно Direct X или OpenGL, для Linux OpenGL.
Мы будем рисовать наши графики в оконном режиме, как наиболее простом для освоения новичками, кроме того он позволяет размещать изображение без потери качества на интерполяцию. То есть, монитор с размером экрана 1920х1080 пикселей, может отдать часть своей площади под окно размером 800х600. И тогда для этого окна картинка размером 800х600 точек и будет иметь 100% масштаб, и отобразиться без потери качества. Этот метод тем более полезен, что мы можем вызвать наименьший размер окна, не заботясь, о том, какое разрешение монитора выставлено у пользователя. Например, мониторы уже давно не поддерживают полноэкранное разрешение 640х480 точек, но ничто нам не мешает использовать оконный режим с подобным параметром и рисовать в нем наши графики. Почему именно 640х480? Для большей совместимости. Представьте себе, что у вас дома установлен отличный монитор с разрешением 1920х1080. Вы отладили дома программу, принесли ее в школу, но есть вероятность, что школьный монитор может поддерживать только разрешение не более 800х600 точек. В этом случае ваша программа будет неработоспособной, и получить положительную оценку вы не сможете. А вот окно размером 640х480 он сможет отобразить без проблем. Правда оконный режим, гораздо медленнее полноэкранного, для игр он не оптимален, но для рисования графиков прямых и парабол, подойдет в самый раз.
В FreeBASICe этот режим вызывается ключевым словом SCREEN 12 (это номер режима графического экрана в стиле QBasic 640x480, позволяет выводить 16 цветов рисунка и 16 цветов фона).
Давайте для начала нарисуем поточечно прямую линию красного цвета идущую с верхнего левого угла в правый нижний угол нашего окна. Единицами измерения (по аналогии с миллиметровкой) у нас будут видеопиксели дисплея вашего монитора. Но есть одна особенность: в оконном режиме система координат повернута сверху вниз:
(Поэтому, когда нам понадобиться начертить линию вверх, значение переменной Y необходимо будет уменьшать от 480 до 0). Кроме того и количество пикселей на экране тоже неодинаково. А именно 640 пикселей (точек) в ширину и 480 пикселей в высоту. И соответственно, чтобы построить прямую, которая будет расти слева направо и сверху вниз, через весь экран под углом в 45 градусов, нам придется использовать поправочный коэфицент 1.333. Откуда он взялся? Это соотношение сторон нашего окна: 640 делим на 480 получаем 1.333, Итак, вот программа построения графика функции Y=X/1.333, при X меняющем значение от 0 до 639:
SCREEN 12 'графический режим640x480
CLS
Dim AS integer Colorp = 4 'цвет точки
Dim AS single X,Y
FORX = 0 TO639 STEP1 ' Линия сверху вниз
REM FOR X = 639 TO 0 STEP -1 ' Линия снизу вверх
Y=X/1.333
sleep2
PSET(X, Y), Colorp
LOCATE1, 50: print"X=";X
LOCATE2, 50: print"Y=";Y
NEXT
sleep
end
Разберем пример подробнее:
В первой строчке мы задаем окно размером 640 на 480 пикселей. CLS очищает экран и устанавливает стандартный черный цвет фона и белый для текстовой информации. Далее объявляем целочисленную переменную и присваиваем ей значение 4 (то есть цвет точек будет красный). Обратите внимание, координаты у нас целочисленные и 33 целых 53 сотых пикселя на экране быть не может. Но так как нам понадобилось вводить поправочный коэфицент, который является вещественным числом, соответственно и координаты должны быть вещественными. Вообще это неправильно, считать равнозначными величинами значение величин X и Y и расположение точек на экране монитора, но для упрощения понимания пока оставим так.
Далее, в цикле FOR NEXT мы перебираем значения X от 0 до 639, и вычисляем значение Y с учетом поправочного коэфицента:
Y=X/1.333
Чтобы ваш глаз успел заметить, как рисуется линия, вводим небольшую задержку равную 2 миллисекундам, после рисования каждой точки: sleep 2
И рисуем точки посредством ключевого слова PSET (X, Y), Colorp.
После того как цикл завершится, «усыпляем» программу оператором sleep, чтобы терминал не закрылся сразу после того как программа выполнит свою работу, а ждал, пока вы нажмете любую клавишу. В связи с тем, что изображение создается нами непосредственно в видеопамяти, в параметры PSET() и других ключевых слов предназначенных для работы с графикой, необходимо подставлять уже готовые значения.
В результате работы программы мы видим красную линию пересекающую экран с верхнего левого угла в правый нижний. Конечно же, в этом бейсике имеется возможность построить линию с помощью специальной графической команды LINE. Ради интереса, можете вставить эти три строчки перед циклом FOR TO
Dim As integer Cl = 14 'Цвет линии
LINE (0,0)-(639, 479), Cl 'линия под углом 45 градусов
sleep 2000
И понаблюдать, как вторая наша линия закрасит через 2 секунды первую. Надеюсь, вы уже сами поняли почему в нашей программе указаны координаты точек на единицу меньше, чем разрешение экрана. Ответ прост, отсчет координат ведется от нулевого значения, ноль в программировании имеет значение, не забывайте об этом.
Согласитесь, не очень удобно анализировать перевернутые графики. Давайте напишем программу, для функции Y=X, в привычной для нас системе координат, то есть проводящую линию слева направо и снизу вверх:
SCREEN 12'графический режим640x480
CLS
Dim AS integer Colorp = 4 'цвет точки
Dim AS integer ScreenY = 480
Dim As single X,Y
Dim As single X1,Y1
FOR x = 0 TO 639 STEP 1 ' цикл построения прямой
y=x ' функция
x1=x
y1=ScreenY-y
sleep 10
PSET(x1, y1), Colorp
LOCATE 1, 20: print"X=";X;" "
LOCATE2, 20: print"Y=";Y;" "
LOCATE3, 20: print "Y1=";y1;" "
NEXT
sleep
end
Как видим, нам пришлось производить дополнительные вычисления с координатой Y, так как именно она является «перевернутой». То есть для того, чтобы построить следующую точку выше предыдущей, нам надо уменьшить значение Y. В выражении Y1=ScreenY-Y, мы вычисляем необходимое нам значение, в результате чего с увеличением значения X, у нас увеличивается значение X1, но уменьшается значение Y1. Соответственно в оператор рисования точки PSET (X1, Y1), мы помещаем увеличивающееся значение X1 и уменьшающееся значение Y1. Итак, линия начерчена! Победа! Не совсем. Обратите внимание на последние значения переменных Y и Y1:
Х=639
Y=639
Y1= -159
Согласитесь, у переменной Y1 какое-то странное отрицательное значение, которое и передается в PSET (X1, Y1). Как оно получилось? 639-159 =480. Все правильно. Цикл вычисляет 640 значений X, но по оси Y размер окна равен только 480. То есть получается, что 159 расчетов были проведены совершенно напрасно, более того, непонятно, насколько корректно программа обработала эти отрицательные значения, в PSET ( ). Другими словами, не стоит в функцию Y=X передавать значения X больше максимальной высоты экрана. (То есть в нашем случае 479, а если бы размер окна или разрешение экрана было 600х800, то не более 599).
Итак, мы научились рисовать линии, анализировать полученные данные и для наглядности давайте напишем программу, которая сможет выводить оси координат X и Y, и делать на осях разметку, и рисовать линии, не допуская выхода за пределы экрана.
SCREEN 12 REM графический режим 640x480
Dim As integer Colorl = 2 REM цвет линии
Dim As integer ScreenY = 480, ScreenX = 640
Dim As integer dt=30 REM смещение осей х и у от края экрана
Dim As integer MX = 40 REM масштаб по оси X
Dim As integer MY = 40 REM масштаб по оси Y
LOCATE 2, 6: print "Y" REM указываем наименования осей
LOCATE28, 78: print "X"
LINE(dt, 0)-(dt, ScreenY), Colorl REM чертим линию по вертикали, ось Y
Dim As integer Y2 REM черточки масштаба на вертикальной оси Y
FORY2 = ScreenY-dt TO 0 STEP -MY
LINE(dt-2, Y2)-(dt+2, Y2)
'sleep 1000
'LOCATE10, 60: print "Y2=";Y2;" "
NEXT
LINE(0, ScreenY-dt)-(ScreenX, ScreenY-dt), Colorl REM горизонтальная линия, ось X
Dim AS integer X2 REM черточки масштаба на горизонтальной оси X
FORX2 = MX+dt TO ScreenX STEP MX
LINE(X2, ScreenY-dt-2)-(X2, ScreenY-dt+2)
'sleep1000
'LOCATE 10, 70: print "X2=";X2;" "
NEXT
Dim As integer Colorp = 12 REM цвет точек линии
Dim As single X,Y REM значения точек функций
Dim As single X1,Y1 REM вычисленные значения координат
FORx = 0 TOScreenY-dt STEP 1
y=x REM функция
x1=x+dt REM поправка на смещение
y1=(ScreenY-y)-dt
sleep 10 REM временная задержка 0,01 сек после каждой точки
PSET (x1, y1), Colorp REM функция «поставить точку»
LOCATE3, 60: print"X=";X
LOCATE4, 60: print"Y=";Y
LOCATE5, 60: print"X1=";X1
LOCATE6, 60: print"Y1=";Y1;" "
NEXT
sleep
end
За разметку у нас отвечают переменные MX и MY, так как значения переменных у нас равны 40, на каждом сороковом пикселе от начала осей координат, мы рисуем небольшие горизонтальные черточки для оси Y:
FORY2 = MY+dt TO 480 STEP MY
LINE(dt-2, Y2)-(dt+2, Y2)
NEXT
Для оси X вертикальные черточки:
FORX2 = MX+dt TO 640 STEP MX
LINE(X2, ScreenY-dt-2)-(X2, ScreenY-dt+2)
NEXT
Две нижние строки понадобятся только если вы захотите в замедленном режиме посмотреть как рисуется ваш график:
'sleep1000
'LOCATE10, 70: print"X2=";X2;" "
Как видим, мы получили 11 разметок по оси Y и 15 по оси X. Так же, что бы значения Y1 не смогли принять отрицательные значения, мы ограничили максимальное значение переменной X, наибольшим значением ScreenY вычтя из него поправку на смещение осей графика:
FOR x = 0 TO ScreenY-dt STEP 1
На самом деле в данной программе 1 пиксель равен 1 единице измерения, поэтому переменные MX и MY задающие расстояния между разметками на осях X и Y, пока никакого практического значения не имеют. Но они нам очень пригодятся, при рисовании нелинейных функций, например таких, как y=x*x. В переменной dt у нас хранится значение смещения осей координат от края экрана, иначе оси систем координат сольются с краями и будут плохо видны.
Изменение масштаба. Параболы
Условно считая экран монитора листком миллиметровки, мы научились чертить прямые линии, вычисляя координаты каждой точки, используя пиксел как единицу измерения. Но для построения графиков функций типа Y=X*X такой метод малопригоден из-за минимальной наглядности, так как полученная кривая просто «прилипнет» к оси Y. Все дело из-за квадратичной зависимости графика функции. То есть уже на 20 точке отложенной по оси X, мы получим 400 точку по оси Y.
Поэтому, для большей наглядности, нам надо изменить масштаб измерения. Давайте будем считать, что одна единица измерения MX на экране равна 40 видеопикселам. (помните аналогию с тетрадным листом, где единица измерения может равняться 1 миллиметру, 1 клетке, или 1 сантиметру?). Нетрудно вычислить и максимальное количество новых единиц измерения:
480-dt/40=11
Итак, чтобы перейти к новым единицам измерения, мы, в нашей предыдущей программе немного изменим цикл, ответственный за построение графика функций:
FORx = 0 TO11 STEP 1
Y=X
x1=x*MX+dt REM поправка на смещение
y1=(ScreenY-y*MY)-dt
sleep 10
PSET (x1, y1), Colorp REM функция «поставить точку»
И в результате вместо сплошной линии получим только одиннадцать точек едва заметных на экране монитора. И это правильно, 11 значений X, дало 11 значений Y.
Давайте представим этот цикл так:
FORx = 0 TO11 STEP 1/ MX
И теперь мы снова сможем любоваться сплошной линией, так как несмотря на то что циклов у на всего 12, шагов цикла (STEP) стало в 40 раз больше.
Но согласитесь, нам хотелось бы сделать программу более универсальной (работоспособной при любом значении MX) и защитить значения от выхода Y1 за пределы экрана.
Для этого мы введем новые переменные koldX и koldY, в которых хранится количество делений по оси X и Y.
koldX= (ScreenX-dt)/MX
koldY= (ScreenY-dt)/MY.
Переменная koldY выполняет пока информативную функцию, а вот koldX очень полезна – она задает количество максимально возможных итераций в цикле вычисляющей значение нашей графической функции.
FORx = 0 TOkoldX STEP1/MX
Для нашего размера окна 640 точек на 680, с размером dt=30 и MX=MY=40, мы получим значения в 11 делений по оси Y и 15 делений по оси X.
А теперь давайте создадим график нелинейной функции. Ну, например y=x*x
Но так как значение Y от X квадратичное, мы рискуем опять получить отрицательные значения Y1. Что бы раз и навсегда решить проблему с выходом координат за пределы видимой области экрана, давайте воспользуемся особенностью цикла FOR NEXT, а именно используем конструкцию
If Y1<=0 Then
'Print " Y1<=0 EXIT !!!"
Exit FOR ' Выход из цикла
Endif
Позволяющую выходить из цикла при выполнении дополнительного условия. В нашем случае, это когда график функции достигнет своего минимального значения по оси Y (то есть 0).
FORx = 0 TO11 STEP1/MX
y=x*x REM функция
x1=x*MX+dt REM поправка на смещение
y1=(ScreenY-y*MY)-dt
sleep 10 REM временная задержка 0,01 сек после каждой точки
PSET (x1, y1), Colorp REM функция «поставить точку»
If Y1<=0 Then
Print "Отрицательное чило, выход!!!"
Exit For ' Выход из цикла
Endif ' Конец проверки условия
LOCATE3, 60: print"X=";X
LOCATE4, 60: print"Y=";Y
LOCATE5, 60: print"X1=";X1
LOCATE6, 60: print"Y1=";Y1;" "
NEXT
sleep
end
Вот она наша программа в готовом виде:
Программа Graf2:
SCREEN 12 REM графический режим640x480
Dim As integer Colorl = 2 REM цвет линии
Dim As integer ScreenY = 480, ScreenX = 640
Dim As integer dt=30 REM смещение осей х и у от края экрана
Dim As integer MX = 40 REM масштаб по оси X
Dim As integer MY = 40 REM масштаб по оси Y
LOCATE 2, 6: print "Y" REM указываем наименования осей
LOCATE28, 78: print"X"
LINE(dt, 0)-(dt, ScreenY), Colorl REM чертим линию по вертикали, ось Y
Dim As integer Y2 REM черточки масштаба на вертикальной оси Y
FOR Y2 = ScreenY-dt TO0 STEP -MY
LINE(dt-2, Y2)-(dt+2, Y2)
'sleep 1000
'LOCATE10, 60: print"Y2=";Y2;" "
NEXT
LINE(0, ScreenY-dt)-(ScreenX, ScreenY-dt), Colorl REM горизонтальная линия, ось X
Dim As integer X2 REM черточки масштаба на горизонтальной оси X
FOR X2 = MX+dt TOScreenX STEPMX
LINE(X2, ScreenY-dt-2)-(X2, ScreenY-dt+2)
'sleep1000
'LOCATE10, 70: print"X2=";X2;" "
NEXT
Dim As integer Colorp = 12 REM цвет точек линии
Dim As single X,Y REM значения точек функции
Dim AS single X1,Y1 REM вычисленные значения координат на экране
Dim As integer koldX, koldY REM кол-во делений по оси X и Y
koldX= (ScreenX-dt)/MX
koldY= (ScreenY-dt)/MY
LOCATE7, 60: print"koldx=";koldX
LOCATE8, 60: print"koldY=";koldY
FORx = 0 TOkoldX STEP1/MX
Y=X
'Y=X*X ' Чтобы построить параболу, просто уберите "верхнюю" запятую
x1=x*MX+dt REM поправка на смещение и масштаб
y1=(ScreenY-y*MY)-dt
sleep 10 REM временная задержка 0,01 сек после каждой точки
PSET (x1, y1), Colorp REM функция «поставить точку»
If Y1<0 Then
Print "Y1 Отрицательное чило, выход!"
Exit For ' Выход из цикла
Endif ' Конец проверки условия
LOCATE3, 60: print"X=";X
LOCATE4, 60: print "Y=";Y
LOCATE5, 60: print "X1=";X1
LOCATE6, 60: print "Y1=";Y1;" "
NEXT
sleep
end
Данная программа способна изображать как графики функции Y=X, так и Y=X*X. Но так как мы используем только правую верхнюю сторону квадранта, то и работать можем только с положительными координатами. Согласитесь, было бы неплохо научить нашу программу работать и с отрицательными значениями оси X
Об этом я вам расскажу в следующей статье. Жду ваши комментарии, замечания и пожелания.