반응형

로드한 이미지에 컬러키를 설정하고 투명하게 처리해 보자.

 

player.bmp

파란 배경의 BMP 파일을 준비한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <iostream>
#include "SDL.h"
 
#pragma comment(lib, "sdl2.lib")
#pragma comment(lib, "sdl2main.lib")
 
int main(int argc, char* argv[]) {
    SDL_Init(SDL_INIT_VIDEO);
    SDL_Window* window = SDL_CreateWindow("SDL Test", SDL_WINDOWPOS_UNDEFINED,
        SDL_WINDOWPOS_UNDEFINED, 640480, SDL_WINDOW_RESIZABLE);
    SDL_Renderer* renderer = SDL_CreateRenderer(window, -10);
 
    SDL_Surface* bmpSurface = SDL_LoadBMP("player.bmp");
    SDL_SetColorKey(bmpSurface, SDL_TRUE, SDL_MapRGB(bmpSurface->format, 000xFF));
    // Set the color key (transparent pixel) in a surface.
    // The color key defines a pixel value that will be treated as transparent in a blit.
    // For example, one can use this to specify that cyan pixels should be considered
    // transparent, and therefore not rendered.
    SDL_Rect destRect = { 00, bmpSurface->w, bmpSurface->h };
    SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, bmpSurface);
 
    SDL_Event event;
    bool quit = false;
 
    while (!quit) {
        while (SDL_PollEvent(&event)) {
            switch (event.type) {
            case SDL_QUIT:
                quit = true;
                break;
            case SDL_KEYDOWN:
                printf("Key pressed: %s\n", SDL_GetKeyName(event.key.keysym.sym));
                if (event.key.keysym.sym == SDLK_ESCAPE)
                    quit = true;
                break;
            default:
                break;
            }
        }
 
        SDL_RenderCopy(renderer, texture, NULL&destRect);
        SDL_RenderPresent(renderer);
    }
 
    SDL_FreeSurface(bmpSurface);
    SDL_DestroyTexture(texture);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
 
    return 0;
}
 

 

파란색(0, 0, 255)을 컬러키로 설정하는 코드를 작성하고 빌드한다.

 

파란색이 투명하게 처리되어 표시된다.

 

컬러키 설정 부분을 주석처리하고 빌드해 보자.

 

//SDL_SetColorKey(bmpSurface, SDL_TRUE, SDL_MapRGB(bmpSurface->format, 0, 0, 0xFF));

 

파란색이 그대로 표시된다.

 

반응형
Posted by J-sean
:
반응형

SDL 기본 라이브러리는 BMP 형식의 이미지만 처리할 수 있다. JPG나 PNG 형식을 처리하기 위해서는 SDL_image 라이브러리가 필요하다.

 

우선 SDL 기본 라이브러리로 BMP 이미지를 렌더링 해 보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include <iostream>
#include "SDL.h"
 
#pragma comment(lib, "sdl2.lib")
#pragma comment(lib, "sdl2main.lib")
 
int main(int argc, char* argv[]) {
    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        printf("SDL Initialization Fail: %s\n", SDL_GetError());
        return -1;
    }
 
    SDL_Window* window = SDL_CreateWindow("SDL Test", SDL_WINDOWPOS_UNDEFINED,
        SDL_WINDOWPOS_UNDEFINED, 640480, SDL_WINDOW_RESIZABLE);
 
    if (!window) {
        printf("SDL_CreateWindow Error: %s\n", SDL_GetError());
        SDL_Quit();
        return -1;
    }
 
    SDL_Renderer* renderer = SDL_CreateRenderer(window, -10);
 
    SDL_Surface* bmpSurface = SDL_LoadBMP("image.bmp");
    // Load a BMP image from a file path.
    if (bmpSurface == NULL) {
        SDL_DestroyRenderer(renderer);
        SDL_DestroyWindow(window);
        printf("SDL_LoadBMP Error: %s\n", SDL_GetError());
        SDL_Quit();
        return -1;
    }
    SDL_Rect destRect = { 00, bmpSurface->w, bmpSurface->h };
 
    SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, bmpSurface);
    // Create a texture from an existing surface.
    if (texture == NULL) {
        SDL_FreeSurface(bmpSurface);
        // Free an RGB surface.
        SDL_DestroyRenderer(renderer);
        SDL_DestroyWindow(window);
        printf("SDL_CreateTextureFromSurface Error: %s\n", SDL_GetError());
        SDL_Quit();
        return -1;
    }
    SDL_FreeSurface(bmpSurface);
 
    SDL_Event event;
    bool quit = false;
 
    while (!quit) {
        while (SDL_PollEvent(&event)) {
            switch (event.type) {
            case SDL_QUIT:
                quit = true;
                break;
            case SDL_KEYDOWN:
                printf("Key pressed: %s\n", SDL_GetKeyName(event.key.keysym.sym));
                if (event.key.keysym.sym == SDLK_ESCAPE)
                    quit = true;
                break;
            default:
                break;
            }
        }
 
        SDL_SetRenderDrawColor(renderer, 255255255, SDL_ALPHA_OPAQUE);
        SDL_RenderClear(renderer);
        SDL_RenderCopy(renderer, texture, NULL&destRect);
        // Copy a portion of the texture to the current rendering target.
        SDL_RenderPresent(renderer);
    }
 
    SDL_DestroyTexture(texture);
    // Destroy the specified texture.
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
 
    return 0;
}
 

 

코드를 작성하고 빌드한다.

 

실행하면 윈도우에 이미지가 렌더링 된다.

 

만약 그래픽 카드의 지원을 받을 수 없는 Embedded System 등에서 소프트웨어 방식의 렌더링만 사용할 수 있다면 Texture, Renderer 를 사용하지 않고 Window Surface, BMP Surface 를 사용해 직접 화면에 이미지를 출력할 수 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <iostream>
#include "SDL.h"
 
#pragma comment(lib, "sdl2.lib")
#pragma comment(lib, "sdl2main.lib")
 
int main(int argc, char* argv[]) {
    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        printf("SDL Initialization Fail: %s\n", SDL_GetError());
        return -1;
    }
 
    SDL_Window* window = SDL_CreateWindow("SDL Test", SDL_WINDOWPOS_UNDEFINED,
        SDL_WINDOWPOS_UNDEFINED, 640480, SDL_WINDOW_RESIZABLE);
 
    if (!window) {
        printf("SDL_CreateWindow Error: %s\n", SDL_GetError());
        SDL_Quit();
        return -1;
    }
 
    SDL_Surface* windowSurface = SDL_GetWindowSurface(window);
    // Get the SDL surface associated with the window.
    // A new surface will be created with the optimal format for the window,
    // if necessary. This surface will be freed when the window is destroyed.
    // Do not free this surface.
    // This surface will be invalidated if the window is resized. After resizing
    // a window this function must be called again to return a valid surface.
    // You may not combine this with 3D or the rendering API on this window.
 
    SDL_Surface* bmpSurface = SDL_LoadBMP("image.bmp");
    if (bmpSurface == NULL) {
        SDL_DestroyWindow(window);
        printf("SDL_LoadBMP Error: %s\n", SDL_GetError());
        SDL_Quit();
        return -1;
    }
 
    SDL_Event event;
    bool quit = false;
 
    while (!quit) {
        while (SDL_PollEvent(&event)) {
            switch (event.type) {
            case SDL_QUIT:
                quit = true;
                break;
            case SDL_KEYDOWN:
                printf("Key pressed: %s\n", SDL_GetKeyName(event.key.keysym.sym));
                if (event.key.keysym.sym == SDLK_ESCAPE)
                    quit = true;
                break;
            default:
                break;
            }
        }
 
        SDL_BlitSurface(bmpSurface, NULL, windowSurface, NULL);
        // Use this function to perform a fast surface copy to a destination surface.
        SDL_UpdateWindowSurface(window);
        // Copy the window surface to the screen. This is the function you use to
        // reflect any changes to the surface on the screen.
    }
 
    SDL_FreeSurface(bmpSurface);
    SDL_DestroyWindow(window);
    SDL_Quit();
 
    return 0;
}
 

 

코드를 작성하고 빌드한다.

 

윈도우에 이미지가 출력된다.

 

 

 

이번엔 SDL_image를 이용해 JPG, PNG 등의 이미지를 출력해 보자. 아래 링크에서 SDL_image를 다운받고 적당히 설치한다.

SDL Libraries

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <iostream>
#include "SDL.h"
#include "SDL_image.h"
 
#pragma comment(lib, "sdl2.lib")
#pragma comment(lib, "sdl2main.lib")
#pragma comment(lib, "sdl2_image.lib")
 
int main(int argc, char* argv[]) {
    SDL_Init(SDL_INIT_VIDEO);
    SDL_Window* window = SDL_CreateWindow("SDL Test", SDL_WINDOWPOS_UNDEFINED,
        SDL_WINDOWPOS_UNDEFINED, 640480, SDL_WINDOW_RESIZABLE);
    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    // SDL_RENDERER_SOFTWARE: the renderer is a software fallback
    // SDL_RENDERER_ACCELERATED : the renderer uses hardware acceleration
    // SDL_RENDERER_PRESENTVSYNC : present is synchronized with the refresh rate
    // SDL_RENDERER_TARGETTEXTURE : the renderer supports rendering to texture
    // Note that providing no flags gives priority to available SDL_RENDERER_ACCELERATED
    // renderers.
 
    IMG_Init(IMG_INIT_JPG | IMG_INIT_PNG);
    // Initialize SDL_image.
 
    SDL_Surface* imageSurface = IMG_Load("image.png");
    // Load an image from a filesystem path into a software surface.
    SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, imageSurface);
    SDL_Rect destRect = { 00, imageSurface->w, imageSurface->h };
 
    SDL_Event event;
    bool quit = false;
 
    while (!quit) {
        while (SDL_PollEvent(&event)) {
            switch (event.type) {
            case SDL_QUIT:
                quit = true;
                break;
            case SDL_KEYDOWN:
                printf("Key pressed: %s\n", SDL_GetKeyName(event.key.keysym.sym));
                if (event.key.keysym.sym == SDLK_ESCAPE)
                    quit = true;
                break;
            default:
                break;
            }
        }
 
        //SDL_SetRenderDrawColor(renderer, 255, 255, 255, SDL_ALPHA_OPAQUE);
        //SDL_RenderClear(renderer);
        SDL_RenderCopy(renderer, texture, NULL&destRect);
        SDL_RenderPresent(renderer);
    }
 
    SDL_DestroyTexture(texture);
    SDL_FreeSurface(imageSurface);
    IMG_Quit();
    // Deinitialize SDL_image.
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
 
    return 0;
}
 

 

코드를 입력하고 빌드한다.

 

실행하면 윈도우에 이미지가 렌더링 된다.

 

주석처리된 배경 처리 부분을 아래와 같이 해제하고 알파 채널이 있는 PNG 파일을 렌더링 해 보자.

 

SDL_SetRenderDrawColor(renderer, 0, 0, 255, SDL_ALPHA_OPAQUE);
SDL_RenderClear(renderer);

 

player.png
0.00MB

 

 

 

특별한 처리 없이도 알파 채널이 보존되어 투명한 부분이 잘 표현된다.

 

※ 참고

SDL_image API

 

반응형
Posted by J-sean
:
반응형

픽셀 이미지(JPG, PNG)를 벡터(AI) 이미지로 바꿔보자.

 

일러스트레이터를 실행하고 변환할 이미지를 불러온다.

 

snoopy.jpg
0.09MB

 

 

Tracing으로 바꾼다.

 

변환할 이미지를 선택하고 Image Trace 탭에서 Auto-Color를 선택한다. 필요하다면 다른 옵션을 조절하고 Expand 버튼을 클릭한다.

 

픽셀 이미지가 벡터 이미지로 변환된다.

 

반응형
Posted by J-sean
:
반응형

OpenGL 화면에 Pygame 이미지(스프라이트)를 렌더링 해 보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import pygame
from pygame.locals import *
 
from OpenGL.GL import *
from OpenGL.GLU import *
 
verticies = ((1-1-1), (11-1), (-11-1), (-1-1-1),
             (1-11), (111), (-1-11), (-111))
edges = ((0,1), (0,3), (0,4), (2,1), (2,3), (2,7), (6,3), (6,4),
         (6,7), (5,1), (5,4), (5,7))
 
def drawCube():
    glBegin(GL_LINES)
    for edge in edges:
        for vertex in edge:
            glVertex3fv(verticies[vertex])
    glEnd()
 
def drawImage(x, y):
    image = pygame.image.load("player.png").convert()
    image.set_colorkey(image.get_at((00)))
    image = pygame.transform.scale(image, (image.get_width()*1.5,
                                           image.get_height()*1.5))
    imageData = pygame.image.tostring(image, "RGBA"True)
    glWindowPos2d(x-image.get_width()/2, y-image.get_height()/2)
    glDrawPixels(image.get_width(), image.get_height(), GL_RGBA,
                 GL_UNSIGNED_BYTE, imageData)
 
pygame.init()
clock = pygame.time.Clock()
display = (640480)
pygame.display.set_mode(display, DOUBLEBUF | OPENGL)
 
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
 
gluPerspective(45, (display[0]/display[1]), 0.150.0)
glTranslatef(0.00.0-5)
 
run = True
while run:    
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
 
    glRotatef(1311)
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
    drawCube()
    drawImage(display[0]/2, display[1]/2)
    
    pygame.display.flip()
    clock.tick(60)
 
pygame.quit()
exit()
 

 

 

 

반응형
Posted by J-sean
:
반응형

이미지 파일을 Drag & Drop으로 열어보자.

 

WinForm에 PictureBox를 적당히 배치한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
 
namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
 
            pictureBox1.SizeMode = PictureBoxSizeMode.Zoom;
            pictureBox1.AllowDrop = true;
            // 인텔리센스에는 pictureBox1.AllowDrop가 표시되지 않는다.
            // 하지만 사용은 가능하며 인텔리센스를 사용하고 싶다면 아래처럼
            // Control로 형변환해서 해도 된다.
            // ((Control)pictureBox1).AllowDrop = true;
        }
 
        private void pictureBox1_DragOver(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                e.Effect = DragDropEffects.Copy;
            }
        }
 
        private void pictureBox1_DragDrop(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
                // 드롭된 파일들의 전체 경로명을 반환.
                foreach (string file in files)
                {
                    if (file.EndsWith("jpg"|| file.EndsWith("JPG"))
                    {
                        // JPG 파일만 디스플레이
                        pictureBox1.Image = System.Drawing.Image.FromFile(file);
                    }
                }
            }
        }
    }
}
 

 

소스를 입력하고 빌드한다.

 

프로그램을 실행하고 PictureBox 위로 JPG파일을 드래그 & 드롭한다.

 

이미지가 표시된다.

 

반응형
Posted by J-sean
:
반응형

2021.12.23 - [C#] - C# TCP/IP Image transfer - 이미지(파일) 전송 1

2021.12.24 - [C#] - C# TCP/IP Image transfer - 이미지(파일) 전송 2

 

위 두 링크에서 만든 프로그램을 개선해 클라이언트의 화면을 서버로 연속 전송, 영상으로 재생해 보자.

간단한 화면공유(Screen Share) 프로그램을 만드는 것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
 
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.IO;
 
namespace Server
{
    public partial class Form1 : Form
    {
        TcpListener listener;
        TcpClient client;
        NetworkStream networkStream;
        MemoryStream memoryStream;
        Bitmap bitmap;
        IPHostEntry ipHostEntry;
        Thread thread;
 
        string serverIP;
        int serverPort;
        byte[] data;
        byte[] dataSizeFromClient;
        int receivedDataSize;
        int expectedDataSize;
 
        public Form1()
        {
            InitializeComponent();
 
            pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
 
            serverPort = 7000;
            // 호스트 이름으로 검색되는 첫 번째 IP4 주소 확인
            string hostName = Dns.GetHostName();
            ipHostEntry = Dns.GetHostEntry(hostName);
            foreach (IPAddress address in ipHostEntry.AddressList)
            {
                if (address.AddressFamily == AddressFamily.InterNetwork)
                {
                    serverIP = address.ToString();
                    break;
                }
            }
            listBox1.Items.Add("Server IP: " + serverIP);
 
            data = new byte[1048576 * 10]; // 1MB * 10
                                           // 클라이언트로 부터 전송되는 PNG 파일은 상황에 따라 4MB를 넘기도 한다.
            dataSizeFromClient = new byte[sizeof(int)];
        }
 
        private void ThreadProc()
        {
            listener = new TcpListener(IPAddress.Any, serverPort);
            //listener = new TcpListener(IPAddress.Parse("127.0.0.1"), serverPort);
            listener.Start();
 
            client = listener.AcceptTcpClient();
            listBox1.Items.Add("Client IP: " + client.Client.RemoteEndPoint.ToString().Split(':')[0]);
            networkStream = client.GetStream();
 
            while (true)
            {
                try
                {
                    receivedDataSize = 0;
 
                    if (networkStream.CanRead)
                    {
                        // 클라이언트로 부터 받아야 할 데이터 사이즈 정보 확인.
                        networkStream.Read(dataSizeFromClient, 0, dataSizeFromClient.Length);
                        expectedDataSize = BitConverter.ToInt32(dataSizeFromClient, 0);
                        //listBox1.Items.Add("Expected data size: " + (expectedDataSize / 1024).ToString() + "KB");
 
                        // 데이터 수신.                        
                        do
                        {
                            // 클라이언트로 부터 받아야 할 데이터 사이즈 만큼만 정확히 받는다.
                            receivedDataSize += networkStream.Read(data, receivedDataSize, expectedDataSize - receivedDataSize);
                            // Reads data from the NetworkStream and stores it to a byte array.
                        } while (expectedDataSize != receivedDataSize);
                        //while (networkStream.DataAvailable);
                        // NetworkStream.DataAvailable 은 네트워크 상황에 따라 정확하지 않을 가능성이 크다.
                        
                        //listBox1.Items.Add("Data size: " + (receivedDataSize / 1024).ToString() + "KB");
                        // 클라이언트로 부터 받은 데이터 사이즈가 버퍼 사이즈(10MB) 보다 크다면 버퍼 사이즈를 늘려야 한다.
                        // 그렇지 않으면 NetworkStream.Read()에서 ArgumentOutOfRangeException 예외 발생.
                    }
 
                    //listBox1.Items.Add("Data received: " + (receivedDataSize / 1024).ToString() + "KB");
                    memoryStream = new MemoryStream(data, 0, receivedDataSize);
                    // Initializes a new non-resizable instance of the MemoryStream class
                    // based on the specified region (index) of a byte array.            
                    bitmap = new Bitmap(memoryStream);
                    pictureBox1.Image = bitmap;
                }
                catch (Exception e)
                {
                    listBox1.Items.Add(e.Message);
                    listBox1.Items.Add(e.StackTrace);
                    break;
                }
            }
 
            listener.Stop();
            client.Close();
            networkStream.Close();
            memoryStream.Close();
        }
 
 
        private void button1_Click(object sender, EventArgs e)
        {
            // 클라이언트 접속 대기를 위한 스레드 생성(스레드는 1개만 생성한다)
            if (thread == null || !thread.IsAlive)
            {
                thread = new Thread(new ThreadStart(ThreadProc));
                thread.Start();
                listBox1.Items.Add("Waiting for a client...");
            }
        }
 
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (thread != null && thread.IsAlive)
            {
                // TcpListener를 정지 시키지 않고 클라이언트 연결 대기시 프로그램을
                // 종료하려 하면 스레드가 종료되지 않아 프로그램이 종료되지 않는다.
                listener.Stop();
 
                // 클라이언트와 연결된 스레드를 종료하지 않으면 프로그램을 종료해도
                // 백그라운드에서 계속 실행된다.                
                thread.Abort();
            }
        }
    }
}
 

 

서버 코드를 입력하고 빌드한다.

Server.zip
0.00MB

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
 
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.IO;
 
namespace Client
{
    public partial class Form1 : Form
    {
        TcpClient client;
        NetworkStream networkStream;
        MemoryStream memoryStream;
        Bitmap screen;
        Thread thread;
 
        string serverIP;
        int serverPort;
        byte[] data;
        byte[] dataSizeForServer;
 
        public Form1()
        {
            InitializeComponent();
 
            pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
            textBox1.Text = "192.168.0.100";
        }
 
        private void ThreadProc()
        {
            serverIP = textBox1.Text;
            serverPort = 7000;
            try
            {
                client = new TcpClient(serverIP, serverPort);
                networkStream = client.GetStream();
                listBox1.Items.Add("Connected to: " + client.Client.RemoteEndPoint.ToString().Split(':')[0]);
            }
            catch (Exception e)
            {
                listBox1.Items.Add(e.Message);
                listBox1.Items.Add(e.StackTrace);
 
                return;
            }
 
            try
            {
                while (true)
                {
                    screen = GetScreen();
                    memoryStream = new MemoryStream();
                    screen.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png);
                    data = memoryStream.ToArray();
                    pictureBox1.Image = screen;
 
                    if (networkStream.CanWrite)
                    {
                        // 보낼 데이터 사이즈를 서버에 미리 공유
                        dataSizeForServer = BitConverter.GetBytes(data.Length);
                        networkStream.Write(dataSizeForServer, 0, dataSizeForServer.Length);
 
                        // 데이터 송신
                        networkStream.Write(data, 0, data.Length);
                        //listBox1.Items.Add("Data sent: " + (data.Length / 1024).ToString() + "KB");
                    }
 
                    // Thread.Sleep()을 삭제하면 더 부드러운 화면을 볼 수 있다.
                    // 하지만 CPU 점유율이 크게 높아진다. 적절히 조정하자.
                    Thread.Sleep(100);
                }
            }
            catch (Exception e)
            {
                listBox1.Items.Add(e.Message);
                listBox1.Items.Add(e.StackTrace);
            }
 
            client.Close();
            networkStream.Close();
            memoryStream.Close();
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            // 클라이언트 접속 대기를 위한 스레드 생성(스레드는 1개만 생성한다)
            if (thread == null || !thread.IsAlive)
            {
                thread = new Thread(new ThreadStart(ThreadProc));
                thread.Start();
                listBox1.Items.Add("Connecting to the server...");
            }
        }
 
        private Bitmap GetScreen()
        {
            Bitmap bitmap = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
            Graphics g = Graphics.FromImage(bitmap);
            g.CopyFromScreen(0000, bitmap.Size);
            g.Dispose();
 
            return bitmap;
        }
 
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (thread != null && thread.IsAlive)
            {
                // 스레드를 종료하지 않으면 백그라운드에서 계속 실행된다.
                thread.Abort();
            }
        }
    }
}
 

 

클라이언트 코드를 입력하고 빌드한다.

Client.zip
0.01MB

 

한 컴퓨터에서 서버, 클라이언트를 모두 실행한 화면

 

 

NetworkStream.DataAvailable 프로퍼티는 네트워크 상황에 따라 부정확한 경우가 많다. 그러므로 서버에서는 클라이언트로 부터 받을 데이터의 사이즈를 미리 확인하고 그 사이즈만큼 정확히 받는게 중요하다.

작은 크기의 데이터는 사이즈 정보 교환없이 한번에 송수신해도 별 문제는 없다. 하지만 이 프로그램처럼 수백 KB의 데이터를 주고 받는 경우 한번에 송수신이 되지 않을 가능성이 크다.

 

클라이언트에서는 서버로 데이터를 전송하기 전, 보낼 데이터 사이즈를 미리 공유해 서버에서 수신 준비할 수 있도록 한다.

MemoryStream으로 저장할 데이터 포맷을 ImageFormat.Jpeg로 바꾸면 ImageFormat.Png에 비해 훨씬 작은 사이즈의 데이터가 만들어지고 영상의 FPS를 향상시킬 수 있다. (PNG는 비손실 압축, JPEG는 손실 압축)

 

반응형
Posted by J-sean
:
반응형

TcpListener, TcpClient, NetworkStream, MemoryStream등을 이용해 클라이언트의 화면을 서버로 전송하는 프로그램을 만들어 보자. (이 예제는 thread를 사용하지 않기 때문에 상황에 따라 프로그램이 freeze될 수 있다)

 

서버 폼에 PictureBox, ListBlox, Button을 적당히 배치한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
 
using System.Net;
using System.Net.Sockets;
using System.IO;
 
namespace Server
{
    public partial class Form1 : Form
    {
        TcpListener listener;
        TcpClient client;
        NetworkStream networkStream;
        MemoryStream memoryStream;
        Bitmap bitmap;
        IPHostEntry ipHostEntry;
 
        string serverIP;
        int serverPort;
        byte[] data = new byte[1048576]; // 1MB
        byte[] dataSizeFromClient;
        int receivedDataSize;
        int expectedDataSize;
 
        public Form1()
        {
            InitializeComponent();
 
            pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
 
            serverPort = 7000;
            // 호스트 이름으로 검색되는 첫 번째 IP4 주소 확인
            string hostName = Dns.GetHostName();
            ipHostEntry = Dns.GetHostEntry(hostName);
            foreach (IPAddress address in ipHostEntry.AddressList)
            {
                if (address.AddressFamily == AddressFamily.InterNetwork)
                {
                    serverIP = address.ToString();
                    break;
                }
            }
            listBox1.Items.Add("Server IP: " + serverIP);
 
            listener = new TcpListener(IPAddress.Any, serverPort);
            //listener = new TcpListener(IPAddress.Parse("127.0.0.1"), serverPort);            
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            listener.Start();
 
            client = listener.AcceptTcpClient();
            listBox1.Items.Add("Client IP: " + client.Client.RemoteEndPoint.ToString().Split(':')[0]);
 
            networkStream = client.GetStream();
 
            receivedDataSize = 0;
            dataSizeFromClient = new byte[sizeof(int)];
 
            if (networkStream.CanRead)
            {
                // 클라이언트로 부터 받아야 할 데이터 사이즈 정보 확인.
                networkStream.Read(dataSizeFromClient, 0, dataSizeFromClient.Length);
                expectedDataSize = BitConverter.ToInt32(dataSizeFromClient, 0);
                listBox1.Items.Add("Expected data size: " + (expectedDataSize / 1024).ToString() + "KB");
 
                // 데이터 송신
                do
                {
                    receivedDataSize += networkStream.Read(data, receivedDataSize, data.Length - receivedDataSize);
                    // Reads data from the NetworkStream and stores it to a byte array.                    
                } while (networkStream.DataAvailable);
                // while (expectedDataSize > receivedDataSize);
            }
 
            listBox1.Items.Add("Data received: " + (receivedDataSize / 1024).ToString() + "KB");
            memoryStream = new MemoryStream(data, 0, receivedDataSize);
            // Initializes a new non-resizable instance of the MemoryStream class
            // based on the specified region (index) of a byte array.            
            bitmap = new Bitmap(memoryStream);
            pictureBox1.Image = bitmap;
 
            listener.Stop();
            client.Close();
            networkStream.Close();
            memoryStream.Close();
        }
    }
}
 

 

서버 소스를 입력하고 빌드한다.

 

클라이언트 폼에 PictureBox, ListBox, Label, TextBox, Button을 적당히 배치한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
 
using System.Net;
using System.Net.Sockets;
using System.IO;
 
namespace Client
{
    public partial class Form1 : Form
    {
        TcpClient client;
        NetworkStream networkStream;
        MemoryStream memoryStream;
        Bitmap screen;
 
        string serverIP;
        int serverPort;
        byte[] data;
        byte[] dataSizeForServer;
 
        public Form1()
        {
            InitializeComponent();
 
            pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
            textBox1.Text = "192.168.0.100";
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            memoryStream = new MemoryStream();
            screen = GetScreen();
            pictureBox1.Image = screen;
            screen.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png);
            data = memoryStream.ToArray();
 
            serverIP = textBox1.Text;
            serverPort = 7000;
            client = new TcpClient(serverIP, serverPort);
            listBox1.Items.Add("Connected to: " + client.Client.RemoteEndPoint.ToString().Split(':')[0]);
 
            networkStream = client.GetStream();
 
            if (networkStream.CanWrite)
            {
                // 보낼 데이터 사이즈를 서버에 미리 공유
                dataSizeForServer = BitConverter.GetBytes(data.Length);
                networkStream.Write(dataSizeForServer, 0, dataSizeForServer.Length);
 
                // 데이터 전송
                networkStream.Write(data, 0, data.Length);
                listBox1.Items.Add("Data sent: " + (data.Length / 1024).ToString() + "KB");
            }
 
            client.Close();
            networkStream.Close();
            memoryStream.Close();
        }
 
        private Bitmap GetScreen()
        {
            Bitmap bitmap = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
            Graphics g = Graphics.FromImage(bitmap);
            g.CopyFromScreen(0000, bitmap.Size);
            g.Dispose();
 
            return bitmap;
        }
    }
}
 

 

클라이언트 소스를 입력하고 빌드한다.

 

 

서버를 실행하면 IP주소가 표시된다.

 

클라이언트를 실행하고 서버 IP주소를 입력한다.

 

서버의 'Start Server' 버튼을 클릭하면 보안 경고가 나타난다. '액세스 허용' 버튼을 클릭한다.

 

클라이언트의 'Start Client' 버튼을 클릭한다.

 

서버 프로그램에는 접속된 클라이언트의 화면과 함께 클라이언트 IP주소, 전송될 데이터 사이즈, 실제 전송된 데이터 사이즈가 표시된다.

같은 컴퓨터에서 서버와 클라이언트를 동시에 실행했기 때문에 같은 IP주소가 표시된다.

 

 

클라이언트 프로그램에는 캡쳐한 화면과 함께 접속한 서버의 IP주소, 전송된 데이터 사이즈가 표시된다.

 

※ 참고

2021.12.24 - [C#] - C# TCP/IP Image transfer - 이미지(파일) 전송 2

2021.12.25 - [C#] - C# TCP/IP Image transfer - 이미지(파일) 전송 3

 

반응형
Posted by J-sean
:
반응형

2021.11.29 - [C#] - C# Desktop Capture and Image Display Program - 바탕화면 캡쳐 & 이미지 출력 프로그램

 

바탕화면에 이미지를 출력하는 크리스마스 프로그램 예제.

 

폼, 버튼, 링크 레이블등을 적당히 배치하고 리소스를 등록한다.

 

Images.zip
2.28MB

폼에는

FormBorderStyle - None

ShowInTaskBar - False

TopMost - True

등의 조건을 준다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
 
using System.Runtime.InteropServices;
using System.Media;
 
namespace Christmas
{
    public partial class Form1 : Form
    {
        [DllImport("User32.dll")]
        public static extern IntPtr GetDC(IntPtr hwnd);
 
        [DllImport("user32.dll")]
        public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC);
 
        // 바탕화면 refresh
        [DllImport("Shell32.dll")]
        private static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2);
 
        int screenWidth;
        int screenHeight;
 
        IntPtr desktopPtr;
 
        // 이미지 파일은 화면과 해상도가 달라서 작거나 크게 표시될 수 있다.
        // Image로 받지 말고 비트맵으로 변환해 사용하면 된다.
        Bitmap tree;                
        Bitmap leftTop;
        Bitmap rightTop;
        Bitmap rightDeer;
        Bitmap cane;
        Bitmap bulb;
        Bitmap snowMan;
        Bitmap merry;
 
        public Form1()
        {
            InitializeComponent();
 
            screenWidth = Screen.PrimaryScreen.Bounds.Width;
            screenHeight = Screen.PrimaryScreen.Bounds.Height;
 
            desktopPtr = GetDC(IntPtr.Zero);
 
            tree = new Bitmap(Properties.Resources.tree1);
            rightDeer = new Bitmap(Properties.Resources.RightDeer);
            leftTop = new Bitmap(Properties.Resources.LeftTop);
            rightTop = new Bitmap(Properties.Resources.RightTop);
            cane = new Bitmap(Properties.Resources.cane);
            bulb = new Bitmap(Properties.Resources.bulb);
            snowMan = new Bitmap(Properties.Resources.BottomSnowMan);
            merry = new Bitmap(Properties.Resources.Merry);
        }
 
        ~Form1()
        {
            ReleaseDC(IntPtr.Zero, desktopPtr);
        }
        
        private void Form1_Load(object sender, EventArgs e)
        {
            // 폼의 시작 위치를 생성자에서 설정하면 크기가 줄어드는 등 비정상적으로 동작한다.
            StartPosition = FormStartPosition.Manual;
            Location = new Point(screenWidth - Size.Width, screenHeight - Size.Height - 40);
 
            // Gets or sets a value indicating whether the form will receive key events before the event is passed
            // to the control that has focus.
            // true if the form will receive all key events; false if the currently selected control on the form
            // receives key events. The default is false.
            // 키가 눌렸을때 포커스를 갖고 있는 컨트롤보다 폼이 먼저 키 이벤트를 받을 수 있게 한다. 단축키 지정을 위해.
            KeyPreview = true;
 
            // 타이머를 작동시키면 반투명 이미지 부분이 계속 진해진다. 필요하면 작동시키자.
            //timer1.Enabled = true;
 
            SoundPlayer p = new SoundPlayer();
            p.Stream = Properties.Resources.Song;
            p.Play();
            
            using (Graphics g = Graphics.FromHdc(desktopPtr))
            {
                g.DrawImage(bulb, screenWidth - rightDeer.Width - bulb.Width, 0);
                g.DrawImage(leftTop, 00);
                g.DrawImage(rightTop, screenWidth - rightTop.Width, 0);
                g.DrawImage(rightDeer, screenWidth - rightDeer.Width, (screenHeight - rightDeer.Height) / 2);
                g.DrawImage(cane, cane.Width / 4, (screenHeight - cane.Height) / 2);                
                g.DrawImage(snowMan, 0, screenHeight - snowMan.Height);
                g.DrawImage(merry, screenWidth - merry.Width, screenHeight - merry.Height - 40);
                g.DrawImage(tree, (screenWidth - tree.Width) / 240);
            }
        }
 
        private void Form1_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.Escape:
 
                case Keys.Q:
                    Close();
                    break;
 
                case Keys.R:
                    Reload();
                    break;
 
                case Keys.H:
                    System.Diagnostics.Process.Start("https://s-engineer.tistory.com/");
                    break;
 
                default:
                    break;
            }
        }
        
        private void button1_Click(object sender, EventArgs e)
        {
            Close();
        }
        private void button2_Click(object sender, EventArgs e)
        {
            Reload();
        }
        private void Reload()
        {
            using (Graphics g = Graphics.FromHdc(desktopPtr))
            {
                g.DrawImage(bulb, screenWidth - rightDeer.Width - bulb.Width, 0);
                g.DrawImage(leftTop, 00);
                g.DrawImage(rightTop, screenWidth - rightTop.Width, 0);
                g.DrawImage(rightDeer, screenWidth - rightDeer.Width, (screenHeight - rightDeer.Height) / 2);
                g.DrawImage(cane, cane.Width / 4, (screenHeight - cane.Height) / 2);
                g.DrawImage(snowMan, 0, screenHeight - snowMan.Height);
                g.DrawImage(merry, screenWidth - merry.Width, screenHeight - merry.Height - 40);
                g.DrawImage(tree, (screenWidth - tree.Width) / 240);
            }
        }
        private void timer1_Tick(object sender, EventArgs e)
        {
            using (Graphics g = Graphics.FromHdc(desktopPtr))
            {
                g.DrawImage(bulb, screenWidth - rightDeer.Width - bulb.Width, 0);
                g.DrawImage(leftTop, 00);
                g.DrawImage(rightTop, screenWidth - rightTop.Width, 0);
                g.DrawImage(rightDeer, screenWidth - rightDeer.Width, (screenHeight - rightDeer.Height) / 2);
                g.DrawImage(cane, cane.Width / 4, (screenHeight - cane.Height) / 2);
                g.DrawImage(snowMan, 0, screenHeight - snowMan.Height);
                g.DrawImage(merry, screenWidth - merry.Width, screenHeight - merry.Height - 40);
                g.DrawImage(tree, (screenWidth - tree.Width) / 240);
            }
        }
 
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            // 바탕화면 refresh
            SHChangeNotify(0x80000000x1000IntPtr.Zero, IntPtr.Zero);
        }
 
        private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            linkLabel1.LinkVisited = true;
            System.Diagnostics.Process.Start("https://s-engineer.tistory.com/");
        }
    }
}
 

 

소스를 입력하고 빌드한다.

 

실행하면 바탕화면에 이미지가 표시된다.

 

반응형
Posted by J-sean
: