Saturday, March 07, 2009

Scaling images while keeping aspect ratio and max size

In an application I am writing, I recently came across the need to accept uploaded pictures and scale them to a set size for inclusion in a table. Simple stuff, just a “let’s make all pictures the same size for easier layout” kind of deal. Say the “target” size was 800x600, and given a size originalSize, the function needs to figure out a new size for the image that meets the following demands:

1. The entire image must fit inside the target size.

2. The image must keep its’ aspect ratio.

3. The size of the new image must never be larger in either direction than the original (no upscaling)

4. The image must be as large as possible without breaking the three other rules.

After a few days of wrapping my head around this (and doing other stuff), I realized it was all the aspect ratio – which way we used to calculate the width/height that should be returned, depends entirely on which of the aspect ratios are larger; That of the original image, or that of the target size.

I of course started with the unit tests – a MbUnit RowTest with 20 rows of different combinations of picture sizes, new sizes, and expected result sizes. I had done all the calculations on paper in advance and was pretty sure they were correct.

After a little bit of experimentation, I ended up with this method:

  1: public static Size GetRenderSize(Size originalSize, Size targetSize)
  2: {
  3:     float targetRatio = targetSize.Width / (float)targetSize.Height;
  4:     float imageRatio = originalSize.Width / (float)originalSize.Height;
  5: 
  6:     if (imageRatio < targetRatio) // Target ratio is wider than the source ratio, scale by height
  7:     {
  8:         var smallerY = Math.Min(originalSize.Height, targetSize.Height);
  9:         return new Size((int)Math.Round(smallerY * imageRatio,0), smallerY);
 10:     }
 11:     // Target ratio taller than source ratio or the same - scale by width
 12:     var smallerX = Math.Min(originalSize.Width, targetSize.Width);
 13:     return new Size(smallerX, (int)Math.Round(smallerX / imageRatio,0));
 14: }


A lot simpler than I thought, and seems to give correct results on each and every calculation. :)

Wednesday, February 25, 2009

Silverlight vs. FireFox

After struggling with getting some Silverlight (2.0) apps working in FireFox (v3), I thought I’d post the solutions here so that I can find them easily in the future.

The first problem was getting the application to load in the first place. In one of the parameters to the <object> that will represent your application, you pass in a ‘data’ parameter, like this:

data="data:application/x-silverlight-2,"

See that pesky little comma after the “2”? Well, without it, your app probably won’t show up in FireFox, at least mine wouldn’t. It works fine in IE and Chrome, but no way the ‘fox would love it.

The second issue seems to be that if you give your app a percentage height, and the outer elements don’t have a set height themselves, you could end up with an application with a height of 0 pixels – which means there is nothing to show. Fortunately other people have already figures this out, such as this here guy, who I thank for saving me some gray hairs. :)

That’s it!

Sunday, January 11, 2009

Making a dropdownlist in ASP.Net MVC from an enum

Some times your business objects use a simple enum to indicate something, for instance a status. For one of my projects I have a TaskStatus defined like this:

public enum TaskStatus
{
    NotStarted,
    InProgress,
    Waiting,
    Finished,
    Cancelled
}

Nothing exciting here. Now I want to turn this into a SelectList that can be used with the Html.DropDownList helper. Turns out this is embarrassingly easy using LINQ and anonymous methods:

  1: var statuses = from TaskStatus s in Enum.GetValues(typeof(TaskStatus))
  2:                select new { ID = s, Name = s.ToString() };
  3: ViewData["taskStatus"] = new SelectList(statuses, "ID", "Name", task.Status);


Now I can just use it in my view using the helper method:



  1: <td><b>Status:</b></td><td><%=Html.DropDownList("taskStatus")%></td></tr> 


Easy peasy. :)

Monday, December 01, 2008

You can’t do that in WPF?

One of the things that virtually everyone talks about whenever they start learning WPF is how amazing the experience is – there are virtually no limits to the amazing stuff you can do. Or put in a different way; The hard becomes easy, the impossible becomes possible.

However, one thing I have not found a way to do (and which is apparently not currently possible, according to the answer I got on StackOverflow.com), is how to handle databinding in the case where you want to bind to an interface, and not the actual object.

In my case, I have a list of business objects that represent the ItemsSource for a ListBox. Each of these objects contains two other objects. These objects can be one of several different types, the only thing they have in common is that they implement a specific interface. The problem here is that the ItemTemplate I use cannot refer to the Interface properties – the binding engine will only see the object as the type it really is, not the interface.

While this isn’t a HUGE thing, it is a weakness that would be very nice to be able to work around in an upcoming update of WPF.

Thursday, November 06, 2008

WPF ComboBox binding to LINQ to SQL

This is mostly for my own use – I am senile enough to run into this problem again in the future.

Like all controls in WPF, the ComboBox is full of configurability and extensibility. This can get quite hairy quite quickly – and if you set the wrong parameters when you databind to a LINQ to SQL data source, really funky stuff can happen.

So here is something that worked for me:

<ComboBox Grid.Column="1" Grid.Row="1" VerticalAlignment="Center" IsEditable="False"   
          Margin="10,2" 
          ItemsSource="{Binding Path=Produkt.Modeller}"  
          DisplayMemberPath="Modell"  
          SelectedValuePath="Modell"  
          SelectedItem="{Binding Path=ProduktModell}"  
          />
ItemsSource is relatively easy – just point at the collection of options.


DisplayMemberPath is also relatively easy – it just tells WPF what to display in the ComboBox.



But SelectedValuePath and SelectedItem took some experimentation to get right – the above works for me, but with some variation came some pretty strange bugs. Models being renamed and stuff.



So now I know.

Sunday, October 19, 2008

Book impression – Programming WPF

So, I was a complete newbie when it came to WPF, but because of Sloth and other applications, I was curious about what this was, as I must admit that UI programming in Windows has been a serious annoyance. Perhaps that’s the way it has to be when you come from a background of Amiga programming, with som web stuff thrown in for good measure.

I think I can safely say that I now have a pretty good grasp of how I can use WPF for creating better experiences for the users of my software. There are a lot of things I can do now that I couldn’t before, and the whole layout model makes a lot more sense. Declarative programming is definately the way to go when it comes to GUI programming, IMHO.

So, what about the book? Well, like many programming books, it’s hard to just read it without a keyboard and a compiler nearby. You want to try out the stuff as you go along, and the book rightly encourages you to do so. The logical progression of things felt a little off at times, and in places it felt more like a reference book than an end-to-end learning experience. Particularly when it came to look at data binding, I must admit I struggled a bit; Their explanations were very good, but I left those chapters feeling like the authors should have given more attention to databinding towards CLR objects; After all, WPF representing the presentation layer, I would expect most non-trivial applications to have some model data to show; It won’t all be created as part of the XAML.

Other than small gripes like this, I feel like I have a pretty good idea of the things you can do with WPF. Of course it doesn’t touch on features of .Net 3.5 Sp1 such as the possibilities of creating grid views (the book came out long before Sp1), so while this is not the fault of the authors, if you want to learn about the new features of Sp1, you have to look elsewhere.

So, is this brick worth reading through? If you’re stuck with WinForms and want a better way to do things, go for it! There is a lot you can do with WPF that would be very hard with the old ways, and I for one believe that WPF represents the future of UI programming. I will surely use it for my coming client applications, and I will keep this book nearby as a handy reference.

My copy of the book is “Programming WPF, Second Edition”. It is published by O’Reilly, and authored by Chris Sells and Ian Griffiths.

Sunday, August 31, 2008

Collection sorting – when the built-in stuff doesn’t work

C# and the the .Net framework has a lot of excellent support for collections – no more mucking about creating your own linked list structures, hash tables etc. This means you get easy to use lists of your stuff almost for free, saving you a lot of testing/debugging whenever you make a brain fart during tedious boilerplate coding.

The network also has a lot in place for sorting these collections – the Sort() function allows you to plug in your own comparer and will then perform a relatively performant sort for you (QuickSort? I dunno). Isn’t it nice that we can skip this stuff and concentrate on getting the logic unique to our application right?

Well, yes. However, there are cases where the default sort does not work. I recently encountered one of those in one of my applications.

This application keeps a bunch of tags. You know, the del.icio.us kind, that you can use to label your data in different ways. We can mark certain products with a tag, and then, when we search for the tag, we’ll find all producst tagged by it. Simple stuff.

One particular feature here is that each tag keeps a list of “implied tags” – i.e. if you add this tag, you also implicitly have these tags as well. An example will hopefully clarify;

If you have the products ham, bacon and sirloin, you might want a tag for the first two named pork, and a tag for the last named beef. In addition, you would typically have a tag named meat that is implied both by pork and beef. That way, if you search for products that contain meat, you will find any products that contain ham, bacon or sirloin.

This is a simple, contrived example, but it should illustrate how implied tags work. It also leads to some limitations on the implementation of this system. Data in this case is to be stored in an XML file, and will be fetched in such a way that when a tag references (implies) another tag, the implied tag will already have to be defined; I.e. an implied tag has to come before the tag doing the implying in the XML file.

In my case, recursive tag implication is not allowed. Tag A can not imply tag B which in turn implies tag A, or any deeper variation of that. Therefore, the solution is simple; In the XML file, we need to make sure that tags that don’t imply anything else come first (there will always be at least one of these, because of the limitation on recursion). Then come the tags that only imply one or more of these first tags, and then in turn the tags that imply the tags we already have. Basically, a tag can only be added to the file once all tags it implies are already in the file.

My first attempt to take care of this was a relatively boneheaded one in retrospect; I implemented a comparer, implementing IComparer<Tag> to figure out the sort order, based on wether or not tag X implies tag Y, or Y implies X (and if they both implied each other, I threw – that’s tag implication recursion right there). Now, in theory this would work, but unfortunately the List.Sort() method was a little to clever for me. The result of one single comparison in my case only says something about the relation between these two elements. However, for the Sort() method, if A comes before B, and B comes before C, then we don’t even bother comparing A and C – we’ve already worked that part out implicitly. It doesn’t take a lot of imagination to come up with a situation where a tag list wouldn’t be sorted in the right order with this kind of sorting. And a quick unit test or two easily demonstrated that this would not handle all cases:

[Test]
public void TestImplicationSortingCreatesCorrectOrder()
{
var t1 = new Tag("Level 1");
var t2 = new Tag("Level 2");
var t3 = new Tag("Level 3");

t2.ImpliedTags.Add(t1);
t3.ImpliedTags.Add(t2);

var list = new List<Tag>() { t3, t2, t1 };
var newlist = Tag.SortTagListByImplication(list);

Assert.AreEqual(t1, newlist[0]);
Assert.AreEqual(t2, newlist[1]);
Assert.AreEqual(t3, newlist[2]);
}


This test would easily fail using the built-in sorting. Tag.SortTagListByImplication( IEnumerable<Tag>) was just a static wrapper that created the correct comparer and performed the sort.

To get this to work properly, however, I had to implement the sorting myself, so I chose to do it in the same method:


public static List<Tag> SortTagListByImplication(IEnumerable<Tag> tags)
{
if (tags == null)
throw new ArgumentNullException();

if (tags.Count() == 0)
return new List<Tag>(tags);

var newlist = new List<Tag>();
var sourceitemlist = new List<Tag>(tags);

do
{
var newbatch = new List<Tag>();
foreach (var t in sourceitemlist)
{
var hasAnyUnresolvedImplications = false;

foreach (var it in t.ImpliserteTags)
{
if (!newlist.Contains(it))
hasAnyUnresolvedImplications = true;
}
if (!hasAnyUnresolvedImplications)
newbatch.Add(t);
}
if (newbatch.Count == 0)
throw new InvalidOperationException("No new Tag items added to list - recursion nightmare!");
newlist.AddRange(newbatch);
foreach (Tag t in newbatch)
sourceitemlist.Remove(t);

}
while (sourceitemlist.Count > 0);

return newlist;
}
This code is very much a “first pass” – i.e. I stopped working on it once the unit test started working. Yes, I am quite sure it can be improved. However, right now, it does what I need it to do, so I will leave it alone.

The point of this post? I just wanted to mention that there are situations where the built in sorting logic is not proper. If it helps someone in the future, great. :)