ThinkGeo.com    |     Documentation    |     Premium Support

Adding GDI+ Run-time Images On Layers

Hello,


I'm trying to add run-time GDI+ images on layers, but I haven't been successful to this point.  From the documentation, it appears that GdiPlusRasterSource and GdiPlusRasterLayer are the way to go.  What I did to this point is created a derived class from the GdiPlusRasterLayer class to allow setting the ImageSource property.  I would then set the ImageSource to a GdiPlusRasterSource instance that calls the StreamLoading event which creates a memorystream from my GDI+ Bitmap.  However, I keep getting a "Parameter not valid" exception when the WinformsMap attempts to refresh.


Here is the code:



public partial class MainForm : Form
    {
        // ...

        private void MainForm_Load(object sender, EventArgs e)
        {
            // ...

            // Create the raster source and handle the stream loading
            GdiPlusRasterSource gdiSource = new GdiPlusRasterSource("doesNotExist.bmp");
         
            // Removed gator brackets in case it causes formatting issues
            gdiSource.StreamLoading += new EventHandler[StreamLoadingEventArgs](gdiSource_StreamLoading);

            // Create the custom GDI raster layer and set the 
            // image source
            MyGdiRasterLayer gdiLayer = new MyGdiRasterLayer();
            gdiLayer.ImageSource = gdiSource;

            // Add the custom raster layer to the dynamic layers
            this.winformsMap.DynamicOverlay.Layers.Add(gdiLayer);

            // Refresh the WinformsMap
            this.winformsMap.Refresh();
        }

        // ...

        private void gdiSource_StreamLoading(object sender, StreamLoadingEventArgs e)
        {
            // Create a simple bitmap and color it purple
            Bitmap bitmap = new Bitmap(100, 100);
            using (Graphics g = Graphics.FromImage(bitmap))
            {
                g.Clear(Color.Purple);
            }

            // Lock the bits of the bitmap
            BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height),
                                                    ImageLockMode.ReadWrite,
                                                    PixelFormat.Format32bppArgb);

            // Copy the bytes of the bitmap to a byte array
            byte[] bitmapBytes = new byte[bitmapData.Stride * bitmapData.Height];
            Marshal.Copy(bitmapData.Scan0, bitmapBytes, 0, bitmapBytes.Length);

            // Unlock the bitmap bits
            bitmap.UnlockBits(bitmapData);

            // Create a memory stream of the bitmap bytes
            MemoryStream bitmapStream = new MemoryStream(bitmapBytes);

            // Set the alternate stream to the bitmap stream
            e.AlternateStream = bitmapStream;
            e.AlternateStreamName = "NewStream";
            e.FileAccess = System.IO.FileAccess.Read;
            e.FileMode = System.IO.FileMode.Open;
        }

        // ...
    }

    public class MyGdiRasterLayer : GdiPlusRasterLayer
    {
        public MyGdiRasterLayer()
            : base()
        {
        }

        public new RasterSource ImageSource
        {
            get
            {
                return (base.ImageSource);
            }

            set
            {
                base.ImageSource = value;
            }
        }
    }


 


Any help would be appreciated, as this is the main purpose for my using this library.


Thanks for your time,


Phil



Phil, 
  
 Thanks for joining the community. hope you enjoy the learning and sharing here. 
  
 We recreated your problem and found the "bitMapStream" you made is not correct. Here is one line of code I added in gdiSource_StreamLoading method which shows the exception. 
  
 

//…

// Create a memory stream of the bitmap bytes
MemoryStream bitmapStream = new MemoryStream(bitmapBytes);

// I added this code which will throw that exception.
Bitmap testBitmap = new Bitmap(bitmapStream);

//…
 
  
 So can you double check the way you recrated that bitmap stream? 
  
 Thanks, 
  
 Ben 


I apologize for that, I guess I should have checked that myself.  However, when I do create a valid stream, I now get an ArgumentException that states “Word file should has 6 lines” (which isn’t very good English to be honest, hehe). 
  
 This is how I created the valid stream: 
 
            MemoryStream bitmapStream = new MemoryStream();

            bitmap.Save(bitmapStream, ImageFormat.Bmp);
 
  
 Any ideas what this error implies?  
  
 Thanks for your timely response, 
 Phil

Hi Phil,


I had that same problem, the event asking for a stream gets called twice.  Once is for the image stream and the other is for the world file.  Check the AlternateStreamName on the EventArgs to see which stream is needed, look at the extension.  Your excpetion is happening when the event is being called for the World File your method is giving it a stream of the bitmap.


Brian



Thanks for your sharing, Brian. 


Phil, I think the problem is exactly what Brian described. Following is the sample codes you can take a reference, I used the latest DesktopEdition(3.0.307 RC1).

        private void gdiSource_StreamLoading(object sender, StreamLoadingEventArgs e)
        {
            if (e.AlternateStreamName.Contains(".BMP"))
            {
                // load image stream
                // ...
            }
            else if (e.AlternateStreamName.Contains(".BPW"))
            {
                // load world file stream
                // ...
            }
        }

 
Let me know if you have more problems.
 
Yale
 

Thanks for the help, but I'm still having a problem.  I have corrected the issue with the StreamLoading event, where I only set the AlternateStream on the first pass, but now I get a "FileNotFoundException:  The file specified does not exist" during the Refresh call.


When I look in the view detail of the exception, it has the following information:  "The file specified does not exist.":".BMP|.GIF|.EXIG|.JPG|.PNG|.TFw".


Anymore ideas?



Hi Phil


You mention only setting it on the first pass, does that mean you are only setting one of the streams?  I believe you need to set both the image stream and the world file stream.  Without the world file, MapSuite won't know where to display the image, so I believe you need to provide it.  If your image source doesn't have world file if you know the upper left point and the image resolution in map units you can create the world file to pass in.  I had to do this with my custom raster source to wrap an image class we use in a custom storage system.


From Wikipedia en.wikipedia.org/wiki/World_file


World files do not specify a coordinate system, so the generic meaning of world file parameters are:



        
  • Line 1: A, pixel size in the x-direction in map units/pixel

  •     
  • Line 2: D: rotation about y-axis

  •     
  • Line 3: B: rotation about x-axis

  •     
  • Line 4: E: pixel size in the y-direction in map units, almost always negative[3]

  •     
  • Line 5: Cx-coordinate of the center of the upper left pixel

  •     
  • Line 6: Fy-coordinate of the center of the upper left pixel



A UTM Example where 1 pixel = 32 meters
32.0
0.0
0.0
-32.0
691200.0
4576000.0

 Here is a code example to create the world file stream from variables



public void SaveToStream(Stream stream, bool closeStreamWhenFinished)
        {
            // Nothing to save if embedded
            if( IsEmbedded )
                return;

            StreamWriter writer = null;
            try
            {
                writer = new StreamWriter(stream);

                // Write 6 lines.
                writer.WriteLine(PixelSizeX);
                writer.WriteLine(RotationX);
                writer.WriteLine(RotationY);
                writer.WriteLine(PixelSizeY);
                writer.WriteLine(UtmX);
                writer.Write(UtmY);

                //Flush the writer in case we don't close it
                writer.Flush();

                //Reset the stream position
                if( stream.CanSeek )
                    stream.Position = 0;
            }
            catch( Exception exc )
            {
                throw exc;
            }
            finally
            {
                if( writer != null && closeStreamWhenFinished )
                    writer.Close();
            }
        }


Thanks for your sharing, Phil. 


Brian, I think it is exactly what Phil mentioned above. Besides, there is another way to use if you do not want to use World File, passed in the “BoundingBox” for the Raster Image in the constructor of GdiPlusRasterSource:
 

// Create the raster source and handle the stream loading
GdiPlusRasterSource gdiSource = new GdiPlusRasterSource("doesNotExist.bmp", new RectangleShape(-180, 90, 180, -90));

 

Any queries just let me know.
 
Yale