Wednesday, October 30, 2013

Rotating text with GDI Graphics within given bounds

An unlikely blog post from me, about graphics; and not any kind of graphics, but GDI graphics. It involves something that may seem simple at first: rotating a text in a rectangle container so that it is readable when you turn the page to the left. It is useful to write text in containers that have a height that is bigger than the width. This is not about writing vertically, that's another issue entirely.
So, the bird's eye view of the problem: I had to create a PDF that contains some generated images, a sort of chart with many colored rectangles that contain text. The issue is that some of them are a lot higher than they are wide, which means it is better to write text that is rotated, in this case to -90 degrees, or 270 degrees, if you like it more. To the left, as Beyoncé would put it.

I created the image, using the Bitmap class, then got a Graphics instance from it, then starting drawing things up. It's trivial to draw a line, fill a rectangle, or draw an arc. Just as easy it is to write some text, using the DrawString method of the Graphics object. I half expected there to be a parameter that would allow me to write rotated, but there wasn't. How hard could it be?

Let's start with the basics. You want to draw a colored rectangle and write some text into it. This is achieved by:
var rectangle=new Rectangle(x,y,width,height); // reuse the dimensions
g.FillRectangle(new SolidBrush(Color.Blue),rectangle); // fill the rectangle with the blue color
g.DrawRectangle(new Pen(Color.Black),rectangle); // draw a black border
g.DrawString("This is my text",new Font("Verdana",12,GraphicsUnit.Pixel),new SolidBrush(Color.Black),rectangle, new StringFormat {
    LineAlignment=StringAlignment.Center,
    Alignment=StringAlignment.Center,
    Trimming = StringTrimming.None
}); // this draws a string in the middle of the rectangle, wrapping it up as needed

All very neat. However, you might already notice some problems. One of them is that there is no way to "overflow" the container. If you worked with HTML you know what I mean. If you use the method that uses a rectangle as a parameter, then the resulting text will NOT go over the edges of that rectangle. This is usually a good thing, but not all the time. Another issue that might have jumped in your eyes is that there is no way to control the way the text is wrapped. In fact, it will wrap the text in the middle of words or clip the text in order to keep the text inside the container. If you don't use the container function, there is no wrapping around. In other words, if you want custom wrapping you're going to have to go another way.
Enter TextRenderer, a class that is part of the Windows.Forms library. If you decide that linking to that library is acceptable, even if you are using this in a web or console application, you will see that the parameters given to the TextRenderer.DrawText method contain information about wrapping. I did that in my web application and indeed it worked. However, besides drawing the text really thick and ugly, you will see that it completely ignores text rotation, even if it has a specific option to not ignore translation tranforms (PreserveGraphicsTranslateTransform).

But let's not get into that at this moment. Let's assume we like the DrawString wrapping of text or we don't need it. What do we need to do in order to write at a 270 degrees angle? Basically you need to use two transformations, one translates and one rotates. I know it sounds like a bad cop joke, but it's not that complicated. The difficulty comes in understanding what to rotate and how.
Let's try the naive implementation, what everyone probably tried before going to almighty Google to find how it's really done:
// assume we already defined the rectangle and drew it
g.RotateTransform(-270);
g.DrawString("This is my text",new Font("Verdana",12,GraphicsUnit.Pixel),new SolidBrush(Color.Black),rectangle, new StringFormat {
    LineAlignment=StringAlignment.Center,
    Alignment=StringAlignment.Center,
    Trimming = StringTrimming.None
}); // and cross fingers
g.ResetTranformation();
Of course it doesn't work. For once, the rotation transformation applies to the Graphics object and, in theory, the primitive drawing the text doesn't know what to rotate. Besides, how do you rotate it? On a corner, on the center, the center of the text or the container?
The trick with the rotation transformation is that it rotates on the origin, always. Therefore we need the translate transformation to help us out. Here is where it gets tricky.

g.TranslateTransform(rectangle.X+rectangle.Width/2,rectangle.Y+rectangle.Height/2); // we define the center of rotation in the center of the container rectangle
g.RotateTransform(-270); // this doesn't change
var newRectangle=new Rectangle(-rectangle.Height/2,-rectangle.Width/2,rectangle.Height,rectangle.Width);  // notice that width is switched with height
g.DrawString("This is my text",new Font("Verdana",12,GraphicsUnit.Pixel),new SolidBrush(Color.Black),newRectangle, new StringFormat {
    LineAlignment=StringAlignment.Center,
    Alignment=StringAlignment.Center,
    Trimming = StringTrimming.None
});
g.ResetTranformation();
So what's the deal? First of all we changed the origin of the entire graphics object and that means we have to draw anything relative to it. So if we would not have rotated the text, the new rectangle would have had the same width and height, but the origin in 0,0.
But we want to rotate it, and therefore we need to think of the original bounding rectangle relative to the new origin and rotated 270 degrees. That's what newRectangle is, a rotated original rectangle in which to limit the drawn string.

So this works, but how do you determine if the text needs to be rotated and its size?
Here we have to use MeasureString, but it's not easy. It basically does the same thing as DrawString, only it returns a size rather than drawing things. This means you cannot measure the actual text size, you will always get either the size of the text or the size of the container rectangle, if the text is bigger. I created a method that attempts to get the maximum font size for normal text and rotated text and then returns it. I do that by using a slightly larger bounding rectangle and then going a size down when I find the result. But it wasn't nice.

We have a real problem in the way Graphics wraps the text. A simple, but incomplete solution is to use TextRenderer to measure and Graphics.DrawString to draw. But it's not exactly what we need. The complete solution would determine its own wrapping, work with multiple strings and draw (and rotate) them individually. One interesting question is what happens if we try to draw a string containing new lines. And the answer is that it does render text line by line. We can use this to create our own wrapping and not work with individual strings.

So here is the final solution, a helper class that adds a new DrawString method to Graphics that takes the string, the font name, the text color and the bounding rectangle and writes the text as large as possible, with the orientation most befitting.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace GraphicsTextRotation
{
    public static class GraphicsExtensions
    {
        public static void DrawString(this Graphics g, string text, string fontName, Rectangle rect, Color textColor, int minTextSize=1)
        {
            var textInfo = getTextInfo(g, text, fontName, rect.Width, rect.Height); // get the largest possible font size and the necessary rotation and text wrapping
            if (textInfo.Size < minTextSize) return;
            g.TranslateTransform(rect.X + rect.Width / 2, rect.Y + rect.Height / 2); // translate for any rotation
            Rectangle newRect;
            if (textInfo.Rotation != 0) // a bit hackish, because we know the rotation is either 0 or -90
            {
                g.RotateTransform(textInfo.Rotation);
                newRect = new Rectangle(-rect.Height / 2, -rect.Width / 2, rect.Height, rect.Width); //switch height with width
            }
            else
            {
                newRect = new Rectangle(-rect.Width / 2, -rect.Height / 2, rect.Width, rect.Height);
            }
            g.DrawString(textInfo.Text, new Font(fontName, textInfo.Size, GraphicsUnit.Pixel), new SolidBrush(textColor), newRect, new StringFormat
            {
                Alignment = StringAlignment.Center,
                LineAlignment = StringAlignment.Center,
                Trimming = StringTrimming.None
            });
            g.ResetTransform();
        }

        private static TextInfo getTextInfo(Graphics g, string text, string fontName, int width, int height)
        {
            var arr = getStringWraps(text); // get all the symmetrical ways to split this string
            var result = new TextInfo();
            foreach (string s in arr) //for each of them find the largest size that fits in the provided dimensions
            {
                var nsize = 0;
                Font font;
                SizeF size;
                do
                {
                    nsize++;
                    font = new Font(fontName, nsize, GraphicsUnit.Pixel);
                    size = g.MeasureString(s, font);
                } while (size.Width <= width && size.Height <= height);
                nsize--;
                var rsize = 0;
                do
                {
                    rsize++;
                    font = new Font(fontName, rsize, GraphicsUnit.Pixel);
                    size = g.MeasureString(text, font);
                } while (size.Width <= height && size.Height <= width);
                rsize--;
                if (nsize > result.Size)
                {
                    result.Size = nsize;
                    result.Rotation = 0;
                    result.Text = s;
                }
                if (rsize > result.Size)
                {
                    result.Size = rsize;
                    result.Rotation = -90;
                    result.Text = s;
                }
            }
            return result;
        }

        private static List<string> getStringWraps(string text)
        {
            var result = new List<string>();
            result.Add(text); // add the original text
            var indexes = new List<int>();
            var match = Regex.Match(text, @"\b"); // find all word breaks
            while (match.Success)
            {
                indexes.Add(match.Index);
                match = match.NextMatch();
            }
            for (var i = 1; i < indexes.Count; i++)
            {
                var pos = 0;
                string segment;
                var list = new List<string>();
                for (var n = 1; n <= i; n++) // for all possible splits 1 to indexes.Count+1
                {
                    var limit = text.Length / (i + 1) * n;
                    var index = closest(indexes, limit); // find the most symmetrical split
                    segment = index <= pos
                                ? ""
                                : text.Substring(pos, index - pos);
                    if (!string.IsNullOrWhiteSpace(segment))
                    {
                        list.Add(segment);
                    }
                    pos = index;
                }
                segment = text.Substring(pos);
                if (!string.IsNullOrWhiteSpace(segment))
                {
                    list.Add(segment);
                }
                result.Add(string.Join("\r\n", list)); // add the split by new lines to the list of possibilities
            }
            return result;
        }

        private static int closest(List<int> indexes, int limit)
        {
            return indexes.OrderBy(i => Math.Abs(limit - i)).First();
        }

        private class TextInfo
        {
            public int Rotation { get; set; }
            public float Size { get; set; }
            public string Text { get; set; }
        }
    }
}

I hope you like it.

Friday, October 25, 2013

Transposition of ranges in SQL and charts

I had this database table containing ranges (a start value and an end value). The challenge was creating a query that overlaps and transposes those ranges so I can say how many ranges are at any point in the total interval or values. As an example, "SELECT * FROM Ranges" would result in a table like:
StartEnd
1020
1030
2535
2040
and I am looking for something like this:
ValueCount
00
10
......
102
112
......
242
253
263

A naive implementation would get the minimum Start (or start with 0, as I did) and the maximum End, create an in memory or temporary table (Values) from min to max using an ugly WHILE block, then join it with the Ranges tables something like:
SELECT v.Val,Count(1) as Nr
FROM #Values v
INNER JOIN Ranges r
ON r.Start<=v AND r.[End]>=v

This kind of works, but for large ranges it becomes difficult. It takes a lot to create the Values table and the join and for extreme cases, like I had with values from 0 to 6 billion, it becomes impossible. The bottleneck here is this Values table, which is pretty much a horror to create and maintain. But what if you don't need all the values?

Before I tell you the solution I found, be warned that you have to properly define what a range is. Is a range 10-20 actually 10-19? In my case it was so, so that is why there are some subtractions with 1 or less than rather than less or equal conditions.

The solution is this:
SELECT DISTINCT Val
INTO #Values
FROM (
  SELECT 0 as Val
    UNION ALL
  SELECT Start FROM Ranges
    UNION ALL
  SELECT [End]-1 FROM Ranges
) x
ORDER BY Val

The idea is that after you compute the ranges count per each of the start and end values you know that between one and the next the count of ranges will remain the same. The join is significantly faster, there is no ugly WHILE block and you don't need a 6 billion value table. It's easier to plot on a chart as well, with either of these variations:
See the variations:
SELECT v.Val,
       Count(r.Start) as Nr
FROM #Values v
LEFT JOIN Ranges r
  ON r.Start<=v.Val AND r.[End]>v.Val
GROUP BY v.Val

The result of the query above will be:
ValueNr
00
102
192
202
253
293
342
391
Hope this helps you

The Drunken Botanist - The Plants That Create the World's Great Drinks, by Amy Stewart

Book cover I will go forth immediately and say that this book is hard to read. It's not just that it uses English names of fruits and plants not often met in normal conversation or movies, it's not only that it uses the English measurements units that most Europeans have no understanding of and it's not even the author's apparent obsession with Angostura bitters :). It's because the book is filled with information and it is better suited to be used as a reference than a one-off read. It is obvious that in The Drunken Botanist the author, Amy Stewart, put a great deal of research; such an amount, in fact, that it would be impossible to be able to absorb all of it in one read. You realize that a book is great when you see affecting you in your every day life. No, I did not get drunk every day on strange cocktails, even if the temptation certainly existed, but I found myself looking at plants everywhere and wondering how they would taste in a drink. "Is that fruit edible? Probably not, it's red and the fruit of a decorative tree, so probably it is poisonous. But surely there is a way to destroy the poison and enjoy the taste in an alcoholic drink." That sort of thing.

And what I find even nicer is that the author is ready to continue the conversation started in the book on a special website called DrunkenBotanist.com

The book is split into very little chapters, each on a certain plant, a little bit of history - the degree of details varying wildly, but described with great humor and passion, the way they are used in drinks, cocktail recipes, some botanical or chemical info - although not too much. There are organized by themes, like leaves or fruits, grasses or trees, and so on, but essentially it's an enumeration of plants and their uses. This is something I didn't like that much. My issue is that I expected to see more botanical information, like how plants are related and how the drinks made from them are related. As such, you only get a few words at the beginning of a chapter about the plant's family, but no connection is made. Of course, no one stops you for researching and doing it yourself.
Another thing that bothered me a little was the images. I agree that full color and size images of the plants described would have printed as a huge book, but the e-book version I read had no such limitations. Instead of seeing a simplistic representation of some plants, I would have liked to see the plant itself. That would have helped me understand what each plant was in my language, as well.

I have to conclude that the book is a very interesting one, albeit difficult to finish reading. I understand the need to earn money and thus sell it as a book, but for me it seems that the book's format would have been a lot more appropriate for a web site and that some features should have been different in the electronic version than the printed one. Instead, the Drunken Botanist site is actually a blog, in a blog format, which is unusable as a reference, really. I recommend browsing the book and certainly have it available. Some information in it is great at parties and the botanical insight into the common drinks and their ingredients is fascinating. But think of it as something to have ready for when you need to know what you are mixing. I would say it's a mandatory read for any bartender, though.

Tuesday, October 15, 2013

TV Series I've Been Watching - Part 16

It's the middle of autumn again and more and more series are making their entry into the list. But there are a lot of shows that I carry around from post to post without actually watching them. I've decided to remove them from the list until that time comes.



Let's start with the already described ones:

  • Doctor Who - Math Smith has left the show and the new doctor is an actor which I really like, Peter Capaldi, who you might remember from The Thick of It. Unfortunately, he will be very polite in Doctor Who; I would have loved to see him as irreverent, sweary and Scottish as in his other role.
  • Dexter - season 8 of the series has just ended (disastrously) and thus the show. It was a good run, I guess, even if the last seasons were quite ridiculous.
  • True Blood - I am becoming conflicted about this show. On one hand some of the storylines are very interesting, while on the other the multitude of stories, one more over the top than the other, shown in parallel in every episode, make it all feel like one supernatural soap opera. The ending of the season, with the invasion of the zombie vampires, also left me deflated.
  • The Good Wife - the fifth season just started, but I haven't got around to it. I think the show continues to be good, but they probably should wrap it up and do something else before it too dissolves into pointlessness.
  • Haven - the fourth season has just started. I will probably watch it with the wife, she seems to enjoy it.
  • Falling Skies - the third season was just weird. It's difficult enough to justify an almost medieval human resistance while being caught in an interstellar war even without having incompetent writers, which unfortunately the show has.
  • Southpark - Southpark is still running hot at season 17. The Butters episode in which he becomes a DMV witness was delicious.
  • Suits - Suits started fun, I really enjoyed it; for stupid reasons, but I did. The third season showed me that even those reasons are quickly fading. Everybody acts over the top and with almost no connection to real life. It's like a movie made by advertisers.
  • Breaking Bad - I started watching the first episode from season 5 only to realize I don't understand what the hell is going on. Did I even watch the fourth season? Unclear.
  • Homeland - season three starts with Carrie being thrown to the wolves as she goes more and more insane. Having a similar situation in the family doesn't make it easy to watch.
  • The Walking Dead - the fourth season just started, but I didn't get around to it, yet.
  • Game of Thrones - there was a lot of outrage on the Internet linked to the Red Wedding episode. It's the first true test of the show. If people will continue watching it even after blatantly killing some of their favourite characters, then the show will go on until Martin stops writing (or maybe not even then). I am afraid it might not happen that way, though.
  • Mad Men - does anyone remember this started as a movie about advertising people? Right now it seems to be more about the "mad" part of the title, as Don devolves more and more into a crazy person.
  • Continuum - Continuum continues, even if it got kind of weird in the last season. Everybody is related to everybody and makes deals with all. It's becoming an intertemporal high-school drama.
  • Copper - Copper started as in interesting show about Irish policeman in Five Points, but after the second season it got cancelled. The last season was pretty confusing and less and less having to do with the job and more with the personal. I did not like that.
  • Longmire - who killed the killer of madam Longmire? Was it sir Longmire? No. Was it Lou Diamond Philips? No again. Was it the killer hired by Lou Diamond? No, because he wasn't a killer. Does that mean people will stop worrying about it? Definitely not. I really want Longmire to retain that authentic countryside sheriff feel; the screenwriters seem to feel otherwise.
  • The Newsroom - drama, politics, revenge, more drama and finally, some drama. All over the top and on high speed. No wonder the city never sleeps if everybody is overactive.
  • Arrow - new Arrow season, with tales of bringing more superheroes into the story, like The Flash. Will this become the testbed for Playstation and XBox game scripts?
  • Elementary - the second season started with some interesting episodes. One even features Lestrade, as a celebrity addict. Funny, that.
  • House of Cards - this show is the first to be launched on Netflix (and produced by them as well). This means the entire first season was launched on the same day. No more waiting week after week to see a bit of the show, but how much time will it pass now until the next season? It's almost like releasing 10 hour movies.
  • Father Ted - I watched almost all episodes of this very old and obscure show, basically just because it was old and obscure. It's a laughing-track comedy about two idiotic priests on a small remote island in Ireland. Supposedly ireverent to religion, it's just a very silly show.

New shows:

  • The Tomorrow People (2013) - this remake of a really dumb old series just started. I haven't started watching it, but I've read a review that seemed to praise it. It stars beautiful young actors with superpowers, though.
  • The Legend of Korra - the second season has started with a transparent nefarious plot that only the dumb female avatar doesn't seem to get. Her character is so annoying!! Ugh!
  • Atlantis - I expected Atlantis to be a sort of Rome or Spartacus. The bronze-punk science fiction would have made a great show. Unfortunately it seems to be more a kind of a Hercules/Xena kind of show. There is this modern guy who gets into Atlantis through a portal, meets Pithagoras and Hercules, who are both different from the myths, and runs around with swords.
  • By Any Means - I could barely survive through the first episode. This is a British show about a group of people that are hired by the police to bring people to justice. Why doesn't the police do that directly? Because this group uses illegal and imoral acts to achieve their goals, then rationalize them by them being "the good guys" and acting all cool and funny. The show was basically offensive to me.
  • Hostages - a show about a doctor who must opperate on the United States president. Her family is taken hostage to force her to kill the president. Yeah, like that could happen. It has good actors and an intriguing plot, but it could go both ways. I've watched just the first episode.
  • Ironside - another show about a cop. This time the cop is black and in a wheelchair, even if he continues to strong arm suspects and his team with impunity. I found it pretentious and yet another show which is trying to force feed us vigilante justice from charismatic characters.
  • King and Maxwell - a show about a detective agency run by former Secret Service agents. At last a detective agency not run by complete amateurs that got bored. Rebecca Romjin is one of the pair, which makes the show nice to look at, at least. Now, the first episodes felt a bit silly, like a comedy, so I didn't continue watching it, but it could become better.
  • Low Winter Sun - TV series starring two less known, but nevertheless good actors: Lennie James and Mark Strong. It's very dark, with corrupt policemen and rampant gangs, but I can't say I enjoyed it much. Probably because it seems to miss a point.
  • Ray Donovan - something that stars Liev Schreiber and John Voight should be better than this, but the show is not bad. There is this guy that acts like a sort of damage control expert who has to contend with his father getting out of jail and being a dick. Control that damage, if you can!
  • Rewind - it is the season of shows about people playing God. This time they are not policemen, thank you, but an agency that uses technology to go back in time and "fix" things. All seemed straightforward until the end of the first episode, which promises a longer story arch and maybe even more interesting action.
  • Serangoon Road - another Australian show about private detectives, this time placed in Malaysia. Chinese, Australians, Javanese, British and Americans are all dancing around each other, trying to win the jackpot in this country torn by civil unrest and government dissolution. An interesting show, which I continue to watch.
  • Siberia - I almost want to ask "What is Siberia?" in a Matrixy sort of way. It is a series that starts as a reality show, only to have the contestants meet strange phenomena until the entire premise explodes and they find themselves stuck in Siberia, with no contact to the outside world and in mortal (supranatural/scifi) danger. The problem with the show is that it is pretty inconsistent and difficult to watch. It's like it doesn't know what it wants to be: a reality show, a Lost rip-off or something else.
  • Sleepy Hollow - Sleepy Hollow the movie was a masterpiece, combining a moody atmosphere that hinted of the supernatural with a main character that used logic and science to prove that it actually wasn't. The Sleepy Hollow series is the complete opposite, with a clear supernatural storyline involving good and bad witches, demons and Ichabod Crane, a civil war soldier who has found himself in the present and now helps (of course) the police deal with the rise of evil. It could have been good, but I really don't like any of the actors or characters, except maybe the sheriff, but he dies.
  • The Blacklist - I really wanted to like the series, because it stars James Spader (an old and bald one), an actor which I really liked in the past. But it's a confused mess. Spader plays the role of a rogue FBI agent that surrenders after 20 years only to help the FBI find catch the "truly bad" guys. He enlists the help of a girl FBI agent who probably is his daughter, even if he doesn't say it out loud. She is basically a clone of the FBI agent from Haven, while Spader's character is an arrogant prick. Impossible to empathise with anyone and even harder to see a point of this show.
  • The Bridge - an American remake of the Danish/Swedish series Bron, it involves a murderer that leaves a body on the exact border of Mexico and the US, forcing the creation of a joint taskforce. Beautiful Diane Kruger plays the functional autist American agent, while Demián Bichir plays the complex Mexican policeman. There are other characters, but some are even detrimental to the story, in my view. The show is solid and has some interesting and good acting. I hope it stays that way.
  • The Crazy Ones - just so you don't say I don't watch any comedy shows, this stars Robin Williams as a crazy and inspirational ad man, helped by two equally crazy guys and his daughter, played by Sarah Michelle Gellar. It has its moments, but it isn't really my style. Why can't they make comedy that also seems real, rather than completely over the top?
  • The Originals - oh, what a bunch of pretentious crap! a (too) late comer in the vampire TV show business, it even tries to reuse the vampire aristocrat variation. The only modern take of this soap opera like show is that it also adds witches to the mix. This is they year of the witches, after we got tired of vampires and werewolves and zombies didn't quite catch on.
  • The Psychopath Next Door - not an easy show to watch, it's full of psychological violence. It features a young very attractive female psychopath and how she deals with the world. Being a British show she really acts like a sociopath, shows no remorse and does the most atrocious acts just to get ridiculously unimportant things, rather than help the police catch bad guys or whatever :) A really good beginning and a fascinating glimpse in the mind of a true psycho.
  • Under The Dome - when in doubt, use a Stephen King story. Alas, I usually don't like King's writing it tends to drag on. Making a TV series out of one makes a rather boring watch. This is a sci-fi show about a transparent and impenetrable dome that completely isolates a small community from the outside world. The situation quickly devolves because of the people there, as primate tribes are wont to do when consequences disappear.
  • What Remains - British show, I just started to watch the first episode. It seems to revolve around human remains, discovered a long while after the crime (if it was one) and how people lives are affected by the investigation. Could be good.
  • Witches of East End - told you this is the season of the witch. This show is about a family of witches, incredibly good looking ones. The mother is Julia Ormond, her sister is Madchen Amick and one of the daughters is this hot and curvy brunette. Of course good looking guys and evil witches appear soon after. It seems a typical teen show, with drama coming exclusively from social situations, people who are romantic interests or/and are trying to kill you.
  • The Last Witch - The show just appeared and I haven't had the chance to watch it, but it's about witches, which validates my view that witches are the new vampires. At least this show is British.
  • Blackout - a faux documentary British series about a fictional British power blackout that lasted for more than a week. Kind of hard to watch because it's made like a montage of different camera shots taken by different people on handhelds and phones.

Well, it seems that removing the shows that I am not actively watching makes this list more manageable, but it also means you will have to look at more "TV Series I've been watching" blog posts to get an overview.

Monday, October 14, 2013

Dishonored has addins! Daud the Knife of Dunwall and The Brightmore Witches

Daud's face, rendered as a painting It's difficult to remember that in the original Dishonored storyline there were two people carrying the mark of the Outsider. There was Corvo, but then there was Daud. The Knife of Dunwall and Brigmore Witches extended missions of the game both come as Dishonored downloadable content and both star Daud as the lead. He first has to fight an army of whale butchers then the overseers who come to destroy his army of assassins, then he goes to find the Brigmore Witches and foil their plans. It was a nice touch that they changed characters. Someone who either killed everything that moved or took great care to finish up Dishonored the non-lethal way would probably have issues with changing their game style in the continuation. Having a different character frees our conscience and lets us play this game as we wish at that moment. It also hints that the story is not in the characters, but in the island universe created in the game.

The story here is that the Outsider tips Daud, who is already conflicted about his choice to murder the empress and kidnap her daughter, about a mysterious woman called Delilah. It soon becomes evident that she is aware of Daud's interests when she seeks the Overseers on Daud's hidden base. She apparently is the leader of a coven of witches based in Brigmore Manor. Rumors about them appeared in the main Corvo story, as well. Delilah, originally a servant in Dunwall Tower and a talented painter, is attempting to take over Emily Kaldwin, the young daughter of the empress, and by defeating her you become a hero that, just as Corvo but unbeknownst to anyone but the Outsider, saves Emily. That was a nice twist, also, binding the two stories together. Events in Daud's story also parallel Corvo's, as NPCs talking to each other often reveal.

It is interesting that, besides Blink and Dark Vision which seem to be essential to playing the game, Daud has different magical powers as well as different weaponry. That annoying power that he used to overpower Corvo at the beginning of the main story is available to you and very handy. As with Dishonored, you can choose your level of mayhem which in turn, I suppose, changes the story. I tried to play it as non-lethal as I could, but having the reputation of a renowned assassin for hire really made me itch for bloodthirsty apocalypse. It felt great to know that I can kill everybody, even when I chose not to, I guess.

An intriguing idea came to me. Besides Corvo and Daud there were other people involved with Outsider powers: Delilah and Granny Rags. If they make more downloadable content for the game (which I really really hope they will) it could be interesting to play Delilah, or even Granny, as prequels to these stories. It would serve multiple purposes, as it would probably appeal to female players more, as well as changing the weaponry and magic almost entirely. Witches in this game use magic arrows and use dead animals and plants to do their bidding, while Granny Rags uses hordes of rats and an amulet that makes her immortal until you destroy it. There are neat tricks that would be a waste not to be used by the player. Both Corvo and Daud actually survive in the end and don't forget that the Dishonored universe is placed in an archipelago of islands, only one of them having been explored in any detail, with a lot of rumors and information about the others and a lot more opportunities. A story on the whaling ships, perhaps? Something in a wide open space, as demonstrated by the Brigmore Witches manor grounds, maybe? Dishonored may have started like something that seemed to clone Assassin's Creed, but it has a lot more potential. Knowing the guys at Arkane Studios, that potential is going to be used, even if the wiki on The Brigmore Witches says it is the last DLC for Dishonored. Perhaps Dishonored II will be made soon.

As a conclusion, I really enjoyed the game, even if Daud's voice was Michael Madsen's, who I usually dislike at first glance. It's good he wasn't visually in the game, then :)

Thursday, October 10, 2013

OpenLayers & AngularJS - add features, choose their appearance and behaviour with clustering

Being a beginner in both OpenLayers and AngularJS it took me a long while to do this simple thing: add stuff on a map and make it show as I wanted. There were multiple gotchas and I intend to chronicle each and every one of those bastards.
First, while creating a map and doing all kinds of stuff with it using OpenLayers is a breeze, doing it "right" with AngularJS is not as simple. I thought I would not reinvent the wheel and looked for some integration of the two technologies and I found AzimuthJS. In order to add a map with Azimuth all you have to do is:
<div ol-map controls="zoom,navigation,layerSwitcher,attribution,mousePosition" control-opts="{navigation:{handleRightClicks:true}}">
    <az-layer name="Street" lyr-type="tiles"></az-layer>
    <az-layer name="Airports" lyr-type="geojson" lyr-url="examples/data/airports.json" projection="EPSG:4326"></az-layer>
</div>
You may notice that it has a simple syntax, it offers the possibility of multiple layers and one of them is even loading features dynamically from a URL. Perfect so far.
First problem: the API that I am using is not in the GeoJSON format that Azimuth know how to handle and I cannot or will not change the API. I've tried a lot of weird crap, including adding a callback on the loadend layer event for a GeoJson layer in order to reparse the data and configure what I wanted. It all worked, but it was incredibly ugly. I've managed to add the entire logic in a Javascript file and do it all in that event. It wasn't any different from doing it from scratch in Javascript without any Angular syntax, though. So what I did was to create my own OpenLayers.Format. It wasn't so complicated, basically I inherited from OpenLayers.Format.JSON and added my own read logic. Here is the result:
OpenLayers.Format.RSI = OpenLayers.Class(OpenLayers.Format.JSON, {

    read: function(json, type, filter) {
        type = (type) ? type : "FeatureCollection";
        var results = null;
        var obj = null;
        if (typeof json == "string") {
            obj = OpenLayers.Format.JSON.prototype.read.apply(this,
                                                              [json, filter]);
        } else { 
            obj = json;
        }    
        if(!obj) {
            OpenLayers.Console.error("Bad JSON: " + json);
        }

        var features=[];
        for (var i=0; i<obj.length; i++) {
            var item=obj[i];
            var point=new OpenLayers.Geometry.Point(item.Lon,item.Lat).transform('EPSG:4326', 'EPSG:3857');
            if (!isNaN(point.x)&&!isNaN(point.y)) {
                var feature=new OpenLayers.Feature.Vector(point,item);
                features.push(feature);
            }
        }
        
        return features;
    },
    

    CLASS_NAME: "OpenLayers.Format.RSI" 

});
All I had to do is load this in the page. But now the problem was that Azimuth only knows some types of layers based on a switch block. I've not refactored the code to be plug and play, instead I shamelessly changed it to try to use the GeoJson code with the format I provide as the lyr-type, if it exists in the OpenLayers.Format object. That settled that. By running the code so far I see the streets layer and on top of it a lot of yellow circles for each of my items.
Next problem: too many items. The map was very slow because I was adding over 30000 items on the map. I was in need of clustering. I wasted almost an entire day trying to figure out what it wouldn't work until I realised that it was an ordering issue. Duh! But still, in this new framework that I was working on I didn't want to add configuration in a Javascript event, I wanted to be able to configure as much as possible via AngularJS parameters. I noticed that Azimuth already had support for strategy parameters. Unfortunately it only supported an actual strategy instance as the parameter rather than a string. I had, again, to change the Azimuth code to first search for the name of the strategy parameters in OpenLayers.Strategy and if not found to $parse the string. Yet it didn't work as expected. The clustering was not engaging. Wasting another half an hour I realised that, at least in the case of this weirdly buggy Cluster strategy, I not only needed it, but also a Fixed strategy. I've changed the code to add the strategy instead of replacing it and suddenly clustering was working fine. I still have to make it configurable, but that is a detail I don't need to go into right now. Anyway, remember that the loadend event was not fired when only the Cluster strategy was in the strategies array of the layer; I think you need the Fixed strategy to load data from somewhere.
Next thing I wanted to do was to center the map on the features existent on the map. The map also needed to be resized to the actual page size. I added a custom directive to expand a div's height down to an element which I styled to be always on the bottom of the page. The problem now was that the map was getting instantiated before the div was resized. This means that maybe I had to start with a big default height of the div. Actually that caused a lot of problems since the map remained as big as first defined and centering the map was not working as expected. What was needed was a simple map.updateSize(); called after the div was resized. In order to then center and zoom the map on the existent features I used this code:
        var bounds={
            minLon:1000000000,
            minLat:1000000000,
            maxLon:-1000000000,
            maxLat:-1000000000
        };
    
        for (var i=0; i<layer.features.length; i++) {
            var feature=layer.features[i];
            var point=feature.geometry;
            if (!isNaN(point.x)&&!isNaN(point.y)) {
                bounds.minLon=Math.min(bounds.minLon,point.x);
                bounds.maxLon=Math.max(bounds.maxLon,point.x);
                bounds.minLat=Math.min(bounds.minLat,point.y);
                bounds.maxLat=Math.max(bounds.maxLat,point.y);
            }
        }
        map.updateSize();
        var extent=new OpenLayers.Bounds(bounds.minLon,bounds.minLat,bounds.maxLon,bounds.maxLat);
        map.zoomToExtent(extent,true);

Now, while the clustering was working OK, I wanted to show stuff and make those clusters do things for me. I needed to style the clusters. This is done via:
        layer.styleMap=new OpenLayers.StyleMap({
                "default": defaultStyle,
                "select": selectStyle
            });

        layer.events.on({
            "featureselected": clickFeature
        });

        var map=layer.map;

        var hover = new OpenLayers.Control.SelectFeature(
            layer, {hover: true, highlightOnly: true}
        );
        map.addControl(hover);
        hover.events.on({"featurehighlighted": displayFeature});
           hover.events.on({"featureunhighlighted": hideFeature});
        hover.activate();

        var click = new OpenLayers.Control.SelectFeature(
            layer, {hover: false}
        );
        map.addControl(click);
        click.activate();
I am adding two OpenLayers.Control.SelectFeature controls on the map, one activates on hover, the other on click. The styles that are used in the style map define different colors and also a dynamic radius based on the number of features in a cluster. Here is the code:
        var defaultStyle = new OpenLayers.Style({
            pointRadius: "${radius}",
            strokeWidth: "${width}",
            externalGraphic: "${icon}",
            strokeColor: "rgba(55, 55, 28, 0.5)",
            fillColor: "rgba(55, 55, 28, 0.2)"
        }, {
            context: {
                width: function(feature) {
                    return (feature.cluster) ? 2 : 1;
                },
                radius: function(feature) {
                    return feature.cluster&&feature.cluster.length>1
                        ? Math.min(feature.attributes.count, 7) + 2
                        : 7;
                }
            }
        });
You see that the width and radius are defined as dynamic functions. But here we have an opportunity that I couldn't let pass. You see, in these styles you can also define the icons. How about defining the icon dynamically using canvas drawing and then toDataURL? And I did that! It's not really that useful, but it's really interesting:
        function fIcon(feature,type) {
                    var iconKey=type+'icon';
                    if (feature[iconKey]) return feature[iconKey];
                    if(feature.cluster&&feature.cluster.length>1) {
                        var canvas = document.createElement("canvas");
                        var radius=Math.min(feature.cluster.length, 7) + 2;
                        canvas.width = radius*2;
                        canvas.height = radius*2;
                        var ctx = canvas.getContext("2d");
                            ctx.fillStyle = this.defaultStyle.fillColor;
                        ctx.strokeStyle = this.defaultStyle.strokeColor;
                        //ctx.fillRect(0,0,canvas.width,canvas.height);
                        ctx.beginPath();
                        ctx.arc(radius,radius,radius,0,Math.PI*2);
                        ctx.fill();
                        ctx.stroke();
                            ctx.fillStyle = this.defaultStyle.strokeColor;
                        var bounds={
                            minX:1000000000,
                            minY:1000000000,
                            maxX:-1000000000,
                            maxY:-1000000000
                        };
                        for(var c = 0; c < feature.cluster.length; c++) {
                              var child=feature.cluster[c];
                            var x=feature.geometry.x-child.geometry.x;
                            var y=feature.geometry.y-child.geometry.y;
                                bounds.minX=Math.min(bounds.minX,x);
                                bounds.minY=Math.min(bounds.minY,y);
                                bounds.maxX=Math.max(bounds.maxX,x);
                                bounds.maxY=Math.max(bounds.maxY,y);
                        }
                        var q=0;
                        q=Math.max(Math.abs(bounds.maxX),q);
                        q=Math.max(Math.abs(bounds.maxY),q);
                        q=Math.max(Math.abs(bounds.minX),q);
                        q=Math.max(Math.abs(bounds.minY),q);
                        q=radius/q;
                        var zoom=2;
                        for(var c = 0; c < feature.cluster.length; c++) {
                              var child=feature.cluster[c];
                            var x=-(feature.geometry.x-child.geometry.x)*q+radius;
                            var y=(feature.geometry.y-child.geometry.y)*q+radius;
                                ctx.fillRect(parseInt(x-zoom/2), parseInt(y-zoom/2), zoom, zoom);
                        }
                        feature[iconKey] = canvas.toDataURL("image/png");
                    } else {
                        feature[iconKey] = OpenLayers.Marker.defaultIcon().url;
                    }
                    return feature[iconKey];
                };

        defaultStyle.context.icon=function(feature) {
            return fIcon.call(defaultStyle,feature,'default');
        }
        selectStyle.context.icon=function(feature) {
            return fIcon.call(selectStyle,feature,'select');
        }
This piece of code builds a map of the features in the cluster, zooms it to the size of the cluster icon, then also draws a translucent circle as a background.
I will not bore you with the displayFeature and clickFeature code, enough said that the first would set the html title on the layer element and the other would either zoom and center or display the info card for one single feature. There is a gotcha here as well, probably caused initially by the difference in size between the map and the layer. In order to get the actual pixel based on latitude and longitude you have to use map.getLayerPxFromLonLat(lonlat), not map.getPixelFromLonLat(lonlat). The second will work, but only after zooming or moving the map once. Pretty weird.

There are other issues that come to mind now, like making the URL for the data dynamic, based on specific parameters, but that's for another time.

Tuesday, October 08, 2013

Clustering (or any other Strategy) not working in OpenLayers

As promised, even if somewhat delayed, I am starting programming posts again. This time is about working with OpenLayers, a free wrapper over several Javascript mapping frameworks. The problem: after successfully creating and displaying a map and adding "features" to it (in this case a yellow circle for each physical location of a radio station), I wanted to add clustering; it didn't work. Quick solution: add the layer to the map BEFORE you add the features to the layer.

For the longer version, I have to first explain how OpenLayers operates. First order of business is creating an OpenLayers.Map object which receives the html element that will contain it and some options. To this map you add different layers, which represent streets, satellite imagery and so on. One can also add to any of these layers a number of features which will be displayed on the map. A common problem is that for many data points the map becomes cluttered with the rendering of these features, making for an ugly and slow interface. Enter "clustering" which means using a layer strategy of type OpenLayers.Strategy.Cluster to clump close features together into a larger one. It should be as easy as setting the layer 'strategies' array or adding to it an instance of a Cluster strategy. But for me it did not work at all.

After many attempts I finally realized that the clustering mechanism was dependent on the existence of an event handler for the 'beforefeaturesadded' event of the layer. Unfortunately for me and my time, this handler is only added when the layer is added to a Map! So all I had to do was to add the layer to the map before adding the features to it. Good thing that I recently shaved my head, or I would have tufts of bloody hair in my fists right now.

Monday, October 07, 2013

Starcraft II - Heart of the Swarm

After playing the second campaign in the Starcraft II game, the Zerg one, I have to say that I wasn't terribly impressed. 27 missions in all, nothing very fancy, and a linear script that involves only Kerrigan getting stronger and going after Mengsk. The only delight here was the character of Abathur, the Zerg "scientist", who speaks like that Korean doctor in Monday Mornings and his only concern is to find new strands of evolution for the swarm. Kerrigan, even if her voice was that of Tricia Helfer, is a cardboard character who acts arrogantly like a queen (well, she is one) taking about vision and cunning, but acting impulsively and in a direct manner most of the time. She is the character I was playing, so that is probably why I didn't really enjoy the story so much. Mark my words, if you want to find the funny moments in the game, just click on Abathur whenever you can.

There were some nice surprises in the game, though, like when destroying a neutral vehicle on a map and receiving a communication from a human soldier complaining he just paid for that car. A huge game, made with people from three continents, SCII must appease the masses to pay for itself, therefore the script could not have been too complex. They also concentrated on the multiplayer action, not on the storymode one plays on Casual difficulty in order to see the "movie", but still... a good script and a captivating story could have brought millions more into the fold, so to speak. The Starcraft universe is, after all, a very interesting one, describing the interactions between three interstellar cultures (four, if you consider the Xel'Naga). The potential here is immense, with books and movies to keep people interested for generations.

I liked the concept of evolution missions. You see, Abathur had the ability to alter strains of units and gave you two choices which were mutually exclusive. But before you chose, you had to play two mini-missions that showed the different strains in action. You usually had to kill some indigenous lifeform, absorb its essence and integrate it into your creatures. Also in the game there was a creature called an Infestor, which, judging by how the Terran campaign went, you will not see it in the multiplayer game. It allowed you to capture any enemy unit, except the heroic ones. Pretty fun. One of the evolution missions gave you the ability to morph hydralisks to lurkers, one of my favourite units from the old Starcraft game.

Overall I enjoyed playing the campaign, even if I felt that it could have been a lot greater. Finished it in about 10 hours, too. Of course, it would have taken a lot longer if I hadn't played on the easiest difficulty, but I didn't have a mouse and I really was only interested in the story. Unfortunately, I didn't feel like I gained much by playing the campaign as opposed to watching all the cinematics one after the other on YouTube, and that is, probably, what bothered me most.

So here is a compilation of Abathur dialogs.

Friday, October 04, 2013

Star Trek Enterprise - a good show corrupted

The crew of EnterpriseThere are multiple problems with Star Trek Enterprise that doomed the show from the beginning. One, I am sad to say, is Scott Bakula, who I couldn't really see as a smart and resourceful captain till the very end of the show. Another reason was the name, making people confuse Star Trek Next Generation with this series.

Yet another, and perhaps the most important reason, were the producers who abused their station to steer the show into an impossible position: an Earth ship that is older than Kirk's starship stuck in the middle of epic conflicts through time and space. Even the writers complained that it should have remained a show about early exploration and abstain from the flamboyant temporal cold war or the Xindi saga.

You see, the show started very nicely, with the launch of the first warp 5 capable human starship. The Vulcans are still not sure whether the humans should attempt this, if they are ready for the big bad universe, while humans react like teenagers that have been forbidden something. We get to see the birth of technologies so important in the Star Trek universe: the transporter, newly invented and feared (maybe rightly so, considering the multitude of episodes that involve some accident with it in the other series); they use grapnel hooks to tow; they use explosive torpedoes at start and phaser cannons that they constantly upgrade; the replicators can only manufacture simple food and they have cooks on board for the rest. The interactions of the crew were also welcome, as all of them seemed perfect for the job.

That is why the first season was good, promising a very interesting show about the beginning of human exploration. Then they botched it, with the introduction of a silly and pointless race, the Suliban, who have a faction of people receiving instructions from the future, while other future people are trying to stop them interfering with the "proper" timeline. Basically they should have asked Jean Claude VanDamme to come. This almost destroyed the second season.

A strange decision, which I can't quite say was bad or good, was to make a season that is continuous, rather than the individual episodes of Star Trek until then. They introduced the Xindi, another intriguing concept, of a coalition of species: reptilian, arboreal, simian, insectoid and aquatic. This probably wreaked havoc with their budget, so only the humanoid species usually appear. Somehow they wanted to write about terrorism, since all Americans were being influenced by 9/11, therefore the theme appeared throughout season three and quite a few episodes in season 4. First were the Xindi, who (for no reason that I can see) tested a small variation of a weapon on Earth, in preparation for a bigger one that they were still constructing. Rather than compare it with the American atomic bombs, which were the natural analogy here, they, of course, compared it with the 9/11 attacks.

A strange patch of the universe where the laws of physics are warped by huge metal spheres that have weird effects is the stage for the entire season three, while Enterprise goes to find the Xindi and stop them from destroying Earth. I felt that it was an interesting season, the only bad bits being some inconsistencies of the overall story, rather than individual episodes.

Then they had a religious sect that threatened to blow themselves inside Enterprise unless Archer destroys their enemies. The caricatured religion and condescending jabs at Palestinians should have angered Muslims more than cartoons of Mohamed. Then there was the final act of terrorism, when a faction of xenophobic humans take over the Verteron array on Mars. That script was a mess, probably because it was the last episode before the show ending one. Too much terrorism for a show that defines the transition of humanity into a peaceful future.

And then there was T'Pol, played by the lovely Jolene Blalock. That was probably the problem: she was too damn sexy. They had, therefore, to make her have emotional issues, get into relationships with everybody, make regular trips in the decontamination chamber and rub her body with antiseptic cream and so on. It is a disgrace for women in Hollywood that her role was massacred by all of these preconceptions and easy ways to get audience attention. And she was a Vulcan, the precursor of Spock, for crying out loud!

In other words the powers that be exaggerated everything and allowed Archer to be moving through time or singlehandedly creating the federation, while trying to keep something for Kirk and Picard to do. I know it is difficult to make an enticing and modern prequel to series that you watched and loved as a kid, but randomly choosing themes and ideas to "fix" the show to be more something or another is not a solution. That is why, while it the newest series of Star Trek, it was also one of the worst.

The disappointment comes, for me, when I see several portions of the show actually being great. The MAKO concept, for example, highly trained military personnel that accompanied Enterprise in the Xindi story arch was a good thing and the interaction with the regular security personnel. The controversial decisions that Archer had to make to protect Earth lent more character depth than most of the moral cardboard crap that usually infests Star Trek. The old school technology made the voyages of the new starship bring smart new levels of excitement to the exploration of space. I liked the "In the Mirror Darkly" episode, which was not a crossover to an alternate universe, but only a show about the Enterprise of the alternate universe where there were all ambitious and evil. Even the start credits looked different, showing the glorious history of Earth conquests.

My conclusion, especially today, when the number of sci-fi shows explodes as the audience requires more and more fantasy, is that Star Trek represents the hope of humanity for a bright future and that allowing it to be defined by money hungry Hollywood production companies was ultimately a mistake, no matter how much soul the writers, directors and actors put into it. A space exploration show made by Europeans, something that would mirror Star Trek's United Federation of Planets in cinematography, bringing talent and ideas from Britain, Germany, France, Russia, etc, and borrowing from the sci-fi legacy of all. I would love to see that.