[c#] Problème de manipulation d'images

Bonjour,

Je suis face à un problème que je ne comprends pas vraiment et j’espère que vous pourrez m’apporter quelques pistes.
Je développe une application en C# sous le framework .NET 2.0.

Mon appli reçoit une image en évènement et je m’occupe de la nouvelle image reçue à chaque évènement.
L’image que je reçois est dans un tableau de pixels (int[,]). La taille pourra variée plus tard mais pour l’instant
je reçois des images de 512*512. Voici le code que j’exécute (et qui ne me semble pas optimal d’ailleurs)

[code] private int indice = 0;

private void Scanner_DataChanged(object sender, newimageevent ev)
{
//ev.data est mon tableau de pixels
int[] tab = new int[ev.data.Length];
int x = ev.data.GetLength(0);
int y = ev.data.GetLength(1);
int index=0;
for (int i = 0; i < x; i++)
{
for (int j = 0; j < y; j++)
{
tab[index++] = ev.data[i, j];
}
}

   IntPtr dest = Marshal.AllocHGlobal(tab.Length);
   Bitmap b = new Bitmap(512, 512, 1024, System.Drawing.Imaging.PixelFormat.Format16bppGrayScale, dest);

   b.Save("c:\\test" + indice++ + ".jpg"); // ici external exception

}[/code]
J’obtiens une external exception au moment de la sauvegarde.

Si toutefois je tente d’afficher mon image dans une form (au lieu de la sauvegarder)

Form f = new Form(); f.BackgroudImage = b; Application.Run(f);
J’obtiens une out of memory exception

Je précise que l’erreur apparaît au premier évènement généré

L’un d’entre vous a t-il une idée ?

Merci

Pourquoi tu fait pas tout simplement une image de la bonne taille, que tu remplis a coups de Bitmap.SetPixel() ?
Ca me semble un peu overkill de passer par un AllocHGlobal (qui doit etre manipulé avec precaution, puisqu’il faut penser a delete les datas apres).
Moi je regarderais de ce coté, sachant qu’en unsafe, tu peux passer direct par un BitmapData pour set les pixels encore plus vite. Je pourrais te poster le code demain si ca t’interesse.
Si tu veux creuser un peu plus, tu peux aussi configurer un serveur de symbole pour jumper direct dans le code des assembly MS.

j’suis pas spécialiste du C# donc peut etre a coté de la plaque, mais:

Me semblait que t’as 2 dimensions dans tab (vu que tu dois faire 2 getlength).
Cette fonction gère sans souci les dimensions multiple ou la ca fait comme en Cplouchplouch et ca va te prendre ta dimension 0 ?

Ca me parait bien sale comme maniere de faire. BitmapData me semble la maniere appropriee de faire ce genre de choses.

Salut les gars.

Merci pour vos réponses, elles m’éclairent un peu.
Je savais effectivement que ma méthode était très sale, en plus elle ne marche pas…

Je vais regarder du côté de BitmapData alors et si tu veux me poster un morceau de code pour m’éclairer, c’est avec plaisir que je m’en inspirerai.

A +

Cree ton Bitmap avant, chope la structure bitmapdata du bitmap en question, fais ta loop et fais le set des bits en memoire, unlock ton BitmapData, sauve ton bitmap. Utilise des try/finally et aussi Bitmap est IDisposable, Dispose le quand t’as fini avec.

PS: Et oublie pas d’utiliser le Stride et pas d’assumer que tout est sequentiel en memoire.

[quote=“Berzehk, post:3, topic: 49833”]j’suis pas spécialiste du C# donc peut etre a coté de la plaque, mais:

Me semblait que t’as 2 dimensions dans tab (vu que tu dois faire 2 getlength).
Cette fonction gère sans souci les dimensions multiple ou la ca fait comme en Cplouchplouch et ca va te prendre ta dimension 0 ?[/quote]

Merci pour ta réponse mais les getLength sont faits sur ev.data. “tab” est n’a qu’une dimension donc je suis pas embêté de ce côté là. Par contre si je passe à AllocHGlobal mon ev.data, j’aurais tout simplement une erreur de compilation.

La classe que j’utilise, c’est celle la :

[codebox]
namespace Editor.Tools
{
#region Using Directives

using System.Drawing;
using System.Drawing.Imaging;
using System.Security.Permissions;


#endregion

[SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
public unsafe class FastBitmap
{
	private bool m_locked;
	private int m_width;
	private int m_height;

	private Bitmap m_bitmap;
	private BitmapData m_bitmapData;

	private Rectangle m_rect;

	private int* m_baseAddress = null;

	public FastBitmap(Bitmap image)
	{
		m_width = image.Width;
		m_height = image.Height;

		m_bitmap = image;

		m_rect = new Rectangle(0, 0, m_width, m_height);
	}

	public void Lock()
	{
		if (m_locked)
			return;

		m_locked = true;

		m_bitmapData = m_bitmap.LockBits(m_rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
		m_baseAddress = (int*) m_bitmapData.Scan0.ToPointer();
	}

	public void UnLock()
	{
		if (!m_locked)
			return;

		m_locked = false;

		m_bitmap.UnlockBits(m_bitmapData);
		m_bitmapData = null;
		m_baseAddress = null;
	}

	public Color GetPixel(int x, int y)
	{
		return Color.FromArgb(*( m_baseAddress + x + y * m_width ));
	}

	public void SetPixel(int x, int y, Color color)
	{
		*( m_baseAddress + x + y * m_width ) = color.ToArgb();
	}
}

}
[/codebox]

et elle s’utilise de la facon suivante :

[code]FastBitmap fb = new FastBitmap(bitmap);
fb.Lock();

for (int i = 0; i < Width; i++)
for (int j = 0; j < Height; j++)
{
// ton traitement a base de fb.set/get pixel ici.
}

fb.UnLock();[/code]

Ha, et ca va marcher qu’avec des bitmap encodés en ARGB32 (ou RGBA, je sais jamais). Avec des trucs en niveaux de gris et tout, c’est plus relou. La, c’est la version ultra optimisée pour notre usage.

Merci beaucoup pour le code.
Par contre (et c’est bien ma veine!!), je reçois des images en niveau de gris…
Tu dis que c’est beaucoup plus reloud dans ce cas là, pour quelles raisons ? Est ce parce que tu ne peux pas utiliser la méthode “Color.FromArgb” ou bien est-ce plus subtil ?

Bah, tu peux plus utiliser la methode Color.FromArgb ouais, et faut que tu prennes en compte le Stride comme disait Glop, parce que la, les bytes pourront etre décalés. (Ici, dans mon cas, il ne le sont jamais).
Par contre, au lieu de chopper un pointeur sur un int (Color est codé sur un int, donc je me suis permis de taper direct sur ce pointeur la), tu peux te contenter de taper sur un byte*, et ca fera tout aussi bien l’affaire. Un coup de google pourra te renseigner un peu plus sur l’utilisation de BitmapData.

Salut à tous,

Merci AnA-I, la classe et les explications que tu m’as fournies m’ont permis d’avancer un peu et de comprendre (après
avoir laissé tomber quelques temps…)
L’image que je reçois est en 16 bit et niveaux de gris. Après des dizaines d’exceptions reçues, je me suis rendu compte
que le Format16BppGrayscale n’est tout simplement pas supporté par gdi.
Qu’à cela ne tienne, j’ai converti mon image en une image 8 bit indexé (Format8BppIndexed) mais du coup je me retrouve
avec une image en couleur… J’ai vu qu’il fallait modifier toutes les valeurs de la palette pour repasser en niveaux
de gris mais ça ne marche pas. Le code que j’utilise est simplement celui ci :

ColorPalette pal = bitmap.Palette; for (int i = 0; i < pal.Entries.Length; i++) pal.Entries[i] = Color.FromArgb(i, i, i); bitmap.Palette = pal;

Quelqu’un voit-il pourquoi la modification de la palette ne convertit pas mon image en niveau de gris… ?

En fait, c’était un peu plus compliqué que ça. Il ne fallait pas uniquement modifié la palette de l’image. Il fallait créer une nouvelle image avec une palette définie telle que je l’avais fait. En voici le code pour les curieux :

[code]
static unsafe Bitmap ConvertToMonochrome(Bitmap bmColor)
{
int cx = bmColor.Width;
int cy = bmColor.Height;

		// Create a new 8-bit indexed bitmap of the same size

		Bitmap bmMono = new Bitmap(cx, cy, PixelFormat.Format8bppIndexed);
		bmMono.SetResolution(bmColor.HorizontalResolution,
							 bmColor.VerticalResolution);

		// Set the palette for gray shades

		ColorPalette pal = bmMono.Palette;

		for (int i = 0; i < pal.Entries.Length; i++)
			pal.Entries[i] = Color.FromArgb(i, i, i);

		bmMono.Palette = pal;

		// Because SetPixel won't work with index bitmaps, we need
		//  direct access to the bits

		BitmapData bmd = bmMono.LockBits(
			new Rectangle(Point.Empty, bmMono.Size),
			ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);

		// Scan through the pixels, converting color to B&W

		Byte* pPixel = (Byte*)bmd.Scan0;

		for (int y = 0; y < cy; y++)
		{
			for (int x = 0; x < cx; x++)
			{
				Color clr = bmColor.GetPixel(x, y);
				Byte byPixel = (byte)((30 * clr.R + 59 * clr.G + 11 * clr.B) / 100);
				pPixel[x] = byPixel;
			}
			pPixel += bmd.Stride;
		}

		bmMono.UnlockBits(bmd);

		return bmMono;
	}[/code]

Et pour l’info, j’ai trouvé ma réponse ici