See what processes have a file locked in C#

[file locked]

In older versions of Windows, it was impossible to determine what processes had a file locked. The system kept track of the number of locks on a file and processes were responsible for incrementing and decrementing the count to represent their own locks.

Recently (I think starting in Windows XP) Windows included a new Restart Manager to aid in restarting the system during installs. To make restarts easier, Windows now keeps track of the processes that have a file locked, and you can use the Restart Manager to read that list of processes.

The .NET Framework doesn’t have any tools for working with the Restart Manager, so you need to use its API and that requires the usual ugly declarations. Download the example to see all of them in their full glory.

The following code shows the FindLockers method that this example uses to get a list of the processes that have a file locked.

// Return a list of processes that have locks on a file.
static public List<Process> FindLockers(string filename)
{
    // Start a new Restart Manager session.
    uint session_handle;
    string session_key = Guid.NewGuid().ToString();
    int result = RmStartSession(out session_handle, 0, session_key);
    if (result != 0)
        throw new Exception("Error " +
            result + " starting a Restart Manager session.");

    List<Process> processes = new List<Process>();
    try
    {
        const int ERROR_MORE_DATA = 234;
        uint pnProcInfoNeeded = 0, num_procs = 0,
            lpdwRebootReasons = RmRebootReasonNone;
        string[] resources = new string[] { filename };
        result = RmRegisterResources(
            session_handle, (uint)resources.Length,
            resources, 0, null, 0, null);
        if (result != 0)
            throw new Exception("Could not register resource.");

        // There's a potential race around condition here.
        // The first call to RmGetList() returns the total
        // number of process. However, when we call RmGetList()
        // again to get the actual processes this number may
        // have increased.
        result = RmGetList(session_handle, out pnProcInfoNeeded,
            ref num_procs, null, ref lpdwRebootReasons);
        if (result == ERROR_MORE_DATA)
        {
            // Create an array to store the process results.
            RM_PROCESS_INFO[] processInfo =
                new RM_PROCESS_INFO[pnProcInfoNeeded];
            num_procs = pnProcInfoNeeded;

            // Get the list.
            result = RmGetList(session_handle,
                out pnProcInfoNeeded, ref num_procs,
                processInfo, ref lpdwRebootReasons);
            if (result != 0)
                throw new Exception("Error " + result +
                    " listing lock processes");

            // Add the results to the list.
            for (int i = 0; i < num_procs; i++)
            {
                try
                {
                    processes.Add(
                        Process.GetProcessById(processInfo[i].
                            Process.dwProcessId));
                }
                // In case the process is no longer running.
                catch (ArgumentException) { }
            }
        }
        else if (result != 0)
            throw new Exception("Error " + result +
                " getting the size of the result.");
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
    finally
    {
        RmEndSession(session_handle);
    }

    return processes;
}

The method first creates a new Restart Manager session. It then makes an empty list to hold Process objects and enters a try-catch block.

The method then calls the RmRegisterResources method to register the file’s name with the Restart Manager. Next it calls RmGetList method to see how many processes have locks on the file. It calls RmGetList again to get information on the processes. Then code then loops through the results and uses the returned process IDs to get Process objects representing the objects that have the file locked.

The method finishes by returning the list of Process objects.

Download the example to see more details. This example was based on the MSDN post, How to know the process locking a file. See that post for additional information.

To test the program, start it and then open the executable file in Microsoft Word. (It will look like gibberish in Word.) Then press the program’s Find Locks button. You should see processes named howto_find_file_locker.vshost (Visual Studio running the program) and WINWORD.

If you then double-click the executable file, you can run the program again. It and the previously instance of the program should list the two earlier processes plus howto_find_file_locker representing the new instance of the program.

I don’t know of a way to figure out what kind of locks a process might have on a file. For example, if you write a program that uses File.OpenRead to open a file for reading, then that program appears in the list of processes that have the file locked, but I don’t know how to determine whether the lock is for reading or writing. If you figure that out please say something in a comment below.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in files, system | Tagged , , , , , , , , , | Leave a comment

Use “banker’s rounding” and “normal rounding” in C#

[banker's rounding]

By default the Math.Round method uses “banker’s rounding.” In banker’s rounding a number with a final digit of 5 is rounded to the nearest even number rather than to the next larger number as you might expect. The idea is that statistically half of a sample of numbers are rounded up and half are rounded down.

For example, if you want to round to the nearest tenth, the value 1.35 and 1.45 are both rounded to 1.4 because 4 is the closest even tenth.

In contrast if you use normal rounding, values are rounded away from 0. For example, 1.45 is rounded up to 1.5 and -1.45 is rounded down to -1.5.

Note that neither rule is needed if the number doesn’t end with 5 in the final digit and that digit must be the one after the smallest digit that you want to keep. For example, suppose again that you’re rounding to the nearest tenth. The value 1.4500001 is slightly bigger than 1.45 so it rounds up to 1.5 no matter which rounding scheme you’re using.

The Math.Round method can take up to three parameters: the number to round, the number of digits after the decimal point to display, and a flag telling the method whether to use banker’s rounding or to round away from 0.

The following code demonstrates using banker’s rounding and normal rounding.

// By default, banker's rounding gives 1.4. 
double i = Math.Round(1.45, 1);

// Rounding away from 0 gives 1.5.
double j = Math.Round(1.45, 1, MidpointRounding.AwayFromZero);

The example program displays values for several numbers rounded with banker’s rounding and normal “away from 0” rounding.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in algorithms, mathematics | Tagged , , , , , , , , , , , , | Leave a comment

See if a file is locked in C#

[locked]

Some applications lock files so you cannot write, read, delete, or otherwise mess with them. For example, when you open a file in Microsoft Word, it locks the file so you cannot delete it or open it for writing with another application.

The following FileIsLocked method returns true if a file is locked for a given kind of access. For example, pass this routine the access value FileAccess.Write if you want to learn whether the file is locked to prevent you from writing into it.

// Return true if the file is locked for the indicated access.
private bool FileIsLocked(string filename, FileAccess file_access)
{
    // Try to open the file with the indicated access.
    try
    {
        FileStream fs =
            new FileStream(filename, FileMode.Open, file_access);
        fs.Close();
        return false;
    }
    catch (IOException)
    {
        return true;
    }
    catch (Exception)
    {
        throw;
    }
}

The code simply tries to open the file for the given access method and sees whether that causes an error. It uses a try-catch block to handle any error that might occur. If the error is an IOException, then the method assumes the file is locked.

Note, however, that there may be other reasons the method could not access the file. For example, if the file’s path is on a nonexistent disk drive, the code will receive an IOException.

To avoid this kind of false positive, you might want to first check whether the file exists, at least if you’re checking read permissions. If you’re checking for a write lock, you may not need the file to exist if you’re going to overwrite the file. I decided to leave this issue out of the example to keep things simpler.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in files, system | Tagged , , , , , , , , , , , , , , | 3 Comments

Map between host names and IP addresses in C#

[IP addresses]

When you enter a host name and click Go, the program uses the following code to look up the host and display the IP addresses associated with it.

using System.Net;
...
// Display the entered host's IP address.
private void btnGo_Click(object sender, EventArgs e)
{
    this.Cursor = Cursors.WaitCursor;
    lstAddresses.Items.Clear();
    txtHost.Clear();
    try
    {
        IPHostEntry ip_host_entry =
            Dns.GetHostEntry(txtHost.Text);
        foreach (IPAddress address in ip_host_entry.AddressList)
        {
            lstAddresses.Items.Add(address.ToString());
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
    this.Cursor = Cursors.Default;
}

The code clears its ListBox and then uses the Dns.GetHostEntry method to get information about the host. It loops through the host information’s AddressList collection of IPAddress objects and adds the IP addresses to the ListBox.

When you select an entry in the ListBox, the following code displays the host assigned to the selected IP address.

// Look up the selected IP address's host.
private void lstAddresses_SelectedIndexChanged(
    object sender, EventArgs e)
{
    IPHostEntry ip_host_entry =
        Dns.GetHostEntry(lstAddresses.SelectedItem.ToString());
    txtRecoveredHost.Text = ip_host_entry.HostName;
}

This code uses the Dns.GetHostEntry method to get information about the selected IP address. It then displays the assigned host’s name.

Previously this program returned multiple IP addresses for a single host. For example, the loookup for maps.google.com might return a dozen or more IP addresses. The current program seems to return a single address. I suspect this is a change in Windows 10 but I’m not sure. If you have more information about this, please post it in a comment below.

If you look up the host localhost, the program lists the two special IP addresses ::1 and 127.0.0.1. If you click either of those in the ListBox, the program displays your computer’s name.

If you enter your computer’s name in the Host text box or if you leave it blank, the program seems to return your current IP address plus some IPv6 addresses. See WikiPedia for more information on IPv6 addresses. If you click one of those valeus in the ListBox, you should see the host name followed by a dot and the DNS domain name. For more information on this, see this WikiPedia entry.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in internet, network, system | Tagged , , , , , , , , , , , , , , , | Leave a comment

List available culture codes in C#

[culture codes]

This example shows how you can make a list of the culture codes that are available on the system.

Several C# Helper examples deal with globalization including Localize a program in C#. To localize a program, you need to specify a culture code as in en-US for English in the United States or de-CH for German in Switzerland.


This example uses the following code to list the available culture codes.

// List the available culture names.
private void Form1_Load(object sender, EventArgs e)
{
    lvwCultures.FullRowSelect = true;

    // Add the names to the ListView.
    foreach (CultureInfo culture_info in
        CultureInfo.GetCultures(CultureTypes.AllCultures))
    {
        string specific_name = "(none)";
        try
        {
            specific_name = CultureInfo.CreateSpecificCulture(
                culture_info.Name).Name;
        }
        catch { }

        ListViewItem lvi =
            lvwCultures.Items.Add(culture_info.Name);
        lvi.SubItems.Add(specific_name);
        lvi.SubItems.Add(culture_info.EnglishName);
    }

    // Sort the names.
    lvwCultures.Sorting = SortOrder.Ascending;
    lvwCultures.Sort();

    // Color related cultures.
    Color color1 = Color.FromArgb(192, 255, 192);
    Color color2 = Color.LightGreen;
    Color bg_color = color2;
    string last_name = "";
    foreach (ListViewItem lvi in lvwCultures.Items)
    {
        string item_name = lvi.Text.Split('-')[0];
        if (item_name != last_name)
        {
            // Switch colors.
            bg_color = (bg_color == color1) ? color2 : color1;
            last_name = item_name;
        }
        lvi.BackColor = bg_color;
    }

    // Size the columns.
    lvwCultures.Columns[0].Width = -2;
    lvwCultures.Columns[1].Width = -2;
    lvwCultures.Columns[2].Width = -2;
}

The code starts by looping through the CultureInfo objects returned by CultureInfo.GetCultures(CultureTypes.AllCultures). For each CultureInfo object, the program adds the culture’s name, specific name, and English name to a ListView control. For some cultures the CreateSpecificCulture method may fail so the program uses a try-catch block to protect itself.

The culture codes initially come unsorted so the the program makes the ListView sort its values. It then loops through the values to give groups of related cultures the same background colors. As it loops through the values, it splits the culture’s name at the dash character. For example, the German languages have culture codes de-DE, de-AT, de-CH, and so forth. This code pulls out the de at the beginning.

If a culture’s prefix is different from the previous one, the code switches the background color it is using. In either case, the program sets the current item’s background color to the current color.

The program finishes by sizing the ListView control’s columns to fit their data.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in globalization, internationalization, system | Tagged , , , , , , , , , , , , , | Leave a comment

Make a cannon game in C#


[cannon game]

This simple cannon game randomly positions a target house. Then when you click the Shoot button, it gets angle and speed information for the cannon and enables a timer. To keep the graphics and timing simple, the program uses a scale of 1 pixel = 1 meter.

If you set the compile-time variable DEBUGGING to true, then the program draws the position where the cannon ball should ideally cross the Y coordinate where it was launched. If the ball started at ground level and the ground were flat, this is where the ball would land. It calculates this position using the formula:

    Distance = 2 * V * V * Sin(T) * Cos(T) / g

Here g is the acceleration due to gravity, which is 9.8 meters per second. The program redraws TICKS_PER_SECOND times per second so the acceleration per tick is 9.8 /(TICKS_PER_SECOND * TICKS_PER_SECOND) meters per second squared.

The following code shows how the cannon game sets up when you click the Shoot button.

// Launch a cannonball.
private void btnShoot_Click(object sender, EventArgs e)
{
    // Redraw.
    using (Graphics gr = picCanvas.CreateGraphics())
    {
        DrawField(gr);
    }

    // Get the speed.
    float speed;
    try
    {
        speed = float.Parse(txtSpeed.Text);
    }
    catch
    {
        MessageBox.Show("Invalid speed", "Invalid Speed",
            MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
        return;
    }
    if (speed < 1)
    {
        MessageBox.Show("Speed must be at least 1 mps",
            "Invalid Speed", MessageBoxButtons.OK,
            MessageBoxIcon.Exclamation);
        return;
    }

    // Get the speed components in meters per tick.
    Vx = speed * Math.Cos(Theta) / TICKS_PER_SECOND;
    // Negative to go up.
    Vy = -speed * Math.Sin(Theta) / TICKS_PER_SECOND;

    // Disable UI elements.
    btnShoot.Enabled = false;
    txtDegrees.Enabled = false;
    txtSpeed.Enabled = false;
    Cursor = Cursors.WaitCursor;
    Application.DoEvents();

#if DEBUGGING
    // Draw the location where the cannon ball
    // should pass the Y position where it started.
    // Distance =
    //     2 * V^2 * Sin(T) * Cos(T) / g = V^2 * Sin(2*T) / g
    gr.DrawEllipse(Pens.Blue,
        (float)(BulletX + 2 * speed * speed * Math.Sin(Theta) *
            Math.Cos(Theta) / 9.8),
        (float)(BulletY), CANNON_HGT, CANNON_HGT);
#endif

    // Start moving the cannon ball.
    tmrMoveShot.Enabled = true;
}

The cannon game keeps track of the cannonball’s position and velocity with the variables BulletX, BulletY, Vx, and Vy. When the timer event occurs, the program moves the cannonball. It uses the velocity components to update the ball’s position and then redraws the ball.

private double Theta, BulletX, BulletY, Vx, Vy;

private const int TICKS_PER_SECOND = 10;

// Acceleration in meters per tick squared.
private const double YAcceleration =
    9.8 / (TICKS_PER_SECOND * TICKS_PER_SECOND);

private void tmrMoveShot_Tick(object sender, EventArgs e)
{
    // Erase the cannon ball's previous position.
    using (Graphics gr = picCanvas.CreateGraphics())
    {
        using (Brush br = new SolidBrush(picCanvas.BackColor))
        {
            gr.FillEllipse(br, (float)(BulletX), (float)(BulletY),
                CANNON_HGT, CANNON_HGT);
        }

        // Move the cannon ball.
        Vy += YAcceleration;
        BulletX += Vx;
        BulletY += Vy;

        // Draw the new cannon ball.
        gr.FillEllipse(Brushes.Black,
            (float)(BulletX), (float)(BulletY),
            CANNON_HGT, CANNON_HGT);

        // See if we should stop.
        if ((BulletY > picCanvas.ClientRectangle.Height) ||
            (BulletX > picCanvas.ClientRectangle.Width))
        {
            // Stop running.
            tmrMoveShot.Enabled = false;

            // Re-enable UI elements.
            btnShoot.Enabled = true;
            txtDegrees.Enabled = true;
            txtSpeed.Enabled = true;
            Cursor = Cursors.Default;
        }
    }
}

Some improvements that you could make to the cannon game include hit detection to know when the ball hits the target, drawing a nice irregular surface between the cannon and the target, placing the target at different altitudes,allowing the target to shoot back, etc. Perhaps I’ll get around to some of them one day.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in algorithms, animation, games, graphics | Tagged , , , , , , , , , , , , , | Leave a comment

Convert a bitmap into a cursor in C#

[cursor]

Converting a bitmap into a cursor isn’t easy, but you can do it by using the CreateIconIndirect API function. This example uses the following BitmapToCursor method to create a cursor from a bitmap.

// Create a cursor from a bitmap.
private Cursor BitmapToCursor(Bitmap bmp, int hot_x, int hot_y)
{
    // Initialize the cursor information.
    ICONINFO icon_info = new ICONINFO();
    IntPtr h_icon = bmp.GetHicon();
    GetIconInfo(h_icon, out icon_info);
    icon_info.xHotspot = hot_x;
    icon_info.yHotspot = hot_y;
    icon_info.fIcon = false;    // Cursor, not icon.

    // Create the cursor.
    IntPtr h_cursor = CreateIconIndirect(ref icon_info);
    return new Cursor(h_cursor);
}

Download the example and look at the code to see the API declarations.

This code creates an ICONINFO structure to describe the cursor that it will create. It calls the bitmap’s GetHicon method to get a handle to an icon that has the same image as the bitmap. It passes that handle to the GetIconInfo API function to get ICONINFO data describing the icon.

Next the code sets the X and Y coordinates of the cursor’s hot spot, the position inside the cursor that represents the mouse’s position. For example, an arrow cursor would normally use the arrow’s tip as the hot spot. A cross would normally use the center of the cursor as its hotspot.

The code then sets the ICONINFO structure’s fIcon property to false to indicate that this should be a cursor and not an icon. It then calls the CreateIconIndirect method to create the icon and get a new handle to it. Finally the code passes the handle to the Cursor class’s constructor and returns the resulting Cursor object.

Note that these API functions allocate unmanaged memory that is never freed by the program. If the program only creates a few cursors, that’s not a problem. If the program could create hundreds of cursors on the fly, this will cause a memory leak that may be a problem. In that case, use the DestroyIcon API function to release cursors that are no longer needed.

The program uses the following code to create its cursor.

private void Form1_Load(object sender, EventArgs e)
{
    // Make pixels that match the one in the
    // upper left corner transparent.
    Bitmap bm = Properties.Resources.Diamond;
    bm.MakeTransparent(bm.GetPixel(0, 0));
    this.Cursor = BitmapToCursor(bm, 7, 7);
}

The program gets the bitmap resource named Diamond and makes the pixels that match the one in its upper left corner transparent. It then calls the BitmapToCursor method and uses the result as the form’s cursor.

Using this technique you can make cursors of any size, but you should probably stick to relatively small sizes such as the typical 16×16 pixels.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in graphics, image processing | Tagged , , , , , , , , , , , , , | Leave a comment

Use a bitmap for an icon in C#

[icon]

You can’t set a form’s Icon property equal to a bitmap. Fortunately it’s easy enough to create an icon from a bitmap.

This example uses the following code to make a form’s icon display the image in a bitmap.

// Convert the bitmap resource to an icon and use it.
private void Form1_Load(object sender, EventArgs e)
{
    // Get the bitmap.
    Bitmap bm = new Bitmap(Properties.Resources.Spiral);

    // Convert to an icon and use for the form's icon.
    this.Icon = Icon.FromHandle(bm.GetHicon());
}

The program gets the bitmap stored in its Spiral resource. It then uses the bitmap’s GetHicon method to get a handle to an icon holding the same image. It passes the handle to the Icon class’s FromHandle method to create an Icon object and sets the form’s Icon property equal to the result.

Note that the GetHicon method creates an icon in unmanaged memory. If the form stops using the icon, its memory is not freed. If you only create a few icons in this way, that’s no big deal, but if the program makes hundreds of icons at run time, this may result in a memory leak. If you need to create and destroy many icons at run time, use the DestroyIcon API function to free the icon handle’s memory.

Note also that the system may need to display the form’s icon in many sizes. For example, it may display different sizes in the form’s upper left corner, in the Alt-Tab task switcher, and the task bar. Because an icon created from a bitmap only has one size, it will be scaled as needed and the results may not look very good. To get better results, build a real icon file and include all of the sizes that the system might need.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in forms, graphics, image processing | Tagged , , , , , , , , , , | Leave a comment

Make a tool that creates PNG files with transparent backgrounds in C#

[transparent]

When you use the File menu’s Open command, the following code lets you select a image file.

// The image.
private Bitmap Bm = null;

// Offset for displaying the image.
private const int Offset = 10;

// Open a file.
private void mnuFileOpen_Click(object sender, EventArgs e)
{
    if (ofdFile.ShowDialog() == DialogResult.OK)
    {
        try
        {
            Bm = new Bitmap(ofdFile.FileName);
            picImage.ClientSize = new Size(
                Bm.Width + 2 * Offset,
                Bm.Height + 2 * Offset);
            picImage.Visible = true;
            picImage.Refresh();
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }
}

This code displays an OpenFileDialog. If the user selects a file and clicks Open, the code sets Bm to a new Bitmap created from the file.

The program then sizes the picImage PictureBox control to fit the Bitmap plus a margin. When the code then refreshes the PictureBox, the following Paint event handler executes.

// Draw the picture.
private void picImage_Paint(object sender, PaintEventArgs e)
{
    if (Bm == null) return;
    e.Graphics.DrawImage(Bm, Offset, Offset);
}

At design time, I set the PictureBox control’s background image to the blue cloud design shown in the picture. The Paint event handler draws the Bitmap on top of the PictureBox, leaving a margin around the picture so you can see the background.

If you click on the picture, the following code makes the pixels with the clicked color transparent.

// Set the transparent pixel.
private void picImage_MouseClick(object sender, MouseEventArgs e)
{
    // Get the color clicked.
    Color color = Bm.GetPixel(e.X - Offset, e.Y - Offset);

    // Make that color transparent.
    Bm.MakeTransparent(color);

    // Show the result.
    picImage.Refresh();
}

This code uses the Bitmap class’s GetPixel method to get the color of the clicked pixel. It then calls the Bitmap object’s MakeTransparent method to make all pixels of that color transparent. Finally the code refreshes the PictureBox so you can see the image with its newly transparent pixels.

The last piece of the program executes when you select the File menu’s Save command.

// Save the file.
private void mnuFileSave_Click(object sender, EventArgs e)
{
    if (sfdFile.ShowDialog() == DialogResult.OK)
    {
        try
        {
            Bm.Save(sfdFile.FileName, ImageFormat.Png);
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }
}

This code displays a SaveFileDialog. If the user selects a file name and clicks Save, the program calls the Bitmap object’s Save method, passing it the parameter Png to indicate that it should save the Bitmap in a PNG file.

The program only saves in the PNG format because that’s the only format it can use that saves transparent pixels. (It also provides decent compression.)

Note that image formats such as GIF and most JPGs sometimes smoothly shade areas that should be solid color so if you load them into this program, you may have trouble clicking on all of the pixels that you want to make transparent.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in graphics, image processing | Tagged , , , , , , , , , , , , , | Leave a comment

Give an image a transparent background in C#

[transparent]

The Bitmap class’s MakeTransparent method changes all of the pixels with a given color to the transparent color A = 0, R = 0, G = 0, B = 0. When the program starts, the following code makes the background transparent for the two images stored in the program’s Smile and Frown resources.

// The images.
private Bitmap SmileBitmap, FrownBitmap;

// Make the images' backgrounds transparent.
private void Form1_Load(object sender, EventArgs e)
{
    SmileBitmap = Properties.Resources.Smile;
    SmileBitmap.MakeTransparent(SmileBitmap.GetPixel(0, 0));

    FrownBitmap = Properties.Resources.Frown;
    FrownBitmap.MakeTransparent(FrownBitmap.GetPixel(0, 0));
}

The code saves the Smile resource in a Bitmap variable. It then uses the Bitmap object’s MakeTransparent method to make all of its pixels that match the color of the pixel in the upper left corner transparent. The code then repeats those steps for the Frown image.

The following Paint event handler displays the two images.

// Draw the two images overlapping.
private void Form1_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.DrawImage(FrownBitmap, 30, 30);
    e.Graphics.DrawImage(SmileBitmap, 95, 85);
}

This code simply draws the two images overlapping so you can see that they have transparent backgrounds.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in graphics, image processing | Tagged , , , , , , , , , , , , | Leave a comment