commit be818315f63545af24da0bf9ec534843a3499d46 Author: KoroLion Date: Sun Apr 26 16:08:03 2020 +0300 Initial commit diff --git a/BMPImage.pas b/BMPImage.pas new file mode 100644 index 0000000..850dc62 --- /dev/null +++ b/BMPImage.pas @@ -0,0 +1,258 @@ +unit BMPImage; + +interface + +uses classes; + +type + TColor = object + r: byte; + g: byte; + b: byte; + + procedure set_color(tr: byte; tg: byte; tb: byte); + procedure reset(); + end; + + TPxData = array of array of TColor; + + TBMPImage = object + private + width, height: integer; + + function get_padding(): integer; + public + pixel_data: array of array of TColor; + + constructor init(); + constructor init(w: integer; h: integer; c: TColor); + constructor init(px_data: TPxData); + destructor done(); + + procedure set_pixel_data(px_data: TPxData); + procedure set_width(w: integer); + procedure set_height(h: integer); + + function get_width(): integer; + function get_height(): integer; + + procedure open(path: string); + procedure save(path: string; monochrome: boolean); + end; + +implementation + +procedure TColor.reset(); +begin + r := 0; + g := 0; + b := 0; +end; + +procedure TColor.set_color(tr: byte; tg: byte; tb: byte); +begin + r := tr; + g := tg; + b := tb; +end; + +procedure TBMPImage.set_pixel_data(px_data: TPxData); +var + i, j: longint; +begin + width := length(px_data); + height := length(px_data[0]); + setLength(pixel_data, width, height); + + for i := 0 to width - 1 do begin + for j := 0 to height - 1 do begin + pixel_data[i][j] := px_data[i][j]; + end; + end; +end; + +constructor TBMPImage.init(); +begin + setLength(pixel_data, 0, 0); + width := 0; + height := 0; +end; + +constructor TBMPImage.init(w: integer; h: integer; c: TColor); +var + i, j: integer; +begin + setLength(pixel_data, w, h); + width := w; + height := h; + + for i := 0 to width - 1 do begin + for j := 0 to height - 1 do begin + pixel_data[i][j] := c; + end; + end; +end; + +constructor TBMPImage.init(px_data: TPxData); +begin + set_pixel_data(px_data); +end; + + +destructor TBMPImage.done(); +begin + setLength(pixel_data, 0, 0); // not neccesary + width := 0; + height := 0; +end; + +function TBMPImage.get_padding(): integer; +var + bytes_per_row, padding: integer; +begin + bytes_per_row := width * 3; + padding := 4 - bytes_per_row mod 4; + if padding = 4 then padding := 0; + get_padding := padding; +end; + +procedure TBMPImage.set_width(w: integer); +var + color: TColor; + i, j: integer; +begin + setLength(pixel_data, w, height); + if (w > width) then begin + for i := width to w - 1 do begin + for j := 0 to height - 1 do begin + color.set_color(255, 255, 255); + pixel_data[i][j] := color; + end; + end; + end; + width := w; +end; + +procedure TBMPImage.set_height(h: integer); +var + color: TColor; + i, j: integer; +begin + setLength(pixel_data, width, h); + if (h > height) then begin + for i := 0 to width - 1 do begin + for j := height to h - 1 do begin + color.set_color(255, 255, 255); + pixel_data[i][j] := color; + end; + end; + end; + height := h; +end; + +function TBMPImage.get_width(): integer; +begin + get_width := width; +end; + +function TBMPImage.get_height(): integer; +begin + get_height := height; +end; + +procedure TBMPImage.open(path: string); +var + instream: TFileStream; + px_data_offset, i, j, k, padding: integer; + color: TColor; +begin + instream := TFileStream.Create(path, fmOpenRead); + + instream.position := 10; + px_data_offset := instream.readword(); + + instream.position := 18; + width := instream.readword(); + instream.position := 22; + height := instream.readword(); + + setLength(pixel_data, width, height); // indexes begins with zero + + padding := get_padding(); + + instream.position := px_data_offset; + k := 1; + for i := height - 1 downto 0 do begin + for j := 0 to width - 1 do begin + color.b := instream.readbyte(); + color.g := instream.readbyte(); + color.r := instream.readbyte(); + + pixel_data[j][i] := color; + + inc(k); + end; + instream.position := instream.position + padding; + end; +end; + +procedure TBMPImage.save(path: string; monochrome: boolean); +var + ostream: TFileStream; + padding, i, j, cs: integer; + color: TColor; + buf: array[1..1000000] of byte; + k, size: longint; +begin + ostream := TFileStream.Create(path, fmCreate + fmOpenWrite); + + ostream.writeword(19778); // "BM" - format specifier + + ostream.position := 10; + ostream.WriteDWord(54); // pixel data offset is bytes (54) + ostream.WriteDWord(40); // second header size in bytes (40, 14 - first header) + ostream.WriteDWord(width); + ostream.WriteDWord(height); + ostream.position := 26; + ostream.writeword(1); // must be one + // bits per pixel + if (monochrome) then ostream.writeword(1) else ostream.writeword(24); + + padding := get_padding(); + ostream.position := 54; + + size := width * 3 + padding; + for k := 1 to size do begin // we need zero bytes to fill padding + buf[k] := 0; + end; + + if (monochrome) then begin + {color := pixel_data[j][i]; + cs := color.r + color.g + color.b; + if (cs > 425) then begin + cs := 255; + end else begin + cs := 0; + end; + ostream.writebyte(cs); + ostream.writebyte(cs); + ostream.writebyte(cs);} + end else begin + for i := height - 1 downto 0 do begin + k := 1; + for j := 0 to width - 1 do begin + color := pixel_data[j][i]; + + buf[k + 0] := color.b; + buf[k + 1] := color.g; + buf[k + 2] := color.r; + + k := k + 3; + end; + ostream.writebuffer(buf, size); + end; + end; +end; + +begin +end. diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..a9b2d4c --- /dev/null +++ b/build.sh @@ -0,0 +1 @@ +fpc gui.pas -Fu/usr/local/Cellar/fpc/3.0.4_1/lib/fpc/3.0.4/units/x86_64-darwin/fv diff --git a/generate.sh b/generate.sh new file mode 100644 index 0000000..c4116aa --- /dev/null +++ b/generate.sh @@ -0,0 +1 @@ +./main 13 -100 1800 diff --git a/gui/constants.pas b/gui/constants.pas new file mode 100644 index 0000000..54aa779 --- /dev/null +++ b/gui/constants.pas @@ -0,0 +1,8 @@ +unit constants; + +interface + const cmAbout = 1001; + const cmGenerate = 1002; + +implementation +end. diff --git a/gui/gui.pas b/gui/gui.pas new file mode 100644 index 0000000..bab439a --- /dev/null +++ b/gui/gui.pas @@ -0,0 +1,214 @@ +uses + Process, SysUtils, + App, Dialogs, Objects, Menus, Drivers, Views, MsgBox, + utils, constants; + +type + PGenerateData = ^TGenerateData; + TGenerateData = record + path: string[128]; + width: string[16]; + height: string[16]; + power: string[64]; + zoom: string[64]; + centerX: string[64]; + centerY: string[64]; + end; + + PGeneratorWindow = ^TGeneratorWindow; + TGeneratorWindow = object(TDialog) + winRect: TRect; + curY: integer; + + constructor Init; + procedure addField(caption: string; maxLength: integer); + procedure addButton(caption: string; command: integer); + end; + + TMSApp = object(TApplication) + GeneratorWindow: PGeneratorWindow; + + procedure InitStatusLine; virtual; + procedure InitMenuBar; virtual; + procedure HandleEvent(var Event: TEvent); virtual; + + procedure NewWindow(); + procedure GenerateMandelbrotSetImage(); + procedure ShowAbout(); + procedure ShowSuccess(fname: string; t: extended); + end; + +var + MSApp: TMSApp; + GenerateData: TGenerateData; + +procedure TMSApp.GenerateMandelbrotSetImage(); +var + s: AnsiString; + startTime: int64; + success: boolean; +begin + GeneratorWindow^.GetData(GenerateData); + startTime := getTimestamp(); + + with GenerateData do begin + success := RunCommand('../main', [ + width, + height, + power, + zoom, + centerX, + centerY, + path + ], s); + end; + + if (success) then begin + ShowSuccess(s, (getTimestamp() - startTime) / 1000); + end; +end; + +procedure TGeneratorWindow.addField(caption: string; maxLength: integer); +var + il: PInputLine; + r: TRect; +begin + caption := wrapFirstLetter(caption, '~'); + + r.assign(14, curY, winRect.b.x - 2, curY + 1); + il := New(PInputLine, Init(r, maxLength)); + Insert(il); + r.assign(2, curY, 13, curY + 1); + Insert(New(PLabel, Init(r, caption, il))); + + curY := curY + 2; +end; + +procedure TGeneratorWindow.addButton(caption: string; command: integer); +var + r: TRect; +begin + r.assign(3, curY, winRect.b.x - 1, curY + 1); + Insert(New(PButton, Init(r, caption, command, bfNormal))); + + curY := curY + 2; +end; + +constructor TGeneratorWindow.Init(); +var + wr: TRect; +begin + wr.assign(0, 0, 60, 19); + inherited Init(wr, 'Generator'); + Options := Options or ofCentered; + HelpCtx := $F000; + + winRect := wr; + curY := 2; + + with GenerateData do begin + path := './images'; + width := '512'; + height := '512'; + power := '2'; + zoom := '1'; + centerX := '0'; + centerY := '0'; + end; + + addField('Path:', 128); + addField('Width:', 16); + addField('Height:', 16); + addField('Power:', 64); + addField('Zoom:', 64); + addField('Center X:', 64); + addField('Center Y:', 64); + + addButton('Generate', cmGenerate); +end; + +procedure TMSApp.NewWindow(); +begin + GeneratorWindow := New(PGeneratorWindow, Init); + GeneratorWindow^.SetData(GenerateData); + InsertWindow(GeneratorWindow); +end; + +procedure TMSApp.ShowAbout(); +begin + MessageBox( + #3'Multibrot Set Generator GUI'#13 + + #3'Developed by Artem K.'#13 + + #3'MF BMSTU 2019', + nil, + mfInformation or mfOkButton + ); +end; + +procedure TMSApp.ShowSuccess(fname: string; t: extended); +begin + MessageBox( + #3'Set image was generated!'#13 + + 'File name: ' + fname + #13 + + 'Time spent: ' + FloatToStr(t) + ' s', + nil, + mfInformation or mfOkButton + ); +end; + +procedure TMSApp.HandleEvent(var Event: TEvent); +begin + inherited HandleEvent(Event); + + if Event.What = evCommand then begin + case Event.Command of + cmNew: begin + NewWindow(); + ClearEvent(Event); + end; + cmAbout: begin + ShowAbout(); + ClearEvent(Event); + end; + cmGenerate: begin + GenerateMandelbrotSetImage(); + ClearEvent(Event); + end; + end; + end; +end; + +procedure TMSApp.InitStatusLine(); +var + r: TRect; +begin + GetExtent(r); + r.a.y := r.b.y - 1; + New(StatusLine, Init(r, + NewStatusDef(0, $EFFF, + NewStatusKey('~Alt+X~ Exit', kbAltX, cmQuit, + NewStatusKey('~F3~ New', kbF3, cmNew, + nil + )), nil) + )); +end; + +procedure TMSApp.InitMenuBar(); +var + r: TRect; +begin + GetExtent(r); + r.b.y := r.a.y + 1; + MenuBar := New(PMenuBar, Init(r, NewMenu( + NewItem('~N~ew', '', kbNoKey, cmNew, hcNew, + NewItem('~A~bout', '', kbNoKey, cmAbout, hcNoContext, + NewItem('~E~xit', 'Alt+X', kbAltX, cmQuit, hcExit, + nil))))) + ); +end; + +begin + MSApp.Init(); + MSApp.Run(); + MSApp.Done(); +end. diff --git a/gui/utils.pas b/gui/utils.pas new file mode 100644 index 0000000..f5a269f --- /dev/null +++ b/gui/utils.pas @@ -0,0 +1,34 @@ +unit utils; + +interface + +uses SysUtils; + +function wrapFirstLetter(s: string; wrapper: string): string; +function getTimestamp(): int64; + + +implementation + +function wrapFirstLetter(s: string; wrapper: string): string; +var + i: integer; + ts: string; +begin + ts := ''; + for i := 2 to length(s) do begin + ts := ts + s[i]; + end; + s := wrapper + s[1] + wrapper + ts; + wrapFirstLetter := s; +end; + +function GetTimestamp(): int64; +var + ts: TTimeStamp; +begin + ts := DateTimeToTimestamp(Now()); + GetTimestamp := ts.time + ts.date * 24 * 60 * 60 * 1000; +end; + +end. diff --git a/images/generated/.gitignore b/images/generated/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/images/generated/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/images/k.bmp b/images/k.bmp new file mode 100644 index 0000000..67d7aa5 Binary files /dev/null and b/images/k.bmp differ diff --git a/main.pas b/main.pas new file mode 100644 index 0000000..760bfb8 --- /dev/null +++ b/main.pas @@ -0,0 +1,240 @@ +uses sysutils, BMPImage, ucomplex; + +type + TArgs = record + width: longint; // TryStrToInt wants longint + height: longint; + power: longint; + zoom: extended; + center_x: extended; + center_y: extended; + out_path: string; + end; + TPxData = array of array of TColor; + +const save_path = './images/generated/'; +const help_text = 'Usage: command '; + +function get_filename(base_name: string; zoom: extended; center_x: extended; center_y: extended): string; +var + z, cx, cy: string; +begin + z := FloatToStr(zoom); + cx := FloatToStr(center_x); + cy := FloatToStr(center_y); + + get_filename := base_name + '_(z=' + z + ';cx=' + cx + ';cy=' + cy + ').bmp' +end; + +type Point2D = object + x: extended; + y: extended; + + procedure update(px: extended; py: extended); +end; + +procedure Point2D.update(px: extended; py: extended); +begin + x := px; + y := py; +end; + +{function f(x: integer): integer; +begin + f := round(2 * sin(x) + 3); +end; + +function check_a(x: integer; y: integer): boolean; +begin + check_a := sqr(x - 50) + sqr(y - 50) - 6 <= 900; +end; + +function fr(x: integer; c: integer): integer; +begin + fr := sqr(x) + c; +end;} + +function fc(x: complex; c: complex; n: integer): complex; +begin + fc := complex_sum(x.power(n), c); +end; + +function translate_point(p: Point2D; width: integer; height: integer): Point2D; +var + base_zoom: integer; +begin + base_zoom := round(width * 0.8) div 2; + p.update((p.x - width / 2) / base_zoom, (p.y - height / 2) / base_zoom); + translate_point := p; +end; + +function multibrot_check(x: extended; y: extended; n: integer): integer; +// f defined as f(x) = x^2 + c, where c (complex number) is a constant +// point (x, y) is in Mandelbrot's set if f(f(...f(0))) converges, c = x + yi +var + i: integer; + v, c: complex; +begin + i := 0; + v.update(0, 0); + while (i < 255) and (v.length() <= 2) do begin + c.update(x, y); + v := fc(v, c, n); + inc(i); + end; + multibrot_check := i; +end; + +procedure setDefaultArgs(var args: TArgs); +begin + with args do begin + width := 512; + height := 512; + power := 2; + zoom := 1; + center_x := 0; + center_y := 0; + out_path := './images'; + end; +end; + +function getArgs(var args: TArgs): boolean; +var + error: boolean; +begin + error := false; + + if (ParamCount > 7) then begin + error := true; + end else begin + if (ParamCount >= 1) then begin + if (not TryStrToInt(ParamStr(1), args.width)) then error := true; + end; + if (ParamCount >= 2) then begin + if (not TryStrToInt(ParamStr(2), args.height)) then error := true; + end; + if (ParamCount >= 3) then begin + if (not TryStrToInt(ParamStr(3), args.power)) then error := true; + end; + if (ParamCount >= 4) then begin + if (not TryStrToFloat(ParamStr(4), args.zoom)) then error := true; + end; + if (ParamCount >= 5) then begin + if (not TryStrToFloat(ParamStr(5), args.center_x)) then error := true; + end; + if (ParamCount >= 6) then begin + if (not TryStrToFloat(ParamStr(6), args.center_y)) then error := true; + end; + if (ParamCount = 7) then begin + args.out_path := ParamStr(7); + end; + end; + + getArgs := not error; +end; + +function generateMultibrot( + width: integer; height: integer; + power: longint; zoom: extended; center_x: extended; center_y: extended +): TPxData; +var + color: TColor; + i, j, mb_out: integer; + p: Point2D; + palette: array[0..255] of TColor; + px_data: TPxData; +begin + setLength(px_data, width, height); + + color.reset(); + + for i := 0 to 254 do begin + palette[i].set_color(i, abs(64 - i), abs(128 - i)); + end; + palette[255].set_color(0, 0, 0); + + for i := 0 to width - 1 do begin + for j := 0 to height - 1 do begin + p.update(i, j); + p := translate_point(p, width, height); + + p.x := (p.x / zoom) + center_x; + p.y := (p.y / zoom) + center_y; + + color.reset(); + if ((abs(p.x) < 0.000001) or (abs(p.y) < 0.000001)) then begin + color.g := 255; + end else begin + mb_out := multibrot_check(p.x, p.y, power); + color := palette[mb_out]; + end; + + px_data[i][j] := color; + end; + end; + + generateMultibrot := px_data; +end; + +var + args: TArgs; + img: TBMPImage; + out_path, fname: string; + px_data: TPxData; +begin + {img.init(); + img.open('./images/k.bmp'); + + // draw sin + for i := 0 to img.get_width() - 1 do begin + color.set_color(255, 0, 0); + img.pixel_data[i][f(i)] := color; + end; + // convert color + for i := 0 to img.get_width() - 1 do begin + for j := 0 to img.get_height() - 1 do begin + color := img.pixel_data[i][j]; + new_color.r := color.b; + new_color.g := color.g; + new_color.b := color.r; + img.pixel_data[i][j] := new_color; + end; + end; + + img.set_width(400); + img.set_height(400); + img.save('./images/generated/k_generic.bmp', false); + img.done();} + + setDefaultArgs(args); + + if (getArgs(args)) then begin + px_data := generateMultibrot(args.width, args.height, args.power, args.zoom, args.center_x, args.center_y); + + img.init(px_data); + + fname := get_filename('mb' + IntToStr(args.power) + '_set', args.zoom, args.center_x, args.center_y); + out_path := args.out_path + '/' + fname; + + img.save(out_path, false); + img.done(); + + writeln(fname); + + {color.set_color(255, 255, 255); + img.init(100, 100, color); + color.set_color(0, 255, 0); + for i := 0 to img.get_width() - 1 do begin + for j := 0 to img.get_height() - 1 do begin + if (check_a(i, j)) then begin + img.pixel_data[i][j] := color; + end; + end; + end; + img.save('./images/generated/circle.bmp', false); + img.done();} + end else begin + writeln(help_text); + end; +end. + diff --git a/ucomplex.pas b/ucomplex.pas new file mode 100644 index 0000000..d0603b6 --- /dev/null +++ b/ucomplex.pas @@ -0,0 +1,71 @@ +unit ucomplex; + +interface + +type complex = object + r: extended; + i: extended; + function length(): extended; + function squared(): complex; + function mult(a: complex): complex; + function power(n: integer): complex; + procedure update(re: extended; im: extended); +end; + +function complex_sum(a: complex; b: complex): complex; + +implementation + +procedure complex.update(re: extended; im: extended); +begin + r := re; + i := im; +end; + +function complex.length(): extended; +begin + length := sqrt(r * r + i * i); +end; + +function complex.squared(): complex; +var + res: complex; +begin + res.r := sqr(r) - sqr(i); + res.i := 2 * r * i; + squared := res; +end; + +function complex.mult(a: complex): complex; +var + res: complex; +begin + res.r := r * a.r - i * a.i; + res.i := r * a.i + i * a.r; + mult := res; +end; + +function complex.power(n: integer): complex; +var + j: integer; + c, res: complex; +begin + c.update(r, i); + res.update(r, i); + for j := 1 to n - 1 do begin + res := res.mult(c); + end; + power := res; +end; + +function complex_sum(a: complex; b: complex): complex; +var + res: complex; +begin + res.r := a.r + b.r; + res.i := a.i + b.i; + complex_sum := res; +end; + +begin +end.