vbscripttransparencywia

The WIA.Vector object loses the transparency of its pixels after invoking its ImageFile property


The title of this question can also be, "The alpha values in the WIA.Vector object don't work."
I'm trying to render an ellipse on a transparent background using my own discovered algorithm, and then save the resulting image into a .bmp file. The ellipse is rasterized properly with a black stroke, but my program does not make the background of the image transparent.
As I examined the program, it turned out that when I retrieve an ImageFile object from the Vector object, its IsAlphaPixelFormat property is set to false, indicating that the alpha channel is not available in the output image. Even though I set the alpha value of the background color to zero in the vector, the ImageFile object generates an opaque white background.

So could you please help me make the background transparent? Here is my VBScript code, which must be run with cscript.exe.
Note: This program requires Windows Image Acquisition (WIA) library v2.0 in order to create a .bmp image file. So it must be run on Window Vista or higher.

Const width = 500
Const height = 500
color_transparent = GetARGB(0, 255, 255, 255) ' This does not work, renders as opaque white
color_black = GetARGB(255, 0, 0, 0)
PI = 4 * Atn(1)

Dim oVector, oImageFile

Set oVector = NewBlankPage()
rasterizeEllipse oVector, 220, 120, 200, 100, color_black

Set oImageFile = oVector.ImageFile(width, height)
oImageFile.SaveFile "ellipse.bmp"

WScript.StdOut.WriteLine "Done! Press Enter to quit."
WScript.StdIn.SkipLine

Function NewBlankPage()
    Dim oVector, i
    WScript.StdOut.WriteLine "Creating a new blank page... Please wait..."
    Set oVector = CreateObject("WIA.Vector")
    
    For i = 1 To (width * height)
        oVector.Add color_transparent
    Next
    
    Set NewBlankPage = oVector
End Function

Function getPointOnEllipse(cx, cy, rx, ry, d)
    Dim theta
    theta = d * Sqr(2 / (rx * rx + ry * ry))
    ' theta = 2 * PI * d / getEllipsePerimeter(rx, ry)
    
    Dim point(1)
    point(0) = Fix(cx + Cos(theta) * rx)
    point(1) = Fix(cy - Sin(theta) * ry)
    getPointOnEllipse = point
End Function

Function getEllipsePerimeter(rx, ry)
    getEllipsePerimeter = Fix(PI * Sqr(2 * (rx * rx + ry * ry)))
End Function

Sub SetPixel(oVector, x, y, color)
    x = x + 1
    y = y + 1
    
    If x > width Or x < 1 Or y > height Or y < 1 Then
        Exit Sub
    End If
    
    oVector(x + (y - 1) * width) = color
End Sub

Sub rasterizeEllipse(oVector, cx, cy, rx, ry, color)
    Dim perimeter, i
    WScript.StdOut.WriteLine "Rendering ellipse..."
    perimeter = getEllipsePerimeter(rx, ry)
    
    For i = 0 To (perimeter - 1)
        Dim point
        point = getPointOnEllipse(cx, cy, rx, ry, i)
        SetPixel oVector, point(0), point(1), color
    Next
End Sub

' These functions are taken from examples in the documentation
Function Get1ByteHex(val)
    Dim s
    s = Hex(val)
    Do While Len(s) < 2
        s = "0" & s
    Loop
    Get1ByteHex = Right(s, 2)
End Function

Function GetARGB(a, r, g, b)
    Dim s
    s = "&h" & Get1ByteHex(a) & Get1ByteHex(r) & Get1ByteHex(g) & Get1ByteHex(b)
    GetARGB = CLng(s)
End Function

After running the code, you can test the transparency of the output image using this simple HTA:

<html>
<head>
<title>Test</title>
</head>
<body bgcolor="blue">
<img src="ellipse.bmp">
</body>
</html>

And you will see that a white box is displayed behind the ellipse, which indicates non-transparent background.


Solution

  • This can be done by using the ARGB filter to change all white pixels to transparent and saving the image as a PNG file. Unfortunately, this means iterating through every pixel, so your script will take twice as long to run. I could not find a way to create the initial image with transparency. See here for info regarding the performance issue. Here's the revised script:

    Const wiaFormatPNG = "{B96B3CAF-0728-11D3-9D7B-0000F81EF32E}"
    Const width = 500
    Const height = 500
    color_white = GetARGB(255, 255, 255, 255)
    color_transparent = GetARGB(0, 255, 255, 255)
    color_black = GetARGB(255, 0, 0, 0)
    PI = 4 * Atn(1)
    
    Dim oVector, oImageFile
    
    Set oVector = NewBlankPage()
    rasterizeEllipse oVector, 220, 120, 200, 100, color_black
    Set oImageFile = oVector.ImageFile(width, height)
    Set IP = CreateObject("WIA.ImageProcess")
    IP.Filters.Add IP.FilterInfos("ARGB").FilterID
    Set oVector = oImageFile.ARGBData
    Wscript.StdOut.WriteLine "Changing white pixels to transparent... Please wait..."
    For i = 1 To oVector.Count
        If oVector.Item(i) = color_white Then oVector.Item(i) = color_transparent
    Next
    IP.Filters(1).Properties("ARGBData").Value = oVector
    IP.Filters.Add IP.FilterInfos("Convert").FilterID
    IP.Filters(2).Properties("FormatID").Value = wiaFormatPNG
    Set oImageFile = IP.Apply(oImageFile)
    oImageFile.SaveFile "ellipse.png"
    
    Wscript.StdOut.WriteLine "Done! Press Enter to quit."
    Wscript.StdIn.SkipLine
    
    Function NewBlankPage()
        Dim oVector, i
        Wscript.StdOut.WriteLine "Creating a new blank page... Please wait..."
        Set oVector = CreateObject("WIA.Vector")
        
        For i = 1 To (width * height)
            oVector.Add color_white
        Next
        
        Set NewBlankPage = oVector
    End Function
    
    Function getPointOnEllipse(cx, cy, rx, ry, d)
        Dim theta
        theta = d * Sqr(2 / (rx * rx + ry * ry))
        ' theta = 2 * PI * d / getEllipsePerimeter(rx, ry)
        
        Dim point(1)
        point(0) = Fix(cx + Cos(theta) * rx)
        point(1) = Fix(cy - Sin(theta) * ry)
        getPointOnEllipse = point
    End Function
    
    Function getEllipsePerimeter(rx, ry)
        getEllipsePerimeter = Fix(PI * Sqr(2 * (rx * rx + ry * ry)))
    End Function
    
    Sub SetPixel(oVector, x, y, color)
        x = x + 1
        y = y + 1
        
        If x > width Or x < 1 Or y > height Or y < 1 Then
            Exit Sub
        End If
        
        oVector(x + (y - 1) * width) = color
    End Sub
    
    Sub rasterizeEllipse(oVector, cx, cy, rx, ry, color)
        Dim perimeter, i
        Wscript.StdOut.WriteLine "Rendering ellipse..."
        perimeter = getEllipsePerimeter(rx, ry)
        
        For i = 0 To (perimeter - 1)
            Dim point
            point = getPointOnEllipse(cx, cy, rx, ry, i)
            SetPixel oVector, point(0), point(1), color
        Next
    End Sub
    
    ' These functions are taken from examples in the documentation
    Function Get1ByteHex(val)
        Dim s
        s = Hex(val)
        Do While Len(s) < 2
            s = "0" & s
        Loop
        Get1ByteHex = Right(s, 2)
    End Function
    
    Function GetARGB(a, r, g, b)
        Dim s
        s = "&h" & Get1ByteHex(a) & Get1ByteHex(r) & Get1ByteHex(g) & Get1ByteHex(b)
        GetARGB = CLng(s)
    End Function
    

    Note: If you only need an ellipse in your HTA, it can be done instantly with CSS:

    <!DOCTYPE html>
    <html>
    <head>
    <title>Test</title>
    <meta http-equiv="X-UA-Compatible" content="IE=9">
    <style>
    .ellipseDiv
    {
      height:200px;
      width:400px;
      border: 1px solid #005;
      border-radius:200px / 100px;
    }
    </style>
    </head>
    <body bgcolor="blue">
    <div class=ellipseDiv>
    </div>
    </body>
    </html>