NGMsoftware

NGMsoftware
로그인 회원가입
  • 매뉴얼
  • 학습
  • 매뉴얼

    학습


    C# C# .NET 매크로 프로그램 만들기. (화면에서 같은 이미지 모두 찾기 드래그 하기 2부)

    페이지 정보

    본문

    안녕하세요. 엔지엠소프트웨어입니다. 오늘은 윈도우 화면에서 유사한 모든 이미지를 찾는 이미지 전체 매치에 몇가지 기능을 추가할 예정입니다. 아무래도 여러개의 이미지를 찾다보니 클릭할 좌표가 여러개일수도 있고, 같은 모양의 이미지가 2개라면 시작과 끝으로 드래그를 하고 싶을수도 있습니다. 이외에도 찾은 모든 이미지를 클릭하거나 특정 위치에서 가까운 순서대로 또는 먼 순서대로 클릭하고 싶을수도 있는데요. 워낙 경우의 수가 많다보니 로직이 많이 복잡해지는 부분도 있네요.

     

    그렇더라도 크게 걱정할 필요는 없습니다. 옵션만 많다뿐이지 비즈니스 로직이 복잡하진 않거든요. 대부분의 로직들이 단순 산수에 불과합니다. 2차원 평면 모니터 또는 화면에서 어떤 기준으로 좌표를 뽑아내서 사용할지에 대한것들이라서 더하기 빼기만 존재합니다. 알고리즘과 같이 거창한 뭔가가 있는건 아니예요.

     

    이전 글을 참고하셔서 이미지 전체 매치를 만들고, 여기부터는 추가적인 내용만 코딩하도록 하겠습니다. 우선, 마우스 동작을 먼저 구현해야겠네요.

    [LocalizedCategory("Mouse")]
    [LocalizedDisplayName("IsAllClick")]
    [LocalizedDescription("IsAllClick")]
    [Browsable(true)]
    [DefaultValue(false)]
    public bool IsAllClick { get; set; }
    
    [LocalizedCategory("Mouse")]
    [LocalizedDisplayName("AllClickInterval")]
    [LocalizedDescription("AllClickInterval")]
    [Browsable(true)]
    [DefaultValue(0)]
    public int AllClickInterval { get; set; }

     

    위 속성의 이름을 보면 어떤 느낌인지 알 수 있을건데요. 찾은 모든 이미지를 클릭하는데, 클릭 지연도 설정할 수 있도록 했습니다. 앞서 예제에서는 구글 크롬에서 이미지 검색을 통해 테스트를 해봤습니다. 하지만, 마우스 클릭을 테스트하기엔 부적절합니다. 실제로 웹브라우저에서 클릭이 발생하면 링크를 타고 넘어가기 때문입니다. 이번에는 연속해서 찾은 모든 이미지를 클릭해야 하기 때문에 로컬에 이미지를 저장하고, 클릭을 테스트 해야 합니다.

     

    테스트용 이미지를 바탕화면에 슈퍼마리오.jpg로 저장했습니다.

    QSv7OU3.jpeg

     

     

    그림판에서 저장한 이미지를 불러옵니다.

    eoYBwQR.jpeg

     

     

    이제 테스트할 준비가 완료 되었습니다. 아래 그림과 같이 속성을 설정하고, 테스트를 해볼께요.

    • 마우스 사용: Click
    • 유사 거리 제거: 1
    • 유사성: 150
    • 전체 클릭: True
    • 전체 클릭 간격: 1000

    fMpy2Kk.jpeg

     

     

    매크로를 실행하면, 아래와 같이 1촤 간격으로 슈퍼마리오 코인을 상단 좌측부터 우측으로 하나씩 클릭하고, 하단 좌측부터 우측으로 클릭이 이어서 진행됩니다. 시각화를 위해서 브러시 크기는 가장 큰 사이즈로 변경하고, 색은 진한 파란색으로 선택했습니다.

    EnjoKF9.jpeg

     

     

    이번에는 반대로 하나씩 클릭해볼까요? 이렇게 하려면 옵션의 정렬 속성을 리버스(Reverse)로 변경하면 됩니다.

     

     

    이 부분을 처리하려면 정렬에 대한 속성을 추가해야 합니다. 적절한 이름을 지어주기가 어렵네요. 옵션이라고 할까요?

    [LocalizedCategory("Option")]
    [LocalizedDisplayName("FindOption")]
    [LocalizedDescription("FindOption")]
    [Browsable(true)]
    [DefaultValue(typeof(Ai.Definition.FindOption), "First")]
    public Ai.Definition.FindOption FindOption { get; set; } = Ai.Definition.FindOption.First;
    
    [LocalizedCategory("Option")]
    [LocalizedDisplayName("FindIndex")]
    [LocalizedDescription("FindIndex")]
    [Browsable(true)]
    [DefaultValue(0)]
    public int FindIndex { get; set; }
    
    [LocalizedCategory("Option")]
    [LocalizedDisplayName("FindPoint")]
    [LocalizedDescription("FindPoint")]
    [Browsable(true)]
    [DefaultValue(typeof(System.Drawing.Point), "0,0")]
    [Editor(typeof(TypeEditor.MouseTrackingEditor), typeof(System.Drawing.Design.UITypeEditor))]
    public System.Drawing.Point FindPoint { get; set; }
    
    [LocalizedCategory("Option")]
    [LocalizedDisplayName("Sort")]
    [LocalizedDescription("Sort")]
    [Browsable(true)]
    [DefaultValue(typeof(Ai.Definition.Sort), "None")]
    public Ai.Definition.Sort Sort { get; set; } = Ai.Definition.Sort.None;
    
    [LocalizedCategory("Option")]
    [LocalizedDisplayName("SortOptionXY")]
    [LocalizedDescription("SortOptionXY")]
    [Browsable(true)]
    [DefaultValue(typeof(Ai.Definition.PointFormulaOption), "All")]
    public Ai.Definition.PointFormulaOption SortOptionXY { get; set; } = Definition.PointFormulaOption.All;

     

    정렬은 2가지 방식으로 처리해야 합니다. 하나는 찾은 이미지의 목록을 정렬하는 방법입니다. 그리고, 두번째 정렬 옵션은 X 또는 Y 또는 XY와 같이 어떤 방향성을 적용할 수 있도록 해야 합니다. 가능하면 쉽게 가려고 했으나, 디테일을 포기하기가 쉽지 않네요. 간혹, "이런 기능도 되나요?" 라고 물어보시는 분들이 많아서요. 고객분들중에 누군가는 좌표로 정렬할 때 특정 방향성만 가지길 원할지도 모릅니다.

     

    그래서, 일반적인 정렬은 아래와 같은 옵션들을 제공해줍니다.

    public enum Sort
    {
        None = 0,
        Ascending = 1,
        Descending = 2,
        Reverse = 3,
        Far = 4,
        Near = 5,
        Shuffle = 6
    }

     

    None은 정렬을 하지 않습니다. 화면에서 찾기 알고리즘에 의해 찾아진 순서를 사용합니다. Ascending(어센딩)과 Descending(디센딩)은 아래 옵션에 영향을 받습니다.

    public enum PointFormulaOption
    {
        All = 0,
        X = 1, 
        Y = 2
    }

     

    어센딩은 오름 차순이고, 디센딩은 내림 차순입니다. 일반적으로 1, 2, 3, 4, 5... 와 같은 숫자라면 또는 a, b, c, d, e, f..와 같은 문자라면 ASC와 DESC 옵션만 있어도 됩니다. 기준이 명확하기 때문이죠. 그런데, 좌표는 X, Y가 의미하는바가 다릅니다. 그렇기 때문에 기준이 될 항목도 추가적으로 제공해야 합니다.

     

    아래 동영상처럼 X 또는 Y 축으로 내림 차순과 오름 차순으로 실행 해보세요. 클릭 순서가 달라지는것을 확인할 수 있습니다.

     

     

    이번에는 좌측 하단의 첫번째 코인에서 우측 상단의 마지막 코인까지 드래그하는 방법을 알아볼께요. 우선, 마우스 처리 방식을 어떻게 할지를 결정해야 합니다.

    [LocalizedCategory("Action")]
    [LocalizedDisplayName("UseMouse")]
    [LocalizedDescription("UseMouse")]
    [Browsable(true)]
    [DefaultValue(typeof(Ai.Definition.ImageMouseAction), "None")]
    public Ai.Definition.ImageMouseAction UseMouse { get; set; } = Definition.ImageMouseAction.None;

     

    더 다양한 옵션을 제공하는건 어떨지 생각해봤는데요. 현재로써는 멀티 이미지와 마우스를 매칭시킬 때 처리할 수 있는 경우의 수는 아래와 같습니다. 이외에더 뭔가 더 있을수도 있겠지만, 이 부분은 나중에 알아보도록 할께요. Drag는 일부러 좀 간격을 두었습니다. 혹시라도 중간에 뭔가 추가될지도 모르니까요.

    public enum ImageMouseAction
    {
        None = 0,
        Click = 1,
        DoubleClick = 2,
        Drag = 9
    }

     

    정렬 처리는 단순 노가다의 반복입니다. 코딩하는 입장에서 그렇다는 뜻이예요. 왜냐하면 비슷한 코드들을 계속 동일하게 작성해야 해서 그렇습니다.

    switch (EndSort)
    {
        case Definition.Sort.Ascending:
            switch (SortOptionXY)
            {
                case Definition.PointFormulaOption.All:
                    rects = rects.OrderBy(o => o.Location.X + o.Location.Y).ToList();
                    break;
                case Definition.PointFormulaOption.X:
                    rects = rects.OrderBy(o => o.X).ToList();
                    break;
                case Definition.PointFormulaOption.Y:
                    rects = rects.OrderBy(o => o.Y).ToList();
                    break;
            }

     

    나중에 리펙토링을 해야 할거 같긴한데... 지금은 작성해야 할 내용도 많고, 테스트할 케이스도 많아서 일단은 다음으로 미룰께요.

    case Definition.Sort.Reverse:
        rects.Reverse();
        break;
    case Definition.Sort.Far:
        rects = Ai.Common.Helper.Far(FindPoint, SortOptionXY, rects);
        break;
    case Definition.Sort.Near:
        rects = Ai.Common.Helper.Near(FindPoint, SortOptionXY, rects);
        break;
    case Definition.Sort.Shuffle:
        Random rand = new Random();
        rects = rects.OrderBy(_ => rand.Next()).ToList();
        break;

     

    이제 드래그하는 부분을 스크립트로 어떻게 처리할지 알아봅시다. 시작과 끝을 각각 설정할 수 있도록 했으니 문제는 없을거예요. 다만, 위와 아래 코인 위치가 다르니 정렬은 X 축으로만 계산하도록 해야 할거 같네요. 그래야 정상 동작할겁니다.

    Vz5dMf9.jpeg

     

     

    매크로를 실행하고, 결과를 살펴볼까요? 예상한데로 잘 동작하는군요.

     

     

    마지막으로 특정 좌표를 기준으로 정렬하는 방법을 알아볼께요. 좌표를 기준으로 계산해야 하다보니 좌표 정보를 받아야 합니다. 이번에도 드래그를 기준으로 테스트 해볼께요. 가깝거나 먼 거리를 기준으로 테스트를 진행하기 때문에 아래 그림과 같이 시작과 끝 지점을 알아볼 수 있도록 미리 점을 찍어 두었습니다.

    GKfmvBn.jpeg

     

     

    좌표를 아래 그림과 같이 설정하세요. 참고로, 계산하기 쉽게 마지막 좌표도 시작과 동일하게 정렬하고, 첫번째 찾은 이미지를 가져오도록 해야 합니다.

    rJ7pYwY.jpeg

     

     

    매크로를 실행하면, 아래와 같이 시작과 끝 설정 좌표에서 가장 가까운 이미지가 선택되고 드래그가 실행됩니다.

     

     

    가장 가까운 위치와 먼 위치를 찾는 방법은 아래와 같습니다. Pow 함수를 이용해서 x의 n 제곱근을 구합니다. 그리고, Sqrt를 이용해서 루트를 구해줍니다.

    System.Math.Sqrt((System.Math.Pow(nearFarPoint.X - (rectangle.X + rectangle.Width / 2), 2) + System.Math.Pow(nearFarPoint.Y - (rectangle.Y + rectangle.Height / 2), 2)));

     

    이렇게 해서 화면에 보이는 모든 이미지를 찾고, 특정 조건에 따라서 정렬하거나 마우스를 드래그하는 방법에 대해서 알아봤습니다. 만들면서 테스트를 하다보니 실제로 코딩 내용은 많은 부분들이 변경되었는데요. 개발자 분들은 위 내용만으로도 비슷하게 구현은 가능할겁니다. 그리고, 필요한 것들은 구글에 물어보면 더 멋진 정답들이 나올수도 있고요. 결국은 개발자 스타일에 따라서 코딩도 변화하는데요. 기본적인 로직들은 대략 힌트 정도로만 생각하고 봐주시면 좋을듯 합니다.

     

    디테일한 부분들을 구현하는건 직접 로직을 만들고, 고민해보시는게 좋을거예요. 코드를 어느정도 이해하고 있어야 버그를 수정하거나 개선할 수 있기 때문입니다. 그리고, 언젠가는 요구 사항을 반영하다가 중복 또는 불합리한 코드들이 발견될텐데요. 이 때를 위해서라도 간단한 주석과 의견들을 적어두는게 좋습니다. 한번 사용하고 버리는 코드는 아니니까요^^

     

    개발자에게 후원하기

    MGtdv7r.png

     

    추천, 구독, 홍보 꼭~ 부탁드립니다.

    여러분의 후원이 빠른 귀농을 가능하게 해줍니다~ 답답한 도시를 벗어나 귀농하고 싶은 개발자~

    감사합니다~

    • 네이버 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 카카오스토리 공유하기
    추천0 비추천0

    댓글목록

    등록된 댓글이 없습니다.