2013-05-31 54 views
1

在任何人指出之前,我知道有一個問題的相同標題has already been asked here它只是不回答我的問題。可拖動的選擇矩形

在.NET 3.5中工作正如在那個問題中,我正在做一個區域選擇組件來選擇圖片上的區域。該圖片使用自定義控件顯示,其中圖片在OnPaint期間繪製。

我有以下代碼爲我的選擇長方形:

internal class AreaSelection : Control 
{ 
    private Rectangle selection 
    { 
     get { return new Rectangle(Point.Empty, Size.Subtract(this.Size, new Size(1, 1))); } 
    } 

    private Size mouseStartLocation; 

    public AreaSelection() 
    { 
     this.Size = new Size(150, 150); 
     this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.SupportsTransparentBackColor, true); 
     this.BackColor = Color.FromArgb(70, 200, 200, 200); 
    } 

    protected override void OnMouseEnter(EventArgs e) 
    { 
     this.Cursor = Cursors.SizeAll; 

     base.OnMouseEnter(e); 
    } 

    protected override void OnMouseDown(MouseEventArgs e) 
    { 
     this.mouseStartLocation = new Size(e.Location); 

     base.OnMouseDown(e); 
    } 

    protected override void OnMouseMove(MouseEventArgs e) 
    { 
     if (e.Button == MouseButtons.Left) 
     { 
      Point offset = e.Location - this.mouseStartLocation; 
      this.Left += offset.X; 
      this.Top += offset.Y; 
     } 

     base.OnMouseMove(e); 
    } 

    protected override void OnPaint(PaintEventArgs e) 
    { 
     e.Graphics.DrawRectangle(new Pen(Color.Black) { DashStyle = DashStyle.Dash }, this.selection); 
     Debug.WriteLine("Selection redrawn"); 
    } 
} 

,給了我一個很好的半透明的長方形,我可以左右拖動。我遇到的問題是,拖動通過矩形顯示的底層圖像滯後於矩形的位置。

移動矩形的速度越快,這越明顯。當我停止移動它時,圖像會立刻出現,並且所有東西再次完美對齊。 我認爲矩形繪製方式有問題,但我真的不知道它是什麼... 任何幫助將不勝感激。

編輯:

我已經注意到了,觀衆被重繪兩次儘可能多的選擇區域,當我拖動選擇區域。這可能是問題的原因嗎?

編輯2:

這裏是在情況下,相關的觀衆代碼:

public enum ImageViewerViewMode 
{ 
    Normal, 
    PrintSelection, 
    PrintPreview 
} 

public enum ImageViewerZoomMode 
{ 
    None, 
    OnClick, 
    Lens 
} 

public partial class ImageViewer : UserControl 
{ 
    /// <summary> 
    /// The current zoom factor. Note: Use SetZoom() to set the value. 
    /// </summary> 
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 
    public float ZoomFactor 
    { 
     get { return this.zoomFactor; } 
     private set 
     { 
      this.zoomFactor = value; 
     } 
    } 

    /// <summary> 
    /// The maximum zoom factor to use 
    /// </summary> 
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 
    public float MaximumZoomFactor 
    { 
     get 
     { 
      return this.maximumZoomFactor; 
     } 
     set 
     { 
      this.maximumZoomFactor = value; 
      this.SetZoomFactorLimits(); 
     } 
    } 

    /// <summary> 
    /// The minimum zoom factort to use 
    /// </summary> 
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 
    public float MinimumZoomFactor 
    { 
     get 
     { 
      return this.minimumZoomFactor; 
     } 
     set 
     { 
      this.minimumZoomFactor = value; 
      this.SetZoomFactorLimits(); 
     } 
    } 

    /// <summary> 
    /// The multiplying factor to apply to each ZoomIn/ZoomOut command 
    /// </summary> 
    [Category("Behavior")] 
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] 
    [DefaultValue(2F)] 
    public float ZoomStep { get; set; } 

    /// <summary> 
    /// The image currently displayed by the control 
    /// </summary> 
    [Category("Data")] 
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] 
    public Image Image 
    { 
     get { return this.image; } 
     set 
     { 
      this.image = value; 
      this.ZoomExtents(); 
      this.minimumZoomFactor = this.zoomFactor/10; 
      this.MaximumZoomFactor = this.zoomFactor * 10; 
     } 
    } 

    public ImageViewerViewMode ViewMode { get; set; } 

    public ImageViewerZoomMode ZoomMode { get; set; } 

    private ImageViewerLens Lens { get; set; } 

    private float zoomFactor; 
    private float minimumZoomFactor; 
    private float maximumZoomFactor; 
    private bool panning; 
    private Point imageLocation; 
    private Point imageTranslation; 
    private Image image; 
    private AreaSelection areaSelection; 

    /// <summary> 
    /// Class constructor 
    /// </summary> 
    public ImageViewer() 
    { 
     this.DoubleBuffered = true; 
     this.MinimumZoomFactor = 0.1F; 
     this.MaximumZoomFactor = 10F; 
     this.ZoomStep = 2F; 
     this.UseScannerUI = true; 
     this.Lens = new ImageViewerLens(); 
     this.ViewMode = ImageViewerViewMode.PrintSelection; 
     this.areaSelection = new AreaSelection(); 
     this.Controls.Add(this.areaSelection); 

     // TWAIN 

     // Initialise twain 
     this.twain = new Twain(new WinFormsWindowMessageHook(this)); 
     // Try to set the last used default scanner 
     if (this.AvailableScanners.Any()) 
     { 
      this.twain.TransferImage += twain_TransferImage; 
      this.twain.ScanningComplete += twain_ScanningComplete; 
      if (!this.SetScanner(this.defaultScanner)) 
       this.SetScanner(this.AvailableScanners.First()); 
     } 
    } 

    /// <summary> 
    /// Saves the currently loaded image under the specified filename, in the specified format at the specified quality 
    /// </summary> 
    /// <param name="FileName">The file name (full file path) under which to save the file. File type extension is not required.</param> 
    /// <param name="Format">The file format under which to save the file</param> 
    /// <param name="Quality">The quality in percent of the image to save. This is optional and may or may not be used have an effect depending on the chosen file type. Default is maximum quality.</param> 
    public void SaveImage(string FileName, GraphicFormats Format, uint Quality = 100) 
    { 
     ImageCodecInfo encoder; 
     EncoderParameters encoderParameters; 

     if (FileName.IsNullOrEmpty()) 
      throw new ArgumentNullException(FileName); 
     else 
     { 
      string extension = Path.GetExtension(FileName); 
      if (!string.IsNullOrEmpty(extension)) 
       FileName = FileName.Replace(extension, string.Empty); 
      FileName += "." + Format.ToString(); 
     } 

     Quality = Math.Min(Math.Max(1, Quality), 100); 

     if (!TryGetEncoder(Format, out encoder)) 
      return; 

     encoderParameters = new EncoderParameters(1); 
     encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, (int)Quality); 

     this.Image.Save(FileName, encoder, encoderParameters); 
    } 

    /// <summary> 
    /// Tries to retrieve the appropriate encoder for the chose image format. 
    /// </summary> 
    /// <param name="Format">The image format for which to attempt retrieving the encoder</param> 
    /// <param name="Encoder">The encoder object in which to store the encoder if found</param> 
    /// <returns>True if the encoder was found, else false</returns> 
    private bool TryGetEncoder(GraphicFormats Format, out ImageCodecInfo Encoder) 
    { 
     ImageCodecInfo[] codecs; 

     codecs = ImageCodecInfo.GetImageEncoders(); 
     Encoder = codecs.First(c => c.FormatDescription.Equals(Format.ToString(), StringComparison.CurrentCultureIgnoreCase)); 

     return Encoder != null; 
    } 

    /// <summary> 
    /// Set the zoom level to view the entire image in the control 
    /// </summary> 
    public void ZoomExtents() 
    { 
     if (this.Image == null) 
      return; 

     this.ZoomFactor = (float)Math.Min((double)this.Width/this.Image.Width, (double)this.Height/this.Image.Height); 
     this.LimitBasePoint(imageLocation.X, imageLocation.Y); 
     this.Invalidate(); 
    } 

    /// <summary> 
    /// Multiply the zoom 
    /// </summary> 
    /// <param name="NewZoomFactor">The zoom factor to set for the image</param> 
    public void SetZoom(float NewZoomFactor) 
    { 
     this.SetZoom(NewZoomFactor, Point.Empty); 
    } 

    /// <summary> 
    /// Multiply the zoom 
    /// </summary> 
    /// <param name="NewZoomFactor">The zoom factor to set for the image</param> 
    /// <param name="ZoomLocation">The point in which to zoom in</param> 
    public void SetZoom(float NewZoomFactor, Point ZoomLocation) 
    { 
     int x; 
     int y; 
     float multiplier; 

     multiplier = NewZoomFactor/this.ZoomFactor; 

     x = (int)((ZoomLocation.IsEmpty ? this.Width/2 : ZoomLocation.X - imageLocation.X)/ZoomFactor); 
     y = (int)((ZoomLocation.IsEmpty ? this.Height/2 : ZoomLocation.Y - imageLocation.Y)/ZoomFactor); 

     if ((multiplier < 1 && this.ZoomFactor > this.MinimumZoomFactor) || (multiplier > 1 && this.ZoomFactor < this.MaximumZoomFactor)) 
      ZoomFactor *= multiplier; 
     else 
      return; 

     LimitBasePoint((int)(this.Width/2 - x * ZoomFactor), (int)(this.Height/2 - y * ZoomFactor)); 
     this.Invalidate(); 
    } 

    /// <summary> 
    /// Determines the base point for positioning the image 
    /// </summary> 
    /// <param name="x">The x coordinate based on which to determine the positioning</param> 
    /// <param name="y">The y coordinate based on which to determine the positioning</param> 
    private void LimitBasePoint(int x, int y) 
    { 
     int width; 
     int height; 

     if (this.Image == null) 
      return; 

     width = this.Width - (int)(Image.Width * ZoomFactor); 
     height = this.Height - (int)(Image.Height * ZoomFactor); 

     x = width < 0 ? Math.Max(Math.Min(x, 0), width) : width/2; 
     y = height < 0 ? Math.Max(Math.Min(y, 0), height) : height/2; 

     imageLocation = new Point(x, y); 
    } 

    /// <summary> 
    /// Verify that the maximum and minimum zoom are correctly set 
    /// </summary> 
    private void SetZoomFactorLimits() 
    { 
     float maximum = this.MaximumZoomFactor; 
     float minimum = this.minimumZoomFactor; 

     this.maximumZoomFactor = Math.Max(maximum, minimum); 
     this.minimumZoomFactor = Math.Min(maximum, minimum); 
    } 

    /// <summary> 
    /// Mouse button down event 
    /// </summary> 
    protected override void OnMouseDown(MouseEventArgs e) 
    { 
     switch (this.ZoomMode) 
     { 
      case ImageViewerZoomMode.OnClick: 
       switch (e.Button) 
       { 
        case MouseButtons.Left: 
         this.SetZoom(this.ZoomFactor * this.ZoomStep, e.Location); 
         break; 

        case MouseButtons.Middle: 
         this.panning = true; 
         this.Cursor = Cursors.NoMove2D; 
         this.imageTranslation = e.Location; 
         break; 

        case MouseButtons.Right: 
         this.SetZoom(this.ZoomFactor/this.ZoomStep, e.Location); 
         break; 
       } 
       break; 

      case ImageViewerZoomMode.Lens: 
       if (e.Button == MouseButtons.Left) 
       { 
        this.Cursor = Cursors.Cross; 
        this.Lens.Location = e.Location; 
        this.Lens.Visible = true; 
       } 
       else 
       { 
        this.Cursor = Cursors.Default; 
        this.Lens.Visible = false; 
       } 
       this.Invalidate(); 
       break; 
     } 

     base.OnMouseDown(e); 
    } 

    /// <summary> 
    /// Mouse button up event 
    /// </summary> 
    protected override void OnMouseUp(MouseEventArgs e) 
    { 
     switch (this.ZoomMode) 
     { 
      case ImageViewerZoomMode.OnClick: 
       if (e.Button == MouseButtons.Middle) 
       { 
        panning = false; 
        this.Cursor = Cursors.Default; 
       } 
       break; 

      case ImageViewerZoomMode.Lens: 
       break; 
     } 

     base.OnMouseUp(e); 
    } 

    /// <summary> 
    /// Mouse move event 
    /// </summary> 
    protected override void OnMouseMove(MouseEventArgs e) 
    { 
     switch (this.ViewMode) 
     { 
      case ImageViewerViewMode.Normal: 
       switch (this.ZoomMode) 
       { 
        case ImageViewerZoomMode.OnClick: 
         if (panning) 
         { 
          LimitBasePoint(imageLocation.X + e.X - this.imageTranslation.X, imageLocation.Y + e.Y - this.imageTranslation.Y); 
          this.imageTranslation = e.Location; 
         } 
         break; 

        case ImageViewerZoomMode.Lens: 
         if (this.Lens.Visible) 
         { 
          this.Lens.Location = e.Location; 
         } 
         break; 
       } 
       break; 

      case ImageViewerViewMode.PrintSelection: 
       break; 

      case ImageViewerViewMode.PrintPreview: 
       break; 
     } 

     base.OnMouseMove(e); 
    } 

    /// <summary> 
    /// Resize event 
    /// </summary> 
    protected override void OnResize(EventArgs e) 
    { 
     LimitBasePoint(imageLocation.X, imageLocation.Y); 
     this.Invalidate(); 

     base.OnResize(e); 
    } 

    /// <summary> 
    /// Paint event 
    /// </summary> 
    protected override void OnPaint(PaintEventArgs pe) 
    { 
     Rectangle src; 
     Rectangle dst; 

     pe.Graphics.Clear(this.BackColor); 

     if (this.Image != null) 
     { 
      switch (this.ViewMode) 
      { 
       case ImageViewerViewMode.Normal: 
        src = new Rectangle(Point.Empty, new Size(Image.Width, Image.Height)); 
        dst = new Rectangle(this.imageLocation, new Size((int)(this.Image.Width * this.ZoomFactor), (int)(this.Image.Height * this.ZoomFactor))); 
        pe.Graphics.DrawImage(this.Image, dst, src, GraphicsUnit.Pixel); 

        this.Lens.Draw(pe.Graphics, this.Image, this.ZoomFactor, this.imageLocation); 
        break; 

       case ImageViewerViewMode.PrintSelection: 
        src = new Rectangle(Point.Empty, new Size(Image.Width, Image.Height)); 
        dst = new Rectangle(this.imageLocation, new Size((int)(this.Image.Width * this.ZoomFactor), (int)(this.Image.Height * this.ZoomFactor))); 
        pe.Graphics.DrawImage(this.Image, dst, src, GraphicsUnit.Pixel); 
        break; 

       case ImageViewerViewMode.PrintPreview: 
        break; 
      } 
     } 

     //Debug.WriteLine("Viewer redrawn " + DateTime.Now); 
     base.OnPaint(pe); 
    } 
} 

編輯3:

經歷進一步的圖形相關設定高度的東西的時候麻煩大。例如,如果在構造函數AreaSelection中將高度設置爲500,則拖動該控件真的會將繪畫擰緊。

+0

也許你能提供查看器的代碼? –

+0

我可以和我會,但它是很多代碼... –

+0

至少只是OnPaint部分? –

回答

1

同時拖動底層圖像,其示出了通過矩形落在後面

這是相當不可避免的,更新所述矩形也重繪圖像滯後。如果這很昂貴,比如說超過30毫秒,那麼這可能會引起人們的注意。

對於像現代機器上的圖像那樣簡單的事情,這需要大量的毫秒。它可以採取這麼長的唯一方法是當圖像是,並需要重新調整,以適應picturebox。像素格式與視頻適配器的像素格式不兼容,因此每個像素格式都必須從圖像像素格式轉換爲視頻適配器的像素格式。這確實可以加起來達到幾毫秒。

您需要幫助避免PictureBox在每次圖像被繪製時不得不刻錄很多cpu週期。通過進行預縮放圖像,將其從一個巨大的位圖變爲更適合控制的圖像。通過改變像素格式,32bppPArgb格式最適合長距離拍攝,因爲它匹配絕大多數視頻適配器的像素格式。它比所有其他格式快得多十倍倍。您將在this answer中找到樣板代碼以進行此轉換。

+0

感謝您的回答。我試了一下,但似乎沒有任何區別。說實話,我用來測試這個圖像是「只有」32k,所以沒有什麼特別大的。我想我會直接在觀看者中執行選擇,以便一切都可以同時重新繪製,就像我對鏡頭所做的一樣,看看讓我感覺到了什麼。 –