[Mono-list] Progress on System.Drawing.Bitmap

J. Perkins jason@379.com
Tue, 25 Feb 2003 15:50:26 -0500


This is a multi-part message in MIME format.
--------------090606020101010705000500
Content-Type: text/plain; 
 charset=us-ascii; 
 format=flowed
Content-Transfer-Encoding: 7bit

I finally found some time to work on the Bitmap class, and have implemented:

   Bitmap(Stream)
   Width, Height
   PixelFormat
   LockBits  (partially - doesn't work on subsections yet)
   UnlockBits

Since the original version of Bitmap.cs only contained a few stubs, I 
thought it would be easier to just attach the whole file. Just replace 
the file with this version.

While the code works, it needs to convert from the sensible RGB layout 
to Microsoft's BGR convention. I implemented the conversion using the 
Marshal.ReadByte() and Marshal.WriteByte() but it's *slow*. Hopefully 
GDK will add a BGR colorspace in the future.

If someone wants to provide me with cvs write access, I can apply all of 
my future changes there. Otherwise I will just send patches to the list.

Thanks!

Jason
379

--------------090606020101010705000500
Content-Type: text/plain; 
 charset=us-ascii; 
 name=Bitmap.cs
Content-Transfer-Encoding: 7bit
Content-Disposition: inline; 
 filename=Bitmap.cs

//
// System.Drawing.Bitmap.cs
//
// (C) 2002 Ximian, Inc.  http://www.ximian.com
//
// Authors: 
//   Jason Perkins (jason@379.com)
//
// The first step is to get a (possibly slow) portable solution, implementing
// everything in C#. Once that is up and running, native calls can be swapped
// in to improve performance. 
//
// I am going to use GdkPixBuf internally for now, but this will have to be
// converted to the MS BITMAP format in order to work with the Forms code.
//

using System;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;

namespace System.Drawing 
{
	public sealed class Bitmap : Image
	{
		[DllImport("gdk_pixbuf-2.0")] 
		static extern int gdk_pixbuf_get_width(IntPtr pixbuf);
		
		[DllImport("gdk_pixbuf-2.0")] 
		static extern int gdk_pixbuf_get_height(IntPtr pixbuf);

		[DllImport("gdk_pixbuf-2.0")] 
		static extern int gdk_pixbuf_get_has_alpha(IntPtr pixbuf);
		
		[DllImport("gdk_pixbuf-2.0")] 
		static extern int gdk_pixbuf_get_rowstride(IntPtr pixbuf);
		
		[DllImport("gdk_pixbuf-2.0")] 
		static extern IntPtr gdk_pixbuf_get_pixels(IntPtr pixbuf);
		
		[DllImport("gdk_pixbuf-2.0")] 
		static extern IntPtr gdk_pixbuf_loader_new();
		
		[DllImport("gdk_pixbuf-2.0")]
		static extern int gdk_pixbuf_loader_close(IntPtr loader, IntPtr error);

		[DllImport("gdk_pixbuf-2.0")]
		static extern int gdk_pixbuf_loader_write(IntPtr loader, byte[] buffer, int size);
		
		[DllImport("gdk_pixbuf-2.0")]
		static extern IntPtr gdk_pixbuf_loader_get_pixbuf(IntPtr loader);
		
		[DllImport("gdk_pixbuf-2.0")]
		static extern void gdk_pixbuf_ref(IntPtr pixbuf);
		
		[DllImport("gdk_pixbuf-2.0")]
		static extern void gdk_pixbuf_unref(IntPtr pixbuf);
		
		[DllImport("gobject-2.0")]
		static extern void g_object_unref(IntPtr obj);
		
		[DllImport("gtk-x11-2.0")] 
		static extern void gtk_init(IntPtr argc, IntPtr argv);
		
		private IntPtr pixbuf;
			
		//---------------------------------------------------------------------	

		#region constructors

		static Bitmap()
		{
			// The gdk_pixbuf functions require gtk_init() to be called first.
			// This doesn't seem to interfere with Gtk#, though it could be I
			// just didn't test it thoroughly enough. Will investigate more.

			gtk_init(new IntPtr(0), new IntPtr(0));
		}
		
		//---------------------------------------------------------------------	

		[MonoTODO]
		public Bitmap (Image original) 
		{
			throw new NotImplementedException ();
		}

		//---------------------------------------------------------------------	

		public Bitmap (Stream stream) 
		{
			IntPtr loader = gdk_pixbuf_loader_new();
		
			byte[] buffer = new byte[stream.Length];
			int length = stream.Read(buffer, 0, (int)stream.Length);
			
			if (gdk_pixbuf_loader_write(loader, buffer, length) == 0)
				throw new ArgumentException("Not a valid image file.");
			
			gdk_pixbuf_loader_close(loader, new IntPtr(0));
			
			pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
			gdk_pixbuf_ref(pixbuf);
			
			g_object_unref(loader);
			
			// Now the image is loaded, but it is (A)BGR instead of 
			// (A)RGB. Convert it now (slow!) (really slow!)
			
			IntPtr pixels = gdk_pixbuf_get_pixels(pixbuf);
			int    width  = gdk_pixbuf_get_width(pixbuf);
			int    height = gdk_pixbuf_get_height(pixbuf);
			int    stride = gdk_pixbuf_get_rowstride(pixbuf);
			
			int pixelSize = (gdk_pixbuf_get_has_alpha(pixbuf) != 0) ? 4 : 3;

			int rowOffset = 0;
			for (int row = 0; row < height; ++row)
			{
				byte red, blue;
				for (int offset = rowOffset; offset < rowOffset + stride; offset += pixelSize)
				{
					red  = Marshal.ReadByte(pixels, offset);
					blue = Marshal.ReadByte(pixels, offset + 2);
					
					Marshal.WriteByte(pixels, offset, blue);
					Marshal.WriteByte(pixels, offset + 2, red);
				}
				
				rowOffset += stride;
			}
		}

		//---------------------------------------------------------------------	

		[MonoTODO]
		public Bitmap (string filename) 
		{
			throw new NotImplementedException ();
		}

		//---------------------------------------------------------------------	

		[MonoTODO]
		public Bitmap (Image original, Size newSize) 
		{
			throw new NotImplementedException ();
		}

		//---------------------------------------------------------------------	

		[MonoTODO]
		public Bitmap (int width, int height) 
		{
			throw new NotImplementedException ();
		}

		//---------------------------------------------------------------------	

		[MonoTODO]
		public Bitmap (Stream stream, bool useIcm) 
			: this(stream)
		{
			// For now I'm ignoring useIcm
		}

		//---------------------------------------------------------------------	

		[MonoTODO]
		public Bitmap (string filename, bool useIcm)
			: this(filename)
		{
			// For now I'm ignoring useIcm
		}

		//---------------------------------------------------------------------	

		[MonoTODO]
		public Bitmap (Type type, string resource) 
		{
			throw new NotImplementedException ();
		}

		//---------------------------------------------------------------------	

		[MonoTODO]
		public Bitmap (Image original, int width, int heigth) 
		{
			throw new NotImplementedException ();
		}

		//---------------------------------------------------------------------	

		[MonoTODO]
		public Bitmap (int width, int height, Graphics g) 
		{
			throw new NotImplementedException ();
		}

		//---------------------------------------------------------------------	

		[MonoTODO]
		public Bitmap (int width, int heigth, PixelFormat format) 
		{
			throw new NotImplementedException ();
		}

		//---------------------------------------------------------------------	

		[MonoTODO]
		public Bitmap (int width, int height, int stride, PixelFormat format, IntPtr scan0) 
		{
			throw new NotImplementedException ();
		}

		#endregion

		//---------------------------------------------------------------------	

		[MonoTODO]
		public Bitmap Clone (Rectangle rect, PixelFormat format) 
		{
			throw new NotImplementedException ();
		}

		[MonoTODO]
		public Bitmap Clone (RectangleF rect, PixelFormat format) 
		{
			throw new NotImplementedException ();
		}

		//---------------------------------------------------------------------	

		public override void Dispose()
		{
			if (pixbuf != IntPtr.Zero)
			{
				gdk_pixbuf_unref(pixbuf);
				pixbuf = IntPtr.Zero;
			}
			
			base.Dispose();
		}
		
		//---------------------------------------------------------------------	

		[MonoTODO]
		public static Bitmap FromHicon (IntPtr hicon) 
		{
			throw new NotImplementedException ();
		}

		//---------------------------------------------------------------------	

		[MonoTODO]
		public static Bitmap FromResource (IntPtr hinstance, string bitmapName) 
		{
			throw new NotImplementedException ();
		}

		//---------------------------------------------------------------------	

		[MonoTODO]
		public IntPtr GetHbitmap () 
		{
			throw new NotImplementedException ();
		}

		[MonoTODO]
		public IntPtr GetHbitmap (Color background) 
		{
			throw new NotImplementedException ();
		}

		//---------------------------------------------------------------------	

		[MonoTODO]
		public IntPtr GetHicon () 
		{
			throw new NotImplementedException ();
		}

		//---------------------------------------------------------------------	

		[MonoTODO]
		public Color GetPixel (int x, int y) 
		{
			throw new NotImplementedException ();
		}

		//---------------------------------------------------------------------	

		public override int Height
		{
			get 
			{
				if (pixbuf == IntPtr.Zero)
					throw new NullReferenceException();
				
				return gdk_pixbuf_get_height(pixbuf);
			}
		}
		
		//---------------------------------------------------------------------	

		[MonoTODO]
		public BitmapData LockBits (Rectangle rect, ImageLockMode flags, PixelFormat format) 
		{
			if (pixbuf == IntPtr.Zero)
				throw new NullReferenceException();
			
			if (rect.Left != 0 || rect.Top != 0 || rect.Width != Width || rect.Height != Height)
				throw new NotImplementedException("Rectangle is not implemented");
			
			if ((flags & ImageLockMode.UserInputBuffer) != 0)
				throw new NotImplementedException("UserInputBuffer not implemented");
			
			// Make sure 'rect' fits within the image
			
			if (rect.Left < 0 || rect.Top < 0 || rect.Right > Width || rect.Bottom > Height)
				throw new ArgumentException("Rectangle is out of range");

			BitmapData data = new BitmapData();
			data.Width       = rect.Width;
			data.Height      = rect.Height;
			data.PixelFormat = this.PixelFormat;
			data.Stride      = gdk_pixbuf_get_rowstride(pixbuf);
			data.Scan0       = gdk_pixbuf_get_pixels(pixbuf);
			
			return data;
		}

		//---------------------------------------------------------------------	

		[MonoTODO]
		public void MakeTransparent () 
		{
			throw new NotImplementedException ();
		}

		[MonoTODO]
		public void MakeTransparent (Color transparentColor) 
		{
			throw new NotImplementedException ();
		}

		//---------------------------------------------------------------------	

		[MonoTODO]
		public override PixelFormat PixelFormat
		{
			get 
			{
				if (pixbuf == IntPtr.Zero)
					throw new NullReferenceException();
				
				if (gdk_pixbuf_get_has_alpha(pixbuf) != 0)
					return PixelFormat.Format32bppArgb;
				else
					return PixelFormat.Format24bppRgb;
			}
		}
		
		//---------------------------------------------------------------------	

		[MonoTODO]
		public void SetPixel (int x, int y, Color color) 
		{
			throw new NotImplementedException ();
		}

		//---------------------------------------------------------------------	

		[MonoTODO]
		public void SetResolution (float xDpi, float yDpi) 
		{
			throw new NotImplementedException ();
		}

		//---------------------------------------------------------------------	

		public void UnlockBits (BitmapData bitmapdata) 
		{
		}

		//---------------------------------------------------------------------	

		public override int Width
		{
			get 
			{
				if (pixbuf == IntPtr.Zero)
					throw new NullReferenceException();
				
				return gdk_pixbuf_get_width(pixbuf);
			}
		}
		
		//---------------------------------------------------------------------	
	}
}

--------------090606020101010705000500--