1

Тема: Модуль Graph

Решил сделать частичную поддержку модуля Graph.

Здесь поддерживается только 1 режим 640x480 с палитрой 256 цветов.
Поддерживается только 1 шрифт — системный шрифт KolibriOS 6x9.
Хотя у использования такого шрифта есть и минусы, но это лучше, чем ничего, к тому же, обычно предполагают, что по умолчанию должен быть шрифт 8x8, а системный 6x9 к нему ближе всего.

Процедуры InitGraph и CloseGraph есть, но они ничего не делают,
инициализация происходит при инициализации самого модуля Graph.

Пришлось реализовать некоторые функции, аналогичные функциям из модуля CRT:

  • KeyPressed

  • ReadKey

  • ReadLn

  • Delay

Из плюсов: подключать модуль CRT для них не нужно.
Но если всё-таки он понадобится одновременно
с модулем Graph(будет 2 окна: консоль и графическое), то
модуль Graph должен идти после CRT в блоке uses

uses 
  CRT, Graph;

чтобы одноимённые функции брались именно из него.
Помимо ожидания нажатия клавиатуры производится также проверка необходимости перерисовки всего окна и нажатия кнопки закрытия.
Так было проще реализовать, но, возможно, лучше было бы сделать по аналогии с консолью — с двумя потоками(хотя оно и так работоспособно).

Некоторые вещи не поддерживается, например:

  • пунктирные линии

  • штриховки

  • текстовые стили

  • WriteMode

  • AspectRatio

  • ViewPort

Зато дополнительно поддерживаются некоторые расширения TMT Pascal:

  • ClearPage

  • SetFillColor

  • Triangle

  • FillTriangle

Вообще-то существуют полезные исходники модуля Graph для FreePascal, но там лицензия другая.

У меня нет TurboPascal, чтобы что-то из примеров протестировать, но имеется FreePascal и TMT Pascal.
Однако, стоит заметить, что у них в модуле Graph кое-что не реализовано, а кое-что реализовано иначе по сравнению с TurboPascal.

Прикладываю архив с модулем Graph.7z.

И в качестве примера программа "вращающиеся шестерёнки".
Идею взял с форума, но там был только каркас 2-ух шестерёнок, а я сделал 3 шестерёнки с заливкой.
misc.php?action=pun_attachment&item=168&download=0 

Исходный код примера "вращающиеся шестерёнки"
program GearsProgram;

uses
  Graph;

type
 TGear = record
  X, Y, R, TC, TH, A: Integer;
  Color: Word;
 end;

var
  grDriver, grMode: Integer;
  Page: Integer = 0;
  Gears: array[1..3] of TGear =
  ((X     : 145;
    Y     : 240;
    R     : 60;
    TC    : 6;
    TH    : 30;
    A     : 0;
    Color : LightCyan),
   (X     : 309;
    Y     : 240;
    R     : 60;
    TC    : 6;
    TH    : 30;
    A     : 50;
    Color : Yellow),
   (X     : 473;
    Y     : 240;
    R     : 60;
    TC    : 6;
    TH    : 30;
    A     : 0;
    Color : LightMagenta));

procedure ChangePage;
begin
  SetVisualPage(Page);
  Page := Page xor 1;
  SetActivePage(Page);
  ClearDevice;
end;

procedure AnimateGear(G: TGear; Color: Integer);
var
  AC1, AC2: ArcCoordsType;
  i, Rad, F: Integer;
begin
  with G do
  begin
    SetColor(Color);
    Circle(X, Y, R div 2);
    Rad := 360 div (TC * 2);
    for i := 1 to TC * 2 + 1 do
      if Odd(i) then
      begin
        Arc(X, Y, i * Rad + A, i * Rad + A + Rad, R);
        GetArcCoords(AC1);
        if i > 1 then Line(AC1.XStart, AC1.YStart, AC2.XEnd, AC2.YEnd);
      end
      else
      begin
        Arc(X, Y, i * Rad + A + Rad div 4, i * Rad + A + Rad - Rad div 4, R + TH);
        GetArcCoords(AC2);
        Line(AC1.XEnd, AC1.YEnd, AC2.XStart, AC2.YStart);
      end;
    F := R div 3 * 2;
    SetFillStyle(1, Color - 8);
    FloodFill(X + F, Y + F, Color);
  end;
end;

procedure Animate;
var
  i: Integer;
begin
  repeat
    for i := Low(Gears) to High(Gears) do
      AnimateGear(Gears[i], Gears[i].Color);
    ChangePage;
    Delay(20);
    for i := Low(Gears) to High(Gears) do
      with Gears[i] do
      begin
        Dec(A, -(i mod 2) or 1);
        if A > 360 then
          A := 0
        else if A < 0 then
          A := 360;
      end;
  until KeyPressed;
end;

begin
  grDriver := Detect;
  InitGraph(grDriver, grMode, '');
  Animate;
  CloseGraph;
end.

Прикладываю уже скомпилированное приложение Gears.kex.

Ниже я буду выкладывать ещё некоторые другие примеры.

Post's attachments

Gears.gif, 372.38 Кб, 549 x 220
Gears.gif 372.38 Кб, 29 скачиваний с 2023-03-11 

Иконка вложений Gears.kex 2.75 Кб, 23 скачиваний с 2023-03-11 

Иконка вложений Graph.7z 8.06 Кб, 23 скачиваний с 2023-03-11 

2

Re: Модуль Graph

0CodErr пишет:

Процедуры InitGraph и CloseGraph есть, но они ничего не делают

А не должна InitGraph устанавливать размер окна? Если правильно помню, в параметрах можно было передавать как явные значения разрешения, указывая имя драйвера BGI, так и пустые переменные, и тогда драйвер вписывал в них фактические значения, установив максимально поддерживаемое разрешение. 640×480 для клиентской области по умолчанию не будет логичным?

В модуле Graph были даже процедуры, регистрирующие код драйвера, скомпонованного внутрь exe-файла. Сам пользовал. Уже не помню, как потом они меняли поведение InitGraph. Можно поизучать логику и реализовать выставление умолчательного размера окна — регистрирующие процедуры вызывались до InitGraph.

Какова вообще цель данного модуля в SDK? Портирование программ для DOS, использующих графику BGI? Или что?

3

Re: Модуль Graph

Freeman пишет:

А не должна InitGraph устанавливать размер окна?

Как написано в первом сообщении:

поддерживается только 1 режим 640x480

Поэтому другие размеры установить не получится.

Freeman пишет:

в параметрах можно было передавать как явные значения разрешения, указывая имя драйвера BGI, так и пустые переменные

Да, всё верно, именно это и происходит в исходном коде приведённого в первом сообщении примера "вращающиеся шестерёнки":

var
  grDriver, grMode: Integer;
.......
begin
  grDriver := Detect;
  InitGraph(grDriver, grMode, '');
Freeman пишет:

640×480 для клиентской области по умолчанию не будет логичным?

Да, конечно, это будет логичным. Поэтому именно так и сделано по умолчанию.
Именно разрешение 640х480 обычно определяется автоматически.

Freeman пишет:

выставление умолчательного размера окна

Как было сказано в первом сообщении:

инициализация происходит при инициализации самого модуля Graph.

Поэтому InitGraph ничего и не делает — всё инициализируется ещё до неё.

Freeman пишет:

Какова вообще цель данного модуля в SDK?

Как и у любого другого модуля, цель — это его использование.

Freeman пишет:

Портирование программ для DOS, использующих графику BGI?

Да, вполне можно что-то портировать.
Кроме того, при желании можно и что-то написать заново — программы, использующие Graph, будут работать в любой ОС, для которой существует этот модуль.
Но и это ещё не всё smile
Для нашей GUI-библиотеки будет необходим компонент Canvas, уж как минимум для элемента PaintBox.
Так вот, модуль Graph как раз уже содержит большинство графических функций для рисования:

  • Line — линий

  • Arc — дуг

  • Bar, Rectangle — прямоугольников

  • Circle — круга

  • DrawPoly — многоугольника

  • Ellipse — эллипса

  • Sector, Pie — сектора круга и эллипса

  • Triangle, FillTriangle — треугольников

  • FloodFill — заливки области

  • PutImage — вывода изображения

Нужно будет просто заменить функции SetPixel и GetPixel.
Да, я понимаю, что для Canvas не хватает ещё PolyBezier, но у меня уже есть черновой вариант этой функции для рисования кривой Безье третьей степени по нескольким точкам.

Вот пример для Windows рисования самодельной функции PolyBezier
unit Unit1;

interface

uses
  Windows, Classes, Forms;

type
  TForm1 = class(TForm)
    procedure FormPaint(Sender: TObject);
     procedure Bezier(const P1, P2, P3, P4: TPoint);
     procedure PolyBezier(const P: array of TPoint);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Bezier(const P1, P2, P3, P4: TPoint);
var
  X, Y, Steps: Integer;
  Step, T, T_2, T_3, OneMinusT, OneMinusT_2, OneMinusT_3, TMul3,
  T_2Mul3, TMul3MulOneMinusT_2, T_2Mul3MulOneMinusT: Extended;
begin
  Steps := 100;
  Step := 1 / Steps;// 0.01;
  Canvas.MoveTo(P1.X, P1.Y);
  T := 0;
  repeat
    OneMinusT := 1 - T;
    OneMinusT_2 := OneMinusT * OneMinusT;
    OneMinusT_3 := OneMinusT_2 * OneMinusT;
    T_2 := T * T;
    T_3 := T_2 * T;
    T_2Mul3 := T_2 * 3;
    TMul3 := T * 3;
    TMul3MulOneMinusT_2 := TMul3 * OneMinusT_2;
    T_2Mul3MulOneMinusT := T_2Mul3 * OneMinusT;

    X := Round(OneMinusT_3 * P1.X + TMul3MulOneMinusT_2 * P2.X +
      T_2Mul3MulOneMinusT * P3.X + T_3 * P4.X);
    Y := Round(OneMinusT_3 * P1.Y + TMul3MulOneMinusT_2 * P2.Y +
      T_2Mul3MulOneMinusT * P3.Y + T_3 * P4.Y);

    Canvas.LineTo(X, Y);
    T := T + Step;
    Dec(Steps);
  until Steps < 0;
  //until T > 1;
end;

procedure TForm1.PolyBezier(const P: array of TPoint);
var
  i, Count: Integer;
begin
  Count := Length(P);
  i := 0;
  // конец текущего участка кривой является началом следующего
  // поэтому число точек должно быть вида (3 * N + 1)
  // Убеждаемся, что количество точек соответствует условию
  if (Count - 1) mod 3 = 0 then
    repeat
      Bezier(P[i], P[i + 1], P[i + 2], P[i + 3]);
      Inc(i, 3);
    until i > Count - 4;
end;

procedure TForm1.FormPaint(Sender: TObject);
var
  Points: array[0..9] of TPoint;
begin
  // Задание точек для кривой Bezier
  points[0] := Point(Random(100), Random(100));
  points[1] := Point(Random(500), Random(700));
  points[2] := Point(Random(1000), Random(300));
  points[3] := Point(Random(150), Random(500));
  points[4] := Point(Random(160), Random(800));
  points[5] := Point(Random(200), Random(130));
  points[6] := Point(Random(250), Random(800));
  points[7] := Point(Random(260), Random(180));
  points[8] := Point(Random(1000), Random(230));
  points[9] := Point(Random(150), Random(180));

// Самодельная функция будет рисовать красным, а системная — синим
// линии будут почти полностью совпадать
  
  Canvas.Pen.color := RGB(200, 20, 20);
  //PolyBezier(Points);
  PolyBezier([Points[0], Points[1], Points[2], Points[3], Points[4],
    Points[5], Points[6], Points[7], Points[8], Points[9]]);
  
  Canvas.Pen.color := RGB(20, 20, 200);
  //Canvas.PolyBezier(Points);
  Canvas.PolyBezier([Points[0], Points[1], Points[2], Points[3], Points[4],
    Points[5], Points[6], Points[7], Points[8], Points[9]]);
  
end;

end.
Freeman пишет:

Или что?

Модуль сам по себе является неплохим тестом для графических функций, которые в будущем могут использоваться где-то ещё.
Если копнуть глубже, то графическое окно, создаваемое этим модулем, может превратиться просто в один большой PaintBox, но можно и оставить всё как есть.

Я планировал выложить ещё примеры, они в основном уже готовы, но используют такие функции как Sin, Cos, Trunc, Round — в релизе этих функций ещё нет, поэтому пример без них не скомпилируется, а копировать эти функции в каждый пример не хотелось бы.

Тем не менее, можно выложить и в текущем виде ещё до добавления нужных функций в основную ветку репозитория и релиза, пока не знаю, как лучше?

4

Re: Модуль Graph

0CodErr пишет:

Как написано в первом сообщении:

поддерживается только 1 режим 640x480

Писал наспех, был невнимателен.

0CodErr пишет:

Тем не менее, можно выложить и в текущем виде ещё до добавления нужных функций в основную ветку репозитория и релиза, пока не знаю, как лучше?

Сделать отдельную ветку.