1

誰かが指摘する前に、ここですでに尋ねられている同じタイトルの質問があることを知っていますが、それは私の問題に答えていないと思います.

Working in .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 倍の頻度で再描画されることに気付きました。これが問題の原因でしょうか?

編集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 に設定した場合、コントロールをドラッグすると、実際に描画が台無しになります。

4

1 に答える 1

1

長方形を通して表示される下にある画像をドラッグしている間、遅れが生じます

これは避けられないことで、四角形を更新すると画像も再描画されます。そして、それが高価な場合、たとえば 30 ミリ秒を超えると、これが目に付くようになる可能性があります。

これは、最新のマシン上の画像のような単純なものでは、数ミリ秒です。時間がかかる唯一の方法は、画像が大きく、画像ボックスに合わせて再スケーリングする必要がある場合です。また、ピクセル フォーマットはビデオ アダプタのピクセル フォーマットと互換性がないため、それらのすべてをイメージ ピクセル フォーマットからビデオ アダプタのピクセル フォーマットに変換する必要があります。それは実際に数ミリ秒まで追加できます。

画像が描画されるたびに、PictureBox が多くの CPU サイクルを消費するのを回避する必要があります。これを行うには、画像を事前にスケーリングして、巨大なビットマップからコントロールにより適したものに変換します。また、ピクセル フォーマットを変更することにより、32bppPArgb フォーマットは、すべてのビデオ アダプタの大部分のピクセル フォーマットと一致するため、最終的には 32bppPArgb フォーマットが最適です。他のすべての形式よりも10倍速く描画されます。この変換を行う定型コードは、この回答にあります。

于 2013-05-31T09:06:18.353 に答える