繁体   English   中英

可拖动选择矩形

[英]Draggable selection rectangle

之前有人指出它我知道,有一个与同一标题的问题已经被问这里它只是不回答我的问题,我认为。

在.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,则拖动控件确实会拧紧绘画。

同时拖动通过矩形显示的基础图像会滞后

这是不可避免的,更新矩形也会重绘图像。 如果这很昂贵,比如说超过30毫秒,那么这可能就变得显而易见。

像现代机器上的图像这样简单的操作要花费很多毫秒。 花费这么长时间的唯一方法是当图像较大且需要重新缩放以适合图片框时。 而且,像素格式与视频适配器的像素格式不兼容,因此,每个像素格式都必须从图像像素格式转换为视频适配器的像素格式。 确实,这可能总计数毫秒。

您需要帮助避免PictureBox每次绘制图像时都必须刻录许多cpu周期。 为此,可以对图像进行预缩放 ,然后将其从巨大的位图转换为更适合控件的位图。 并且通过更改像素格式,长距离拍摄最好达到32bppPArgb格式,因为它与绝大多数视频适配器中的绝大多数像素格式都匹配。 绘制速度比所有其他格式快十倍 您将在此答案中找到进行转换的样板代码。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM