- 전체
- 게임 일반 (make game basics)
- 모바일 기획 및 디자인
- GameMaker Studio
- Unity3D
- Cocos2D
- 3D Engine OGRE
- 3D Engine irrlicht
- copperCube
- corona SDK
- Windows Basic Game
- BaaS (Mobile Backend)
- phnegap & cordova
- ionic & anguler
- parse (backend)
- firebase (backend)
- Game Backend Server / Opt
- web assembly
- Smart Makers
- pyGame & Ren'Py
- 머드(MUD) 게임 만들기
- Xamarin(자마린)
- flutter (플루터 앱 개발)
- construct 2 / 3
- pocketbase
- RPG Maker 시리즈
- godot engine
- playmaker(unity)
- react native
[dart / flutter] Drawing Pixels With Dart : Dart 2d 그래픽 픽셀처리

Dart로 픽셀 그리기
OneLoneCoder 의 비디오에서 영감을 얻어 간단한 픽셀 그래픽으로 놀고 싶었습니다. C++나 Swift(Mac을 사용 중) 대신 Dart를 사용하고 싶었습니다 . 그리고 OLC의 정신에 따라 가능한 가장 기본적인 솔루션을 만들고 싶었습니다. 따라서 SDL을 Dart 콘솔 애플리케이션에 추가하는 것이 올바른 방법일 수 있다고 생각했습니다. 그리고 SDL에서 사용하고 싶은 것은 다채로운 픽셀을 그리는 데 필요한 몇 가지 기본 명령뿐입니다.
불행히도, 아무도 이전에 이 경로를 택하지 않았고, SDL용 네이티브 Dart 확장 기능을 만들려고 했을 때, 적어도 macOS에서는 확장 기능이 UI를 제어할 수 있는 유일한 스레드인 메인 스레드를 사용하도록 하는 방법을 알아낼 수 없어서 제대로 작동하지 않는다는 것을 알게 되었습니다. GLFW (비슷한 기술) 용 확장 기능이 있었지만 , macOS에서는 지원하지 않았습니다.
이러한 이유와 Dart VM에 대한 네이티브 확장 기능을 만드는 것이 그렇게 재밌지 않기 때문에 저는 다음과 같은 계획을 세웠습니다.
SDL 창을 열고 stdin 에서 그리기 명령을 읽고 stdin 이 닫힐 때까지 표시하는 C로 된 최소 콘솔 애플리케이션을 만들어 보는 건 어떨까요 ? 그런 다음 이 애플리케이션은 내 Dart 명령줄 애플리케이션에서 시작되고 내가 원하는 만큼의 그리기 명령이 제공됩니다.
SDL을 사용한 첫 단계
Homebrew를 통해 내 시스템에 SDL을 설치한 후 , 예제에서 파생된 다음 C 코드를 만들었습니다. IDE 지원 없이 이것을 작성하는 것은 흥미로운 경험이었지만, C 부분은 모든 텍스트 편집기에서 해당 코드를 작성할 만큼 간단할 것이라고 생각했습니다.
이 애플리케이션은 그래픽 서브시스템을 초기화하고, 화면 어딘가에 작은 창을 열고, 사용자가 창을 닫을 때까지 기다립니다. 그런 다음 올바르게 종료됩니다.
#include <stdio.h> #include "SDL2/SDL.h"SDL_Window *창;int main(int argc, char **argv) { SDL_Init(SDL_INIT_VIDEO); window = SDL_CreateWindow( "SDL 테스트", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 320, 200, 0 ); if (!window) return 1; SDL_Event e; SDL_WaitEvent(&e) 중에 { e.type == SDL_QUIT인 경우 중단됨; } SDL_DestroyWindow(창); SDL_Quit(); 0을 반환합니다 .
저는 애플리케이션을 컴파일하고 성공적으로 테스트했습니다.
$ cc -I/usr/local/include -L/usr/local/lib -lsdl2 -Wall 콘솔.c
$ ./a.out

그림 그리는 것
무언가를 그리기 위해, 나는 각 프레임에서 모든 것을 다시 만드는 대신(렌더러를 사용하여) 창 표면을 직접 사용하기로 했습니다. 이렇게 하는 것이 더 쉬웠습니다. 하지만 그 표면은 명시적으로 업데이트해야 합니다. 단일 픽셀을 그리는 것이 채우기 사각형으로 일반화될 수 있다는 것을 깨달은 후, 다음 실험을 제시하겠습니다.
... if (!window) return 1; SDL_Surface *screen = SDL_GetWindowSurface(창); (!screen)이 1을 반환하면; SDL_Rect rect = { 20, 40, 60, 80 }; Uint32 rgb = SDL_MapRGB(화면->포맷, 240, 120, 0); SDL_FillRect(화면, &rect, rgb); SDL_UpdateWindowSurface(창); SDL_Event e; ...
컴파일하고 실행해보니 작지만 아름다운 주황색 사각형이 드러났습니다.

그것을 함께 넣기
다음으로, 명령줄 인수를 사용하여 창 제목과 창 위치 및 크기를 지정하기로 했습니다. 선택적으로, 5K 모니터에서 레트로 픽셀 모양을 목표로 픽셀 크기도 지정하고 싶습니다. 돌이켜보면 SDL이 표면을 확장하도록 하는 것이 더 쉽고 효율적일 수 있습니다.
저는 다음과 같은 그래픽 명령 목록을 생각해냈습니다.
c rgb채우기 색상을 설정하다r x y w h현재 색상으로 채워진 사각형을 그립니다.u창을 업데이트하다
아래에 표시된 대로 구현했습니다 process_command(). 이 함수는 애플리케이션이 종료되면 1을 반환하고 그렇지 않으면 0을 반환합니다. main()명령줄 인수를 평가하고, SDL_Surface그릴 를 검색하고, stdinfgets() 에서 줄을 읽어 처리합니다. 이 함수는 EOF에서 반환합니다 .NULL
#include <stdio.h> #include "SDL2/SDL.h"SDL_Window *창; SDL_Surface *화면; int 픽셀z = 1;int 프로세스 명령(char *buf) { 정적 Uint32 rgb; if (!buf)가 1을 반환하면; switch (buf[0]) { case 'c': { int c; sscanf(++buf, "%d", &c); rgb = SDL_MapRGB(screen->format, (c >> 16) & 255, (c >> 8) & 255, c & 255); 0을 반환하면; } case 'r': { SDL_Rect rect; sscanf(++buf, "%d%d%d%d", &rect.x, &rect.y, &rect.w, &rect.h); rect.x *= pixelsz; rect.y *= pixelsz; rect.w *= pixelsz; rect.h *= pixelsz; SDL_FillRect(screen, &rect, rgb); 0을 반환하면; } case 'u': SDL_UpdateWindowSurface(창); 0을 반환하면; 기본값: 0을 반환하면; } }int _pos(int i) { i < 0을 반환합니다. SDL_WINDOWPOS_UNDEFINED : i; }int main(int argc, char **argv) { argc < 6이면 1을 반환합니다. if (argc >= 7) pixelz = atoi(argv[6]); SDL_Init(SDL_INIT_VIDEO); window = SDL_CreateWindow( argv[1], _pos(atoi(argv[2])), _pos(atoi(argv[3])), atoi(argv[4]) * pixelz, atoi(argv[5]) * pixelz , 0 ); if (!window)는 1을 반환합니다; screen = SDL_GetWindowSurface(창); if (!screen) return 1; SDL_Event e; SDL_WaitEvent(&e)인 경우 { e.type이 SDL_QUIT인 경우 중단됨; char buf[64]; fgets(buf, 64, stdin)이 프로세스 명령인 경우 중단됨; } SDL_DestroyWindow(창); SDL_Quit(); 0을 반환합니다 .
컴파일 후에는 이런 식으로 작동합니다.
$ ./a.out '안녕하세요 세계' -1 -1 160 100 4
c 15759360
r 20 30 40 50
u
^D

불행히도 stdin 에서 읽으면 이벤트 루프가 차단되고 창이 응답하지 않습니다. selectSDL의 이벤트 루프 내에서 파일 설명자(와 유사)를 수신하는 방법을 찾았지만 실패했습니다. 그런 다음 stdin을 비차단으로 만들고 SDL_PollEvent작동하도록 전환했지만 100% CPU를 소모했습니다.
그래서 결국 다른 스레드와 사용자 정의 SDL 이벤트를 이렇게 사용했습니다.
Uint32 명령; // 사용자 정의 SDL 이벤트 유형int command_loop(void *data) { SDL_Event e = {}; e.type = command; do { char buf[64]; fgets(buf, 64, stdin)인 경우 { e.user.data1 = strdup(buf); } else { e.user.data1 = NULL; } SDL_PushEvent(&e); } while(e.user.data1); 0을 반환합니다. }...int main() { ... if ((command = SDL_RegisterEvents(1)) == (Uint32)-1) 2를 반환합니다. if (!SDL_CreateThread(command_loop, NULL, NULL)) 2를 반환합니다. SDL_Event e; SDL_WaitEvent(&e)인 경우 { e.type이 SDL_QUIT인 경우 중단합니다. e.type이 명령인 경우 { e.user.data1의 프로세스 명령이 중단되는 경우 해제합니다. } } ... }
이제 stdin 을 닫으면 애플리케이션이 자동으로 종료 되고 응답이 유지되므로 ⌘Q로 창을 닫거나 닫기 버튼을 클릭해도 작동합니다.
마지막으로, 다트
RGB 값에 대한 추상화부터 시작해 보겠습니다. 지금은 32비트 정수만 래핑하지만 나중에 더 밝거나 어두운 색조를 유도하거나 ieHSL과 같은 다른 색상 공간을 지원하는 메서드를 추가할 수 있을 것 같습니다.
클래스 Color { 최종 int 값; const Color(this.value); const Color.rgb(int red, int green, int blue) : value = (red << 16) | (green << 8) | blue; int 빨간색을 얻습니다 => (값 >> 16) & 255; int 초록색을 얻습니다 => (값 >> 8) & 255; int 파란색을 얻습니다 => 값 & 255; }
그런 다음 Flutter 에서 사용하는 것과 같은 이름의 상수를 몇 개 만들었습니다 . 알고 있고 자주 사용하기 때문입니다. Material Design 사양에서 모든 16진수 값을 복사하는 데 5분 정도 걸렸습니다.
클래스 색상 {
정적 const 검정 = 색상(0x000000); 정적 const 흰색 =
색상(0xFFFFFF) ; 정적 const 빨강 = 색상(0xF44336); 정적 const 분홍 = 색상(0xE91E63); 정적 const 보라 = 색상(0x9C27B0) ; 정적 const 진한보라 = 색상(0x673AB7 ); 정적 const 인디고 = 색상(0x3F51B5) ; 정적 const 파랑 = 색상(0x2196F3 ); 정적 const 연한파랑 = 색상(0x03A9F4); 정적 const 청록색 = 색상(0x00BCD4); 정적 const 청록색 = 색상(0x009688 ); 정적 const 녹색 = 색상(0x4CAF50); 정적 const 연한녹색 = 색상(0x8BC34A ); 정적 const 라임 = 색상(0xCDDC39); 정적 const 노랑색 = 색상(0xFFEB3B); 정적 const 호박색 = 색상(0xFFC107); 정적 const 오렌지 = Color(0xFF9800); 정적 const 딥오렌지 = Color(0xFF5722 ); 정적 const 브라운 = Color(0x795548 ); 정적 const 회색 = Color(0x9E9E9E); 정적 const 파란색 회색 = Color(0x607D8B); 색상._(); }
나중에 점과 사각형도 추상화하고 싶지만, 지금은 더 이상 미루지 말고 Console위에서 만든 SDL 명령줄 애플리케이션(여전히 라고 함 a.out)을 래핑하고 구동하는 클래스를 구현해 보겠습니다.
클래스 콘솔 { 최종 프로세스 _프로세스; 최종 int 너비; 최종 int 높이; Console._(이 프로세스, 이 너비, 이 높이) { clear() ; void close() { _process.stdin.close(); } 색상 _color = Colors.white; 색상 색상 가져오기 => _color; 색상 설정(색상 색상) { _color = color; _process.stdin.writeln('c${color.value}'); } void clear([색상 색상 = Colors.black]) { _process.stdin.writeln('c${색상.값}'); _process.stdin.writeln('r0 0 $너비 $높이'); _process.stdin.writeln('c${_색상.값}'); } void point(int x, int y) { _process.stdin.writeln('r$x $y 1 1'); } void rect(int x, int y, int 너비, int 높이) { _process.stdin.writeln('r$x $y $너비 $높이'); } void update() { _process.stdin.writeln('u'); } 정적 Future<Console> create( int width, int height, [int scale = 1] ) 비동기 { return Console._( await Process.start( './a.out', ['Dart', '-1', '-1', '$width', '$height', '$scale'], ), 너비, 높이, ); } }
인스턴스를 만드는 유일한 방법은 Console정적 메서드 입니다 Console.create(...). 그리고 Process.start()future를 사용하기 때문에 이 메서드도 비동기적입니다.
그냥 재밌어서, 저는 또한 line()브레센햄 알고리즘을 사용하여 구현했는데, 위키피디아에서 코드를 올바르게 복사하지 못했고 제가 원했던 만큼 빨리 실수를 찾지 못했기 때문에 예상보다 오래 걸렸습니다. 원이나 삼각형(또는 다각형)을 채우는 알고리즘을 구현하는 것도 흥미로운 연습이 될 수 있습니다. 하지만 제가 딴소리를 하고 있습니다.
무작위 사각형
Console이 예제 애플리케이션을 사용해 보겠습니다 .
void main() 비동기 { 최종 r = Random(); 최종 c = await Console.create(160, 160, 8); while(참) { c.color = Color.rgb( r.nextInt(256), r.nextInt(256), r.nextInt(256), ); 최종 w = r.nextInt(40) + 10; 최종 h = r.nextInt(40) + 10; 최종 x = r.nextInt(c.너비 - w); 최종 y = r.nextInt(c.높이 - h); c.rect(x, y, w, h); c.update(); } }
Dart 소스 코드와 같은 디렉토리에 있는 C 바이너리로 애플리케이션을 실행하면 다음과 같은 a.out다채로운 창이 나타납니다.

아쉽게도 제 접근 방식이 예상대로 작동하지 않았습니다.
몇 초 후에 창은 사각형을 그리는 것을 멈춥니다. Dart 애플리케이션을 디버깅하려고 했고 예상대로 작동합니다. C 애플리케이션도 예상대로 작동합니다. 제 추측은 Dart VM이 일반적으로 단일 스레드에서 실행되기 때문에 무한 while루프로 인해 I/O 하위 시스템이 Dart VM에서 C 애플리케이션으로 더 많은 데이터를 보낼 수 없게 된다는 것입니다.
여기 내가 찾은 (다소 만족스럽지 않은) 해결 방법이 있습니다. 무한 루프를 사용하는 대신, 초당 60회 작동하는 주기적 타이머를 사용합니다. 이렇게 하면 Dart 런타임이 예상대로 작동하는 것 같습니다.
void main() 비동기 { 최종 r = Random(); 최종 c = await Console.create(160, 160, 8);타이머.주기적(기간(밀리초: 1000 ~/ 60), (_) { c.color = Color.rgb( r.nextInt(256), r.nextInt(256), r.nextInt(256), ); c.line( r.nextInt( c.너비), r.nextInt (c.높이), r.nextInt(c.너비), r.nextInt(c.높이), ); c.update(); }); }
이제 내 "엔진"은 예상대로 작동하여 작은 사각형으로 이루어진 블록형 선을 그리며 모든 명령을 C 프로세스로 전송합니다.

나만 볼 수 있는 애니메이션
주기적 타이머를 사용하기 때문에 움직이는 원을 보여주는 간단한 애니메이션을 만들 수 있습니다(Bresenham 중간점 알고리즘을 사용하여 구현했습니다). 원은 다소 복잡하기 때문에 20개 이상을 표시할 수 없으며 Dart 프로세스가 모든 것을 C 애플리케이션으로 전송하지 못합니다.
클래스 Circle { double x, y, vx, vy; int r; Color c; Circle(이것.x, 이것.y, 이것.r, 이것.vx, 이것.vy, 이것.c); 정적 const 색상 = [ 색상.빨간색, 색상.파란색, 색상.녹색, 색상. 갈색, 색상.노란색] ;void main() 비동기 { 최종 r = Random(); 최종 c = await Console.create(160, 160, 8); 최종 원 = List.generate(15, (index) { 최종 ra = r.nextInt(28) + 3; ( r.nextInt(c.width - ra * 2) + ra).toDouble(), (r.nextInt(c.height - ra * 2) + ra).toDouble(), ra, (r.nextInt(11) - 5).toDouble() / 2, (r.nextInt(11) - 5).toDouble() / 2, Circle.colors[index % 5], ); });를 반환합니다. 타이머.주기적(지속 시간(밀리초: 1000 ~/ 60), (_) { c.clear(); 원형 내의 원형에 대해 { circle.x += circle.vx; circle.y += circle.vy; circle.x < circle.r || circle.x >= c.width - circle.r인 경우 circle.vx *= -1; circle.y < circle.r || circle.y >= c.height - circle.r인 경우 circle.vy *= -1; c.color = circle.c; c.drawCircle(circle.x.toInt(), circle.y.toInt(), circle.r); } c.update(); }); }
불행히도, 아래 스크린샷은 제가 위키피디아에서 복사한 알고리즘이 작은 원을 그리는 데 약간의 문제가 있다는 것을 보여줍니다(빨간색 원이 원이라기보다는 사각형에 더 가까워 보입니다).

파일 스트림을 통한 통신에 대한 제 생각에 한계가 있는 이유를 발견하면(또는 발견하게 된다면) 이 글을 업데이트하겠습니다.
업데이트 (05.01.2019)
디버깅을 좀 한 후, 몇 초 후에 창이 그리기를 멈춘 이유를 찾았습니다. Dart 프로세스에서 C 애플리케이션의 메인 스레드로 보낸 명령을 전달하는 데 사용하는 SDL 이벤트 큐는 최대 65535개의 이벤트만 보관할 수 있습니다. 잠시 후에 재시도에 실패할지 command_loop()여부를 확인하는 새 버전은 다음과 같습니다.SDL_PostEvent
int command_loop(void *data) {
SDL_Event e = {};
e.type = command;
do {
char buf[64];
fgets(buf, 64, stdin)인 경우 {
e.user.data1 = strdup(buf);
} else {
e.user.data1 = NULL;
}
Uint32 ms = 1;
SDL_PushEvent(&e) < 0 && ms < 1 << 10인 경우 {
SDL_Delay(ms);
ms <<= 1;
}
} e.user.data1인 경우
0을 반환합니다.
}
이제 모든 것이 예상대로 작동합니다.

[출처] https://medium.com/@eibaan_54644/drawing-pixels-with-dart-a91774d9ed41

Drawing Pixels With Dart
Inspired by the videos of OneLoneCoder I wanted to play around with simple pixel graphics. Instead of using C++ or Swift (as I’m using a Mac) I wanted to use Dart. And in the spirit of OLC I wanted to create the most basic solution that could possibly work. Therefore, I figured that adding SDL to a Dart console application might be the right way to go. And all I want to use from SDL is some basic commands to for drawing colourful pixels.
Unfortunately, so it seems, nobody went this route before, and when I tried to create a native Dart extension for SDL, I found out that at least on macOS, it doesn’t really work because I couldn’t figure out how to make the extension use the main thread which is the only thread that may control the UI. There was an extension for GLFW (a similar technology) but again without support for macOS.
Because of this and because creating native extension for the Dart VM isn’t that fun, I came up with the following plan:
Why not create a minimal console application in C that opens an SDL window, reads drawing commands from stdin and displays them until stdin is closed. This application is then started by my Dart command line application and fed with as much drawing commands as I like.
First steps with SDL
After installing SDL on my system via Homebrew, I created the following C code, derived from an example. It was an interesting experience to write this without any kind of IDE support, but I figured, that the C part should be simple enough to write that code in any text editor.
The application initialises the graphics subsystem, opens a small window somewhere on the screen, and waits for the user to close the window. Then it correctly shuts down.
#include <stdio.h> #include "SDL2/SDL.h"SDL_Window *window;int main(int argc, char **argv) { SDL_Init(SDL_INIT_VIDEO); window = SDL_CreateWindow( "SDL Test", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 320, 200, 0 ); if (!window) return 1; SDL_Event e; while (SDL_WaitEvent(&e)) { if (e.type == SDL_QUIT) break; } SDL_DestroyWindow(window); SDL_Quit(); return 0; }
I compiled the application and successfully tested it:
$ cc -I/usr/local/include -L/usr/local/lib -lsdl2 -Wall console.c
$ ./a.out

Drawing Stuff
To draw something, I went for directly using the window’s surface instead of recreating everything on each frame (using renderers). It seemed easier to do this way. That surface must be explicitly updated, though. After realising, that drawing single pixels can be generalised as filling rectangles, I shall present my next experiment:
... if (!window) return 1; SDL_Surface *screen = SDL_GetWindowSurface(window); if (!screen) return 1; SDL_Rect rect = { 20, 40, 60, 80 }; Uint32 rgb = SDL_MapRGB(screen->format, 240, 120, 0); SDL_FillRect(screen, &rect, rgb); SDL_UpdateWindowSurface(window); SDL_Event e; ...
Compiling and running it revealed a small but beautiful orange rectangle.

Putting It Together
Next, I decided that I want to specify the window title and the window position and size using command line arguments. Optionally, I also want to specify the pixel size, aiming for a retro pixel look on my 5K monitor. In hindsight, it might be easier and more efficient to let SDL scale the surface.
I came up with the following list of graphics commands:
c rgbset the fill colorr x y w hdraw a filled rectangle with the current coloruupdate the window
I implemented them in process_command() shown below. The function will return 1 if the application shall quit and 0 otherwise. In main() I evaluate the command line arguments, retrieved the SDL_Surface to draw on, and read lines using fgets() from stdin to process them. This function will return NULL on EOF.
#include <stdio.h> #include "SDL2/SDL.h"SDL_Window *window; SDL_Surface *screen; int pixelsz = 1;int process_command(char *buf) { static Uint32 rgb; if (!buf) return 1; switch (buf[0]) { case 'c': { int c; sscanf(++buf, "%d", &c); rgb = SDL_MapRGB(screen->format, (c >> 16) & 255, (c >> 8) & 255, c & 255); return 0; } case 'r': { SDL_Rect rect; sscanf(++buf, "%d%d%d%d", &rect.x, &rect.y, &rect.w, &rect.h); rect.x *= pixelsz; rect.y *= pixelsz; rect.w *= pixelsz; rect.h *= pixelsz; SDL_FillRect(screen, &rect, rgb); return 0; } case 'u': SDL_UpdateWindowSurface(window); return 0; default: return 0; } }int _pos(int i) { return i < 0 ? SDL_WINDOWPOS_UNDEFINED : i; }int main(int argc, char **argv) { if (argc < 6) return 1; if (argc >= 7) pixelsz = atoi(argv[6]); SDL_Init(SDL_INIT_VIDEO); window = SDL_CreateWindow( argv[1], _pos(atoi(argv[2])), _pos(atoi(argv[3])), atoi(argv[4]) * pixelsz, atoi(argv[5]) * pixelsz, 0 ); if (!window) return 1; screen = SDL_GetWindowSurface(window); if (!screen) return 1; SDL_Event e; while (SDL_WaitEvent(&e)) { if (e.type == SDL_QUIT) break; char buf[64]; if (process_command(fgets(buf, 64, stdin))) break; } SDL_DestroyWindow(window); SDL_Quit(); return 0; }
After compiling, this kind-of works:
$ ./a.out 'Hello World' -1 -1 160 100 4
c 15759360
r 20 30 40 50
u
^D

Unfortunately, reading from stdin blocks the event loop and makes the window unresponsive. I unsuccessfully searched for some way to also listen for file descriptors (similar to select) within SDL’s event loop. Then I tried to made stdin non-blocking and switched to SDL_PollEvent which worked, but consumed 100% CPU.
Therefore, I eventually used another thread and a user defined SDL event like this:
Uint32 command; // custom SDL event typeint command_loop(void *data) { SDL_Event e = {}; e.type = command; do { char buf[64]; if (fgets(buf, 64, stdin)) { e.user.data1 = strdup(buf); } else { e.user.data1 = NULL; } SDL_PushEvent(&e); } while (e.user.data1); return 0; }...int main() { ... if ((command = SDL_RegisterEvents(1)) == (Uint32)-1) return 2; if (!SDL_CreateThread(command_loop, NULL, NULL)) return 2; SDL_Event e; while (SDL_WaitEvent(&e)) { if (e.type == SDL_QUIT) break; if (e.type == command) { if (process_command(e.user.data1)) break; free(e.user.data1); } } ... }
Now, the application automatically quits when stdin is closed and stays response so that closing the window with ⌘Q or by clicking the close button works, too.
Last But Not Least, Dart
Let’s start with an abstraction for RGB values. Right now, it only wraps a 32 bit integer, but I could imagine to later add methods to derive lighter or darker shades of colours or supports different colour spaces like i.e.HSL.
class Color { final int value; const Color(this.value); const Color.rgb(int red, int green, int blue) : value = (red << 16) | (green << 8) | blue; int get red => (value >> 16) & 255; int get green => (value >> 8) & 255; int get blue => value & 255; }
Then I created some constants with the same names as Flutter uses, only because I know and often use them. It took me five minutes or so to copy all hex values from the Material Design specification.
class Colors {
static const black = Color(0x000000);
static const white = Color(0xFFFFFF);
static const red = Color(0xF44336);
static const pink = Color(0xE91E63);
static const purple = Color(0x9C27B0);
static const deepPurple = Color(0x673AB7);
static const indigo = Color(0x3F51B5);
static const blue = Color(0x2196F3);
static const lightBlue = Color(0x03A9F4);
static const cyan = Color(0x00BCD4);
static const teal = Color(0x009688);
static const green = Color(0x4CAF50);
static const lightGreen = Color(0x8BC34A);
static const lime = Color(0xCDDC39);
static const yellow = Color(0xFFEB3B);
static const amber = Color(0xFFC107);
static const orange = Color(0xFF9800);
static const deepOrange = Color(0xFF5722);
static const brown = Color(0x795548);
static const gray = Color(0x9E9E9E);
static const blueGray = Color(0x607D8B);
Colors._();
}
Later, I also want to abstract points and rectangles, but for now, without further ado, let’s implement the Console class which wraps and drives the SDL command line application created above (still called a.out).
class Console { final Process _process; final int width; final int height; Console._(this._process, this.width, this.height) { clear(); } void close() { _process.stdin.close(); } Color _color = Colors.white; Color get color => _color; set color(Color color) { _color = color; _process.stdin.writeln('c${color.value}'); } void clear([Color color = Colors.black]) { _process.stdin.writeln('c${color.value}'); _process.stdin.writeln('r0 0 $width $height'); _process.stdin.writeln('c${_color.value}'); } void point(int x, int y) { _process.stdin.writeln('r$x $y 1 1'); } void rect(int x, int y, int width, int height) { _process.stdin.writeln('r$x $y $width $height'); } void update() { _process.stdin.writeln('u'); } static Future<Console> create( int width, int height, [int scale = 1] ) async { return Console._( await Process.start( './a.out', ['Dart', '-1', '-1', '$width', '$height', '$scale'], ), width, height, ); } }
The only way to create an instance of Console is the static method Console.create(...). And becauseProcess.start() uses futures, this method is also asynchronous.
Just for fun, I also implemented line() using Bresenham’s algorithm, which took longer than expected because I failed to correctly copy the code from Wikipedia and didn’t find the mistake as fast as I would have liked. It might be an interesting exercise to also implement an algorithm to fill circles or triangles (or polygons). But I digress.
Random Rectangles
Let’s put Console to use with this example application:
void main() async { final r = Random(); final c = await Console.create(160, 160, 8); while (true) { c.color = Color.rgb( r.nextInt(256), r.nextInt(256), r.nextInt(256), ); final w = r.nextInt(40) + 10; final h = r.nextInt(40) + 10; final x = r.nextInt(c.width - w); final y = r.nextInt(c.height - h); c.rect(x, y, w, h); c.update(); } }
Running the application with the C a.out binary in the same directory as the Dart source code will result in a colourful window:

Too, bad, that my approach doesn’t work as expected.
After a few seconds, the window stops drawing rectangles. I tried to debug the Dart application and it works as expected. The C application also works as expected. My guess is, that because the Dart VM normally runs on a single thread, the endless while loop makes it impossible for the I/O subsystem to send more data from the Dart VM to the C application.
Here is a (somewhat unsatisfying) workaround I found: Instead of using an endless loop, I’m using a periodic timer that fires 60 times a second. This way, the Dart runtime seems to work as expected.
void main() async { final r = Random(); final c = await Console.create(160, 160, 8);Timer.periodic(Duration(milliseconds: 1000 ~/ 60), (_) { c.color = Color.rgb( r.nextInt(256), r.nextInt(256), r.nextInt(256), ); c.line( r.nextInt(c.width), r.nextInt(c.height), r.nextInt(c.width), r.nextInt(c.height), ); c.update(); }); }
Now my “engine” works as expected, drawing blocky lines consisting of tiny little rectangles, sending all commands to the C process.

An Animation Only I Can See
Because I’m using a periodic timer, I can create a simple animation demonstrating moving circles (which I implemented using the Bresenham mid point algorithm). Because circles are somewhat complex, I cannot display more than, say, 20 or again the Dart process fails to send everything to the C application.
class Circle { double x, y, vx, vy; int r; Color c; Circle(this.x, this.y, this.r, this.vx, this.vy, this.c); static const colors = [ Colors.red, Colors.blue, Colors.green, Colors.brown, Colors.yellow]; }void main() async { final r = Random(); final c = await Console.create(160, 160, 8); final circles = List.generate(15, (index) { final ra = r.nextInt(28) + 3; return Circle( (r.nextInt(c.width - ra * 2) + ra).toDouble(), (r.nextInt(c.height - ra * 2) + ra).toDouble(), ra, (r.nextInt(11) - 5).toDouble() / 2, (r.nextInt(11) - 5).toDouble() / 2, Circle.colors[index % 5], ); }); Timer.periodic(Duration(milliseconds: 1000 ~/ 60), (_) { c.clear(); for (var circle in circles) { circle.x += circle.vx; circle.y += circle.vy; if (circle.x < circle.r || circle.x >= c.width - circle.r) circle.vx *= -1; if (circle.y < circle.r || circle.y >= c.height - circle.r) circle.vy *= -1; c.color = circle.c; c.drawCircle(circle.x.toInt(), circle.y.toInt(), circle.r); } c.update(); }); }
Unfortunately, the screenshot below also reveals that the algorithm I copied from Wikipedia has some problems with drawing small circles (the red one looks more like a rectangle than a circle).

When (or if) I find out why my idea of communicating via file streams have some limitations, I will update this article.
Update (05.01.2019)
After some debugging, I found the cause for why the window stopped drawing after a few seconds. The SDL event queue, I’m using to pass the commands sent from the Dart process to the C application’s main thread, can hold only up to 65535 events. Here is a new version of command_loop() which checks whether SDL_PostEvent will fail to retry after a short moment:
int command_loop(void *data) {
SDL_Event e = {};
e.type = command;
do {
char buf[64];
if (fgets(buf, 64, stdin)) {
e.user.data1 = strdup(buf);
} else {
e.user.data1 = NULL;
}
Uint32 ms = 1;
while (SDL_PushEvent(&e) < 0 && ms < 1 << 10) {
SDL_Delay(ms);
ms <<= 1;
}
} while (e.user.data1);
return 0;
}
Now, everything works as expected.

[출처] https://medium.com/@eibaan_54644/drawing-pixels-with-dart-a91774d9ed41
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
댓글 0
| 번호 | 제목 | 글쓴이 | 날짜 | 조회 수 |
|---|---|---|---|---|
| 1 |
[godot 엔진] 오픈 소스 고도 프로젝트 모음 - 고도 배우기, Godot
| 졸리운_곰 | 2024.12.16 | 338 |

