2011-09-21 3 views
4

배경이 커다란 비트 맵인 자체 개체 (OnPaint에서 호출 됨)의 사용자 지정 그리기 그래픽이있는 사용자 정의 컨트롤이 있습니다. 확대/축소 및 이동 기능이 내장되어 있으며 캔버스에 그려진 개체의 모든 좌표는 비트 맵 좌표입니다.OnPaint, 무효화, 클리핑 및 영역에 대한 모범 사례

따라서 사용자 컨트롤이 1000 픽셀 너비라면 비트 맵의 ​​너비는 1500 픽셀이고 200 % 확대/축소가 가능하며 주어진 시간은 비트 맵 너비의 1/3 정도입니다. 비트 맵에서 점 100,100에서 시작하는 직사각형을 가진 객체는 맨 왼쪽으로 스크롤 한 경우 화면의 점 200,200에 나타납니다.

기본적으로 내가 그릴 필요가있는 것은 다시 그려야하는 것만 다시 그리는 효율적인 방법을 만드는 것입니다. 예를 들어 객체를 이동하면 해당 객체의 이전 클립 사각형을 영역에 추가하고 해당 객체의 새 클립 사각형을 해당 영역에 결합한 다음 Invalidate (region)를 호출하여 해당 두 영역을 다시 그리도록 할 수 있습니다.

그러나 이렇게하면 무효화하기 위해 화면 좌표로 개체 비트 맵 좌표를 끊임없이 변환해야한다는 것을 의미합니다. 나는 항상 PaintEventArgs의 ClipRectangle이 다른 윈도우가 나의 것을 무효화 할 때 화면 좌표에 있다고 가정해야합니다.

Region.Transform 및 Region.Translate 기능을 사용하여 비트 맵에서 화면 좌표로 변환 할 필요가없는 방법이 있습니까? 화면 좌표에서 PaintEventArgs 수신을 방해하지 않는 방식으로? 여러 지역을 사용해야합니까 아니면 더 좋은 방법이 있을까요? 내가 지금 뭘하는지에 대한

샘플 코드 :

invalidateRegion.Union(BitmapToScreenRect(SelectedItem.ClipRectangle)); 

SelectedItem.UpdateEndPoint(endPoint); 

invalidateRegion.Union(BitmapToScreenRect(SelectedItem.ClipRectangle)); 

this.Invalidate(invalidateRegion); 

그리고 OnPaint를()에 ... 많은 사람들이 이후

protected override void OnPaint(PaintEventArgs e) 
{ 
    invalidateRegion.Union(e.ClipRectangle); 

    e.Graphics.SetClip(invalidateRegion, CombineMode.Union); 
    e.Graphics.Clear(SystemColors.AppWorkspace); 

    e.Graphics.TranslateTransform(AutoScrollPosition.X + CanvasBounds.X, AutoScrollPosition.Y + CanvasBounds.Y); 

    DrawCanvas(e.Graphics, _ratio); 

    e.Graphics.ResetTransform(); 

    e.Graphics.ResetClip(); 

    invalidateRegion.MakeEmpty(); 
} 
+3

수행 할 필요가없는 작업을 수행하고 있습니다. Windows는 이미 클리핑에서 매우 효율적이므로 도움을받을 필요가 없습니다. 퍼프 (perf) 문제가있는 경우 비트 맵의 ​​픽셀 형식을 사용하십시오. 32bppPArgb는 다른 어떤 것보다 10 배 빠릅니다. –

+3

페인트 클리핑이 어떻게 작동하는지 이해하지 못합니다. GDI +를 사용하여 이동, 크기 조정, 회전 등의 개체를 그립니다. MouseMove와 같은 이벤트에서 다시 그리기가 필요하며 실제로 업데이트해야하는 부분 만 다시 그려야합니다. 예를 들어 객체를 변경하면 자체적으로 다시 그려지지 않을 것이므로 Invalidate()를 호출해야합니다. 클립 할 클립 영역을 지정하지 않으면 전체 장면을 다시 그립니다. 예를 들어 MouseMove 이벤트에서 전체 장면을 다시 그리지 않으려 고합니다. –

+9

와우! Hans Passant에게 Windows가 어떻게 작동하는지 이해하지 못한다고 말하는 것은 Jon Skeet에게 그가 C# 작동 방식을 이해하지 못한다고 말하는 것과 같습니다! :-) –

답변

9

나는 것이 질문을보고 앞으로 나아가서 내 현재의 지식을 최대한 활용하여 대답하십시오.

PaintEventArgs와 함께 제공되는 Graphics 클래스는 항상 무효화 요청에 의해 하드 클립됩니다. 이것은 일반적으로 운영 체제에서 수행되지만 코드에 의해 수행 될 수 있습니다.

이 클립이나이 클립 경계에서 벗어날 수는 없지만 그렇게 할 필요는 없습니다. 페인팅 할 때 일반적으로 성능을 최대화해야하는 경우가 아니라면 클리핑 방법을 신경 쓰지 않아야합니다.

그래픽 클래스는 컨테이너 스택을 사용하여 자르기 및 변형을 적용합니다. Graphics.BeginContainer 및 Graphics.EndContainer를 사용하여이 스택을 직접 확장 할 수 있습니다. 컨테이너를 시작할 때마다 Transform이나 Clip에 대한 변경 사항은 임시이며 BeginContainer 전에 구성된 이전의 Transform 또는 Clip 다음에 적용됩니다. 따라서 본질적으로 OnPaint 이벤트가 발생하면 클립이 이미 클리핑되어 새로운 컨테이너에 있으므로 클립을 볼 수 없으므로 (클립 영역 또는 ClipRect가 무한으로 표시됨) 해당 이벤트를 벗어날 수 없습니다 클립 범위.

예를 들어 마우스 또는 키보드 이벤트에서 또는 데이터 변경에 반응하여 시각적 개체의 상태가 변경되면 일반적으로 전체 컨트롤을 다시 칠하는 Invalidate()를 호출하면됩니다. Windows는 CPU 사용량이 적은 순간에 OnPaint를 호출합니다. Invalidate() 호출은 항상 OnPaint 이벤트와 항상 일치하지는 않습니다. 무효화는 다음 페인트 전에 여러 번 호출 될 수 있습니다.따라서 데이터 모델의 10 개의 속성이 모두 한꺼번에 변경되면 각 속성 변경시 Invalidate를 10 회만 호출하면 단일 OnPaint 이벤트 만 트리거 될 가능성이 높습니다.

업데이트() 및 새로 고침()을 사용하는 데주의해야합니다. 동기화 된 OnPaint를 즉시 강제 실행합니다. 단일 스레드 작업 (진행 막대를 아마도 업데이트) 중에 그리기에 유용하지만 잘못된 시간에 사용하면 과도하고 불필요한 그림이 생길 수 있습니다.

장면을 다시 칠하는 동안 성능을 향상시키기 위해 클립 사각형을 사용하려면 집계 된 클립 영역을 직접 추적 할 필요가 없습니다. Windows에서이 작업을 수행합니다. 무효화가 필요한 직사각형이나 영역을 무효화하고 평소처럼 페인트합니다. 예를 들어, 페인팅하는 객체가 이동되면 이전 경계를 무효화하고 새로운 경계를 지정할 때마다 원래 위치의 배경을 다시 페인트하고 새 위치에 페인트합니다. 펜 스트로크 크기 등도 고려해야합니다.

한스 패전트 (Hans Passant)가 언급했듯이 항상 고해상도 이미지의 비트 맵 형식으로 32bppPArgb를 사용하십시오. 이미지를 "고성능"으로로드하는 방법에 대한 코드 스 니펫은 다음과 같습니다.

public static Bitmap GetHighPerformanceBitmap(Image original) 
{ 
    Bitmap bitmap; 

    bitmap = new Bitmap(original.Width, original.Height, PixelFormat.Format32bppPArgb); 
    bitmap.SetResolution(original.HorizontalResolution, original.VerticalResolution); 

    using (Graphics g = Graphics.FromImage(bitmap)) 
    { 
     g.DrawImage(original, new Rectangle(new Point(0, 0), bitmap.Size), new Rectangle(new Point(0, 0), bitmap.Size), GraphicsUnit.Pixel); 
    } 

    return bitmap; 
} 
+0

해상도가 필요합니까? – GorillaApe

+0

원본 비트 맵의 ​​DPI 정보를 새 비트 맵으로 전송하려면 SetResolution이 필요합니다. 그렇지 않으면 DPI 정보가 손실됩니다. 원시 이미지 자체 (픽셀 배열) 만 복사 할 필요는 없습니다. –