Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Persian Gulf in 3D Anaglyph

0.00/5 (No votes)
12 Aug 2013 2  
This application uses some simple 2D methods to make 3D scenes which can be seen with Red/Cyan Anaglyph glasses.

For the executable file, please download all four demo parts.

Table of Contents

Introduction

3D systems are very popular these days, and new technologies are invented to view these images. Most of these techniques need special hardware and are still expensive for some people, so Anaglyph glasses suffice my purposes. The Anaglyph method is one of the most famous but old techniques in 3D, which uses simple special glasses. There are different types of glasses but the Red/Cyan is more popular and available and so we speak more about these glasses in this article. The purpose of all these techniques is to make different images for each eye. Subsequently, the brain will mix them in the form of one 3D image.

Background

In the previous articles, i.e., Stereoscopy and Park3D, we used the eyes free methods, parallel and Cross eyes. After that I worked on the third part of these series in 2009 but because of some personal issues this article was never finished. A few weeks ago, I saw some of my unfinished applications and decided to complete at least this one and post it here. However, it is somehow outdated to speak about anaglyph, but it may be appropriate for those who like 3D techniques or have red/cyan glasses. My initial application was based on the landscape of the Persian Gulf, but I made some changes and added another scene as Golestan National Park, so you can move simply between the sea or the jungle scene and enjoy.

How it works?

The principles governing this application are like other 3D systems and are based on two images with some small differences for each eye, and our brain uses these differences to make depth in our imagination.

Let's start it; prepare two same size canvases, one for the left eye and one for the right.

Put the sky image at the same place on both canvases to make infinity.

Now, put the basement image on both.

To make depth, skew the right basement.

Then put all the objects you like, at this level you can see the 3D scene by parallel view method. First, put both objects at the same place on both canvases, then move the right one horizontally to change its depth.

As you know, we have 3 main visible colors. Red, green, blue (RGB) , in anaglyph we remove green and blue colors from the left image which remains red. Then, we remove red from the right image and it is changed into cyan (green + blue=cyan).

If you use Photoshop, for the right image, go to Levels menu, select Red Channel, and put the output levels to 0.

For the left image, you have to change the output levels of both Green and Blue channels to 0.

Now we again have 3 colors, red from the left and cyan (green + blue) from the right, we put them on one image. In Photoshop, move the left picture to the right and use "Difference" item in the layers toolbox.

The anaglyph image is ready. 

You can download the above Photoshop files here. Download Anaglyph Photoshop sample - 3.1 MB 

Using the code

The algorithm is as I explained:

Now we’ll go step by step:

Define variables and set some values

Load images from hard disk and internal resources

I used some different objects which are the following: 

public enum myObjectTypes
{
    Misc=0,
    Cloud = 1,
    Rock = 2,
    Bird = 3,
    Ship = 4,
    Animal = 5,
    Flower = 6,
    Tree = 7,
    Fish = 8,
    Insect = 9,
    Human = 10,
    Snow = 11,
    Fog = 12,
    Sky = 13,
    Ground = 14,
    Water = 15,
    _3D = 16
}

I have a collection of all images which I prepared in sub folders on the hard disk and internal resources 

private List<Image>[] picsAll= ...  

Which picsAll[1] contains clouds, picsAll [2] contains Rocks and so on. 

I have a class to hold the calculated images information for the scene as defined below:

public class picCollection
{
    internal myObjectTypes myType = 0;
    internal int myTypeNumber = 0;
    internal int myImageNumber = 0;
    internal Image myImage = null;
    internal Image myImage2 = null;
    internal float myX = 0;
    internal float myY = 0;
    internal float myScale = 0;
    internal float myDepth = 0;
    internal int myOrientation = 0;
}

And the collection from the above class is:

List<picCollection> myAllPicCollection = new List<picCollection>() 

I have also a collection to hold menu items information as:

private List<ToolStripItem> myMenuItemCollection = new List<ToolStripItem>(); 

The following arrays hold color matrix information:

float[][][] picMatrix;
float[][][] anaglyphMatrix;

Initially I load resources which are held inside the exe file as: 

private void loadPictures()
{
    // add some pictures from internal resources

    picsAll[myObjectTypes.Snow.GetHashCode()].Add(Properties.Resources.snow1);

    picsAll[myObjectTypes.Cloud.GetHashCode()].Add(Properties.Resources.cloud1);
    picsAll[myObjectTypes.Cloud.GetHashCode()].Add(Properties.Resources.cloud2);
    picsAll[myObjectTypes.Cloud.GetHashCode()].Add(Properties.Resources.cloud3);
…
}

There are several folders in the same folder as the exe file on your hard disk which contains external additional images where you can also add yours to them. We can use the following method to load these files:

private void loadFromHard(string myDir, List<Image> myPicList, List<Image> myPicList2)
{
    DirectoryInfo myFileDir = new DirectoryInfo(Application.StartupPath + "\\" + myDir);
    if (myFileDir.Exists)
    {
        // For each image extension (.jpg, .bmp, etc.)
        foreach (string imageType in imageTypes)
        {
            // all graphic files in the directory 
            foreach (FileInfo myFile in myFileDir.GetFiles(imageType))
            {
                // add image
                try
                {
                    Image image = Image.FromFile(myFile.FullName);
                    myPicList.Add(image);
                }
                catch (OutOfMemoryException)
                {
                    continue;
                }
            }
        }
    }
}

I loaded color matrices which were used as images effects: 

private void setMatrices()
{

    float[][] matrixNormal = new float[][] {
        new float[] {1, 0, 0, 0, 0}, //  Red  
        new float[] {0, 1, 0, 0, 0}, //  Green
        new float[] {0, 0, 1, 0, 0}, //  Blue
        new float[] {0, 0, 0, 1, 0}, // Transparency
        new float[] {0, 0, 0, 0, 1}
    };

    float[][] matrixBW = new float[][] {
        new float[] {.34f, .34f, .34f, 0, 0}, //  Red  
        new float[] {.34f, .34f, .34f, 0, 0}, //  Green
        new float[] {.34f, .34f, .34f, 0, 0}, //  Blue
        new float[] {0, 0, 0, 1, 0}, // Transparency
        new float[] {0, 0, 0, 0, 1}
    };
}

And make a three dimentional array for matrices:

picMatrix = new float[][][] { 
    matrixNormal,matrixNormal,
    matrixNormal,matrixBW,matrixBW_Contrast,matrixBW_Contrast_More,matrixBW_Contrast_Most,
    matrixBW_Brighter,matrixBW_Brighter_More,matrixBW_Brighter_Most,
    ...

Show controls and wait for user orders

The control panel is hidden at first; you can click on anaglyph image or click on "Show controls" button to view this part. 

And then:

Controls are in some categories.

The first category is to select between sea or jungle scene.

I added a new item to this category in the first update; 3D image item is in which you have some real 3D images captured by camera and want to add some objects to it. At the moment working with this item is not very simple but I will make it better in the next.

The general info category contains values which influence all the objects on the scene. For example, if you change Size here, size of all types of objects will be changed. 

  • Overall Size: The main size of objects.
  • Initial Size: the starting size of the object which starts from the infinity (background image) and continues in all distances.
  • Perspective: the change on size of items in the depth. Lower values mean the size is almost equal in all depths, higher values mean the object size will be logarithmically bigger in closer distances than further ones.
  • Starting Depth: This option is added here to work with the real 3d images which are used as background and have different depths.
  • View angle: lower values equal viewing less ground and more sky, and higher values equal as there is more ground and less sky.
  • Altitude: altitude of your view.
  • Digging: a simple effect which change the base placement of all objects in the scene in all depths. It changes Y value for all objects.
  • Depth Behind Monitor: The main control of the depth. If you move it to 0, all objects would be in front of the monitor screen, and when you move it to upper values, you have some space behind the monitor screen, and some space in front of the screen.

I’ll explain these controls more in the section on calculation.

Another category is the images of sky, ground or water; they can also be selected randomly: 

The main controls for working with each type of objects are placed here: 

You can make new scenes by clicking “Run New” button from the following box or from the menu bar: 

The effect combo box is in here, affecting all types of objects which do not have any selected effects.

And the final category is Timer and Auto run:

I made a few samples which are not perfect but will help you understand controls better. I put the samples in three different menus (4 in the first update);

The Persian Gulf menu contains only sea scenes:

The Golestan Park menu contains only jungle scenes:

The Special effects menu is focused on night and winter effects:

In the first update, I added a new item to samples menu; Mix 3D samples show how to put objects on real 3D images captured by camera.

If user clicks one of the sample menu items, the program will load data and put them on the controls. This part is as follows: 

private void runMenuItem(ToolStripItem myMenuItemSelected)
{
    mnuOut1.Text = " -- " + myMenuItemSelected.Text + " -- ";
    bool noRun = false;
    switch (myMenuItemSelected.Name)
    {
    …
   
    case "mnuPersianGulfBinocular":
        putInfo(0,                      //0=Persian Gulf   1=Golestan Park
            20, 10, 45, 15, 20,        //General Info
            20, 1, 5,                   //Depth Control
            0,
            1, 100, 28, 65, 5, 0, 100, 45, 35, 0,       //Clouds
            1, 10, 60, 15, 10, 30, 100, 55, 50, 88,     //Fog
            1, 300, 25, 0, -40, 0, 60, 0, 0, 0,         //Rocks
            1, 40, 32, 0, 0, 0, 10, 50, 40, 0,          //Birds
            1, 10, 40, 45, -10, 10, 300, 100, 80, 0,    //Ships
            1, 5, 20, 5, 0, 0, 50, 0, 0, 0,             //Fishes
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0,               //Humans
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0,               //Animals
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0,               //Flowers
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0,               //Trees
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            1, 20,              //Snow
            0, 0, 0, 0, 0, 0, 0, 0,
            0, 1,              //Sky image
            0, 0, 0, 0, 0, 0, 0, 0,
            0, 1,              //Ground image
            0, 0, 0, 0, 0, 0, 0, 0,
            0, 1,              //Water image
            0);
        break;
    …
}

The sequence of items is like controls on the control panel, and the method which put menu item data on the controls is: 

private void putInfo(params int[] infoArray)
{
    if (infoArray[0] == 0) rdoLandscapePersianGulf.Checked = true; 
         else rdoLandscapeGolestanPark.Checked = true;
    trbGeneralSize.Value = infoArray[1];
    trbInitialSize.Value = infoArray[2];
    trbViewAngle.Value = infoArray[3];
    trbAltitude.Value = infoArray[4];
    trbZoom.Value = infoArray[5];
 
    trbDepth.Value = infoArray[6];
    chkDepthRandom.Checked = Convert.ToBoolean(infoArray[7]);
    if (chkDepthRandom.Checked) updDepthRandom.Value = infoArray[8];

    chkCloudObjects.Checked = Convert.ToBoolean(infoArray[10]);
    if (chkCloudObjects.Checked)
    {
        updCloudObjects.Value = infoArray[11];
        trbSizeCloud.Value = infoArray[12];
        trbInitialCloud.Value = infoArray[13];
        trbZoomCloud.Value = infoArray[14];
        trbDepthCloudsStart.Value = infoArray[15];
        trbDepthCloudsMinimum.Value = infoArray[16];
        trbDepthClouds.Value = infoArray[17];
        trbDepthCloudsNoise.Value = infoArray[18];
        cmbCloudEffect.SelectedIndex = infoArray[19];

    }
….

When you choose a sample item, it also runs the program.

The difference between "Run New" and "Refresh" : in "Run New" all objects will be reset and size, type, depth… will be changed, but in "Refresh", only effects of objects and their orientation will be changed. Sky, ground and water can change in both conditions and if you want to hold them without any change, you have to remove the tick from the random check box of each one.

Calculate new objects randomly based on controls and hold in a collection

In the calculation part, we set the track bar values to our variables:

private void startCalculation()
{
…         
    myGeneralSize = trbGeneralSize.Value * .05f;
    myViewAngle = (140 - trbViewAngle.Value) * .03f
    myAltitude = trbAltitude.Value / 4f;
    myGroundHeight = (int)(picLeft3D.Height / myViewAngle);
    myBackHeight = picLeft3D.Height - myGroundHeight;
…

Then, calculate each item type as follows: 

// selecting Flowers  with different size for painting on the scene
if (chkFlowerObject.Checked && !rdoLandscapePersianGulf.Checked)
{
    if (picsAll[myObjectTypes.Flower.GetHashCode()].Count > 0)
    {
        for (int i = 1; i <= updFlowerObject.Value; i++)
        {
            try
            {
                myPicInfo = new picCollection();
                myPicInfo.myType = myObjectTypes.Flower;
                myPicInfo.myTypeNumber = myObjectTypes.Flower.GetHashCode();
                myPicInfo.myOrientation = myRandom.Next(2);
                myPicInfo.myImageNumber = myRandom.Next(picsAll[myObjectTypes.Flower.GetHashCode()].Count);
                myPicInfo.myImage = picsAll[myObjectTypes.Flower.GetHashCode()][myPicInfo.myImageNumber];
                myPicInfo.myImage2 = myPicInfo.myImage;
                myPicInfo.myDepth = (float)Math.Round(
                    (((Math.Abs(baseDepth) * (trbStartingDepthGeneral.Value + trbDepthFlowerStart.Value) / 100f)
                    + myRandom.NextDouble() * ((Math.Abs(baseDepth) * trbDepthFlowerMinimum.Value / 100f)))
                    + (myRandom.NextDouble() * ((float)trbDepthFlower.Value / 
                      (15 * myRandom.NextDouble() + (100 - trbDepthFlowerNoise.Value) / 100f))))
                    , 2);

                myPicInfo.myScale = (float)(Math.Pow(myPicInfo.myDepth, (1 + trbPerpective.Value / 
                  100f + trbZoomFlowers.Value / 100f)) * Math.Pow(trbSizeFlower.Value, 1.3) / 
                  12000f * myGeneralSize / Math.Pow(myAltitude, .4f) + 
                  (trbInitialSize.Value / 100f + trbInitialFlowers.Value / 100f));

                myPicInfo.myX = calculateX(myPicInfo, cmbXAlineFlowers.Text, 0);
                myAllPicCollection.Add(myPicInfo);
            }
            catch (ArgumentOutOfRangeException)
            {
                continue;
            }
        }
    }
}

...

Most parts of the calculating algorithm are the same for all types of objects, but I didn’t make one method for all of them to make the code simpler to understand. 

At first, one image is selected randomly from the collection:

myPicInfo.myImageNumber = myRandom.Next(picsAll[myObjectTypes.Flower.GetHashCode()].Count);
myPicInfo.myImage = picsAll[myObjectTypes.Flower.GetHashCode()][myPicInfo.myImageNumber];
myPicInfo.myImage2 = myPicInfo.myImage;

As you have seen, there are two images for each object; this is for the situation where you have two stereoscopic images in two files, but at the moment I didn’t put any objects of this type in sub folders and both images are the same..

All objects can be flipped horizontally to make them appear on the right or left:

myPicInfo.myOrientation = myRandom.Next(2); 

Normal orientation as in file 

Flipped horizontally

I used a greater canvas size for the objects. When you flip them, the placement changes a little. This is because when you refresh the scene, you can make more changes in the scene with the same objects. In this case, if there where another object behind the dog, you can see it when flipping the image of the dog. 

Calculating the depth of objects is a complicated process, because it depends on too many items:

myPicInfo.myDepth = (float)Math.Round(
    (((Math.Abs(baseDepth) * trbDepthFlowerStart.Value / 100f)
    + myRandom.NextDouble() * ((Math.Abs(baseDepth) * trbDepthFlowerMinimum.Value / 100f)))
    + (myRandom.NextDouble() * ((float)trbDepthFlower.Value / 
      (15 * myRandom.NextDouble() + (100 - trbDepthFlowerNoise.Value) / 100f))))
    , 2);

In the following guide, the situation of you and your monitor is considered as follows:

baseDepth variable is controlled by a track bar named trbBaseDepth and I’ve shown the effect in the following images:

In previous articles (stereoscopy, Park3D) the base depth was always as 0, and the infinity was in the same place on both left and right images , but here, to make more space, I preferred to send the sky to the back and make some space between the sky and the monitor screen and some space between the monitor screen and the user.

There are 4 other track bars for controlling the depth of each object which act in the following manner:

After calculating the depth, we have to calculate the size of the object depending on its depth. Naturally, all objects must be greater when they are closer to you. I made exceptions for Mountains, Rocks, and Clouds.

myPicInfo.myScale = 
  (float)(Math.Pow(myPicInfo.myDepth, (1 + trbZoom.Value / 100f + trbZoomFlowers.Value / 100f)) * 
  Math.Pow(trbSizeFlower.Value, 1.3) / 12000f * myGeneralSize / Math.Pow(myAltitude, .4f) + 
  (trbInitialSize.Value / 100f + trbInitialFlowers.Value / 100f));

There are some general controls and some specific controls for the size of each object and this is shown as follows: 

The Zero value for "Perspective" Track bar; allows objects to be seen the same size in all distances.

When you use higher values of "Perspective" control, objects appear more different in distances:

And also you can use negative values for specific situations: 

Then, I put the object horizontally in a random place (X value). You can select the horizontal distribution method by changing the X Align combo box: 

The words "+B" (or +Border) mean objects can cross the borders. "Total Width" and "Total Width + Border" distribution are as follows:

And the "LR" means objects are in both left and right places.

// Calculation of horizontal Position of objects
private float calculateX(picCollection myPicInfo, string alignType, int myType)
{
    string alignTypeEdited = alignType.Trim();
    float newX = 0;

    // X alignment
    switch (alignTypeEdited)
    {
        case "Total Width":
            newX = myRandom.Next(
                ((myPicInfo.myDepth + baseDepth) < 0 ? 0 : (int)(myPicInfo.myDepth + baseDepth)),
                        myOutputWidthCorrected - (int)(myPicInfo.myImage.Width * myPicInfo.myScale) - 
                        ((myPicInfo.myDepth + baseDepth) < 0 ? 0 : 
                        -(int)(myPicInfo.myDepth + baseDepth)));
            break;
        case "Total+Border":
            newX = myRandom.Next(
                -(int)(myPicInfo.myImage.Width),
                myOutputWidthCorrected);
            break;
        case "Left 10%":
            newX = myRandom.Next(
                ((myPicInfo.myDepth + baseDepth) < 0 ? 0 : (int)(myPicInfo.myDepth + baseDepth)),
                (int)(myOutputWidthCorrected * .1f - (myPicInfo.myImage.Width * myPicInfo.myScale)));
            break;
        case "Left 10%+B":
            newX = myRandom.Next(
                -(int)(myPicInfo.myImage.Width),
                    (int)(myOutputWidthCorrected * .1f - 
                     (myPicInfo.myImage.Width * myPicInfo.myScale)));
            break;
...

The Y value of objects calculated automatically depends on their depth. I put this calculation in the final place of drawing for some reasons. There are also exceptions for Clouds and birds because they can be everywhere in the sky regardless of their depth or size.

This is the Y value calculation for most objects which are placed in DrawObjects(…) method:

// myP= myPicInfo;
myP.myY = myBackHeight 
- (float)myP.myImage.Height * myP.myScale 
+ myP.myDepth * myAltitude + myAltitude ;

This is the special Y value calculation for clouds: 

myPicInfo.myY = myRandom.Next(
        (int)(1 - myPicInfo.myImage.Height * myPicInfo.myScale),
        (int)(myBackHeight * 3 / 4f - myPicInfo.myImage.Height * myPicInfo.myScale
        - myPicInfo.myDepth * myAltitude 
        +  (trbInitialCloud.Value - 50) * 10 
        + (myPicInfo.myDepth * trbZoomCloud.Value / 2)));

As you’ve seen in the above code, I used "Initial Size" and "Perspective" controls in the other way for clouds. "Initial Size" control changes the altitude of clouds at further distances, and the "Perspective" control is used for altitudes of clouds nearby.

And finally I added the object to the collection:

myAllPicCollection.Add(myPicInfo);

Draw items on the left and right images

This part begins with the Draw3D() method.

Initially, we define two bitmaps and make Graphics for the left and right images:

private void Draw3D()
{
    leftBitmap = new Bitmap(picLeft3D.Width, picLeft3D.Height);
    Graphics gLeft = Graphics.FromImage(leftBitmap);
    gLeft.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
    gLeft.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
 
    rightBitmap = new Bitmap(picRight3D.Width, picRight3D.Height);
    Graphics gRight = Graphics.FromImage(rightBitmap); ;
    gRight.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
    gRight.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
…

After that, we choose images for sky, ground or water and draw on the bitmaps. There are different methods for this job:

if (chkRandomSky.Checked)
    picSkySelected = myRandom.Next(2, picsAll[myObjectTypes.Sky.GetHashCode()].Count);
picSky.BackgroundImage = picsAll[myObjectTypes.Sky.GetHashCode()][picSkySelected];
 
if (chkRandomGround.Checked) picGroundSelected = 
    myRandom.Next(2, picsAll[myObjectTypes.Ground.GetHashCode()].Count);
picGround.BackgroundImage = picsAll[myObjectTypes.Ground.GetHashCode()][picGroundSelected];
 
if (chkRandomWater.Checked) picWaterSelected = 
    myRandom.Next(1, picsAll[myObjectTypes.Water.GetHashCode()].Count);
picWater.BackgroundImage = picsAll[myObjectTypes.Water.GetHashCode()][picWaterSelected];
 
...
 
private void DrawSky(Graphics gLeftx, Graphics gRightx)
{
    // Create ImageAttributes and Set Color Matrix
    ImageAttributes imgAtt = new ImageAttributes();
    // if you select a special effect for sky, system uses it, otherwise system uses general effect
    ColorMatrix myMatrix = new ColorMatrix(picMatrix[(cmbSkyEffect.SelectedIndex > 0 ? 
                cmbSkyEffect.SelectedIndex : cmbGeneralEffect.SelectedIndex)]);
    imgAtt.SetColorMatrix(myMatrix, ColorMatrixFlag.Default, ColorAdjustType.Default);
 
    gLeftx.DrawImage(picsAll[myObjectTypes.Sky.GetHashCode()][picSkySelected], 
      new Rectangle(baseDepth, 0, picLeft3D.Width, myBackHeight), 0, 0, 
      picsAll[myObjectTypes.Sky.GetHashCode()][picSkySelected].Width, 
      picsAll[myObjectTypes.Sky.GetHashCode()][picSkySelected].Height, 
      GraphicsUnit.Pixel, imgAtt);
    gRightx.DrawImage(picsAll[myObjectTypes.Sky.GetHashCode()][picSkySelected], 
      new Rectangle(0, 0, picRight3D.Width, myBackHeight), 0, 0, 
      picsAll[myObjectTypes.Sky.GetHashCode()][picSkySelected].Width, 
      picsAll[myObjectTypes.Sky.GetHashCode()][picSkySelected].Height, 
      GraphicsUnit.Pixel, imgAtt);
} 
 
// Draw Ground or Water on the scene
private void DrawBase(Graphics gLeftx, Graphics gRightx)
{
    Image myBaseImage;
    ColorMatrix myMatrix;
 
    // Persian Gulf or Golestan Park selected?
    if (rdoLandscapePersianGulf.Checked)
    {
        // Persian Gulf
        myBaseImage = picsAll[myObjectTypes.Water.GetHashCode()][picWaterSelected];
        // if you select a special effect for  water,
        // system uses it, otherwise system uses general effect
        myMatrix = new ColorMatrix(picMatrix[(cmbWaterEffect.SelectedIndex > 0 ? 
               cmbWaterEffect.SelectedIndex : cmbGeneralEffect.SelectedIndex)]);
 
    }
    else
    {
        // Golestan Park
        myBaseImage = picsAll[myObjectTypes.Ground.GetHashCode()][picGroundSelected];
        // if you select a special effect for  ground,
        // system uses it, otherwie system uses general effect
        myMatrix = new ColorMatrix(picMatrix[(cmbGroundEffect.SelectedIndex > 0 ? 
          cmbGroundEffect.SelectedIndex : cmbGeneralEffect.SelectedIndex)]);
 
    }
 
    // Create ImageAttributes and Set Color Matrix
    ImageAttributes imgAtt = new ImageAttributes();
    imgAtt.SetColorMatrix(myMatrix, ColorMatrixFlag.Default, ColorAdjustType.Default);
 
    //by skewing one of the images, we make landscape depth
    Point[] skewPoints = {
            new Point(0, myBackHeight ),  // upper-left point
            new Point((int)(picLeft3D.Width+(myGroundHeight /myAltitude)), 
                       myBackHeight),  // upper-right point  
            new Point((int)(-myGroundHeight /myAltitude ), picLeft3D.Height)  // lower-left point 
            };
 
    gLeftx.DrawImage(myBaseImage, new Rectangle(baseDepth, myBackHeight, 
      (int)(picLeft3D.Width + myGroundHeight / myAltitude), myGroundHeight), 0, 0, 
      myBaseImage.Width, myBaseImage.Height, GraphicsUnit.Pixel, imgAtt);
    gRightx.DrawImage(myBaseImage, skewPoints, new Rectangle(0, 0, 
      myBaseImage.Width, myBaseImage.Height), GraphicsUnit.Pixel, imgAtt);
}

Then, we make a loop to draw all the objects in collection on the left and right graphics. Objects will be put on the scene in the order of their depth:

stbInfo = new StringBuilder();
// drawing all objects of the collection in order by depth on the scene
for (int d = 0; d <= maxDepth * 100; d++)
{
    foreach (picCollection myP in myAllPicCollection)
    {
        if (d == (int)(myP.myDepth * 100))
        {
            try
            {
                {
                    DrawObjects(gLeft, gRight, myP);
 
                    //append data to save on file
                    stbInfo.Append(stbPicInfo(myP).ToString());
                }
            }
            catch (Exception e) { Debug.WriteLine("error 234: " + e.Message); }
        }
    }
}

In the above loop, I also gather all the objects information in a string (stbinfo) to save on a file for future uses.

In the DrawObjects(…) method, we add color effects to each object types. Then, select orientation, calculate Y position, and finally draw left and right images on the graphics.

private void DrawObjects(Graphics gLeftx, Graphics gRightx, picCollection myP)
{
    ColorMatrix myMatrix = new ColorMatrix();

    // you can deactivate some objects in refresh time.
    // also make special effects for groups of objects
    switch (myP.myType)
    {
        case myObjectTypes.Cloud:
            if (!chkCloudObjects.Checked) return;
            myMatrix = new ColorMatrix(picMatrix[(cmbCloudEffect.SelectedIndex > 0 ? 
              cmbCloudEffect.SelectedIndex : cmbGeneralEffect.SelectedIndex)]);
            break;
….
 
    }
 
    Bitmap tempImage = new Bitmap(myP.myImage);
    Bitmap tempImage2 = new Bitmap(myP.myImage2);
 
    switch (myObjectsOrientation)
    {
        case 0:
        case 1:
            if (myP.myOrientation == 1)
            {
                tempImage.RotateFlip(RotateFlipType.RotateNoneFlipX);
                tempImage2.RotateFlip(RotateFlipType.RotateNoneFlipX);
            }
            break;
        case 2:
            if (myP.myOrientation == 0)
            {
                tempImage.RotateFlip(RotateFlipType.RotateNoneFlipX);
                tempImage2.RotateFlip(RotateFlipType.RotateNoneFlipX);
            }
            break;
        case 3:
            if (myRandom.Next(2) == 0)
            {
                tempImage.RotateFlip(RotateFlipType.RotateNoneFlipX);
                tempImage2.RotateFlip(RotateFlipType.RotateNoneFlipX);
            }
 
            break;
        case 4:
            // 
            break;
        case 5:
            {
                tempImage.RotateFlip(RotateFlipType.RotateNoneFlipX);
                tempImage2.RotateFlip(RotateFlipType.RotateNoneFlipX);
            }
            break;
    }
 
    // Create ImageAttributes and Set Color Matrix
    ImageAttributes imgAtt = new ImageAttributes();
    imgAtt.SetColorMatrix(myMatrix, ColorMatrixFlag.Default, ColorAdjustType.Default);
 
 
    if (myP.myType != myObjectTypes.Bird &&  myP.myType != myObjectTypes.Cloud)
        myP.myY = myBackHeight 
        - (float)myP.myImage.Height * myP.myScale 
        + myP.myDepth * myAltitude + myAltitude ;
 
    int mynewY =(int) myP.myY + trbdigging.Value;
 
    gLeftx.DrawImage(tempImage, new Rectangle((int)myP.myX, (int)(mynewY  ), 
      (int)(tempImage.Width * myP.myScale), (int)(tempImage.Height * myP.myScale)), 
      0, 0, tempImage.Width, tempImage.Height, GraphicsUnit.Pixel, imgAtt);
    gRightx.DrawImage(tempImage2, new Rectangle((int)(myP.myX - myP.myDepth) - 
      baseDepth, (int)(mynewY  ), (int)(tempImage2.Width * myP.myScale), 
      (int)(tempImage2.Height * myP.myScale)), 0, 0, tempImage2.Width, 
      tempImage2.Height, GraphicsUnit.Pixel, imgAtt);
    tempImage.Dispose();
    tempImage2.Dispose();

You can see the result of left and right images by clicking on the Show Left or Show Right selection on the Tools menu. 

You can also save these left and right images by selecting "Save Left-Right Images" item in the Tools menu.

The result at this level is something like the following images for left and right.

This is the Left image:

And this is the right image:

Make Anaglyph images from drawn images

In this section, we remove all green and blue colors data from left image, so it has only red color data. For this job the following color matrix is used:

float[][] matrixRedChannel = new float[][] {
    new float[] {1, 0, 0, 0, 0}, //  Red  
    new float[] {0, 0, 0, 0, 0}, //  Green
    new float[] {0, 0, 0, 0, 0}, //  Blue
    new float[] {0, 0, 0, 1, 0}, // Transparency
    new float[] {0, 0, 0, 0, 1}
};

Each pixel will be between RGB (0,0,0) to RGB(255,0,0) And the left image will be changed to red: 

Also, we remove all red colors data from right image, so it has only cyan color data. For this job the following color matrix is used:

float[][] matrixCyanChannel = new float[][] {
    new float[] {0, 0, 0, 0, 0}, //  Red  
    new float[] {0, 1, 0, 0, 0}, //  Green
    new float[] {0, 0, 1, 0, 0}, //  Blue
    new float[] {0, 0, 0, 1, 0}, // Transparency
    new float[] {0, 0, 0, 0, 1}
};

Each pixel will be between RGB (0, 0, 0) to RGB (0,255,255) and the right image will be changed to cyan:

If you like to have red and cyan images of your job, click on the "Save Red-Cyan Images" in the Tools menu.

Now we have to put these two images on one image. For this job we add the color information for each pixel in red and cyan images to new image. The color mixing result is shown in the following table:

unsafe 
{
    BitmapData bmpData = bmpOutputLeft.LockBits(myRectangle, 
       ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
    Color myPixel;


    for (int y = 0; y < bmpData.Height; y++)
    {
        byte* myRow = (byte*)bmpData.Scan0 + (y * bmpData.Stride);
        for (int x = 0, p = 0; x < bmpData.Width; p += 3, x++)
        {
            myPixel = bmpOutputRight.GetPixel(x, y);
            myRow[p] += myPixel.B;
            myRow[p + 1] += myPixel.G;
            myRow[p + 2] += myPixel.R;

        }
    }
    bmpOutputLeft.UnlockBits(bmpData);
}

And the result is our final image. 

Show the final image

Color matrices

There are many types of color effects which are predefined in setMatrices() method. Two methods are essential for the anaglyph part (cyan and red matrices), all other matrices are for fun.

private void setMatrices()
{
    float[][] matrixNormal = new float[][] {
        new float[] {1, 0, 0, 0, 0}, //  Red  
        new float[] {0, 1, 0, 0, 0}, //  Green
        new float[] {0, 0, 1, 0, 0}, //  Blue
        new float[] {0, 0, 0, 1, 0}, // Transparency
        new float[] {0, 0, 0, 0, 1}
    };
 
 
 
    float[][] matrixBW = new float[][] {
        new float[] {.34f, .34f, .34f, 0, 0}, //  Red  
        new float[] {.34f, .34f, .34f, 0, 0}, //  Green
        new float[] {.34f, .34f, .34f, 0, 0}, //  Blue
        new float[] {0, 0, 0, 1, 0}, // Transparency
        new float[] {0, 0, 0, 0, 1}
    };
    ...

Actions of some color matrices are shown on the same scene. For this job I selected "Danger Zone" in "Golestan Park" menu, after the first image, I changed General Effects and select Refresh (same direction): 

This is the base image of Danger Zone, and Color Matrix is:

float[][] matrixNormal = new float[][] {
    new float[] {1, 0, 0, 0, 0}, //  Red  
    new float[] {0, 1, 0, 0, 0}, //  Green
    new float[] {0, 0, 1, 0, 0}, //  Blue
    new float[] {0, 0, 0, 1, 0}, // Transparency
    new float[] {0, 0, 0, 0, 1}
};

Half-Color effect:

float[][] matrixHalfColor = new float[][] {
    new float[] {.7f, .2f, .2f, 0, 0}, //  Red  
    new float[] {.2f, .7f, .2f, 0, 0}, //  Green
    new float[] {.2f, .2f, .7f, 0, 0}, //  Blue
    new float[] {0, 0, 0, 1, 0}, // Transparency
    new float[] {0, 0, 0, 0, 1}
};

Black – White effect:

float[][] matrixBW = new float[][] {
    new float[] {.34f, .34f, .34f, 0, 0}, //  Red  
    new float[] {.34f, .34f, .34f, 0, 0}, //  Green
    new float[] {.34f, .34f, .34f, 0, 0}, //  Blue
    new float[] {0, 0, 0, 1, 0}, // Transparency
    new float[] {0, 0, 0, 0, 1}
};

Black-White effect with more contrast:

float[][] matrixBW_Contrast_More = new float[][] {
    new float[] {.5f, .5f,.5f, 0, 0}, //  Red  
    new float[] {.5f, .5f,.5f, 0, 0}, //  Green
    new float[] {.5f, .5f, .5f, 0, 0}, //  Blue
    new float[] {0, 0, 0, 1, 0}, // Transparency
    new float[] {0, 0, 0, 0, 1}
};

Colorize More effect:

float[][] matrixColorize_More = new float[][] {
    new float[] {1.3f, -.1f, -.1f, 0, 0}, //  Red  
    new float[] {-.1f, 1.3f, -.1f, 0, 0}, //  Green
    new float[] {-.1f, -.1f, 1.3f, 0, 0}, //  Blue
    new float[] {0, 0, 0, 1, 0}, // Transparency
    new float[] {0, 0, 0, 0, 1}
};

Colorize full effect:

float[][] matrixColorize_Full = new float[][] {
    new float[] {1.8f, -.3f, -.3f, 0, 0}, //  Red  
    new float[] {-.3f, 1.8f, -.3f, 0, 0}, //  Green
    new float[] {-.3f, -.3f, 1.8f, 0, 0}, //  Blue
    new float[] {0, 0, 0, 1, 0}, // Transparency
    new float[] {0, 0, 0, 0, 1}
};

Darker more + effect:

float[][] matrixDarker_MoreP = new float[][] {
    new float[] {.8f, -.1f, -.1f, 0, 0}, //  Red  
    new float[] {-.1f, .8f, -.1f, 0, 0}, //  Green
    new float[] {-.1f, -.1f, .8f, 0, 0}, //  Blue
    new float[] {0, 0, 0, 1, 0}, // Transparency
    new float[] {-.2f, -.2f, -.2f, 0, 1}
};

Red only effect:

float[][] matrixRedOnly = new float[][] {
    new float[] {1,0, 0,  0,0}, //  Red  
    new float[] {.34f, .34f, .34f, 0, 0}, //  Green
    new float[] {.34f, .34f, .34f, 0, 0}, //  Blue
    new float[] {0, 0, 0, 1, 0}, // Transparency
    new float[] {0, 0, 0, 0, 1}
};

Golden effect:

float[][] matrixGolden = new float[][] {
    new float[] {1.2f, 0, 0, 0, 0}, //  Red  
    new float[] {0, 1, 0, 0, 0}, //  Green
    new float[] {1, 1, 1, 0, 0}, //  Blue
    new float[] {0, 0, 0, 1, 0}, // Transparency
    new float[] {0, 0, 0, 0, 1}
};

Transparent More effect:

float[][] matrixTransparentMore = new float[][] {
    new float[] {1, 0, 0, 0, 0}, //  Red  
    new float[] {0, 1, 0, 0, 0}, //  Green
    new float[] {0, 0, 1, 0, 0}, //  Blue
    new float[] {0, 0, 0, 1, 0}, // Transparency
    new float[] {0, 0, 0, -.5f, 1}
};

And in the last image, I set the general filter to none, and set the specific filters of objects to:

Clouds = Darker More
Rocks= Golden
Animal = Darker More
Flowers= Colorize More+
Tree = Darker Full
Sky: Darker Most
Ground: Darker More+
Fog and Flying Birds = no Effect

Save data and images on the hard disk

The program automatically saves all objects info in a file which is placed in the Save/Data folder. This is somehow forward compatibility for "Load" options. The setDataForSave() and stbPicInfo(…) methods collect the information and the saveData(…) method write them to hard disk.

The program can also save images automatically or manually. There are some menu options in File menu and also an Auto save icon on the menu bar.

The default format for saved images is PNG, which has very good quality but needs more disk space. You can also use JPG format which needs very low disk space (almost 10 times lower than PNG) and medium quality. (See Tools menu)

You can also save left-right pairs or red-cyan for some purposes. All these options are in Tools menu. The ImageSave() and ImageSave2(…) methods are used in this job.

Snow

Snow animation is very simple and I have three purposes to use it:

  1. Show the layer between objects behind the monitor screen and objects in front of the screen.
  2. Show the place where basement (ground or water) meets the monitor screen
  3. Winter

For snow animation I used a very simple method. It works only on one layer and I put it where the left and right images are exactly on each other, so there is no need to change colors or make anaglyph object. Each snow drop is a windows form; this form contains a snow image and a timer to control movement from top to down.

The code by which I made snow is as follows:

if (mnuShowSnow.Checked && chkSnowObjects.Checked)
{
    if (picsAll[myObjectTypes.Snow.GetHashCode()].Count > 0)
    {
 
        for (int i = 1; i < trbSnow.Value; i++)
        {
            try
            {
                frmObject frmSnow = new frmObject();
 
                frmSnow.MyBasePosition = this.Location;
                frmSnow.MyBaseSize = this.Size;
                frmSnow.borderLeft = borderLeft;
                frmSnow.borderTop = borderTop;
                frmSnow.borderMenu = mainMenu.Height;
                frmSnow.isFullScreen = isFullScreen;
                frmSnow.myDepth = myRandom.Next(40 / myRandom.Next(1, 4));
                frmSnow.tmrMain.Interval = myRandom.Next(60 - frmSnow.myDepth, 70 - frmSnow.myDepth);
                frmSnow.MyDirectionVertical = true;
                frmSnow.MyMovementType = 1;
                frmSnow.MyMoveSpeed = 1 + frmSnow.myDepth / 20;
                frmSnow.myMoveDirection = myDirection.Positive;
                //(myRandom.Next(2) == 0 ? myDirection.Positive : myDirection.Negative);

                frmSnow.MyDirectionLimitMin = myRandom.Next(-500, -100);
                frmSnow.MyDirectionLimitMax = picAnaglyph.Height + myRandom.Next(100, 500);
                frmSnow.mySurface = mySnowSurface;
 
                frmSnow.myPosition = new Point(myRandom.Next(this.Width), this.Height + 100);
 
                frmSnow.myType = myObjectTypes.Snow;
                frmSnow.btmR = (Bitmap)picsAll[myObjectTypes.Snow.GetHashCode()][
                  myRandom.Next(picsAll[myObjectTypes.Snow.GetHashCode()].Count)];
                frmSnow.btmL = null;
 
                mySnowCollection.Add(frmSnow);
            }
            catch (ArgumentOutOfRangeException)
            {
                continue;
            }
        }
    }
}

I put all these snow forms in a collection, so working with them is easier when I want to stop or activate them.

I work with them like this:

foreach (frmObject mySnow in mySnowCollection) mySnow.Show(); 

or this one:

foreach (frmObject mySnow in mySnowCollection) mySnow.MyBaseSize = this.Size; 

You can disable the whole snow animation by clicking on the snow icon on the menu bar or click on Tools-> Show Snow. 

Take it easy about the speed, shapes and other mistakes of the snow, it’s only a run time effect and is not saved with the images.

Check for auto run timer, User orders, Edit image...

If you want to rest behind the monitor and watch the 3D images, you can activate auto run timer.  

In this situation "Full Screen Mode" is a good choice. 

If you check "Refresh Times" check box, it makes a few more scenes with the same objects but by changing the sky, basement (ground or water) and orientation of objects.

EDIT Workshop

The program works completely with random objects, random depth and size. So, most of the time you have some disadvantage of being more than one image of the same people (or other main objects) in the scene or objects being too close to each other, etc. So I decided to add a very simple Edit window by which you can change, delete or resize the objects. The Edit window is not very user friendly but it will help in most situations.

Initially, I choose a sample from menu, in this case I selected Samples -> Special Effects -> Farsian Cold Way:

The first thing I hate in this image is that I saw the same two vehicles. So I try to remove one. Select edit window from Edit -> Edit Panel menu.

The window is like this:

In the data grid you‘ll see all the objects of the scene. Scroll down to find the vehicle. 

When you click on the object in data grid, you’ll see it’s placed in the real place and orientation. Now, I want to change this vehicle to something else, so double click on the image in data grid. 

The new smaller window appears and you can scroll to find the right object. In this case I select the Peacock. When I back to last window I select OK, it takes a few seconds to redraw the scene. 

Oh, there is another vehicle there. I edit again and change the vehicle with a snow man. 

You can also change X position, Depth and Scale (Size) of each object by changing the values. Change in Y value is only acceptable for clouds and flying birds, for other objects Y value calculated automatically in terms of depth. The new results are good but I decided to change people to something else. So I edit the image again:

I hope God doesn’t do something like this to us.

Update note:

Now you can select objects by click on them. If there were more than one object in the clicked area, they will be shown on bottom data grid and you can select one of them.

This is a sample edit window.

If you click on the boat, the boat and the ship behind it will be selected and will show in the bottom data grid and one of them will be selected.

Then you can drag the window containing the object to wherever you want.

As I explained before, the vertical position (Y) is calculated based on the depth of objects, so when you move an object, only horizontal place will be saved. But for some objects like Clouds, Birds and edited objects you must have the ability to move them vertically; so there is an option to choose what will happened when you move objects.

  • "Save Only X" option is default, and even when you change the vertical place of object, it will not be saved and only horizontal changes will be saved.
  • "Save X,Y" will let you save both horizontal and vertical changes.
  • "Show Confirmation"  will save changes only when you click the "YES" button.

You can also replace the image with another one from the hard disk. Right click on the object in data grid view:

Have Fun  

I hope you have some fun with this program. Smile | <img src= 

Object Types

I categorized objects into some types like birds, animals, human, flowers… with each type having a special folder to hold images. You can add your images to these folders but you must consider the object heights. For example, if you want to add your personal family pictures to human folder, after cropping the picture of the person and making the space around it transparent, resize it as follows: If someone is about 180 cm, image height must be about 900 pixels. My formula is: 180 /2 -> result * 10 .

There is also a Misc folder which you can add any picture without notice to its size or type. This folder is accessible only in Edit mode and the containing images are not used in auto run.

The best format for objects is PNG with alpha layer (PNG-24 in Photoshop), but the file size of images in this format is huge, so you can choose PNG-8 or Gif formats in some situations.

Quality of the scene

I have some kind of red/cyan glasses with different quality. It’s recommended to choose good quality glasses and use a high contrast monitor with high resolution (I use Samsung 22” Full HD LED monitor and they are very good for this job).

Recently, I have found two types of glasses here in the market. The left one is cheaper but the quality is better!

Points of Interest

It will be good to add load option in the next updates. The edit window must be more interactive and user friendly and you must be able to add objects in run time. Also ability to work with other types of glasses must be nice. These all depend on the popularity of this old fashioned article which remains to be seen.

History

  • First upload: 5 Jan 2013.
  • Updated: 12 Jan 2013. Different output resolutions, ability to use 3D images as background and some new effects.
  • Updated: 24 Jan 2013. Some improvements in "EDIT" panel.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here