ThinkGeo.com    |     Documentation    |     Premium Support

Adding transparency to complex area style?

Hi,


I've built a series of classes to handle complex styling as read from a textual representation in a database. The classes are utilized on top of each other, so a single style may consists of multiple classes deferring control to lower level classes. This "daisy-chain" may be 4 classes deep.


This work as expected.


However, I'd now like to add transparency to such area styles, and do it on top level only, so I don't have to implement it as a hack in classes at all levels.


Is this possible, and if so, how ? What are the caveats ?


An AreaStyle object contain both a FillSolidBrush, an Advanced.FillCustomBrush and a collection named CustomAreaStyles. Either of these may be in use, depending on the actual style constructed.


Would it be feasable to replace the Color component in either/all of these, adding transparency to it ?


(The original styles does not include transparency at all.)


TIA


 



HI again,


I created this function designed to alter an AreaStyle, adding transparency to all colors found.


However it doesn't work :-(



    Private Function AddTransparency(ByRef oldStyle As AreaStyle, ByVal alphaValue As Integer, ByVal canvas As ThinkGeo.MapSuite.Core.GeoCanvas) As AreaStyle

        Dim newStyle As AreaStyle = oldStyle.CloneDeep

        If newStyle.FillSolidBrush IsNot Nothing Then
            newStyle.FillSolidBrush.Color = New GeoColor(alphaValue, newStyle.FillSolidBrush.Color)
        End If

        If newStyle.Advanced.FillCustomBrush IsNot Nothing Then
            Dim brs = newStyle.Advanced.FillCustomBrush

            If TypeOf brs Is GeoSolidBrush Then
                Dim b2 As GeoSolidBrush = brs
                b2.Color = New GeoColor(alphaValue, b2.Color)
                newStyle.Advanced.FillCustomBrush = b2

            ElseIf TypeOf brs Is GeoTextureBrush Then
                Dim b2 As GeoTextureBrush = brs
                'no colors, but make image semi-transparent by making each pixel color semi-transparent
                If canvas IsNot Nothing Then
                    Dim img = b2.GeoImage.GetImageStream(canvas)
                    Dim new_img As New System.Drawing.Bitmap(img) 'cloned

                    For y = 0 To new_img.Height - 1
                        For x = 0 To new_img.Width - 1
                            Dim clr = new_img.GetPixel(x, y)
                            new_img.SetPixel(x, y, Color.FromArgb(alphaValue, clr))
                        Next
                    Next

                    Dim ms = New MemoryStream()
                    new_img.Save(ms, System.Drawing.Imaging.ImageFormat.Tiff) 'must be TIFF (see: GeoImage(stream))

                    newStyle.Advanced.FillCustomBrush = New GeoTextureBrush(New GeoImage(ms))
                End If

            ElseIf TypeOf brs Is GeoHatchBrush Then
                Dim b2 As GeoHatchBrush = brs
                b2.ForegroundColor = New GeoColor(alphaValue, b2.ForegroundColor)
                b2.BackgroundColor = New GeoColor(alphaValue, b2.BackgroundColor)
                newStyle.Advanced.FillCustomBrush = b2

            ElseIf TypeOf brs Is GeoLinearGradientBrush Then
                Dim b2 As GeoLinearGradientBrush = brs
                b2.StartColor = New GeoColor(alphaValue, b2.StartColor)
                b2.EndColor = New GeoColor(alphaValue, b2.EndColor)
                newStyle.Advanced.FillCustomBrush = b2

            End If
        End If

        For Each cas In newStyle.CustomAreaStyles
            newStyle = AddTransparency(newStyle, alphaValue, canvas)
        Next

        Return newStyle

    End Function

Suggestions for improvement (if at all possible) will be greatly appreciated.


 



BTW:


Why is "canvas" necessary when extracting the bytes in a GeoImage stream ?


I.e. in GeoTextureBrush.GeoImage.GetImageStream(canvas)


TIA


 



 Lars,


 
  This is a bit tricky and depending on your code it could be accomplished using a few different techniques.  The one thing to consider is the effect that you want in the end.  It is a different result if all of the polygons styles you draw are transparent and then composed versus drawing them as solids first then making the final result transparent.  
 
  One way I might accomplish this is inside of the highest level style before the big loop where you go through all the features create a custom GeoCanvas that inherits from the GdiPlusGeoCanvas.  Inside your custom geocanvas you override the core methods for drawing that takes the GeoColor.  Inside the overload you clone the geocolor and change the alpha of it then call into the base function to do all the work.  You pass this geocanvas into the lower level classes so they draw on this.  After all of the drawing you call the EndDrawing on the GeoCanvas and then you superimpose the bitmap of the styles that just drew onto the regular geocanvas.  Just an idea.
 
  Another way would be to modify all of your lower level classes to take a master transparency value and they use that to tweak the geocolor when they go to draw.  This might be a bit time consuming.
 
Included below is some sample code of the first way.  I ran it and it seems to do what I want as long as the sub styles all use the temp canvas to draw everything will be transparent.  I hope you can see my meaning.  I originally wrote it in CSharp, included as well as a vb.net translation.
 
David
 


Public Class TransparentStyle
Inherits ThinkGeo.MapSuite.Core.Style

Protected Overrides Sub DrawCore(features As System.Collections.Generic.IEnumerable(Of Feature), canvas As GeoCanvas, labelsInThisLayer As Collection(Of SimpleCandidate), labelsInAllLayers As Collection(Of SimpleCandidate))
Dim tempCanvas As New TransparentGdiPlusGeoCanvas()
Dim transparentBitmap As Bitmap = Nothing
Dim returnStream As Stream = Nothing
Dim image As GeoImage = Nothing

Try
transparentBitmap = New Bitmap(CInt(canvas.Width), CInt(canvas.Height), PixelFormat.Format32bppPArgb)
tempCanvas.BeginDrawing(transparentBitmap, canvas.CurrentWorldExtent, canvas.MapUnit)

Dim areaStyle As New AreaStyle(New GeoSolidBrush(GeoColor.StandardColors.Red))

areaStyle.Draw(features, tempCanvas, labelsInThisLayer, labelsInAllLayers)
tempCanvas.EndDrawing()
returnStream = New MemoryStream()
transparentBitmap.Save(returnStream, ImageFormat.Png)
returnStream.Seek(0, SeekOrigin.Begin)

image = New GeoImage(returnStream)

canvas.DrawScreenImageWithoutScaling(image, canvas.Width / 2, canvas.Height / 2, DrawingLevel.LevelOne, 0, 0, _
0)
Finally
If transparentBitmap IsNot Nothing Then
transparentBitmap.Dispose()
End If
If returnStream IsNot Nothing Then
returnStream.Dispose()
End If
If image IsNot Nothing Then
image.Dispose()
End If
End Try
End Sub
End Class

Public Class TransparentGdiPlusGeoCanvas
Inherits GdiPlusGeoCanvas
Protected Overrides Sub DrawAreaCore(screenPoints As IEnumerable(Of ScreenPointF()), outlinePen As GeoPen, fillBrush As GeoBrush, drawingLevel As DrawingLevel, xOffset As Single, yOffset As Single, _
penBrushDrawingOrder As PenBrushDrawingOrder)
If outlinePen IsNot Nothing Then
outlinePen.Color = New GeoColor(100, outlinePen.Color.RedComponent, outlinePen.Color.GreenComponent, outlinePen.Color.BlueComponent)
End If

If TypeOf fillBrush Is GeoSolidBrush Then
Dim castedFillBrush As GeoSolidBrush = DirectCast(fillBrush, GeoSolidBrush)
castedFillBrush.Color = New GeoColor(100, castedFillBrush.Color.RedComponent, castedFillBrush.Color.GreenComponent, castedFillBrush.Color.BlueComponent)
End If
' Handle the other brushes

MyBase.DrawAreaCore(screenPoints, outlinePen, fillBrush, drawingLevel, xOffset, yOffset, _
penBrushDrawingOrder)
End Sub

Protected Overrides Sub DrawEllipseCore(screenPoint As ScreenPointF, width As Single, height As Single, outlinePen As GeoPen, fillBrush As GeoBrush, drawingLevel As DrawingLevel, _
xOffset As Single, yOffset As Single, penBrushDrawingOrder As PenBrushDrawingOrder)
If outlinePen IsNot Nothing Then
outlinePen.Color = New GeoColor(100, outlinePen.Color.RedComponent, outlinePen.Color.GreenComponent, outlinePen.Color.BlueComponent)
End If

If TypeOf fillBrush Is GeoSolidBrush Then
Dim castedFillBrush As GeoSolidBrush = DirectCast(fillBrush, GeoSolidBrush)
castedFillBrush.Color = New GeoColor(100, castedFillBrush.Color.RedComponent, castedFillBrush.Color.GreenComponent, castedFillBrush.Color.BlueComponent)
End If
' Handle the other brushes

MyBase.DrawEllipseCore(screenPoint, width, height, outlinePen, fillBrush, drawingLevel, _
xOffset, yOffset, penBrushDrawingOrder)
End Sub

Protected Overrides Sub DrawLineCore(screenPoints As IEnumerable(Of ScreenPointF), linePen As GeoPen, drawingLevel As DrawingLevel, xOffset As Single, yOffset As Single)
linePen.Color = New GeoColor(100, linePen.Color.RedComponent, linePen.Color.GreenComponent, linePen.Color.BlueComponent)

MyBase.DrawLineCore(screenPoints, linePen, drawingLevel, xOffset, yOffset)
End Sub
End Class



   public class TransparentStyle : ThinkGeo.MapSuite.Core.Style
    {

        protected override void DrawCore(System.Collections.Generic.IEnumerable<Feature> features, GeoCanvas canvas, Collection<SimpleCandidate> labelsInThisLayer, Collection<SimpleCandidate> labelsInAllLayers)
        {
            TransparentGdiPlusGeoCanvas tempCanvas = new TransparentGdiPlusGeoCanvas();
            Bitmap transparentBitmap = null;
            Stream returnStream = null;
            GeoImage image = null;

            try
            {
                transparentBitmap = new Bitmap((int)canvas.Width, (int)canvas.Height, PixelFormat.Format32bppPArgb);
                tempCanvas.BeginDrawing(transparentBitmap, canvas.CurrentWorldExtent, canvas.MapUnit);

                AreaStyle areaStyle = new AreaStyle(new GeoSolidBrush(GeoColor.StandardColors.Red));

                areaStyle.Draw(features, tempCanvas, labelsInThisLayer, labelsInAllLayers);
                tempCanvas.EndDrawing();
                returnStream = new MemoryStream();
                transparentBitmap.Save(returnStream, ImageFormat.Png);
                returnStream.Seek(0, SeekOrigin.Begin);

                image = new GeoImage(returnStream);

                canvas.DrawScreenImageWithoutScaling(image, canvas.Width / 2, canvas.Height / 2, DrawingLevel.LevelOne, 0, 0, 0);
            }
            finally
            {
                if (transparentBitmap != null) { transparentBitmap.Dispose(); }
                if (returnStream != null) { returnStream.Dispose(); }
                if (image != null) { image.Dispose(); }
            }
        }
    }

    public class TransparentGdiPlusGeoCanvas : GdiPlusGeoCanvas
    {
        protected override void DrawAreaCore(IEnumerable<ScreenPointF[]> screenPoints, GeoPen outlinePen, GeoBrush fillBrush, DrawingLevel drawingLevel, float xOffset, float yOffset, PenBrushDrawingOrder penBrushDrawingOrder)
        {
            if (outlinePen != null)
                outlinePen.Color = new GeoColor(100, outlinePen.Color.RedComponent, outlinePen.Color.GreenComponent, outlinePen.Color.BlueComponent);

            if (fillBrush is GeoSolidBrush)
            {
                GeoSolidBrush castedFillBrush = (GeoSolidBrush)fillBrush;
                castedFillBrush.Color = new GeoColor(100, castedFillBrush.Color.RedComponent, castedFillBrush.Color.GreenComponent, castedFillBrush.Color.BlueComponent);
            }
            // Handle the other brushes

            base.DrawAreaCore(screenPoints, outlinePen, fillBrush, drawingLevel, xOffset, yOffset, penBrushDrawingOrder);
        }

        protected override void DrawEllipseCore(ScreenPointF screenPoint, float width, float height, GeoPen outlinePen, GeoBrush fillBrush, DrawingLevel drawingLevel, float xOffset, float yOffset, PenBrushDrawingOrder penBrushDrawingOrder)
        {
            if (outlinePen != null)
                outlinePen.Color = new GeoColor(100, outlinePen.Color.RedComponent, outlinePen.Color.GreenComponent, outlinePen.Color.BlueComponent);
            
            if (fillBrush is GeoSolidBrush)
            {
                GeoSolidBrush castedFillBrush = (GeoSolidBrush)fillBrush;
                castedFillBrush.Color = new GeoColor(100, castedFillBrush.Color.RedComponent, castedFillBrush.Color.GreenComponent, castedFillBrush.Color.BlueComponent);
            }
            // Handle the other brushes

            base.DrawEllipseCore(screenPoint, width, height, outlinePen, fillBrush, drawingLevel, xOffset, yOffset, penBrushDrawingOrder);
        }

        protected override void DrawLineCore(IEnumerable<ScreenPointF> screenPoints, GeoPen linePen, DrawingLevel drawingLevel, float xOffset, float yOffset)
        {
            linePen.Color = new GeoColor(100, linePen.Color.RedComponent, linePen.Color.GreenComponent, linePen.Color.BlueComponent);

            base.DrawLineCore(screenPoints, linePen, drawingLevel, xOffset, yOffset);
        }
    }



Thanks David, I'll look into this as soon as possible.


So what you're essentially saying is, that the GeoCanvas also needs to be transparent, not just all the colors in the AreaStyles ?


Where in your code does the merging of the original canvas and the transparent canvas take place ?


 



David, 
  
 Any response to my questions ? 
  



  
 Lars, 
  
   I’m not making the GeoCanvas transparent exactly, what I am doing is inheriting from the GdiPlusGeoCanvas and whenever I get a command to draw something like a polygon I intercept the color and make the color transparent temporary.  I do this on a temporary bitmap inside the style.  I call the bitmap TransparentBitmap.  After I have drawn all of the styles onto that bitmap I call the original canvas, the one passed into the Style.DrawCore and call canvas.DrawScreenImageWithoutScaling and draw the temporary bitmap right int he middle of the real canvas.  I happen to make the temporary bitmap transparent so when I composite it on top of the canvas it will still show other things drawn on the canvas. 
  
 I you are still having a hard time I can put together a small sample video for you. 
  
 David

Ah, 
  
 After closer examination - and after my brain has been rebooted by some weeks of summer vacation relief :-) - I get the idea. Your code sample hard codes the alpha component to be 100, but that’s easily made settable with a suitable constructor argument. 
  
 Thanks for your effort, David. I’ll implement this shortly. 


Lars, 
  
   I’m glad it clicked, after reading this I almost forgot I wrote it. :-)  It all came back to me so if you do have problems just let me know and I can help.  Transparency is a tricky thing as making the pens and brushes transparent is a very different thing than making the composite image transparent. 
  
 David