Wednesday, August 23, 2017

Decorating the Oceans, Part Five

The last bit of ocean decoration I'd like to talk about is the ever-popular sea monster:
Two nice examples shown here.  These sorts of nautical illustrations are used to add interest to areas of blank ocean, as well as to indicate unknown waters, i.e., "Beware monsters!" or even "Here Dragons Abounde" :-)  Adding these to the map isn't too hard.  Assuming I can find some appropriate illustrations, I can essentially "paste" them into the map.  But there are a few challenges.

The first challenge is getting the size of the image.  For everything I've done so far with images (such as using them as a pattern or texture for the ocean), the size of the image has been irrelevant -- at least to the code.  It loads in an image and displays it at 100% size and just lets the image fall where it may.  For some cases where the size is actually relevant, like compasses:
I finessed the problem by creating all my compasses to be exactly the same size.  But for sea monsters and other such decorations, the size really will matter.  They won't all be the same size or proportions, so to place them properly I'll need to know the size.  The problem is that the browser doesn't know the size of an image until it has loaded the image.

There are a couple of possible solutions to this problem.  One possibility is to just measure my sea monster images and hard-code the dimensions into the program.  The drawback with that approach is that adding new images requires modifying the code.  Another (better?) possibility is to somehow force the browser to load up the sea monster images and measure them before I get around to using them.

The way to do this is described in this StackOverflow answer.  The basic approach is to create an <img> element, set the URL for that image to the sea monster image and then give control back to the browser to load the image.
function getMeta(url){   
    var img = new Image();
    img.onload = function(){
        alert( this.width+' '+ this.height );
    img.src = url;
Some time later (after the browser has finished fetching and loading the image) the browser will execute the callback function (the one attached to img.onload) and you then have access to the width and height of the image.  The trick is to do all this before you get around to using the image, so that you'll have the size available when you need it.

As it turns out, I'm already doing something like this to pre-load all my fonts, as I described back in this post.  So I need to do essentially the same thing with the images.  The map creation takes a long time (in web browser terms, anyway) so anything I do when the page loads is long since completed by the time I get around to actually displaying the map.  So I'll add some code that executes when the page first loads to fetch and measure the images.

The next step is to find a spot for the sea monster.  These are really just decorations intended to liven up empty spots on the map, so a reasonable approach is to look for a point in the ocean that is the farthest away from land or a map side:
The green circle in the lower right is the spot most distant from land or the map edge.  I find it actually works better to de-weight the map edge a little bit, which gives us this result:
You'll notice that in this case, the label for "Romel" is within the green circle.  I'd really also like to avoid labels as I'm avoiding land or map edges, but there's a bit of a chicken and egg problem here. I'd like to place this image before the labels so the labels can avoid overlapping the image.  But I'd also like to place the image far away from where the labels end up.  I can't do both things at the same time.  The best solution might be to treat the image as a label and let the labeling work out the best spot.  I'll return to that anon, but for now I'm just going to place before I do labels.

Whichever your preference, I can now paste the image into the map at that location.  For the image I use a line art picture of a sea monster, with the background set to transparent so that the sea shows through:
There are a couple of problems here.  One problem is that there are white parts of the image that look weird.  They're not actually white, just lighter grey than the rest of the image, but it's obvious that any part of the image that is lighter in luminance than the sea behind it is going to look strange.  The simple solution is to darken the image until the problem goes away:
But you have to make the lines quite dark, and the result to me is too "heavy" looking.  One way to address this is to reduce the opacity of the image:
This allows some of the water to show through and reduces the overall darkness of the image.

The second problem is that the image is too big -- it takes up too much of the free space.  Since I have the size of the image available, I can scale it to fit into the available space.  For example, I can size the image so that it's longest dimension uses up half the diameter of the free space circle:
Here the image is placed in a tighter area and scaled down accordingly.

Another way to bring this image into the map is to use an SVG filter, the way I did with ocean patterns.  Adding the image this way, I can "multiply" it with the underlying ocean image. This has the advantage that it does a better job of blending in the colors.  The lines are not so heavy, and there aren't any artifacts from colors that are too light:
One advantage of this approach is that all the colors in the image don't have to be dark black.  Lighter colors show up as less opaque, which permits fading out parts of the image if I want to do that:
Overall, adding the image this way seems preferable.  There are a couple of challenges with this approach, though.

First, I'm using pixel images (not vector images) so they may not scale well if I ever have to make one bigger than its base resolution.  This is the same problem I had with the map compasses; I fixed that by finding or making vector art compasses.  I've started doing the same for these illustrations, but so far I haven't found much appropriate vector art.  If anyone can point me to free illustrations - or even good and relatively cheap commercial art - I'd appreciate it!

The second challenge is that a particular SVG layer can only have one filter applied to it.  If you attach a second filter, it simply replaces the first filter -- they don't "stack" up.  Right now I'm only showing one sea monster, and as it happens the layer it needs to go on doesn't already have a filter on it.  But if I wanted a second illustration, or to apply some other filter, I'd need to create new layers for the new filters.

Just to check in on where the map stands, here's an example of how the illustration looks with some other bells and whistles:
(Click through for a larger view.)  

You might have notice that the maps for this posting have labeled islands.  The next series of posts will start talking about labeling various ocean features.

1 comment: