Title: Spiralize an image in C#
This example shows how to spiralize an image. By spiralize I mean shade pieces of the picture with different colors so the colored areas form a spiral as shown in the picture.
This could be a difficult problem. Fortunately I've already posted examples that show how to perform the more difficult pieces, so this program is mostly a matter of reusing those techniques in new ways.
Overview
At a high level, the basic approach to spiralize an image is relatively simple.
- Generate the points needed to define the sections of the spiral
- For each section of the spiral:
- Make a colorized version of the original image
- Use the colorized image to make a TextureBrush and use the brush to fill the spiral section
I covered the hardest parts of this process in earlier posts, but there is still a fair amount of work to do.
Code
To spiralize an image, the program uses following SpiralizeImage method.
// Spiralize the image.
private Bitmap SpiralizeImage(Bitmap original_bm, Color[] colors,
float A, float color_scale, float opacity,
bool outline_spirals, bool make_elliptical)
{
int wid = original_bm.Width;
int hgt = original_bm.Height;
Bitmap bm = new Bitmap(wid, hgt);
using (Graphics gr = Graphics.FromImage(bm))
{
gr.Clear(picSpiral.BackColor);
gr.SmoothingMode = SmoothingMode.AntiAlias;
int num_spirals = colors.Length;
// Angular spacing between different spirals.
float d_start = (float)(2 * Math.PI / num_spirals);
// The angle where the next spiral starts.
float start_angle = 0;
// Center point.
PointF center = new PointF(wid / 2, hgt / 2);
// Draw on the entire image.
Rectangle rect = new Rectangle(0, 0, wid, hgt);
// Find the maximum distance to the rectangle's corners.
float max_dist = Distance(center, rect);
// Calculate the maximum theta value that we need to go to.
float max_theta = max_dist / A + 2 * (float)Math.PI;
// Get points defining the spirals.
List<List<PointF>> spiral_points = new List<List<PointF>>();
// Get the spirals' points.
for (int i = 0; i <= num_spirals; i++)
{
spiral_points.Add(GetSpiralPoints(
center, A, start_angle, max_theta));
start_angle += d_start;
}
// Fill the areas between the spirals.
for (int i = 0; i < num_spirals; i++)
{
// Make a list holding the next spiral's points.
List<PointF> points = new List<PointF>(spiral_points[i]);
// Add the following spiral's points reversed.
List<PointF> points2 =
new List<PointF>(spiral_points[i + 1]);
points2.Reverse();
points.AddRange(points2);
// Make a colorized version of the image.
using (Bitmap colorized_bm =
ColorizeImage(OriginalBm, Colors[i], color_scale))
{
// Fill the spiral.
using (TextureBrush brush =
new TextureBrush(colorized_bm))
{
gr.FillPolygon(brush, points.ToArray());
}
}
// Optional: Outline the spiral's polygon.
if (outline_spirals)
gr.DrawLines(Pens.Black, points.ToArray());
}
}
// Combine the new image with the original one.
Bitmap new_bm = (Bitmap)original_bm.Clone();
using (Graphics gr = Graphics.FromImage(new_bm))
{
// Make a ColorMatrix with the opacity.
ColorMatrix color_matrix = new ColorMatrix();
color_matrix.Matrix33 = opacity;
// Make the ImageAttributes object.
ImageAttributes attributes = new ImageAttributes();
attributes.SetColorMatrix(color_matrix,
ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
// Draw the input bitmap onto the Graphics object.
Rectangle rect = new Rectangle(0, 0,
new_bm.Width, new_bm.Height);
gr.DrawImage(bm, rect,
0, 0, new_bm.Width, new_bm.Height,
GraphicsUnit.Pixel, attributes);
}
// Make it elliptical if desired.
if (make_elliptical) new_bm = MakeElliptical(new_bm);
return new_bm;
}
This method creates a new bitmap with the same size as the original and makes an associated Graphics object.
Next the code uses the number of colors that we should use to calculate the angular spacing between the different spiral sections. It calculates some drawing parameters such as the rectangle that contains the image and the center of the image. It then finds the distance from the center of the rectangle to its corners so we know how far to draw the spirals to spiralize the entire image. It also calculates the largest angle that we need to use when building the spirals to cover the image. Those calculations are described in the post Draw a filled spiral in C#.
After all of this setup, the program creates a list of lists of points to hold the points that define each of the spiral sections. It loops through the spirals and calls the GetSpiralPoints method to find each spiral's points. See the post Draw an Archimedes spiral in C# for information on how that method works.
After it has found each spiral's points, the code loops through the spirals.
At this point, each of the spirals includes points moving from the center outward. To fill the strip between the spiral arms, the program uses a technique described in the post Draw a filled spiral in C#. It first copies the current spiral's points into a list named points. Then it gets the following spiral's points, reverses them, and adds them to the points list.
The result is a list of points that goes out from the center along one spiral arm and then returns on the next spiral arm. This defines a polygon that we can fill to fill the spiral section. That's the technique used by the post Draw a filled spiral in C#.
Next the program calls the ColorizeImage method (described later) to make a colorized version of the original image. It then makes a TextureBrush from the colorized image. If you fill a shape with this brush, the shape is filled with the corresponding parts of the colorized image. This example uses the brush to fill the current spiral section.
If the program's Outline Spirals checkbox is checked, the code also draws the spiral section's lines to outline that section.
After it has finished filling all of the spiral sections, I found that the result sometimes overwhelmed the original picture so it was hard to see. You could adjust the section drawing so the program drew each section with reduced opacity, but I took a simpler approach. After it creates the colorized image, the program copies the whole thing onto the original image with a reduce opacity. In the picture at the top of this post, for example, the program has copied the colorized image at 50% opacity onto the original image.
To copy the colorized image onto the original, the program makes a new bitmap to hold the result. It then creates a ColorMatrix and sets its opacity entry to the desired opacity. It makes an ImageAttributes object to use the ColorMatrix and then draws the colorized image on top of the copied original.
Finally, if the Elliptical checkbox is checked, the program calls the MakeElliptical method described later to make the result elliptical. The method finishes by returning the result image.
ColorizeImage
The ColorizeImage method uses techniques described in the post Rainbowize an image in C#. I simply moved the key code into a new method to make it easier to reuse.
The following code shows the ColorizeImage method.
// Return a colorized image.
// See howtos/howto_rainbowise_image.html
private Bitmap ColorizeImage(Bitmap original_bm,
Color color, float color_scale)
{
// Create a ColorMatrix for this color.
ColorMatrix cm = new ColorMatrix(new float[][]
{
new float[] {color.R / 255f * color_scale, 0, 0, 0, 0},
new float[] {0, color.G / 255f * color_scale, 0, 0, 0},
new float[] {0, 0, color.B / 255f * color_scale, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0, 0, 0, 0, 1},
});
// Make the result bitmap.
int wid = original_bm.Width;
int hgt = original_bm.Height;
Bitmap result_bm = new Bitmap(wid, hgt);
// Color the image.
using (ImageAttributes attr = new ImageAttributes())
{
attr.SetColorMatrix(cm);
// Copy the original image onto the result bitmap.
using (Graphics gr = Graphics.FromImage(result_bm))
{
Point[] dest_points =
{
new Point(0, 0),
new Point(wid, 0),
new Point(0, hgt),
};
Rectangle source_rect = new Rectangle(0, 0, wid, hgt);
gr.DrawImage(original_bm, dest_points,
source_rect, GraphicsUnit.Pixel, attr);
}
}
return result_bm;
}
This method takes as a parameter a color that should be the basis for the colorization. The ColorMatrix multiplies the red, green, and blue components of each pixel by a scale factor that depends on the color and the color_scale parameter.
For example, if the color is red and color_scale is 2, then the first entry in the ColorMatrix is 255 / 255 * 2 = 2. That means the matrix will double the amount of red in each pixel. Because the color red has green and blue components equal to zero, the other scaling factors in the matrix will be 0 / 255 * 2 = 0 and the matrix will remove the pixels' green and blue components.
After it has created the ColorMatrix, the method creates result bitmap and an ImageAttributes object to apply the colors. It then copies the original image onto the result bitmap using the ImageAttributes object and returns the result.
See the post Rainbowize an image in C# to see additional details.
MakeElliptical
The MakeElliptical method shown in the following code makes an image elliptical.
// Make the image elliptical.
private Bitmap MakeElliptical(Bitmap bm)
{
Bitmap new_bm = (Bitmap)bm.Clone();
using (Graphics gr = Graphics.FromImage(new_bm))
{
gr.Clear(Color.Transparent);
using (TextureBrush brush = new TextureBrush(bm))
{
Rectangle rect = new Rectangle(0, 0, bm.Width, bm.Height);
gr.FillEllipse(brush, rect);
}
}
return new_bm;
}
This method is relatively simple. It creates a new bitmap and clears it with the color Transparent. It then uses the original image to make a TextureBrush and uses it to fill an ellipse that covers the result bitmap. The result is a bitmap with transparent corners and that is filled with an ellipse containing whatever was in the corresponding parts of the original image as shown in the picture at the top of the post.
Conclusion
The basic idea behind this example is to fill polygons with colorized versions of the original image. This example filled spiral sections to create a spiralized image, but you could use other polygons. For example, you could cover the image with rectangles, ellipses, or puzzle piece shapes.
Download the example to experiment with it and to see additional details.
|