Laden...

ExBufferedGraphics - Graphics buffer for efficient drawing and blitting

Erstellt von zommi vor 14 Jahren Letzter Beitrag vor 14 Jahren 4.230 Views
zommi Themenstarter:in
1.361 Beiträge seit 2007
vor 14 Jahren
ExBufferedGraphics - Graphics buffer for efficient drawing and blitting

Beschreibung:

Für zwei Programme an denen ich gerade arbeite, hat die normale Leistung der GDI-Operationen, die von Graphics angeboten wurden, nicht ausgereicht.
Doch zum Glück gibt es die BufferedGraphics-Klasse im .NET-Framework, die intern einen Bild-Puffer implementiert, den man blitzschnell (mittels der intern genutzten BitBlt-Funktion) zeichnen kann.

Bisherige Probleme
Doch war die Verwendung für meine Einsatzzwecke aus mehreren Gründen sehr unkomfortabel:*Für das korrekte Arbeiten braucht man nicht nur BufferedGraphics-Objekte sondern auch BufferedGraphicsContext-Objekte, also muss man immer mit 2 Klassen hantieren. *Ein Resizen (aufgrund von nem OnResize des Panels) ist relativ mühsam und erfordert einiges an Code *Das Zeichnen mittels BufferedGraphics.Render(...) bietet keinerlei Einstellung, WO gezeichnet wird, obwohl BitBlit dies anbietet.

Vorteile mit diesem Snippet
Die neue Klasse heißt ExBufferedGraphics (Extended) und kapselt dies alles noch einmal und bietet somit viele Vorteile:*Nur einmal muss so ein Objekt angelegt werden, wobei man dem Konstruktor ein Referenz-Graphics Objekt übergibt (von dem der Buffer die Device-Eigenschaften übernimmt) oder auch direkt ein Control angibt, auf das man später zeichnen will. *Da beim Resizing des Controls meist auch der Buffer mitverändert wird, kann man die Size-Eigenschaft ändern. Beim Verkleinen wird nie neuer Speicher angefordert, sondern nur einfach ein Ausschnitt des bisherigen Buffers weiterverwendet.
Und beim Vergrößern wird - ähnlich zur List-Implementierung - der Buffer lieber gleich etwas mehr vergrößert: Auf den doppelten Speicher, also pro Dimension um sqrt(2). So wird ein ständiges ReAllozieren beim langsamen "Groß-Ziehen" eines Controls verhindert.

*Beim Zeichnen kann nun auch eine Verschiebung angegeben werden, sodass der Buffer versetzt gezeichnet wird.

Mögliche Einsatzgebiete
Ich selbst verwende die Klasse für folgendes:*Lupen-Controls. Ich habe ein großes Control, das ein (Übersichts-)Bild darstellt und drum herum einige kleine, die ausschnitte zeigen. Nun kann ich auf dem Übersichtsbild die Lupenausschnitte verschieben. Damit das Verschieben auch smooth läuft, auch wenn alle Lupen gleichzeitig verschiebt und trotzdem eine Live-Vorschau sehen will, verbietet sich ein x-faches Graphics.DrawImage(...) in den einzelnen Lupen-Controls bei jedem MouseMove.
Alles stockt, Speicher wird alloziert/freigegeben, nichts ist flüssig !
Nun verwende ich diese Klasse in der ich das Bild einmal Groß hineinzeichne. Und die Lupen-Controls Rendern nur noch diese ExBufferedGraphics (was intern mittels BitBlt viel viel schneller geht) am gewünschten Ausschnitt.
Nicht nur, dass es "performanter" geht, es geht nun überhaupt !

*Drag'n'Drop in CustomControls Bei [Tutorial] Gezeichnete Objekte mit der Maus verschieben. wird wunerbar beschrieben, wie das Verschieben klappen kann.
Hat man aber hunderte von Objekte und sogar Bilder dabei, dann wird dass komplette Neuzeichnen in der OnPaint-Methode zum Flaschenhals. Das Draggen ist nicht mehr flüssig und die Anwendung macht keinen Spaß.
Deshalb verwende ich nun "2 Ebenen".
Die Szenerie an sich rendere ich stets in einem ExBufferedGraphics, den ich mit OnPaint nur noch auf das Control blitte (damit hab ich auch zugleich ein custom-double-buffering implementiert).
Wähle ich nun ein Objekt zum Verschieben aus, render ich noch einmal die Szenerie ohne das Objekt in den Buffer und dies wird nun mein Hintergrund, den ich ab sofort nur noch auf das Panel blitte. Das ausgewählte Objekt zeichne ich anschließend direkt oben drauf.
Der Hintergrund wird also eingefroren in einen Buffer und nur noch geblittet. Und nur noch das gerade verschobene Objekt wird "gezeichnet".
(Wenn es sich dabei um ein Bild handelt, kann man natürlich auch dieses nochmal in ein ExBufferedGraphics zeichnen und nur noch verschoben blitten. Aber für einfache Graphiken ist es überflüssig)
So wird auch das Verschieben von Objekte in komplexen Szenen wieder flüssig.


using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
using System.ComponentModel;

namespace System.Drawing
{
    /// <summary>
    /// Provides a graphics buffer for efficient drawing and blitting. 
    /// </summary>
    public sealed class ExBufferedGraphics : IDisposable
    {
        #region Fields
        private BufferedGraphics Buffer;
        private BufferedGraphicsContext Context;
        private bool IsDisposed = false;
        private Size _size = new Size(1, 1);
        #endregion

        const double Sqrt2 = 1.4142135;

        #region Properties
        /// <summary>
        /// Gets or sets the size, in pixels, of this Buffer.
        /// </summary>
        public Size Size
        {
            get
            {
                if (IsDisposed)
                    throw new ObjectDisposedException(this.ToString());
                return this._size;
            }
            set
            {
                if (IsDisposed)
                    throw new ObjectDisposedException(this.ToString());
                if (value.Width * value.Height == 0)
                    throw new ArgumentException();

                this._size = value;
                Size maximumSize = this.Context.MaximumBuffer;
                if (this.Size.Width > maximumSize.Width || this.Size.Height > maximumSize.Height)
                {
                    ReAlloc(new Size(
                        Math.Max((int)(maximumSize.Width * Sqrt2), this.Width),
                        Math.Max((int)(maximumSize.Height * Sqrt2), this.Height)));
                }
                this.Buffer.Graphics.Clip = new Region(new Rectangle(Point.Empty, this.Size));
            }
        }
        /// <summary>
        /// Gets a Graphics object that outputs to the graphics buffer.
        /// </summary>
        public Graphics Graphics
        {
            get
            {
                if (IsDisposed)
                    throw new ObjectDisposedException(this.ToString());
                return this.Buffer.Graphics;
            }

        }

        /// <summary>
        /// Gets the width, in pixels, of this Buffer.
        /// </summary>
        public int Width { get { return this.Size.Width; } }

        /// <summary>
        /// Gets the height, in pixels, of this Buffer.
        /// </summary>
        public int Height { get { return this.Size.Height; } }
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the ExBufferedGraphics class compatible with the specified Graphics object.
        /// </summary>
        /// <param name="g">Reference Graphics object</param>
        public ExBufferedGraphics(Graphics g)
        {
            this.Init(g);
        }

        /// <summary>
        /// Initializes a new instance of the ExBufferedGraphics class compatible with the specified Control object.
        /// </summary>
        /// <param name="g">Reference Control object</param>
        public ExBufferedGraphics(System.Windows.Forms.Control c)
        {
            using (Graphics g = c.CreateGraphics())
            {
                this.Init(g);
            }
        }
        #endregion

        #region BufferManagement
        private void Init(Graphics g)
        {
            Alloc(g, this.Size);
        }

        private void Alloc(Graphics g, Size s)
        {
            if (IsDisposed)
                throw new ObjectDisposedException(this.ToString());

            this.Context = new BufferedGraphicsContext();
            this.Context.MaximumBuffer = s;
            this.Buffer = this.Context.Allocate(g, new Rectangle(Point.Empty, s));
        }

        private void ReAlloc(Size s)
        {
            if (IsDisposed)
                throw new ObjectDisposedException(this.ToString());

            using (BufferedGraphicsContext oldContext = this.Context)
            using (BufferedGraphics oldBuffer = this.Buffer)
            {
                Alloc(oldBuffer.Graphics, s);
            }
        }
        #endregion

        #region Disposing
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        ~ExBufferedGraphics()
        {
            Dispose(false);
        }

        private void Dispose(bool disposing)
        {
            if (!IsDisposed)
            {
                if (disposing)
                {
                    if (this.Buffer != null)
                        this.Buffer.Dispose();
                    if (this.Context != null)
                        this.Context.Dispose();
                }
                IsDisposed = true;
            }
        }
        #endregion

        #region Rendering
        /// <summary>
        /// Writes the contents of the graphics buffer to the specified graphics object.
        /// </summary>
        /// <param name="g">A graphics object to which to write the contents of the graphics buffer.</param>
        public void Render(Graphics target)
        {
            Render(target, 0, 0);
        }

        /// <summary>
        /// Writes the contents of the graphics buffer to the specified graphics object at the specified location. 
        /// </summary>
        /// <param name="target">A graphics object to which to write the contents of the graphics buffer.</param>
        /// <param name="targetLocation">Point structure that represents the location of the upper-left corner of the drawn image.</param>
        public void Render(Graphics target, Point targetLocation)
        {
            Render(target, targetLocation.X, targetLocation.Y);
        }

        /// <summary>
        /// Writes the contents of a section of the graphics buffer to the specified Graphics object at the specified location. 
        /// </summary>
        /// <param name="target">A graphics object to which to write the contents of the graphics buffer.</param>
        /// <param name="targetLocation">Point structure that represents the location of the upper-left corner of the drawn image.</param>
        /// <param name="sourceSection">Rectangle structure that represents the section of the graphics buffer to be drawn.</param>
        public void Render(Graphics target, Point targetLocation, Rectangle sourceSection)
        {
            Render(target, targetLocation.X, targetLocation.Y, sourceSection.Width, sourceSection.Height, sourceSection.X, sourceSection.Y);
        }


        /// <summary>
        /// Writes the contents of the graphics buffer to the specified Graphics object at the location specified by a coordinate pair.
        /// </summary>
        /// <param name="target"></param>
        /// <param name="targetXLocation">The x-coordinate of the upper-left corner of the drawn image. </param>
        /// <param name="targetYLocation">The y-coordinate of the upper-left corner of the drawn image. </param>
        public void Render(Graphics target, int targetXLocation, int targetYLocation)
        {
            Render(target, targetXLocation, targetYLocation, 0, 0, this.Width, this.Height);
        }

        /// <summary>
        /// Writes the contents of a section of the graphics buffer to the specified Graphics object at the location specified by a coordinate pair.
        /// </summary>
        /// <param name="target"></param>
        /// <param name="targetXLocation">The x-coordinate of the upper-left corner of the drawn image. </param>
        /// <param name="targetYLocation">The y-coordinate of the upper-left corner of the drawn image. </param>
        /// <param name="sourceXLocation">The x-coordinate of the upper-left corner of the section of the graphics buffer that is drawn.</param>
        /// <param name="sourceYLocation">The y-coordinate of the upper-left corner of the section of the graphics buffer that is drawn.</param>
        /// <param name="width">The width of the section of the graphics buffer that is drawn.</param>
        /// <param name="height">The height of the section of the graphics buffer that is drawn.</param>
        public void Render(Graphics target, int targetXLocation, int targetYLocation, int sourceXLocation, int sourceYLocation, int width, int height)
        {
            RenderScaled(target, targetXLocation, targetYLocation, width, height, sourceXLocation, sourceYLocation, width, height, InterpolationMode.Invalid);
        }

        /// <summary>
        /// Writes and scales the contents of the graphics buffer into a section of the specified Graphics object. 
        /// </summary>
        /// <param name="target">A graphics object to which to write the contents of the graphics buffer.</param>
        /// <param name="targetSection">Rectangle structure that represents the section of the specified graphics where the image is drawn into.</param>
        /// <param name="interpolationMode">A InterpolationMode enumeration that specifies the interpolation mode for scaling.</param>
        public void RenderScaled(Graphics target, Rectangle targetSection, InterpolationMode interpolationMode)
        {
            RenderScaled(target, targetSection, new Rectangle(Point.Empty, this.Size), interpolationMode);
        }

        /// <summary>
        /// Writes and scales the contents of the graphics buffer into a section of the specified Graphics object. 
        /// </summary>
        /// <param name="target">A graphics object to which to write the contents of the graphics buffer.</param>
        /// <param name="targetXLocation">The x-coordinate of the upper-left corner of the drawn image.</param>
        /// <param name="targetYLocation">The y-coordinate of the upper-left corner of the drawn image.</param>
        /// <param name="targetWidth">The width of the drawn image.</param>
        /// <param name="targetHeight">The height of the drawn image.</param>
        /// <param name="interpolationMode">A InterpolationMode enumeration that specifies the interpolation mode for scaling.</param>
        public void RenderScaled(Graphics target, int targetXLocation, int targetYLocation, int targetWidth, int targetHeight, InterpolationMode mode)
        {
            RenderScaled(target, targetXLocation, targetYLocation, targetWidth, targetHeight, 0, 0, this.Width, this.Height, mode);
        }

        /// <summary>
        /// Writes and scales the contents of a section of the graphics buffer into a section of the specified Graphics object. 
        /// </summary>
        /// <param name="target">A graphics object to which to write the contents of the graphics buffer.</param>
        /// <param name="targetSection">Rectangle structure that represents the section of the specified graphics where the image is drawn into.</param>
        /// <param name="sourceSection">Rectangle structure that represents the section of the graphics buffer that is drawn.</param>
        /// <param name="interpolationMode">A InterpolationMode enumeration that specifies the interpolation mode for scaling.</param>
        public void RenderScaled(Graphics target, Rectangle targetSection, Rectangle sourceSection, InterpolationMode mode)
        {
            RenderScaled(
                target,
                targetSection.X, targetSection.Y, targetSection.Width, targetSection.Height,
                sourceSection.X, sourceSection.Y, sourceSection.Width, sourceSection.Height,
                mode);
        }

        /// <summary>
        /// Writes and scales the contents of a section of the graphics buffer into a section of the specified Graphics object. 
        /// </summary>
        /// <param name="target">A graphics object to which to write the contents of the graphics buffer.</param>
        /// <param name="targetXLocation">The x-coordinate of the upper-left corner of the drawn image.</param>
        /// <param name="targetYLocation">The y-coordinate of the upper-left corner of the drawn image.</param>
        /// <param name="targetWidth">The width of the drawn image.</param>
        /// <param name="targetHeight">The height of the drawn image.</param>
        /// <param name="sourceXLocation">The x-coordinate of the upper-left corner of the section of the graphics buffer that is drawn.</param>
        /// <param name="sourceYLocation">The y-coordinate of the upper-left corner of the section of the graphics buffer that is drawn.</param>
        /// <param name="sourceWidth">The width of the section of the graphics buffer that is drawn.</param>
        /// <param name="sourceHeight">The height of the section of the graphics buffer that is drawn.</param>
        /// <param name="interpolationMode">A InterpolationMode enumeration that specifies the interpolation mode for scaling.</param>
        public void RenderScaled(Graphics target,
            int targetXLocation, int targetYLocation, int targetWidth, int targetHeight,
            int sourceXLocation, int sourceYLocation, int sourceWidth, int sourceHeight, InterpolationMode mode)
        {
            if (IsDisposed)
                throw new ObjectDisposedException(this.ToString());
            if (target == null)
                throw new ArgumentNullException("target");

            using (DCHandle hdcTarget = new DCHandle(target))
            using (DCHandle hdcSource = new DCHandle(this.Graphics))
            {

                if (sourceWidth == targetWidth && targetHeight == sourceHeight)
                {
                    if(!BitBlt(
                        hdcTarget,
                        targetXLocation, targetYLocation, targetWidth, targetHeight,
                        hdcSource,
                        sourceXLocation, sourceYLocation,
                        TernaryRasterOperations.SRCCOPY))
                    {
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                    }
                }
                else
                {
                    SetStretchBltMode(hdcTarget, mode);
                    if(!StretchBlt(
                        hdcTarget,
                        targetXLocation, targetYLocation, targetWidth, targetHeight,
                        hdcSource,
                        sourceXLocation, sourceYLocation, sourceWidth, sourceHeight,
                        TernaryRasterOperations.SRCCOPY))
                    {
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                    }
                }
            }
        }

        private void SetStretchBltMode(DCHandle hdcTarget, InterpolationMode interpolationMode)
        {
            StretchMode sm;
            switch (interpolationMode)
            {
                case InterpolationMode.Bilinear:
                case InterpolationMode.Bicubic:
                case InterpolationMode.High:
                case InterpolationMode.HighQualityBilinear:
                case InterpolationMode.HighQualityBicubic:
                    sm = StretchMode.STRETCH_HALFTONE;
                    break;

                case InterpolationMode.Default:
                case InterpolationMode.Low:
                case InterpolationMode.NearestNeighbor:
                    sm = StretchMode.STRETCH_DELETESCANS;
                    break;

                default:
                    throw new ArgumentException("interpolationMode");
            }
            if (SetStretchBltMode(hdcTarget, sm) == StretchMode.INVALID)
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
        }

        #endregion

        #region Native GDI

        private class DCHandle : SafeHandle
        {
            Graphics graphics;
            internal DCHandle(Graphics graphics)
                : base(graphics.GetHdc(), true)
            {
                this.graphics = graphics;
            }

            protected override bool ReleaseHandle()
            {
                this.graphics.ReleaseHdc(this.handle);
                this.handle = IntPtr.Zero;
                return true;
            }

            public override bool IsInvalid { get { return IsClosed || handle == IntPtr.Zero; } }
        }

        [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
        static extern bool BitBlt(DCHandle hdc, int nXDest, int nYDest, int nWidth,
            int nHeight, DCHandle hdcSrc, int nXSrc, int nYSrc, TernaryRasterOperations dwRop);

        [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
        static extern bool StretchBlt(DCHandle hdcDest, int nXOriginDest, int nYOriginDest,
            int nWidthDest, int nHeightDest,
            DCHandle hdcSrc, int nXOriginSrc, int nYOriginSrc, int nWidthSrc, int nHeightSrc,
            TernaryRasterOperations dwRop);

        [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
        static extern StretchMode SetStretchBltMode(DCHandle hdc, StretchMode iStretchMode);

        enum StretchMode
        {
            INVALID = 0,
            STRETCH_ANDSCANS = 1,
            STRETCH_ORSCANS = 2,
            STRETCH_DELETESCANS = 3,
            STRETCH_HALFTONE = 4,
        }

        enum TernaryRasterOperations : uint
        {
            SRCCOPY = 0x00CC0020, /* dest = source*/
            SRCPAINT = 0x00EE0086, /* dest = source OR dest*/
            SRCAND = 0x008800C6, /* dest = source AND dest*/
            SRCINVERT = 0x00660046, /* dest = source XOR dest*/
            SRCERASE = 0x00440328, /* dest = source AND (NOT dest )*/
            NOTSRCCOPY = 0x00330008, /* dest = (NOT source)*/
            NOTSRCERASE = 0x001100A6, /* dest = (NOT src) AND (NOT dest) */
            MERGECOPY = 0x00C000CA, /* dest = (source AND pattern)*/
            MERGEPAINT = 0x00BB0226, /* dest = (NOT source) OR dest*/
            PATCOPY = 0x00F00021, /* dest = pattern*/
            PATPAINT = 0x00FB0A09, /* dest = DPSnoo*/
            PATINVERT = 0x005A0049, /* dest = pattern XOR dest*/
            DSTINVERT = 0x00550009, /* dest = (NOT dest)*/
            BLACKNESS = 0x00000042, /* dest = BLACK*/
            WHITENESS = 0x00FF0062, /* dest = WHITE*/
        };
        #endregion
    }
}

Schlagwörter: Double Buffering, GDI, BufferedGraphics, BitBlt, Graphics, DrawImage

beste Grüße
zommi

zommi Themenstarter:in
1.361 Beiträge seit 2007
vor 14 Jahren

So,

ich habe die Klasse gerade noch erweitert um eine Skalierungsfunktionalität. (Das macht beim Lupen-Control noch mehr Sinn)

Zum Rendern gibt es nun zwei Methoden-Klassen:

ExBufferedGraphics.Render(...)
ExBufferedGraphics.RenderScaled(...)

Die erste zeichnet den Buffer unverzerrt aber wenn gewünscht an eine andere Position.
Und die zweite kann den Buffer zusätzlichen noch stauchen und strecken.

Die erste arbeitet mit BitBlt und die zweite mit StretchBlt. (StretchBlt ist zwar immernoch schnell, aber natürlich langsamer als BitBlt.)
Für die Verzerrenden kann man noch den InterpolationMode festlegen (wie bei Graphics selbst auch).
Da aber StretchBlt nur wirklich Bilinear oder NearestNeighbor implementiert, werden

High, Bilinear, Bicubic, HighQualityBilinear, HighQualityBicubic

als Bilinear behandelt und

Default, Low, NearestNeighbor

als NearestNeighbor.

beste Grüße
zommi