12
votes

Setpixel est trop lent. Y a-t-il un moyen plus rapide de dessiner au bitmap?

J'ai un petit programme de peinture que je travaille sur. J'utilise SetPixel sur un bitmap pour faire ce dessin des lignes. Lorsque la taille de la brosse devient grande, comme 25 pixels à travers une chute de performances notable. Je me demande s'il y a un moyen plus rapide de dessiner dans un bitmap. Voici un peu de fond du projet:

  • J'utilise des bitmaps afin que je puisse utiliser des couches, comme dans Photoshop ou la GIMP. li>
  • Les lignes sont tracées manuellement car cela utilisera éventuellement une pression graphique de tablette pour modifier la taille de la ligne sur sa longueur. li>
  • Les lignes doivent éventuellement être anti-aliède / lissées le long des bords. li> ul>

    Je vais inclure mon code de dessin juste au cas où il s'agit de cela qui est lent et non le bit de pixel de jeu. p>

    Ceci est dans les fenêtres où la peinture se produit: p> xxx pré>


    Implémentation de Mousemove: P>

        public override void PaintPoint(Layer layer, Point position)
        {
            // Rasterise the pencil tool
    
            // Assume it is square
    
            // Check the pixel to be set is witin the bounds of the layer
    
                // Set the tool size rect to the locate on of the point to be painted
            m_toolArea.Location = position;
    
                // Get the area to be painted
            Rectangle areaToPaint = new Rectangle();
            areaToPaint = Rectangle.Intersect(layer.GetRectangle(), m_toolArea);
    
                // Check this is not a null area
            if (!areaToPaint.IsEmpty)
            {
                // Go through the draw area and set the pixels as they should be
                for (int y = areaToPaint.Top; y < areaToPaint.Bottom; y++)
                {
                    for (int x = areaToPaint.Left; x < areaToPaint.Right; x++)
                    {
                        layer.GetBitmap().SetPixel(x, y, m_colour);
                    }
                }
            }
        }
    


0 commentaires

5 Réponses :


1
votes

Juste une idée: remplissez un bitmap offscreen avec vos pixels de pinceau. Il vous suffit de régénérer ce bitmap lorsque la brosse, la taille ou la couleur est modifiée. Et puis tirez simplement ce bitmap sur votre bitmap existant, où se trouve la souris. Si vous pouvez moduler un bitmap avec une couleur, vous pouvez définir les pixels en niveaux de gris et le moduler avec la couleur de la brosse actuelle.


1 commentaires

Cela ressemble à une idée assez cool. Merci. Je suppose que si j'utilise la transparence, cela fonctionnerait lors du dessin sur les lignes existantes.



5
votes

SetPixel fait ceci: est verrouille l'image entière, définit le pixel et le déverrouille

Essayez de le faire: vous acquérez une serrure pour l'image en pleine mémoire avec des verrouilles, processus vous mettez à jour et relâchez la serrure après.

Lockbits


2 commentaires

3 ans plus tard, toujours aucun exemple


@ rr- c'est juste un long moment



3
votes

J'utilise habituellement un tableau pour représenter les données de pixels brutes. Puis copiez entre ce tableau et le bitmap avec un code dangereux.

Faire la matrice de Couleur est une mauvaise idée, car la couleur struct est relativement grande (12 octets +). Vous pouvez donc définir votre propre struct 4 octets (c'est celui que j'ai choisi) ou utilisez simplement une matrice de int ou octet . .

Vous devez également réutiliser votre tableau, car GC sur le LOH a tendance à être coûteux.

Mon code peut être trouvé à:

https://github.com/codesinchaos/chaosutil/blob/master /Chaos.image/

Une alternative écrit tout votre code à l'aide des pointeurs dans le bitmap directement. C'est un peu plus rapide encore, mais peut rendre le code plus laids et plus d'erreurs sujettes.


2 commentaires

Utilisez-vous un tableau de la taille de la cible Bitmap ou uniquement de la taille de la zone qui va être modifiée?


Dans mon application, j'ai utilisé un tableau de la même taille que le bitmap, mais je devais travailler sur l'ensemble du bitmap. Mais même si je ne travaillerais que sur une partie du bitmap, j'utiliserais toujours le tableau complet, mais éventuellement remplir seulement la partie dont j'ai besoin. De cette façon, je n'ai pas besoin d'allouer un nouveau tableau lorsque vous travaillez sur une section différente de l'image.



0
votes

Vous appelez GetBitmap dans votre boucle imbriquée. On dirait que ce n'est pas nécessaire, vous devriez getBitmap à l'extérieur de la boucle car la référence ne va pas changer.

Regardez également la réponse @fantasticfix, les verrouilles trient presque toujours les problèmes de performances lentes avec les pixels d'obtention / de réglage


0 commentaires

14
votes

Vous pouvez verrouiller les données bitmap et utiliser des pointeurs pour définir manuellement les valeurs. C'est beaucoup plus rapide. Bien que vous deviez utiliser un code dangereux.

public override void PaintPoint(Layer layer, Point position)
    {
        // Rasterise the pencil tool

        // Assume it is square

        // Check the pixel to be set is witin the bounds of the layer

        // Set the tool size rect to the locate on of the point to be painted
        m_toolArea.Location = position;

        // Get the area to be painted
        Rectangle areaToPaint = new Rectangle();
        areaToPaint = Rectangle.Intersect(layer.GetRectangle(), m_toolArea);

        Bitmap bmp;
        BitmapData data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
        int stride = data.Stride;
        unsafe
        {
            byte* ptr = (byte*)data.Scan0;
            // Check this is not a null area
            if (!areaToPaint.IsEmpty)
            {
                // Go through the draw area and set the pixels as they should be
                for (int y = areaToPaint.Top; y < areaToPaint.Bottom; y++)
                {
                    for (int x = areaToPaint.Left; x < areaToPaint.Right; x++)
                    {
                        // layer.GetBitmap().SetPixel(x, y, m_colour);
                        ptr[(x * 3) + y * stride] = m_colour.B;
                        ptr[(x * 3) + y * stride + 1] = m_colour.G;
                        ptr[(x * 3) + y * stride + 2] = m_colour.R;
                    }
                }
            }
        }
        bmp.UnlockBits(data);
    }


5 commentaires

Merci pour cela. Je pense que je vois ce qui se passe; Le PTR est un morceau de données bitmap contenant les valeurs de pixels rouges, vertes et bleues et les paramètre manuellement. Je vais donner ceci aujourd'hui et voir comment ça marche. Merci beaucoup pour cela, je sens que ma compréhension a avancé une autre NOTCH: P Juste une chose cependant, je pensais qu'un bitmap en C # a également un canal alpha. Je pensais que cela serait manipulé dans les informations de couleur pixel avec le RVB.


Vous devez spécifier le MSDN.MicRosoft.com/fr -US / Bibliothèque / ... Pour inclure un composant alpha. Il suffit de changer pixelformat.format24bppprgb à pixelformat.format32bppargb . Je crois que cela devrait être après r alors juste le compenser de +3. Si vous utilisez 32 bits par pixel, vous devez modifier x * 3 sur x * 4 car il sera 4 octets par pixel maintenant.


Je viens d'essayer ça. C'est charmant et rapide maintenant sur toutes les tailles de brosse! Cependant, il y a maintenant un comportement étrange: le dessin réel se produit à un décalage à l'endroit où la souris est à. Je me demande si c'est quelque chose à voir avec cette ligne ici: BitmapData Data = BMP.LOCKBITS (nouveau rectangle (ArreeOpaint.right, arèvage.bottom), ImagElockMode.Readwewrite, PixelFormat.Format24BPPRGB); Il doit y avoir une position spécifiée pour le rectangle. J'ai utilisé la valeur "Position" fournie. Est-ce correct?


La valeur pour le rectangle est la position du rectangle sur votre image ou un panneau. Vous utilisez la position de la souris. Si votre disposition d'image n'est pas centrée, vous pouvez simplement utiliser (0, 0) pour la position. Si elle est centrée et que votre image est plus grande que votre image, vous devez ajuster le décalage. Vous devriez aussi changer nouveau rectangle (arèwopaint.right, arèwopaint.bottom) à nouveau rectangle (0, 0, bmp.width, bmp.height) . Je pensais arèwopaint était les dimensions de l'image entière.


Est-il nécessaire d'utiliser un code dangereux pour faire cela? Impossible d'utiliser marshal.writebyte pour écrire à l'adresse de la mémoire du intPTR au lieu de la convertir en un octet * pour faire un pointeur dangereux opérations? Ou serait-ce plus lent?