Gores Source

If you want to download the Visual Studio project, here you go 🙂
gores_source

If you want to download the executable file, that’s it:
gores_32bit gores_64bit

First of all, this project is in C#, but it should not be difficult to port it into any other language. Only the meaningful parts of the code are described here.

Loading file

Ok, so let’s make a new Visual Studio C# Console App, and load the bitmaps to be modified. We will open a file dialog, which can only open image files:

1
2
3
4
5
6
var openFileDialog = new OpenFileDialog();
openFileDialog.Filter = “Images|*.png;*.bmp;*.jpg;*.jpeg”;
var result = openFileDialog.ShowDialog();

if (result != DialogResult.OK)
    return;

Note that we check if user actually picked a file in the last if instruction.

Then we need to load the bitmap into our program, and ask for number of gores. When everything is ready, we will perform gores projection on the bitmap.

1
2
3
4
5
6
7
8
9
10
string fileName = openFileDialog.FileName;

using (FileStream fs = new FileStream(fileName, FileMode.Open))
{
    Console.WriteLine(“Set the number of gores: “);
    int gores = Int32.Parse(Console.ReadLine());

    Bitmap file = new Bitmap(fs);
    // Make projection…
}

Ok, everything is pretty simple in here. Thanks to using statement, we make sure that FileStream is closed. Then we ask for number of gores, nothing difficult in here.

Projection setup

Let’s skip to the actual projection. To make things nicer I decided to make Projection another class. So don’t hesitate and add a new class to your project.

We will make a static method, which will return a bitmap, with prepared gores.

1
2
3
static public Bitmap Project(Bitmap input, int gores)
{
    Bitmap output = new Bitmap(input.Width, input.Height, input.PixelFormat);

The method gets the input bitmap and number of gores – that is pretty straightforward. We also make a buffer for results – it must be the size of original bitmap, and it also needs to share the same pixel format.

Actual projection

And here is the actual “meat” of the app:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int goreWidth = input.Width/(gores);

int goreStart = 0;
int goreCenter = goreWidth/2;

// iterate through each gore
for (int i = 0; i < gores; ++i)
{
    // iterate through each pixel
    for (int x = 0; x < goreWidth; ++x)
    {
        for (int y = 0; y <; input.Height; ++y)
        {
            // set new position for the pixel
            output.SetPixel((int)(goreCenter + Math.Cos(Math.PI / 2 +
                Math.PI * ((double)y / (double)input.Height)) * (x goreWidth / 2)),
                y, input.GetPixel(goreStart + x, y));
        }
    }

    // move start and center to the correct places
    goreStart += goreWidth;
    goreCenter += goreWidth;
}

Projection explanation

First of all, it is a great idea to learn about sinusoidal projection from Wikipedia. Basically, we only need the equation from there, which is:

This means, that we need to move every pixel in the input bitmap, to the specific position in the output bitmap. Y stays the same, it is X that varies.

Actually, we need to make projection for every gore. That is why we need to declare some additional variables, to help us in the process (goreWidth, goreCenter, goreStart). Then we iterate through gores, and in every gore, we iterate through pixels. We need to find new position for each pixel (check comments in the code above).

Now look how x and y are set:

x = goreCenter + Math.Cos(-Math.PI / 2 + Math.PI * (y / input.Height)) * (x – goreWidth / 2)
What wiki describes as 0 is the center of the gore (what they assume as Earth’s center is meridian 0). For us the center of the gore is goreCenter. Then we need the latitude. Normally the north pole is -90 degrees, and the south pole is 90 degrees. This is a span of 180 degrees. Let’s convert our Y coordinate to actual latitude. We check what fraction of the actual height is our current y (y / height), and multiply it by 180 degrees (PI) – the span. Then we subtract 90 degrees to make it the correct scope. We also need to multiply by (x – goreWidth / 2) according to the equation.

y = y – y stays the same.

When we have iterated through each pixel we move Start and Center to the correct places.

Progress state display

In our code, we’ve also added displaying progress state. Let’s take a look into it. Here is the method to print progress:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static public void WriteState(int step, int steps, int barLength)
{
    Console.Clear();
    Console.WriteLine(step + “/” + steps);

    double fractionDone = (double) step/(double) steps;
    int barSegments = (int) Math.Ceiling(fractionDone*barLength 0.5);

    for (int i = 0; i < barSegments; ++i)
    {
        Console.Write(“|”);
    }
    for (int i = barSegments; i < barLength; ++i)
    {
        Console.Write(“.”);
    }
}

It is pretty straightforward. We get the current step and number of all steps. We also draw progress bar. Nothing difficult in here.

We need to integrate it into our Project() method now:

Let’s add new variables for steps:

1
2
int steps = gores*goreWidth;
int step = 0;

And draw state, before every pixel calculation:

1
2
step++;
WriteState(step, steps, 40);

And some finishing moves…

Ok, the meat is done, now let’s just add it to our main class:

1
2
3
4
5
6
7
8
9
10
11
Bitmap output;
using (FileStream fs = new FileStream(fileName, FileMode.Open))
{
    Console.WriteLine(“Set the number of gores: “);
    int gores = Int32.Parse(Console.ReadLine());

    Bitmap file = new Bitmap(fs);

    // do the actual projection
    output = Projection.Project(file, gores);
}

Save file

And we need to save everything now:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var save = new SaveFileDialog();
save.Filter = “JPeg Image|*.jpg|Bitmap Image|*.bmp|PNG Image|*.png”;
save.Title = “Save the File”;

if(save.ShowDialog() == DialogResult.OK)
{
    string fName = save.FileName;

    if (save.FileName != “”)
    {
        switch (save.FilterIndex)
        {
        case 1:
            output.Save(save.FileName, System.Drawing.Imaging.ImageFormat.Jpeg);
            break;
        case 2:
            output.Save(save.FileName, System.Drawing.Imaging.ImageFormat.Bmp);
            break;
        case 3:
            output.Save(save.FileName, System.Drawing.Imaging.ImageFormat.Png);
            break;
        }
    }
}

We open a save file dialog, and save the bitmap. Again everything is simple here.

To sum up…

And that’s it! I hope you have learnt something useful. If you have any questions or requests, ask in the comments.