Hudzilla Coding Academy: Project Nine

TuxRadar

Hudzilla Coding Academy

Not all our projects need to be big and complicated. In fact, you'll find that once your coding skill gets to a certain point, you'll start tackling everyday problems with a dash of code - not only can you do it faster when you make your PC do all the hard work, but it's much easier to make a small change to the process and repeat it. Of course, it also means you build up a little library of tools that you can customise, repurpose and re-use as often as you want, so your coding just gets more useful with time.

In this project we're going to make an image thumbnail generator. It will loop over all the picture files in a directory, creating smaller versions of them all, then save it all out as HTML for web page viewing. To make all this as easy to re-use as possible, we're going to build the project up piece by piece, adding features in a modular way so that you can just take whatever interests you. As you can imagine, Mono makes all of our task very easy, so let's dive in and see how fast we can get it done...

First steps

Create a new MonoDevelop command-line project called AutoThumb, then add System.Drawing to your References. You'll need to add it to your "using" statements at the top of Program.cs as well, so modify your "using" lines to include these four:

using System.Drawing;

using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;

That second "using" line, System.Drawing.Drawing2D, isn't needed for simple thumbnail generation, but if we include it then we can ask Mono to make the thumbnails as nice as possible. The third one, System.Drawing.Imaging, is really only needed for one tiny part - it allows us to specify the file format of images, which means we can convert images to PNG, JPEG or whatever. The last new "using" line, System.IO, is our old friend for working with files and directories. Remember, we need to loop through each image in the directory, so System.IO will come in very useful.

We're going to start by making a simple loop that goes over every image in a directory, and prints out the HTML tag to show it. If you've never written any HTML before, it's a very simple mark-up language that simply describes what can be seen on a web page. To show an image, we need to use the <img> tag, like this:

<img src="someimage.jpg" />

You can optionally also add "width" and "height" parameters, which let the web browser allocate space for the picture before it has been downloaded, making the whole page render faster. This was more important back in the days of dial-up internet connections, but there's no harm adding it anyway given that it's all done by Mono! Here's how it looks:

<img src="someimage.jpg" width="400" height="300" />

For now, let's just create the HTML tag required to show an image at is current size, which is what happens when you don't specify height or width parameters. So, the code we need to write has to put up a list of all the files in a directory (we can use the Directory.GetFiles() method for this, as used in project three), then loop over each one and print out a HTML tag for it. Here's how to do it - replace your Main() method with this one:

static void Main(string[] args) {

	string[] files = Directory.GetFiles(".", "*.jpg");



	foreach (string file in files) {
		// now for a little tweak...

		string path = Path.GetFileName(file);

		Console.WriteLine("<img src=\"{0}\" />", path);

	}

}

Nothing too complicated in there, right? Well, perhaps you're wondering what the "little tweak" is that requires us to use Path.GetFilename(). Put simply, Directory.GetFiles() is capable of searching in subdirectories as well as the current directory, so when it returns its file list it tells us where each file is relative to the current directory.

But if a file, "foo.jpg" is in the current directory, it doesn't just tell us "foo.jpg" - instead, we get "./foo.jpg", which is the Unix way of saying "foo.jpg in the current directory". In HTML, though, we don't really want that "./", so Path.GetFilename() is used, because that looks at a full file path, eg /foo/bar/baz.wombat and returns only the filename component, ie baz.wombat.

Once we have just the filename for each picture, Console.WriteLine() is used to output a HTML tag to show it. Simple! But, of course, only a small part of this project...

Our program will read through the current folder and resize every JPEG picture it finds.

Our program will read through the current folder and resize every JPEG picture it finds.

Resizing images with Mono

To work with images in Mono, you need to use two classes: Image, which is what stores your picture data, and Graphics, which lets you manipulate an Image. We'll also be using, albeit briefly, a special version of the Image class called Bitmap, which is used for bitmap images - that's the technical name for images like JPEGs and PNGs.

Here's what we need to do:

  1. Create a directory called "thumbs" that will store all our thumbnail images. If the directory exists already, delete it.
  2. Get all the pictures in the current directory.
  3. For each picture, open it as an Image.
  4. Create a new Bitmap object that is the height and width of our thumbnail.
  5. Create a new Graphics object for that thumbnail bitmap, so that we can modify it.
  6. Draw the original image into the thumbnail at the thumbnail size.
  7. Save the thumbnail into the "thumbs" directory as a JPEG.
  8. Print out the HTML tag as before.

So, with all that in mind, replace the contents of your Main() method with this:

// start by creating an empty "thumbs" directory, deleting it if it already exists
if (Directory.Exists("thumbs")) Directory.Delete("thumbs", true);

Directory.CreateDirectory("thumbs");

string[] files = Directory.GetFiles(".", "*.jpg");

foreach (string file in files) {

	string path = Path.GetFileName(file);

	
	// now create an Image from the picture

	Image original = Image.FromFile(path);

	// and create space for the thumbnail as well

	Image thumbnail = new Bitmap(ThumbnailWidth, ThumbnailHeight);

	// create a Graphics object for the thumbnail, then use it to
	// draw the original into the thumbnail
	Graphics graphic = Graphics.FromImage(thumbnail);

	graphic.DrawImage(original, 0, 0, ThumbnailWidth, ThumbnailHeight);

	
	// now save the thumbnail as a JPEG

	thumbnail.Save("thumbs/" + path, ImageFormat.Jpeg);



	Console.WriteLine("<a href=\"{0}\"><img src=\"thumbs/{0}\" /></a> ", path);

}

There are three things that are likely to be confusing in that code block. First, ThumbnailWidth and ThumbnailHeight - I haven't mentioned those two variables before, and yet they get used twice. You need to define these in your code to be whatever size you want your thumbnails to be. In my own code, I used this:

namespace AutoThumb

{

	class MainClass

	{

		static int ThumbnailWidth = 400;

		static int ThumbnailHeight = 300;



		static void Main(string[] args) {

But you can just change 400 and 300 to whatever you like.

The second potentially confusing part is the call to graphic.DrawImage(), mainly because I haven't explained what any of the parameters do. Well, it turns out that there are multiple ways you can draw one image into another using Mono - DrawImageUnscaled(), for example, always draws the source image into the destination image at its original size. But plain old DrawImage() is actually more interesting, because it lets us specify both the position and size of the source image, effectively allowing us to scale it freely.

The third potentially confusing thing is the extra HTML tag, <a>. This is used to make clickable links on web pages, like this:

<a href="somepage.html">read this!</a>

Anything can go between the <a> and the </a>, and it all gets made into the clickable link. In our code we put the <img> tag inside the <a> so that the thumbnail picture is clickable, linking through to the full-size version:

<a href="{0}"><img src="thumbs/{0}" /></a>

Back at the beginning of this project, I said that fixing things with a program means that "it's much easier to make a small change to the process and repeat it", and here is a good example. If you ran the program (yes, it should work) then someone told you, "hey, did you know you can specify higher-quality rendering when resizing images?" you wouldn't have to fire up The Gimp and re-do everything by hand. Instead, you just have to insert the code to ask Mono to do high-quality image resizing, then re-run the app.

And yes, Mono really does have an option to make graphics rendering look better - although technically you're just stating your wishes; Mono could, behind the scenes, do very nice rendering by default if it felt like it. But to be absolutely sure you're going to get the best rendering when working images, you need to add three lines beneath the part where you create a new Graphics object:

Graphics graphic = Graphics.FromImage(thumbnail);

graphic.CompositingQuality = CompositingQuality.HighQuality;

graphic.SmoothingMode = SmoothingMode.HighQuality;

graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;

There are several possible values for each of those settings, but High Quality should ensure that you get the best output no matter what.

Resizing images with the lowest options is nice and fast, but doesn't give a very attractive result.

Resizing images with the lowest options is fast, but doesn't give a very attractive result.

Resizing with high quality options set smooths everything nicely, making text easier to read at smaller sizes.

Resizing with high quality options set smooths everything nicely, making text easier to read at smaller sizes.

Customising the thumbnail size

At this point, the program is starting to become useful: it will create thumbnails for every picture in a directory, and output the HTML needed to show it. But one problem is that it simply isn't very flexible: 400x300 pictures might be fine for your current needs, but wouldn't it be neat if we let users pick their thumbnail size when the app is run?

We covered working with command-line arguments way back in project one, but this time we're going to make things a little bit more interesting. You see, the natural way for a user to specify the size for thumbnails is using the notation "WidthxHeight", eg "400x300". But when that gets sent to Mono, it gets sent as a single parameter, so we're going to have to do a little processing to make it work. Fortunately, it just so happens that splitting a string into parts is remarkably useful, so the skills you learn here will prove useful in amy future projects!

What we're going to do is this:

  1. If there is precisely one argument passed into the app, read it in.
  2. If that argument contains an "x", split it into two around that "x".
  3. If we get more than two elements back from splitting the string, it means there was more than one "x", so bail out.
  4. Otherwise, try using the first element as the width value and the second element as the height value.

There are two important things to note here. First, ThumbnailWidth and ThumbnailHeight have been given default values of 400 and 300. This means that if no parameters are given, those default values will be used. Second, trying to convert user input into integers is a bit risky: users might type "ElephantxFish" as their argument, which would cause the app to explode spectacularly. So, the smart thing for a coder to do is wrap these sensitive parts in a try/catch block, printing out usage instructions if there's a problem.

Taking all those plans and converting them to C#, you get this:

if (args.Length == 1) {
	// only change thumbnail size if precisely one parameter is given


	try {
		// we're in the try/catch block now, for safety

		// grab the single argument

		string size = args[0];


		if (size.Contains("x")) {
			// the argument contains an x, so 500x300 is possible, 
			// but so is 500xElephant

			// Here's how to split a string into parts:			

			string[] widthheight = size.Split(new char[] { 'x' });

			

			if (widthheight.Length == 2) {
				// if we got exactly two parts back, try to use them a
				// the thumbnail width and height

				ThumbnailWidth = Convert.ToInt32(widthheight[0]);

				ThumbnailHeight = Convert.ToInt32(widthheight[1]);

			}

		}

	} catch {
		// if ANY errors occur, print out usage information and quit

		Console.WriteLine("AutoThumb");

		Console.WriteLine("If you want to specify a maximum width and height, do it");

		Console.WriteLine("using the format WidthxHeight, eg 300x200");

		return;

	}

}

Put that at the top of your main method so that all the parameter resolution stuff is done before any real work takes place.

All of that code has been used before, with the exception of the Split() method of strings. This looks like quite an ugly little method at first, but once you see how it works it will make more sense. Let's use the string "Hudzilla is the best" as an example for a moment. If you wanted to split that string up so that you can read each word individually, you want to use the Split() method. If you split it by spaces, you'll get four strings back: "Hudzilla", "is", "the", "best".

A detour into the Split() method

There are a few things you should know about the way Split() works, but, because the Split() method is used so commonly, I want to explain it to you using some practical examples. Here is some example code for us to work with:

public static void Main(string[] args)
{
	string meh = "eggs and beans and chips and  spam";
	
	string[] spaces = meh.Split(new char[] { ' ' });
	PrintStringArray(spaces);
	
	string[] spaces2 = meh.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
	PrintStringArray(spaces2);
	
	string[] chars = meh.Split(new char[] { 'a', 'b', 'g' }, StringSplitOptions.RemoveEmptyEntries);
	PrintStringArray(chars);
	
	string[] words = meh.Split(new string[] { "and" }, StringSplitOptions.None);
	PrintStringArray(words);
	
	string[] words2 = meh.Split(new string[] { "and", " " }, StringSplitOptions.RemoveEmptyEntries);
	PrintStringArray(words2);
}

public static void PrintStringArray(string[] arr) {
	foreach(string str in arr) {
		Console.Write(str + ", ");
	}
	
	Console.WriteLine("");
}

Basically that sets up an initial string called "meh", then uses Split() in different ways to get different results. That little PrintStringArray() method is just there to print out the contents of a string array, neatly separated with commas so you can see exactly what happens to the spaces - it gets used a lot, so putting it into a method saves space.

Before we look at what each Split() call does, you need to notice two things: each call to Split() has its own variable name attached to it, and the "meh" string has two spaces between "and" and "spam" - it's "and[space][space]spam" not just "and[space]spam".

The "spaces" Split() is the most simple type. Split() wants to know which characters you want to split on, so you can pass it an array of characters. In the "spaces" example we're creating an array of characters (char[]) and giving it precisely one element: ' ', which is the space character.

The syntax is very precise: you specify your characters inside braces, making "{ ' ' }", and you need to specify your characters inside single-quoted strings. If you use double quotes, ie { " " }, Mono will interpret that as a string rather than a char. Yes, to us a char is simple a single-letter string, but to Mono there's a big difference so you need to make sure you use single quotes for chars and double quotes for strings!

Each time we call Split() we get back as a return value an array of strings, so we can print it out using the PrintStringArray() method. Here's what the "spaces" Split() prints out:

eggs, and, beans, and, chips, and, , spam,

As you can see, the characters we split on get removed - it's "eggs, and, beans," rather than "eggs , and , beans , ". You can also see what happens when there are two spaces in a row: our original string had two spaces between "and" and "spam", so when we split on spaces we get "and, , spam," - an empty element is there between "and" and "spam".

This is why I made the PrintStringArray() method use commas inbetween elements, because it really highlights exactly what each element contains. If you want empty elements to be included in the return value, then leave the code as is. If you'd rather remove empty elements such as this one, then look at the next example: spaces2.

The "spaces2" Split() uses exactly the same split character as "spaces" (the space character), but this time we provide a second parameter to Split(): StringSplitOptions.RemoveEmptyEntries. This is one of those wonderful C# options that is so pleasantly descriptive there's not really much I can add to explain it better: it really does just remove empty items when the string is split. So, the "spaces2" Split() looks like this when passed through PrintStringArray():

eggs, and, beans, and, chips, and, spam,

As you can see, the empty element that was previously shown as "," has now gone, because it has automatically been removed.

Moving on, next is the "chars" Split(), which really highlights how Split() works with arrays. You see, in "spaces" and "spaces2" split the "meh" string on a single character, a space. But Split() can split a string on multiple characters - you just need to specify them in the array input. So, with "chars" we're going to specify an array containing three items: 'a', 'b', 'g'. Note that there is no space (' ') in there, which means spaces will be included in the return value. Here's what we get back:

e, s , nd , e, ns , nd chips , nd  sp, m,

When you're splitting on multiple characters like this, the RemoveEmptyEntries option becomes more important because otherwise if the input string contained 'a', 'b' and 'g' in a row or similar, you'd end up with several empty elements in the returned array. Using RemoveEmptyEntries cleans things up neatly, like this:

string input = "What did you do yesterday? I got married! Really?!? Really. That's amazing!!";
string[] sentences = input.Split(new char[] { '.', '!', '?' }, StringSplitOptions.RemoveEmptyEntries);
PrintStringArray(sentences);

The input this time is five sentences, each separated in different ways: a "?", a "!", a "?!?", a ".", and a "!!". We want to split that up into individual sentences, but because they end in different ways we need to pass an array of multiple characters into Split(). Also, because multiple characters appear side-by-side, eg "?!?", we need to use the RemoveEmptyEntries option so that we don't get back holes in the array. Here's what it outputs when passed through PrintStringArray():

What did you do yesterday,  I got married,  Really,  Really,  That's amazing,

So, feel free to specify as many characters as you want to Split(), but don't forget to use the RemoveEmptyEntries option if you need it.

Moving on, things get a bit more interesting with the "words" Split(), because this time we're splitting on a string array rather than a char array. In this example, we're asking Mono to split the input string up wherever it sees the word "and", so the return value when passed through PrintStringArray() is this:

eggs ,  beans ,  chips ,   spam,

Notice that there are still spaces in there, and we can fix that by simply specifying " " (an empty space - but note that it has double quotes because it's a string now!) in the Split() input. Of course, splitting by more than one thing usually works better when RemoveEmptyEntries is also specified, so we need to add that too, as seen in the "words2" Split() example. Here's what that outputs:

eggs, beans, chips, spam,

...which is basically perfect: it takes a string input, splits it up into chunks, then prints it out neatly. Well, I say "basically perfect", but there is one slight tweak we can make so that it's wholly perfect. Notice that little comma on the end? It doesn't really need to be there, does it? Well, rather than passing the "words2" array through PrintStringArray() I want to briefly show you how to use a method that does the opposite of Split(): it joins an array of strings together into a single string, with a separator that you choose.

As you might imagine, this method is called Join(), but it actually lives under the "string" class directly. That is, rather than typing this:

string foo;
foo.Join(whatever);

...instead you type this:

string foo;
foo = string.Join(whatever);

So, to join the "words2" array together into a single string, we need to tell Join() to use ", " to join the string elements together (a comma and a space, to make the items neatly separated), then tell it which array we want to join. In C# it looks like this:

string wordsjoined = string.Join(", ", words2);
Console.WriteLine(wordsjoined);

When that code is run, it prints out this:

eggs, beans, chips, spam

As you can see, that final comma isn't included, so the output is perfect.

That, in a rather large nutshell, is Split() and Join() explained. Yes, this has been quite a long detour, but you will use Split() all the time so it's important you get a good grasp of how it works!

If nothing else, at least understand that Split() is great at taking a string that has bits separated by a common character, such as a comma, and splitting it up into individual string array elements.

If nothing else, at least understand that Split() is great at taking a string that has bits separated by a common character, such as a comma, and splitting it up into individual string array elements.

Where were we?

OK, so the reason we went off on the Split() tangent was this line of code:

string[] widthheight = size.Split(new char[] { 'x' });

Hopefully you should be able to see what that does: it takes a string like "500x600" and splits it into a string array containing two elements, "500" and "600". We then use Convert.ToInt32() to convert those two strings into numbers, and use them as ThumbnailWidth and ThumbnailHeight.

If you compile and run the program straight from MonoDevelop, no parameters will be sent to the program so it will use the default values of 400 and 300 for width and height. But try opening up a terminal and running the app like this:

./AutoThumb.exe 640x480

Or, if your distro isn't set up to automagically handle Mono .exe files, you'll need this:

mono AutoThumb.exe 640x480

You should now see all the images being resized to 640x480 rather than 400x300. Easy! But also a bit simplistic: yes, being able to set a thumbnail size on the command line makes the whole thing more flexible, but what if your pictures have different sizes? For example, what if half your pictures are 1600x900, and the other half are 1600x1200? In that situation, resizing them all to 640x480 would distort the pictures, making them look stretched.

This problem can be fixed with a little bit of mathematics. If you read the introduction to this course you'll remember that I promised to skip out "techniques that transform 20 lines of easy-to-understand code into 1 line of near-magic", because at this stage of your coding life it's more important to understand how things work rather than how to make them as tiny as possible. So, the code to make images keep their original aspect ratio (ie, not to stretch them when made into thumbnails) is deliberately easy to understand.

It works like this:

  • Rather than ThumbnailWidth and ThumbnailHeight being the size we want to make each thumbnail, instead we'll use them as the maximum width and the maximum height of each thumbnail.
  • For each image, read in its height and width as a floating-point parameter. Yes, clearly image height and width is an integer (a whole number), but...
  • Divide the width and height by ThumbnailWidth and ThumbnailHeight respectively. Using floating-point numbers here ensures maximum accuracy, which is why we're not using integers.
  • If the width difference is bigger than the height difference, it means we need to scale the image down by its width first, then scale the height down a proportional amount so that it isn't stretched.
  • If the width difference is less than 1.0f, it means the picture is actually smaller than the maximum width specified, so we don't need to do anything.
  • Otherwise, we need to divide the image's width by the difference between its width and the maximum width, then divide the height by the difference between its height and the maximum width. Yes, that's "height and maximum width", not "height and maximum height".
  • Finally, we need to use this new width and height for the thumbnail size.

That might sound confusing, which is strange given that I promised I'd make it easy to understand. But if I run you through an example quickly it will all make sense. Hopefully!

Let's take for our example an image that is 1000x500 pixels in size. We have specified the maximum width and height to be 500x500. Here's how things pan out:

  • We divide 1000 (actual width) by 500 (maximum width) to get 2.0. We divide 500 (actual height) by 500 (maximum height) to get 1.0.
  • As the width difference (2.0) is greater than the height difference (1.0), we need to scale this image down using its maximum width.
  • We divide the image's width by the width difference: 1000 / 2.0 is 500. We divide the image's height by the width difference as well: 500 / 2.0 is 250.
  • So, the final thumbnail size is 500x250. This has the same aspect ratio, 2:1, as the original size of 1000x500.

As you can see, if we had divided the image height by the difference between its height and the maximum height (as opposed to the maximum width), we'd have done 500 / 1.0, which is 500 - we'd have taken a rectangular image and made it square! The other thing to note is that if the width difference is less than the height difference (eg if the image is 500x1000 with a maximum thumbnail size of 500x500), then we use the height difference to calculate the thumbnails width and height.

I hope that all makes sense now, because, if it doesn't, it's unlikely to make much more sense when you see it in code! Suffice to say that few images sizes are as neat as 1000x500 with a maximum thumbnail size of 500x500 - it's important to use floating-point numbers for maximum accuracy, rounding them off when you're done so you can convert them to integers.

So, it's time to convert that simple algorithm into C#. And, now that we now the correct size each image should be shown at, we can add the "width" and "height" attributes to our HTML <img> tag. Replace the existing foreach loop with this new one:

foreach (string file in files) {

	string path = Path.GetFileName(file);



	Image original = Image.FromFile(path);


	// pull in the image's size here	

	float imagewidth = original.Width;

	float imageheight = original.Height;

	
	// now calculate the ratio of size vs max size

	float widthdiff = imagewidth / ThumbnailWidth;

	float heightdiff = imageheight / ThumbnailHeight;

	

	if (widthdiff > heightdiff) {

		// we need to resize this down based on its width

		if (widthdiff > 1.0f) {
			// remember: we need to use widthdiff for both of these!

			imagewidth = imagewidth / widthdiff;

			imageheight = imageheight / widthdiff;

		}

	} else {

		// we need to resize this down based on its height

		if (heightdiff > 1.0f) {
			// and we need to use heightdiff here!

			imagewidth = imagewidth / heightdiff;

			imageheight = imageheight / heightdiff;

		}

	}

					
	// round the width and height here so that it's the same throughout the rest
	// of this loop - it's used four times below!

	imagewidth = (float)Math.Round(imagewidth);

	imageheight = (float)Math.Round(imageheight);

	
	// we need to force the floats into ints here

	Image thumbnail = new Bitmap((int)imagewidth, (int)imageheight);

	
	// resize as normal...

	Graphics graphic = Graphics.FromImage(thumbnail);

	graphic.CompositingQuality = CompositingQuality.HighQuality;

	graphic.SmoothingMode = SmoothingMode.HighQuality;

	graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;


	// BUT: specify the new width and height here

	graphic.DrawImage(original, 0, 0, imagewidth, imageheight);

	

	thumbnail.Save("thumbs/" + path, ImageFormat.Jpeg);


	// and specify the width and height in HTML here

	Console.WriteLine("<a href=\"{0}\"><img src=\"thumbs/{0}\" width=\"{1}\" height=\"{2}\" /></a> ",
		path, imagewidth, imageheight);

}

If you run the app now, you'll see that the thumbnails are now generated with their aspect ratio intact - long, thin pictures are now long and thin as thumbnails rather than forced into the same shape.

At this point, our program outputs HTML to the command line ready for use in a web page.

At this point, our program outputs HTML to the command line ready for use in a web page.

Finishing touches: making nice HTML

At this point the program is basically done, but with just a little more effort we can make it output nice HTML that can be copy and pasted into a text file ready for the web. This is very easy to do, because we only need a very thin wrapper around the pictures to make it neat.

This isn't a HTML tutorial, so I'm afraid we're not going to go into any depth as to how HTML works and what tags you need. Instead, just put these six lines of code immediately after the Directory.CreateDirectory() line:

Console.WriteLine("<html>");

Console.WriteLine("<head>");

Console.WriteLine("<title>AutoThumb gallery</title>");

Console.WriteLine("</head>");

Console.WriteLine("");

Console.WriteLine("<body>");

...and put these two just after the end of the foreach loop:

Console.WriteLine("</body>");

Console.WriteLine("</html>");

And that's it: if you run the program now it should generate all the HTML needed to create a simple thumbnail gallery from a directory of pictures. Is there more you can do with this? Of course there is, but that's what homework is for...

The finished product: HTML gets generated with the proper header and footer, making our app output complete web pages.

The finished product: HTML gets generated with the proper header and footer, making our app output complete web pages.

Let's wrap up

This is not a complicated project. Instead, it's a pretty mundane operation that gets solved in a neat way using Mono and C#, because that's a smarter, faster and longer-lasting solution than trying to do everything by hand. Along the way you've learned a little bit about working with images, but also a lot about how to split strings in interesting ways then join them back together again.

If you're looking to dabble a little more, MonoDevelop provides the perfect means: just type "graphics." somewhere after you create the Graphics object on your thumbnail, and you'll see there are all sorts of things you can do - experiment and have some fun!

Homework

If you're following this with a tutor, you will be required to complete the following homework before continuing. If you're working by yourself, I strongly recommend you find someone who can help check your work and provide feedback.

The homework for this project is made up of three coding problems; all are required.

  • Right now, the HTML is being printed out to the console. This is fine if we don't mind asking folks to copy and paste output somewhere, but a nicer solution is to save the output into a file. So, modify the program so that it saves the output to index.html rather than printing it out to the screen.
  • Once index.html is created, modify the program so that it automatically launches Firefox and loads the index.html file. We looked at how to launch Firefox back in project seven, so if you skipped that project you should read back to it now!
  • Optionally allow the user to specify a second parameter, --fast. If this is set (eg "./AutoThumb.exe 400x300 --fast") you should change CompositingQuality, SmoothingMode and InterpolationMode to the fastest options.

If you have problems, try to solve them yourself - you might not succeed, but you'll learn a lot by trying! If you're still having problems, drop your tutor an email and ask for help.

The small print

This work was produced for TuxRadar.com as part of the Hudzilla Coding Academy series. All source code for this project is licensed under the GNU General Public License v3 or later. All other rights are reserved.

You should follow us on Identi.ca or Twitter


Your comments

Typo?

I think the line that says:

string path = Path.GetFileName(file);

should be:

string path = Directory.GetFileName(file);

shouldn't it?

Thanks

Charlie

Actually not

Actually, probably not, ignore me.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Post new comment

CAPTCHA
We can't accept links (unless you obfuscate them). You also need to negotiate the following CAPTCHA...

Username:   Password:
Create Account | About TuxRadar