2012-12-05 10 views
1

저는 며칠 동안 이것을 구현하는 데 어려움을 겪고 있습니다. 비슷한 문제에 대해 광범위하게 조사해 왔지만 문제에 직접적으로 도움이되지는 않습니다.UserControl에서 큰 캔버스 렌더링

기본적으로 타일은 내 UserControl 클래스의 표에 렌더링됩니다. 이것은 내가 개발중인 Tile Engine 기반의 세계 편집자를위한 것입니다. 다음은 열린 세계 문서 및 기생 타일에 대한 스크린 샷입니다.

enter image description here

처음에, 나는 세상의 미리보기 캔버스 것이 내 컨트롤에 Bitmap을 사용하려고했다. 예를 들어 브러시 도구를 사용하여 마우스를 움직이고 왼쪽 버튼을 누르면 커서 아래의 가장 가까운 타일을 브러시의 타일로 설정하고 layer 비트 맵에 칠합니다. 컨트롤의 OnPaint 메서드는 페인트 이벤트의 클리핑 사각형에 대해 layer 비트 맵이 그려지는 위치로 재정의됩니다.

큰 세상을 다룰 때 비트 맵은 매우 커질 수 있습니다. 이 응용 프로그램을 세계 크기로 다목적으로 사용할 필요가 있으며 무효화 될 때마다 큰 비트 맵을 컨트롤에 렌더링 할 때 성능 문제가 있다는 것은 명백합니다.

현재 컨트롤에 덮어 쓰여진 타일 OnPaint에 직접 타일을 그려 넣습니다. 이것은 많은 메모리를 필요로하지 않기 때문에 좋습니다. 예를 들어 타일 당 (20, 20)(1000, 1000) 세계 (전체 캔버스 크기는 (20000, 20000))는 전체 애플리케이션에서 약 18MB 메모리로 실행됩니다. 메모리 집약적 인 것은 아니지만 컨트롤이 무효화 될 때마다 뷰포트의 모든 타일을 반복하기 때문에 꽤 프로세서 집약적입니다. 이것은 매우 짜증나는 깜박임을 만듭니다.

내가 달성하고 싶은 것은 메모리 사용과 성능까지 가운데에서 만나는 방법입니다. 기본적으로 컨트롤을 다시 그릴 때 (폼 크기 조정, 포커스 및 흐리게, 스크롤링 등) 깜빡 거리지 않도록 전 세계를 이중 버퍼링합니다. Photoshop을 예로 들어 봅니다. 컨테이너 뷰포트에서 오버플로되었을 때 열려있는 문서를 어떻게 렌더링합니까?

참고로 위에서 언급 한 직접 그리기 방법을 사용하는 내 컨트롤의 OnPaint 재정의가 여기에 있습니다.

getRenderBounds은 세계의 모든 타일을 반복하고 보이는 지 확인하는 대신 표시되는 타일을 렌더링하는 데 사용되는 PaintEventArgs.ClipRectangle을 기준으로 사각형을 반환합니다.

protected override void OnPaint(PaintEventArgs e) 
{ 
    WorldSettings settings = worldSettings(); 

    Rectangle bounds = getRenderBounds(e.ClipRectangle), 
     drawLocation = new Rectangle(Point.Empty, settings.TileSize); 

    e.Graphics.InterpolationMode = 
     System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor; 
    e.Graphics.SmoothingMode = 
     System.Drawing.Drawing2D.SmoothingMode.None; 
    e.Graphics.PixelOffsetMode = 
     System.Drawing.Drawing2D.PixelOffsetMode.None; 
    e.Graphics.CompositingQuality = 
     System.Drawing.Drawing2D.CompositingQuality.HighSpeed; 

    for (int x = bounds.X; x < bounds.Width; x++) 
    { 
     for (int y = bounds.Y; y < bounds.Height; y++) 
     { 
      if (!inWorld(x, y)) 
       continue; 

      Tile tile = getTile(x, y); 

      if (tile == null) 
       continue; 

      drawLocation.X = x * settings.TileSize.Width; 
      drawLocation.Y = y * settings.TileSize.Height; 

      e.Graphics.DrawImage(img, 
       drawLocation, 
       tileRectangle, 
       GraphicsUnit.Pixel); 
     } 
    } 
} 

내 코드의 컨텍스트가 더 필요하면 의견을 보내주십시오.

답변

1

트릭은 이 아니며,은 큰 비트 맵을 사용합니다. 보이는 영역을 포함하는 비트 맵 만 필요합니다. 그런 다음 무엇이든 그릴 수 있습니다.

이 작업을 수행하려면 비트 맵과 별도로 데이터를 유지 관리해야합니다. 이것은 단순한 배열이나 월드 위치와 같은 각 블록에 대한 정보를 담는 간단한 클래스를 가진 배열 /리스트가 될 수 있습니다.

블록이 보이는 영역 내에 있으면 그 블록을 그립니다. 전체 배열을 반복 할 필요가있을 수도 있고 그렇지 않을 수도 있습니다 (실제로는 별개의 스레드에서 보이는 배열을 계산할 수도 있습니다). 또한 모든 블록을 반복하지 않도록 영역 인덱스를 작성하여 함수를보다 지능적으로 만들 수 있습니다.

새 블록을 배열에 추가하려면 캔버스 위치를 월드 좌표로 계산하고 추가 한 다음 배열을 다시 렌더링합니다 (또는 블록이 그려지는 영역).

이렇게하면 시스템에서 스크롤 가능한 영역이있는 컨트롤을 그리는 방법입니다.

더블 버퍼링을 사용하면 명확하고 깜박임 현상이 적습니다.

이 경우 필자는 별도의 스크롤 막대가있는 패널을 사용하여 스크롤 막대의 상대 위치를 계산합니다.

+0

절대적으로 환상적입니다! 얼마나 오랫동안 내가 이것을 망쳐 놓았는지, 그것은 터널 전망이 가장 그렇기 때문에 나를 생각조차하지 못하게했다. 명확한 설명을위한 톤 감사합니다! –

+1

문제가없는 친구입니다. 작은 증분으로 스크롤하는 경우 전체 캔버스를 다시 그릴 필요가 없다고 덧붙였습니다. 내용을 상하로 blit하고 새로운 노출 된 간격 만 다시 그립니다. – K3N

+0

그래, 직접 드로잉 메서드로 구현하려고했지만이 메서드를 사용할 때 훨씬 더 의미가 있습니다. –

관련 문제