A shadow is upon us

Other people contributing to my projects is often the cause of my improving them. This is because people tend to contribute something that works for the case they care about, without necessarily testing how it combines with the rest of the product. There’s nothing wrong with this. They did some work and wanted to give it back; that’s how open source should work.

A case in point here is how shadows just got added to maphilight. A pull request was submitted for a commit that added shadow support for rectangles in canvas only. I accepted the request because, hey, that’s a nice improvement, and it seemed to work. But I wasn’t really happy with rectangles-only.

So, I started fiddling with it. I had, for whatever reason, never touched shadows in canvas before. In fact, it’s been quite a while since I did the research into canvas that was involved in writing maphilight in the first place.

Looking at the commit I see that the shadows have been implemented with a combination of clipping regions and redrawing the shape with some shadow options on the path.

// [Regular shape drawing happens here]

// Clip regions
context.beginPath();
context.rect(0, 0, canvas.width, canvas.height);
context.rect(200, 200, 100, 100);
context.closePath();
context.clip();

// Shadow drawing
context.beginPath();
context.rect(200, 200, 100, 100);
context.closePath();
context.shadowColor = "rgba(0,0,0,1)";
context.fill();
context.stroke();

Now, I’m confused by the clipping being done, since I’ve never seen it work quite like that before. It’s drawing a rectangle around the whole canvas, then another around the rectangle we’re shadowing, and telling it to clip. So I do a bit of testing, and I find that this isn’t doing what I think the submitter meant it to.

I think they wanted it to set up a clip region only outside the shape, so that the shadows they drew wouldn’t appear inside it. However, in practice it seems that it’s just adding the two rectangles together and leaving us with a clip region the size of the whole canvas. It’s possible that this did work in another browser, but not in Chrome where I was testing…

Since subtractive clipping obviously wasn’t the answer, I looked into globalCompositeOperation to clean up after the fill. It turned out that destination-out was the operation I needed to empty my shape. Also, because the shadow-drawing had been added after the regular shape, I had to move it to be before that, otherwise the shadow was being drawn on top of the stroke and cleaning it up would wipe out the fill.

// So we now have...

// Shadow drawing
context.beginPath();
context.rect(200, 200, 100, 100);
context.closePath();
context.shadowColor = "rgba(0,0,0,1)";
context.fill();
context.stroke();

context.save();
context.globalCompositeOperation = "destination-out";
context.beginPath();
context.rect(200, 200, 100, 100);
context.closePath();
context.fillStyle = "rgba(0,0,0,1);";
context.fill();
context.restore();

// [Regular shape drawing happens here]

Okay! Now we have outline shadows.

But, another issue with this method: it’d fill and stroke on the shape, regardless of the settings you were using. If you had no fill / stroke it’d use the default (flat black) settings, which are ugly. Also, it harmed your opacity settings — the stroke and fill were being done twice. So when I added a shadow to a strokeless mostly-transparent rectangle I noticed that it gained a thin black outline, and was darker than it should have been.

I messed around a little bit with trying to erase the fill or stroke, but eventually decided that this was more hassle than it was worth. What wound up being the simplest option was drawing the shape massively off the edge of the canvas, and using shadow offsets to cast the shadow into the right spot.

// Just the shadow part this time
context.beginPath();
context.rect(200 + 9001, 200 + 9001, 100, 100);
context.closePath();
context.shadowOffsetX = -9001;
context.shadowOffsetY = -9001;
context.shadowColor = "rgba(0,0,0,1)";
context.fill();
context.stroke();

Now we had a shadow that didn’t involve drawing anything stroking or filling onto the canvas near our existing shape. At this point I had completely rewritten the code I’d merged in. About the only thing remaining was the option names Raven24 had chosen.

I went ahead and added some options for whether the shadow was cast inside or outside the shape, since I could see reasons for both, and added some overrides for whether it’d be casting from the fill or the stroke, since the varying possible configurations made it difficult to reliably guess which would look better.

I still didn’t add it to non-canvas. Largely because now that IE has finally given in and implemented canvas I view that as being a dead branch. Needs to keep working, and any major changes have to be ported over… but minor display differences are somewhat acceptable. Also, I don’t have access to IE right now, since I’m away from home. If I ever have reason to look into shadows in VML I’m sure I’ll add it in then, for the heck of it.

You can see all this in action on the demo page if you’re interested.

And that’s how community involvement improves things. 😀

Hiring developers

It turns out to be quite difficult to hire good developers. I’m involved in the hiring process at deviantART, and it has opened my eyes to just how unqualified the majority of applicants to these jobs are.

To give an example of this, I estimate that we hire perhaps 0.2% of the people who apply for a job with us. I haven’t gone back and tallied this up, so all figures in this example are ballpark at best… but I’ve tried to err on the generous side.

First, we collect resumes. We advertise on all sorts of job sites, we post things to Hacker News, and generally we try to get the word out. Filtering the resumes eliminates about 95% of them, depending on where we’ve been advertising. This is simultaneously the step where unqualified applicants are most expected, and the step that’s most likely to be rejecting perfectly qualified people because of some random quirk of their resume.

Then we ask promising applicants to do a simple exercise. This one, in fact. It really is fairly simple… not FizzBuzz, but still trivial. This gets reviewed by the entire hiring team. Passing it is somewhat subjective; there’s a list of things we look for, along with an ineffable “code style” component. This weeds out about 90% of submissions.

Next comes a phone interview with those who passed the test. Again, with the entire hiring team on the line. Since you’ve made it this far we’re pretty positive about you, and strongly suspect that you can code competently, but we’ll still try to dig into your past experiences and quiz you on the reasons for things you did on our exercise. Ideally you’ll have left a small bug in the exercise that we can get you to debug while we’re on the call. It’s not uncommon for us to be underwhelmed here, and we tend to turn down maybe 60% of candidates on this step.

Finally comes a three month trial period. Which we take seriously. I’d worked places with a trial period before, but it was always just a matter of not showing up to work drunk and you’d make it. We evaluate performance, work style, and general team fit… and we seem to lose perhaps 20% of hires here.

Adding all that up, P(we hire you) would seem to be 0.0016. Or 0.16% if you prefer.

The step that seems the most telling is the exercise, because it goes out to people who are generally already making a living as a developer. And, like I said, it’s not difficult. So we get to see how many professional developers apparently can’t perform their job function. For that matter, a hefty chunk of the people who fail that step just plain didn’t complete the listed requirements.

Despite all that, feel free to apply! 😀

UPDATE 2011-10-01: Some people have mentioned that PHP is keeping them from applying. Can’t blame them there; it’s not cool and modern, and it has some ugly warts. But we’re an old website (11 years now), and back in the day it’s what was picked. Rewriting into something cool and modern is technically possible, but would also be a genuinely ridiculous amount of work.

UPDATE 2011-10-02: It was pointed out to me that I may not have conveyed my point correctly. I mean to say “hiring good people is hard”, not “we are too awesome for you”. The numbers are intended to indicate our low success rate more than our exclusivity.

Random recommendation: Virgin Mobile

After resisting smartphones for many years, I got one a few months ago, just before I moved. It turns out that they’re pretty nifty. Who knew, right?

I got an LG Optimus V on Virgin Mobile. One of the big things that had been stopping me from getting one before was that I couldn’t quite bring myself to pay as much as you have to for a phone plan with data. Virgin Mobile gets around this objection by offering phone + unlimited data for $25/month… which is frankly insanely cheap compared to everyone else that I looked at.

It runs Android, and is pretty easy to root and flash to a newer version, which I have done. I held off on it for ages, but then my wife did it and I was impressed enough by CyanogenMOD to want to do it myself.

It’s not a fancy phone; it has a smallish screen, and not a terribly powerful processor. But sufficient for my needs.

So, consider this recommended. If you’re cheap like me. 🙂

Signs of adulthood

I bought a house.

Strangely, this makes me feel like more of a “grown up” than producing a daughter did. Possibly because health insurance meant that producing the daughter cost appreciably less. Actually, according to the explanation of benefits letters I got from my insurance company, childbirth was almost exactly one third the cost of a house. America is crazy.

This lets me add a little bit of extra context into my post, though. Here’s where I’m sitting right now:

IMG_2590

Maphilight 1.3

I released maphilight 1.3 just now. (Though really I consider github the more authoritative source.)

So, IE9 broke maphilight because it was finally exposing the has_canvas codepath to IE. Turns out all the canvas stuff worked beautifully, but one call to setTimeout was relying on a non-IE feature. So that’s fixed!

Also changed since the 1.2 release (one year ago, gosh):

  • New option groupBy lets you bundle several areas together
  • New option wrapClass lets you set a classname for the wrapper div created to hold the canvas elements used by maphilight. If it’s set to true it’ll use the classname from the image.
  • .data(‘maphilight’) is checked for areas, as well as the metadata plugin. With jQuery > 1.4.3 this means that you can use JSON in an HTML5 data- attribute to pass this in. See the API docs for details.
  • Performance on image maps containing a lot of areas was terrible because I was stupid about where I triggered an event.
  • Opera compatibility was harmed by jQuery bug #6708 (fixed in 1.6), so work around that.

Feel free to submit issues / pull requests on the github project.

Unintuitve effect of overflow:hidden

I found myself having to fit a textarea into a space which had user-provided CSS (“skins”) applied to it. This worked surprisingly well, but someone found a skin which had a right-floated sidebar which was playing hell with the textarea, since the textarea needed a fixed width and textareas refuse to overlap floats. So the textare got pushed way down, which looked terrible.

(To make things easier to see, the textarea will be background:transparent throughout.)

Screen-shot-2011-01-31-at-12.05.36-AM

I looked into absolute positioning to solve my woes, and put the textarea in a position:relative div with an appropriate height, and set the textarea to be position:absolute in the top-left of that div. This got me closer.

Screen-shot-2011-01-31-at-12.05.51-AM

After this I just started fiddling with it. It occurred to me to try overflow:hidden on the textarea’s container, which had an effect I did not expect.

Screen-shot-2011-01-31-at-12.06.00-AM

So. Overflow:hidden + absolute positioning + floats = floats not interfering with content. I’m sure this follows rationally from the CSS spec, but I totally didn’t expect the effect.

Further experimentation did reveal that it’s not specific to textareas. Any absolutely positioned content will have this effect if its relative container has overflow:hidden.

Identical effect in: FF3, Chrome, and IE8. Not tested elsewhere yet.

You can see all the CSS involved on this demo page.

(@cheald says that it looks like a variant on Pup’s Box Flow Hack. I’m just happy to have independently stumbled onto something that weird.)

Change your surroundings

I work from home. I like it. However, it’s easy to get distracted and slack off.

I find that the longer I spend with my workplace set up in one location at home, the less productive I get. I seem to come to associate that spot with being distracted, instead of working.

So I move regularly. Spend a few days/weeks with one workplace, then move to another in my home. Or spend a week going out to a coffee shop every day to work there. It breaks the association and lets me get things done.

Works for me, anyway.

My experience working in offices indicates that the peer pressure of other people being around working balances out the accumulated slacking habits of a single desk. Still, given the choice I’d rather work at home and have to move around.

Simplecomic

Another new-ish release, this time a PHP webcomic content management system called simplecomic.

Features:

  • Multiple comics per day
  • Schedule posting of comics in advance
  • Masking of comic filenames so scheduled comics can’t be easily found
  • Comic descriptions, alt text, and transcripts
  • Optional chapter divisions for comics
  • “Rants” as a lightweight blog, with scheduled posting
  • Theme system
  • Static pages
  • Support for the frontpage showing the first comic from the most recent day with comics, to allow posting of “issues”

I wrote it for a friend who wanted to start her own webcomic and wasn’t happy with the existing options in the field of webcomic CMSes.

You can see an example here. It’s a dead webcomic that I happen to be hosting for sentimental reasons. Ignore the Comic Sans… it’s also there for sentimental reasons. 😛

Get it on github.