https://www.codeproject.com/Articles/58815/C-Image-PictureBox-Rotations

 

C# Image/PictureBox Rotations

How to rotate any image from the center of that image

www.codeproject.com

이 곳의 소스를 참고하였습니다! ㅎㅎ

 

주요 준비물 (button, textBox, label, pictureBox, timer)

 

실행 결과

 

아래는 전체 코드입니다.

timer1_Tick 에 주요 이미지 복사, 회전 코드가 있고 흐름을 살펴보면

 

1. 새로운 비트맵을 만들어 기존 이미지 크기와 맞추어 그릴 준비를 합니다.

2. Graphics로 비트맵을 지정해줍니다.

3. 피벗을 센터로 옮겨줍니다. (처음엔 0, 0입니다.)

4. 회전 후 다시 피벗을 원 위치 시켜줍니다.

5. 새 이미지 등록 후 기존 이미지 리소스 해제합니다.

using System;
using System.Drawing;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        private Image masterImage;

        private float centerX;
        private float centerY;

        private float currentAngle = 0;
        private float angle = 1;

        private bool timerOn;

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();
            DialogResult result = openFileDialog.ShowDialog();

            if (result == DialogResult.OK)
            {
                string filePath = openFileDialog.FileName;
                masterImage = new Bitmap(filePath);

                //미리 센터 좌표를 구해둡니다.
                centerX = masterImage.Width / 2;
                centerY = masterImage.Height / 2;

                pictureBox1.Image = (Image)masterImage.Clone(); //참조가 아닌 복사를 해줍니다.
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            if (masterImage == null)
            {
                MessageBox.Show("이미지가 먼저 등록되어야 합니다.");
                return;
            }

            if (timerOn)
            {
                timer1.Stop();
                timerOn = false;
            }
            else
            {
                float newAngle;
                //텍스트 내용이 숫자가 아닐 수도 있으니 조심해줍니다.
                if (float.TryParse(textBox1.Text, out newAngle) == false)
                {
                    newAngle = 1;
                }
                
                angle = newAngle;
                timer1.Start();
                timerOn = true;
            }
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            currentAngle += angle;

            if (currentAngle >= 360)
            {
                currentAngle -= 360;
            }

            label4.Text = string.Format("{0:F2}", currentAngle);

            Bitmap bitmap = new Bitmap(masterImage.Width, masterImage.Height);
            bitmap.SetResolution(masterImage.HorizontalResolution, masterImage.VerticalResolution);

            Graphics graphics = Graphics.FromImage(bitmap);
            graphics.TranslateTransform(centerX, centerY);      //센터로 피벗을 옮겨줍니다.
            graphics.RotateTransform(currentAngle);             //센터를 중심으로 돌려줍니다.
            graphics.TranslateTransform(-centerX, -centerY);    //다시 피벗을 원위치 시켜줍니다.
            graphics.DrawImage(masterImage, PointF.Empty);      //bitmap에 masterImage를 0,0 기준으로 그려줍니다.

            Image oldImage = pictureBox1.Image; //기존 이미지 메모리 해제를 위해 저장
            pictureBox1.Image = bitmap; //새로운 이미지로 대체해줍니다.
            
            if (oldImage != null) //기존 이미지 리소스 해제
            {
                oldImage.Dispose();
            }
        }
    }
}

 

 

C#에서 마우스 매크로 만드는 것을 차례대로 진행 해보겠습니다. (코드는 맨 마지막에 있습니다!)

만들어본 후엔 필요한 기능을 추가하셔서 사용하시면 되겠습니다. (어렵지 않아요~)

 

먼저 Visual Studio에서 Windows Form 만들기를 해줍니다.

전 .NET Framework 를 사용하였는데 .NET Core를 선택하셔도 상관 없습니다.

.NET Framework는 Windows 에서만 사용 가능하고

.NET Core는 다른 운영체제에서도 사용 가능합니다. 점차 .NET Core를 밀고 있는 추세입니다만

전 윈도우로 하니깐 그냥 프레임워크로 가겠슴돠

 

이름은 MouseMacro 입니다. ㄱㄱ

 

다른 기능 다 필요없습니다. 일단 폼 크기를 마음대로 줄여줍니다.

그리고 오른쪽 하단에 있는 속성 창에서 딱 두가지만 세팅해줍니다. (폼 클릭하고도 속성창이 안보이면 F4 누르세요)

FormBorderStyle = FixedToolWindow

Text = Mouse Macro

로 설정해주겠습니다. 훌륭한 UI 세팅으로 여기까지 따라하셨다면 벌써 70% 완성했습니다.

 

이제 F7을 눌러서 (혹은 마우스 오른쪽 클릭-> 코드 보기) 코드 보기로 갑시다.

MouseMacro를 오른쪽 클릭하면 사진처럼 새 항목을 추가할 수 있게 됩니다. 눌러줍시다.

 

클래스를 새로 만들겁니다. 이름은 HookManager로 결정하겠습니다.

Win32 API 를 사용할 것이기 때문에 코드를 복사 붙여넣기 하면 됩니다.

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace MouseMacro
{
    //C:\Windows\System32\user32.dll 참조
    public class HookManager
    {
        #region WindowsAPI_Mouse Event

        [DllImport("user32.dll")]
        static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData, int dwExtraInfo);

        //Microsoft MouseEvent Document
        //https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-mouse_event

        const uint MOUSEEVENTF_MOVE = 0x0001;      // 마우스 이동
        const uint MOUSEEVENTF_ABSOLUTE = 0x8000;   // 전역 위치
        const uint MOUSEEVENTF_LEFTDOWN = 0x0002;    // 왼쪽 마우스 버튼 눌림
        const uint MOUSEEVENTF_LEFTUP = 0x0004;      // 왼쪽 마우스 버튼 떼어짐
        const uint MOUSEEVENTF_RIGHTDOWN = 0x0008;    // 오른쪽 마우스 버튼 눌림
        const uint MOUSEEVENTF_RIGHTUP = 0x00010;      // 오른쪽 마우스 버튼 떼어짐
        const uint MOUSEEVENTF_WHEEL = 0x0800;        // 마우스 휠 (dwData 값으로 조정)

        #endregion

        #region WindowsAPI_Key Hook

        //Microsoft Hook Document
        //https://docs.microsoft.com/en-us/windows/win32/winmsg/hooks

        [DllImport("user32.dll")]
        private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc callback, IntPtr hInstance, uint threadId);

        [DllImport("user32.dll")]
        private static extern bool UnhookWindowsHookEx(IntPtr hInstance);

        [DllImport("user32.dll")]
        private static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, int wParam, IntPtr lParam);

        [DllImport("kernel32.dll")]
        private static extern IntPtr LoadLibrary(string lpFileName);

        private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
        private LowLevelKeyboardProc lowLevelKeyboardProc = HookProc;

        //WM_Keydown
        //https://wiki.winehq.org/List_Of_Windows_Messages
        const int WH_KEYBOARD_LL = 13;
        const int WM_KEYDOWN = 256;
        const int WM_KEYUP = 257;
        const int WM_SYSKEYDOWN = 260; //Alt Key Down

        private static IntPtr hookId = IntPtr.Zero;

        #endregion

        //Custom Variables
        private static Point preMousePosition;

        private static int leftControl = 1;
        private static int controlKey = 0;

        private static uint wheel = 120;
        private static uint backWheel = unchecked((uint)-120);

        public void SetHook()
        {
            IntPtr hInstance = LoadLibrary("User32");
            hookId = SetWindowsHookEx(WH_KEYBOARD_LL, lowLevelKeyboardProc, hInstance, 0);
        }

        public void UnHook()
        {
            UnhookWindowsHookEx(hookId);
        }

        public static IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam)
        {
            if (code >= 0)
            {
                if (wParam == (IntPtr)WM_KEYDOWN)
                {
                    Keys key = (Keys)Marshal.ReadInt32(lParam);

                    switch (key)
                    {
                        case Keys.NumPad0:
                            mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
                            mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
                            return (IntPtr)1;

                        case Keys.NumPad1:
                            Cursor.Position = new Point(preMousePosition.X, preMousePosition.Y);
                            return (IntPtr)1;

                        case Keys.NumPad4:
                            preMousePosition = Cursor.Position;
                            return (IntPtr)1;

                        case Keys.LControlKey:
                            controlKey |= leftControl;
                            break;

                        case Keys.Up:
                            if (controlKey > 0)
                            {
                                mouse_event(MOUSEEVENTF_WHEEL, 0, 0, wheel, 0);
                                return (IntPtr)1;
                            }
                            break;

                        case Keys.Down:
                            if (controlKey > 0)
                            {
                                mouse_event(MOUSEEVENTF_WHEEL, 0, 0, backWheel, 0);
                                return (IntPtr)1;
                            }
                            break;
                    }
                }
                else if (wParam == (IntPtr)WM_KEYUP)
                {
                    Keys key = (Keys)Marshal.ReadInt32(lParam);

                    switch (key)
                    {
                        case Keys.LControlKey:
                            controlKey &= ~leftControl;
                            break;
                    }
                }
            }

            return CallNextHookEx(hookId, code, (int)wParam, lParam);
        }
    }
}

코드가 좀 길지만 다른 곳은 보지 말고 (관심 있다면 살펴봐도 좋습니다.)

public static IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam)

이 곳만 보고 입 맛에 맞게 수정하면 됩니다.

 

로직을 설명해보면

if (code >= 0) // 무언가 키 이벤트가 발생했다.

  if (wParam == (IntPtr)WM_KEYDOWN) //ㅇㅇ 키가 눌렸다고!

    Keys key = (Keys)Marshal.ReadInt32(lParam)

 

      key == Keys.???

      이제 key가 무엇인지에 따라 동작을 해주면 됩니다.

      (참고로 Alt 키는 wParam == WM_SYSKEYDOWN)

      (Label 또는 MessageBox에 출력해서 값을 봐보는 것도 좋습니당)

     

위의 샘플 코드는 numpad4 : 현재 마우스 위치를 저장하고

                       numpad1 : 저장 된 마우스 위치로 마우스 커서 이동

                       numpad0 : 마우스 클릭 발생

 

                       ctrl + 마우스 위/아래 : ctrl + wheel을 구현한 것으로 확대, 축소 기능입니다.

 

자세한 것은 코드를 분석해보시면 되겠습니다. 친절하게 마우스와 키 값에 대한 링크도 해두었습니다!

 

이제 마무리로 HookManager를 인스턴스화 해서 사용하면 됩니다.

이벤트 두 개만 추가해봅시다. 다시 폼 뷰로 돌아와서 속성 창의 번개 모양을 클릭해줍니다.

그러면 이벤트를 등록할 수 있게 됩니다.

Load를 더블 클릭하면 코드가 자동으로 생성되는데 다시 폼뷰로 돌아와

FormClosing도 더블 클릭해줍니다.

 

이제 마지막 코드입니다. 간단해유

using System;
using System.Drawing;
using System.Windows.Forms;

namespace MouseMacro
{
    public partial class Form1 : Form
    {
        private HookManager hookManager;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //화면 가장 오른쪽에 띄우도록 합니다.
            int x = Screen.PrimaryScreen.Bounds.Width - this.Size.Width;
            int y = 0;

            this.Location = new Point(x, y);

            hookManager = new HookManager();
            hookManager.SetHook();
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            hookManager.UnHook();
        }
    }
}

로드 될 때 폼을 오른쪽 상단에 배치하고 훅을 시작합니다.

폼이 꺼질 때 훅을 해제 해주며 마무리 합니다.

여기까지 다 만드셨다면 간단한 마우스 매크로 성공입니다! 실행해서 확인해 보시고..

 

추가 기능은 HookManager의 HookProc 를 수정해서 사용해보세요~

1. int, float 등은 숫자 입력이지만

    ? (물음표) 키워드를 이용해서 명시적으로 null 값을 줄 수 있습니다.

 

2. ?연산자로 null값이면 실행되지 않도록 조건문 대신 사용할 수 있습니다.

 

3. ?? 두개를 통해 null 값이면 다른 값으로 설정합니다.

 

코드가 짧아지는 장점이 있습니다.

하지만 무분별하게 사용하게 되면 가독성을 헤치게 됩니다.

using System;
using System.Collections;

namespace ConsoleApp
{
    class Program
    {
        public static void Main(string[] args)
        {
            int? a = null;
            Console.WriteLine("a의 값 {0}", a);
            float? b = null;
            Console.WriteLine("b의 값 {0}", b);


            ArrayList arraylist = null;
            arraylist?.Add(30); //arraylist가 null이 아니면 Add(30)
            /*
            if (arraylist == null)
            {
                arraylist.Add(30);
            }
            */

            Console.WriteLine(arraylist?[0]); //null 이지만 오류가 나지 않습니다.

            a = a ?? 20; //a가 null이면 20
            b = b ?? 30.9f; //b가 null 이면 30.9f
            Console.WriteLine("a = {0}, b = {1}", a, b);
        }
    }
}

 

[실행 결과] null은 공백으로 출력됩니다.

 

1. 바이너리 파일로 저장하고 읽어오는 코드입니다.

 여러 타입을 저장할 수 있는데 읽어올 때 반드시 저장된 순서대로 읽어줘야 됩니다.

 (문자열은 앞에 문자열의 길이 + 문자열이 저장되어 해당 길이만큼 읽어옵니다.)

using System;
using System.IO; //...Stream

namespace ConsoleApp
{
    class Program
    {
        public static void Main(string[] args)
        {
            //바이너리 파일 생성 및 저장
            FileStream fs = new FileStream("Test.bin", FileMode.Create);
            BinaryWriter bw = new BinaryWriter(fs);

            string input1 = Console.ReadLine();
            int input2 = Convert.ToInt32(Console.ReadLine());
            float input3 = Convert.ToSingle(Console.ReadLine());

            bw.Write(input1); //string
            bw.Write(input2); //int
            bw.Write(input3); //float

            bw.Close();
            fs.Close();

            //바이너리 파일 읽어오기
            FileStream fs2 = new FileStream("Test.bin", FileMode.Open);
            BinaryReader br = new BinaryReader(fs2);

            Console.WriteLine(br.ReadString()); //string
            Console.WriteLine(br.ReadInt32()); //int
            Console.WriteLine(br.ReadSingle()); //float

            br.Close();
            fs2.Close();
        }
    }
}

 

입력을 받고 읽어온 모습
저장된 데이터

 

2. StreamReader/StreamWriter 를 이용한 모습입니다. 문자열 그대로 저장이 됩니다.

using System;
using System.IO; //...Stream

namespace ConsoleApp
{
    class Program
    {
        public static void Main(string[] args)
        {
            StreamWriter sw = new StreamWriter("Test.dat");
            
            for (int i = 0; i < 5; ++i) //5개의 입력
            {
                sw.WriteLine(Console.ReadLine());
            }

            sw.Close();

            StreamReader sr = new StreamReader("Test.dat");

            while (!sr.EndOfStream) //sr.ReadLine() == null인 경우입니다.
            {
                Console.WriteLine(sr.ReadLine());
            }

            sr.Close();
        }
    }
}

5개의 입력을 받고 다시 출력해준 모습
저장된 데이터

 

보안적인 측면은 바이너리 코드가 읽기가 어려워지기 때문에 더 좋습니다.

아니면 암호화나 해시를 하는 방법을!

부모 클래스 또는 인터페이스를 이용하여 하위 요소를 생성 및 사용할 때 이용되는 패턴입니다.

 

using System;

namespace ConsoleApp
{
    class Program
    {
        public static void Main(string[] args)
        {
            int size = 3;
            int player = 0;
            Unit[] unit = new Unit[size];

            while (player < size)
            {
                Console.WriteLine("{0}p 종족을 입력하세요: (Zerg, Protoss, Terran)", player + 1);
                string race = Console.ReadLine();

                switch (race)
                {
                    case "Zerg":
                        unit[player] = new Zerg();
                        player++;
                        break;
                    case "Protoss":
                        unit[player] = new Protoss();
                        player++;
                        break;
                    case "Terran":
                        unit[player] = new Terran();
                        player++;
                        break;
                    default:
                        break;
                }
            }

            Console.WriteLine();
            foreach (Unit u in unit)
            {
                u.ShowType();
            }
        }
    }

    abstract class Unit
    {
        public abstract void ShowType();
    }

    class Zerg : Unit
    {
        public override void ShowType()
        {
            Console.WriteLine("Zerg");
        }
    }

    class Protoss : Unit
    {
        public override void ShowType()
        {
            Console.WriteLine("Protoss");
        }
    }

    class Terran : Unit
    {
        public override void ShowType()
        {
            Console.WriteLine("Terran");
        }
    }
}

실행 결과

 

단 하나의 인스턴스를 통해 해당 클래스에 접근 및 사용할 수 있습니다.

다른 객체들간의 통신 등에도 유용하게 쓸 수 있는 방법!

 

using System;

namespace ConsoleApp
{
    class Program
    {
        public static void Main(string[] args)
        {
            GameManager.GetInstance().PrintHello();
        }
    }

    class GameManager
    {
        private static GameManager instance = new GameManager();
        public static GameManager GetInstance ()
        {
            return instance;
        }

        private GameManager()
        {
            //private로 외부에서 생성을 못하게 막아줍니다.
        }

        public void PrintHello()
        {
            Console.WriteLine("Hello");
        }
    }
}

등록 된 관측자들에게 메시지를 전달해주는 패턴. (EventListener 등과 같이 알게 모르게 많이 쓰이고 있습니다.)

인터페이스보다 델리게이트 사용이 더 간단한 듯합니다.

 

using System;

namespace ConsoleApp
{
    class Program
    {
        public static void Main (string[] args)
        {
            Teacher t = new Teacher();
            Observer_Student marine = new Observer_Student("마린");
            Observer_Student zealot = new Observer_Student("질럿");
            Observer_Student zergling = new Observer_Student("저글링");

            t.AddStudent(marine);
            t.AddStudent(zealot);
            t.AddStudent(zergling);

            Console.WriteLine();
            t.ClassStart("달리기");

            Console.WriteLine();
            t.RemoveStudent(marine);

            Console.WriteLine();
            t.ClassStart("근접 공격");
            
            Console.ReadKey();
        }
    }

    class Teacher
    {
        private delegate void Teach(string subject);
        private Teach teach;
        
        public void AddStudent (Observer_Student student)
        {
            Console.WriteLine("{0}이 등록됩니다.", student.name);
            teach += student.Learn;
        }

        public void RemoveStudent (Observer_Student student)
        {
            Console.WriteLine("{0}이 등록 해제됩니다.", student.name);
            teach -= student.Learn;
        }

        public void ClassStart (string subject)
        {
            teach(subject);
        }
    }

    class Observer_Student
    {
        public string name;
     
        public Observer_Student(string name)
        {
            this.name = name;
        }

        public void Learn (string subject)
        {
            Console.WriteLine("{0}은 {1} 수업을 받고 있습니다.", name, subject);
        }
    }
}

 

실행 결과

 

특정한 동작에 대해 클래스 내부에 대한 수정이 아니라 클래스 외부에서 수정하도록 설계 해둔 패턴!

인터페이스 또는 델리게이트를 사용하여 구현할 수 있겠습니다.

 

using System;

namespace ConsoleApp
{
    class Program
    {
        public static void Main (string[] args)
        {
            Unit marine = new Unit();
            Unit zerling = new Unit();
            Unit medic = new Unit();

            marine.SetAttack(new Marine());
            zerling.SetAttack(new Zergling());
            medic.SetHeal(new Medic());

            marine.Attack();
            marine.Heal();

            zerling.Attack();
            zerling.Heal();
            
            medic.Attack();
            medic.Heal();
        }
    }

    class Unit
    {
        private IAttack attack = null;
        private IHeal heal = null;

        public void Attack ()
        {
            if (attack != null)
            {
                attack.Attack();
            }
        }

        public void Heal ()
        {
            if (heal != null)
            {
                heal.Heal();
            }
        }

        public void SetAttack (IAttack attack)
        {
            this.attack = attack;
        }

        public void SetHeal (IHeal heal)
        {
            this.heal = heal;
        }
    }

    interface IAttack
    {
        void Attack();
    }

    interface IHeal
    {
        void Heal();
    }

    class Marine : IAttack
    {
        public void Attack ()
        {
            Console.WriteLine("가우스 라이플 공격");
        }
    }

    class Zergling : IAttack
    {
        public void Attack()
        {
            Console.WriteLine("발톱 공격");
        }
    }

    class Medic : IHeal
    {
        public void Heal ()
        {
            Console.WriteLine("치료");
        }
    }
}

 

실행 결과

 

using System;
using System.Net; //IPAddress, Dns
using System.Net.Sockets; //AddressFamily

namespace ConsoleServerTest
{
    class Program
    {
        public static void Main (string[] args)
        {
            PrintMyAddress();
        }

        private static void PrintMyAddress()
        {
            IPAddress[] host = Dns.GetHostAddresses(Dns.GetHostName());

            for (int i = 0; i < host.Length; ++i)
            {
                if (host[i].AddressFamily == AddressFamily.InterNetworkV6) //IPv6
                {
                    Console.WriteLine("IPv6 주소 [{0}]", host[i]);
                }

                if (host[i].AddressFamily == AddressFamily.InterNetwork) //IPv4
                {
                    Console.WriteLine("IPv4 주소 [{0}]", host[i]);
                }
            }
        }
    }
}

실행 결과

IP 주소는 고정 아이피를 신청하지 않는 이상 계속 바뀌게 됩니다.

다른 기기 (공유기, 스위칭 허브 등) 연결 없이 바로 연결했기 때문에

바로 제가 쓰고 있는 유동 아이피가 나온 모습입니다 ㅎㅎ

윈도우 폼을 이용하여 화면을 캡쳐하는 프로그램을 만들어 보겠습니다.

System.Drawing에서 제공되는 동작들을 통해 쉽게 결과를 얻을 수 있습니다.

using System;
using System.Windows.Forms;
using System.Drawing.Imaging; //PixelFormat, ImageFormat
using System.Drawing; //Bitmap, Graphics
using System.IO; //Path

namespace WindowsFormsApp
{
    public partial class Form1 : Form
    {
        private void ScreenShot(int width, int height, int x, int y)
        {
            Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
            Graphics graphics = Graphics.FromImage(bitmap);

            graphics.CopyFromScreen(x, y, 0, 0, bitmap.Size);

            //현재 프로젝트 위치에 저장됩니다.
            string path = Environment.CurrentDirectory;
            string fileName = "image.png";

            //MessageBox.Show(Path.Combine(path + fileName));
            bitmap.Save(Path.Combine(path, fileName), ImageFormat.Png);
        }

        public Form1()
        {
            InitializeComponent();
            //Screen.PrimaryScreen : 1번 모니터
            //WorkingArea : 작업표시줄 제외한 범위
            ScreenShot(Screen.PrimaryScreen.WorkingArea.Width, Screen.PrimaryScreen.WorkingArea.Height, 0, 0);
        }
    }
}

 

스크린샷 결과 화면

 

+ Recent posts