Posted 2/8/2010 11:58:46 PM
Creating a thumbnail in C# is fun and easy! Unfortunately, creating one with any halfway decent quality is a huge pain in the ass. Especially if you want to write it to a File instead of a Stream. Cutting to the chase, here's how you do it:
/// <summary>Creates a thumbnail of a given image.</summary>
/// <param name="inFile">Fully qualified path to file to create a thumbnail of</param>
/// <param name="outFile">Fully qualified path to created thumbnail</param>
/// <param name="x">Width of thumbnail</param>
/// <returns>flag; result = is success</returns>
public static bool CreateThumbnail(string inFile, string outFile, int x)
{
// Validation - assume 16x16 icon is smallest useful size. Smaller than that is just not going to do us any good anyway. I consider that an "Exceptional" case.
if (string.IsNullOrEmpty(inFile)) throw new ArgumentNullException("inFile");
if (string.IsNullOrEmpty(outFile)) throw new ArgumentNullException("outFile");
if (x < 16) throw new ArgumentOutOfRangeException("x");
if (!File.Exists(inFile)) throw new ArgumentOutOfRangeException("inFile", "File does not exist: " + inFile);
// Mathematically determine Y dimension
int y;
using (Image img = Image.FromFile(inFile)) {
double xyRatio = (double)x / (double)img.Width;
y = (int)((double)img.Height * xyRatio); }
// All this crap could have easily been Image.Save(filename, x, y)... but nooooo....
using (Bitmap bmp = new Bitmap(inFile))
using (Bitmap thumb = new Bitmap((Image)bmp, new Size(x, y)))
using (Graphics g = Graphics.FromImage(thumb)) {
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
System.Drawing.Imaging.ImageCodecInfo codec = System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders()[1];
System.Drawing.Imaging.EncoderParameters ep2 = new System.Drawing.Imaging.EncoderParameters(1);
ep2.Param[0] = new System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
g.DrawImage(bmp, new Rectangle(0,0,thumb.Width, thumb.Height));
try {
thumb.Save(outFile, codec, ep2);
return true; }
catch { return false; } }
}
This example creates a 400KB thumbnail of a 5MB file (4000x3000 to 900x675) within about a second. Reduce quality, remove antialiasing, or crappify the interpolation mode if you want your implementation to go faster. Personally, I'm patient enough to wait one second.
On a sufficiently related note (and totally uninteresting to most Googlers), the Gallery page now handles automatic thumbnail generation. Figured not everyone wants to bother downloading my 5MB 12.1 Megapixel images nor my 30-100 Megapixel panoramas.
Posted 2/8/2010 10:06:19 PM
Over the past couple days, I've allocated a whopping half hour to a little work on the Gallery. I added a second folder ("Subsequent") of what the lab (read: "Living Room") looks like since I got bored and rearranged it a week or three ago, enhanced the image to display the first image by default instead of a blank image (which of course looked like an error, as it resolved to a tag like <img src="" />. Now I'm thinking I might want to change things up and pick a random image instead. Like I'm doing now with the taglines at the bottom of every page (look now).
Added filemask to pic lookups to remove Windows thumbnail files (i.e. thumbs.db), and enhanced the Tree so it fully expands to level n-1, where n is the maximum level of a given tree substructure. That is to say, it will fully expand all directories that have subdirectories, but will not expand a directory that contains only files.
The code for that last part was really simple, for the recursion-inclined:
private void ExpandNonLeafNodes(TreeNode tn) {
foreach (TreeNode tn2 in tn.ChildNodes)
ExpandNonLeafNodes(tn2);
if (Directory.Exists(tn.Value) && Directory.GetDirectories(tn.Value).Length > 0) tn.Expand(); else tn.Collapse(); }
I think this framework will work well for organizing my public-facing showoff pics, such as my many panoramas (which I haven't taken many of lately... not sure why... Probably because I've seen and recorded panos of all the touristy stuff in the Colorado Springs area... but I digress). Will endeavor to put some of the better ones up on the Gallery.
Next up: Automatic thumbnail generation. Currently it just resizes the existing files to fit the size I want them to fit, but I'd rather it automatically create a thumbnail pic of a digestable size (i.e. 800x600 or so) and use that for the display copy. As always, pics are clicky.
Posted 2/7/2010 7:46:35 PM
Several months ago, I called my bank to complain about an issue I had with their website. It's a minor issue that goes like this:
When you first navigate to the website, keyboard focus goes to the Username textbox. That is to say, a Javascript script runs that moves keyboard focus to that textbox. The downside to that is, some users (i.e. me) are so fast that by the time the page finishes loading, we've clicked the username textbox, filled it in, hit TAB, and started typing in the password. This introduces a race condition whereas the user is racing against the page load complete event.
The page finishes loading sometime while the password is being entered and Javascript pops keyboard focus back to the Username textbox while the user is in the middle of typing their password. Thusly part of the password ends up in the Username textbox, which is not masked. This is a security issue, but moreover it's downright annoying as the user now has to retype both text values.
So I called them up and offered a simple solution: Check to ensure the Password textbox is not currently focused prior to changing keyboard focus to the Username textbox.
Months later, looks like they fixed the issue - just not the way I intended. I load up the website and keyboard focus goes straight to the username textbox, prior to the page load finishing. I guess they changed the script to run when the textbox loads instead of when the page finishes loading. Good enough for me.
I suppose it's possible that my internet connection is simply faster at my new apartment, though speed tests indicate otherwise. Another possibility is that the bank upgraded or fixed a performance problem, thus the script ran faster than I could click on the username textbox. Honestly I'm too lazy to check. Either way, they fixed the problem.
The lesson: Complaining gets things done.
Posted 2/5/2010 5:14:34 PM
Does VB.NET's if-then-else syntax bother the crap out of anyone else out there?
If isCondition AndAlso isOtherCondition Then
DoSomething()
ElseIf isCondition Then
DoSomethingElse()
ElseIf isOtherCondition Then
DoSomethingElseElse()
End If
Wow, that's verbose and wasteful for a simple operation like that, don't ya think? Fortunately, VB.NET still carries over the : operator from the decades of yore, and we can abbreviate it thusly:
If isCondition AndAlso isOtherCondition Then
DoSomething()
ElseIf isCondition Then : DoSomethingElse()
ElseIf isOtherCondition Then : DoSomethingElseElse() : End If
Still needs work. Just for comparison, let's see what this is like in C#:
if (isCondition && isOtherCondition) DoSomething();
else if (isCondition) DoSomethingElse();
else if (isOtherCondition) DoSomethingElseElse();
Aaaaahhhhhh, that's better.
And don't even get me started on the ?? and ?: operators. Come on VB, you need these things!
Posted 1/30/2010 12:46:19 PM
If we have Extension methods:
public static TimeSpan Days(this int input) { return TimeSpan.FromDays(input); }
public static DateTime Ago(this TimeSpan input) { return (DateTime.Now - input); }
Then we can:
DateTime when = 24.Days().Ago();
Pretty neat, huh?
Posted 1/28/2010 10:28:40 PM
Doesn't it bother anyone that wind is the only thing whose direction we measure in reverse? That is to say, we express the direction it's coming from rather than the direction in which it's moving.
Think of all the things whose direction we measure in the direction of travel: Traffic, water currents, and so many other things. You don't say "I'm coming in from the South", you say "I'm heading North".
Then there are the different names we give things. For example, Gale Force Winds are considered to be strong, but range from 31 to 63 MPH. By the technical definition (Beaufort Scale), we experience Gale Force Winds every time a truck passes us on the freeway, or even on a moderately windy day. Winds from 64 MPH and up can be referred to as a Hurricane, Very Severe / Super Cyclonic Storm, Tropical Cyclone, or a Typhoon. The actual name depends on where the wind is, which seems like an entirely arbitrary distinction.
All I'm asking for is a little standardization. How hard can that be?
Posted 1/20/2010 10:17:28 PM
When I started playing with Visual Studio 2008, I liked the Tests feature, which enabled me to write unit tests from within the IDE. Finally I didn't need NUnit or any of those other third-party tools. I don't like third parties. However, I never really saw the advantage in tests until my stint at A Major Insurance Provider, which was all about Enterprise practices and what-not. They showed me how they do their tests and over time, I began to like the idea. MV6 even had 150 unit tests.
Recently I discovered the Code Coverage feature of Visual Studio 2010. For every overload of every method of every class, it tells me what percent of my code is covered by my tests. Here I was thinking I was finally doing the right thing and being all Enterprisey, and along comes another analysis feature I never considered. So I ran it against my new KC Architecture and the results were disappointing.
I thought I had pretty decent code coverage, but the results say otherwise. Almost all of my constructors and destructors had 0% coverage; along with a bunch of helper methods. Luckily, this gave me an opportunity to revisit my initial design and I realized alot of things could be static, thus obsoleting the constructors and destructors entirely. Obviously, none of my UI Event Handlers had coverage. Who thinks about unit tests on the presentation layer?
Even on my Business layer, where most of my logics live, I found tons of cases I hadn't considered tests for. For example, my error logger has four Write() overloads, and I only had tests for one of them. Even then, I only had tests for a couple of execution paths. Additionally, I've been using the Visual Studio Code Analysis feature, which tells me to validate parameters prior to using them; and I hadn't written any tests that involved testing for null or other invalid parameters. VS2010 actually accounts for these items. So much more useful than the VS2005 most .NET shops are using.
So I sat down and started adding Tests. Before I knew it, I had doubled the number of tests in the KC Architecture. Of course, I tend to throw many scenarios into a single test and debug issues with the message overload (i.e. Assert.IsTrue(result, "Failed on record "+i.ToString());); but I'm sure I could easily double or even triple the number again by breaking the tests into more discrete tests like the real Enterprisey people do.
I'm learning. I'm still a code cowboy; but I'm moving into more of an Enterprise mindset. Slowly but surely. I honestly don't see why we have to be Enterprisey Developers to build Enterprisey products. I was once told that I delivered more than an Enterprisey College Graduate delivered in a whole year. I like being the guy that does that.
Posted 1/19/2010 10:00:00 PM
Newest version of the blog (Codename: MV7) is now online. I figured it was "done enough" to launch.
Of course there are still some nagging issues (i.e. the "About" page is still blank and there is no Comment functionality yet), but overall it's working. The new look and feel seems more intuitive, and I threw in a Gallery page for the hell of it. It's really just a TreeView and an image, but it seemed like something I should have a central place for. Expect me to add my "Stuff to show off to everyone" here.
The new blog is part of my new KC Architecture, a 3-Tier Application framework I've been working on in .NET 4.0 and Visual Studio 2010. Rather than bother with Windows Live ID authentication and a whole admin section like I had in MV6, this time I'm working on a CMS WinForm that will handle the management tasks locally. If I want to blog from far away places, I can still remote desktop in :)
Ugh, since I let zi255.com expire and all my image tags pointed to that domain, I'll have to go back and do a huge search and replace... Across 676 posts... But I have to check each one because I mention zi255 in places other than img tags... sigh...
Posted 11/17/2009 3:40:19 AM
I've been messing around with the dynamic typing features in C# 4.0. It took a little while before I actually came up with a good use for them, but I finally found one.
I had a situation that seemed like it should have a small custom class with two properties: A string and an int, representing the number of hits of that string within a List<string>.
That seemed like overkill. Best practices tell me this puny little class would expect its own .cs file, and I just wasn't ready to write a whole .cs file for two variables. I may be leaning toward the whole Enterprise Architecture mentality, but not that much :)
I tried a Dictionary<string,int>, but that didn't work as it doesn't provide an iterator.
So I have a problem whereas I want to create a complex duplicate filter based on a List<string>. Well, not really a duplicate filter so much as a "nearly duplicate" filter. That is, I want to remove any instances from the list if they contain a word that occurs in multiple items in the List, except short and common words. Oh sure, it sounds simple. But I wanted to do it a new way.
At first I thought I might be able to use anonymous types and stick them into a list of their own type.
List <(new { Word = "", Instances = 0 }).GetType()> words2 =
new List<(new { Word = "", Instances = 0 }).GetType()>();
Obviously, that didn't work. I got tons of compiler errors like Using the generic type 'System.Collections.Generic.List<T>' requires 1 type arguments and Argument 1: Cannot convert from 'AnonymousType#1' to 'int'. But I was passing in a Type... bah, that's not going to work.
My figuring is that I just ran into a contravariance issue whereas the compiler tried to find the most suitable overload for the List<type> constructor and thought List<int> was the closest match, possibly because my anonymous type had an int property and the int constructor comes before the string constructor in the framework assembly.
What I needed was a way to force it to use the List<object> constructor without having to box the variable. Why avoid boxing? While object is the only type you can cast an anonymous type to, for some reason they don't like being cast back from objects into anonymous types, so unboxing would have been impossible.
So then I remembered I was working in .NET 4.0, and thus I could use the new dynamic keyword as a type. That made it simpler. I'd still use anonymous types, but the anonymous type itself would be the dynamic type in my Generic. Dynamic directly extends object, so the compiler should see that constructor overload first and go directly there based on base class inferrence.
My brain goes into recursion just thinking about how this resolves at runtime :)
private static List<string> ComplexDuplicateFilter(List<string> input)
{
if (input == null || input.Count == 0)
return input;
input = input.Distinct().ToList();
// Build a list of words and how many times they occur in the overall list
List<dynamic> words2 = new List<dynamic>();
foreach (string line in input)
foreach (string word in line.Split(new char[] { ' ', ',', ';', '\\', '/', ':', '\r', '\n' }))
{
if (string.IsNullOrEmpty(word))
continue;
else if (ExistsInList(word, words2))
foreach (dynamic dyn in words2)
{
if (dyn.Word == word)
dyn.Instances++;
}
else words2.Add(new { Word = word, Instances = 0 });
}
// Remove extraneous entries for common word permutations
List<string> output = new List<string>();
foreach (dynamic dyn in words2)
if ((dyn.Iterations > 5 && !output.Contains(dyn.Word)) ||
dyn.Iterations <= 5)
output.Add(dyn.Word);
return output;
}
private static bool ExistsInList(string word, List<dynamic> words)
{
foreach (dynamic dyn in words)
if (dyn.Word == word)
return true;
return false;
}
Of course then I realized a couple of problems:- When you declare an anonymous type variable, its properties are read-only for some reason. Rather than changing their properties, one must instead overwrite them with a new anonymous type of the same signature (but with a different value per requirements).
- CA1502: Method has a cyclomatic complexity of 33. Microsoft recommends it be <= 25. That is, the method has 33 different execution paths. Spiffy, I never knew that.
- This method will be used to suggest SearchStatuses (i.e. tracked Google queries and commensurate ranks) in the Daily Report I'll get from META. Search engine queries tend to have quotes. D'oh! Easy fix, added another char to the string.Split argument.
- A couple minor changes here and there... Obviously Instances would start at 1 instead of 0, etc.
I'm pretty satisfied with the final result:
private static List<string> FilterByDuplicateWords(List<string> input)
{
if (input == null || input.Count == 0)
return input;
input = input.Distinct().ToList();
// Build a list of words and how many times they occur in the overall list
List<dynamic> words2 = new List<object>();
foreach (string line in input)
foreach (string word in line.Split(new char[] { ' ', ',', ';', '\\', '/', ':', '\"', '\r', '\n', '.' }))
{
if (string.IsNullOrEmpty(word))
continue;
else if (ExistsInList(word, words2))
for (int i=words2.Count - 1; i >= 0; i--)
{
if (words2[i].Word == word)
words2[i] = new { Word = words2[i].Word, Instances = words2[i].Instances + 1};
}
else words2.Add(new { Word = word, Instances = 1 });
}
// Remove extraneous entries for common word permutations
List<string> output = new List<string>();
foreach (string line in input)
{
int Dupes = 0;
foreach (string word in line.Split(new char[] { ' ', ',', ';', '\\', '/', ':', '\"', '\r', '\n', '.' })
.Where(p => p.Length > 7)
.Distinct())
{
int Instances = 0;
foreach (dynamic dyn in words2)
if (word == dyn.Word)
{
Instances = dyn.Instances;
if (Instances > 1)
Dupes++;
break;
}
}
if (Dupes == 0)
output.Add(line);
}
return output;
}
private static bool ExistsInList(string word, List<dynamic> words)
{
foreach (dynamic dyn in words)
if (dyn.Word == word)
return true;
return false;
}
So this solved the items on my issue list (bulleted above), and even resolved the cyclomatic complexity issue by accident.
Notice I'm also becoming more comfortable with lambda expressions and functions. They're big and scary before you figure them out, but once you do, you wonder how you ever lived without them.
Now I just need to find a good use for yield return and I'll be set.
Posted 11/9/2009 3:48:05 PM
A couple of my domains will be expiring soon, and I don't think I'm going to bother renewing them.
These domains will be expiring soon:- zi255.com - blog website
- zindex255.com - blog website (dupe)
These domains aren't expiring soon but I probably won't bother renewing them when they come up:- damnednice.com - Name wasn't as "cool" as I thought it was when I bought it
- multimononline.com - Dupe of Manymon.com; doesn't work with Windows Live ID
- googlejuicer.com - Another one of my great ideas that I didn't bother doing anything with. Was going to be an SEO service.
So, going forward, these are the only domains I plan to keep and make use of:- kconnolly.net - portfolio and blog (public-friendly)
- manymon.com - multi-monitor enthusiast site. Still plan to do this someday :)
I'm not planning to cancel them; just let them expire naturally. Anyone wanna buy some of these domain names before the domain farmers gobble them up?
Keep in mind, in addition to buying a domain, you still have to renew it. If I get a good offer, I'll sell it off early (i.e. Googlejuicer is a great name for an SEO service).
Expiration dates:- damnednice.com: 12/12/2011
- googlejuicer.com: 01/01/2012
- multimononline.com: 07/12/2010
- zi255.com: 11/25/2009
- zindex255.com: 11/25/2009
I've turned off automatic renewal on these domains. The other domains (which I'm keeping) don't expire for a couple years.
Anyhow, I'll be taking them all offline for the big move sometime around Nov 20-30th and will remain offline for a still-unknown timeframe. When the blog goes back up at kconnolly.net, I plan to have a shiny new interface - version 7. Why not, I have free time now.
Incidentally, if anyone is using one of these domains to send me E-mail (i.e. kevin@zi255.com), these addresses will become defunct when the domains expire. I suggest you use kevin@kconnolly.net - I plan to keep that domain for a long time.
< Older