2010-05-28

Arcane Image Magick

ImageMagick is the sort of thing I cannot fathom. It seems to me that it follows this sort of idea:

{binaryname} [global options] <filename> [[operator or global options] <other filename>, ...] <output filename>

That is, you choose the command from the ImageMagick suite that you're going to use as your binaryname, then at the most basic level you list all the filenames you are using as inputs and finally the filename you want as output.

Before the filenames you can set things like gravity or background, which apply to the entire canvas at the time (the canvas can be resized independently of the image, as with any image editor), and after each filename you can specify an operator, which will perform an operation on the entire in-memory image and the file specified.

Basically I have discovered that you need to put the operations *after* the image filename, not before. That way, you can string multiple operations to multiple files and produce a single output file, which goes on the end.

Just yesterday I got a new monitor and it is widescreen. Its vertical pixel size is lower than the old one and its horizontal is larger. This posed a problem to my script, because my script basically just tiles two images together. With the new monitor setup, the images will no longer be centred.

The background switcher you get with KDE doesn't feature in Gnome. Instead you have to set up a cron script. Originally I used the montage program from the ImageMagick suite, which worked quite simply like this:


montage -geometry '1280x1024>' "$selbg1" "$selbg2" "$tmp_fn"

This was part of a script that took two filenames from my wallpapers directory, joined them together, and set them as my desktop background. I had to do this because in Gnome there is no way of setting a different desktop background for each monitor. So I create a single background as wide as both monitors and use that.

The script sets the geometry to use for all images to 1280x1024>. The > means that the geometry is only applied if the current geometry is greater in either dimension than the one specified. The geometry argument itself will resize the image; thus we only resize images that are bigger than the screen.

Here is my finished background-changer cron script.


monitor_left_size = "1280x1024"
monitor_right_size = "1440x900"

convert \
    \( "$selbg1" -gravity center -resize "${monitor_left_size}>" -background none -extent "$monitor_left_size" \) \
    \( "$selbg2" -gravity center -resize "${monitor_right_size}>" -background none -extent "$monitor_right_size" \) \
    -gravity center -background none +append "$tmp_fn"

mv "$tmp_fn" "$fn"

gconftool-2 -u /desktop/gnome/background/picture_filename 2>&1;
gconftool-2 -t string -s /desktop/gnome/background/picture_filename "$fn" 2>&1;

logger "Changed background to - $selbg1, $selbg2"

I have left it up to the reader to decide how to find the two image filenames to join together, and also to determine what the wallpaper filenames will be. I use $HOME/wallpaper.png as the wallpaper itself and $HOME/wallpaper.tmp.png for the file output by ImageMagick.

The script works by using magick. Instead of an image filename you can provide a section in parentheses that works with another image (or the same image again), and outputs a virtual image that is then used as though you had given it a filename.

Here you can see that the first background image is centred (-gravity center) for the operations that follow. Those operations are first to shrink the image if either dimension exceeds the monitor size, as we did in the original script above. Then the -extent option sets the canvas to the size of the monitor. Note that in each case we use -background none to make sure the extra canvas is transparent.

This then ensures that there is a virtual image the size of the monitor. Its background is transparent, and it has an image in the exact centre. The image is shrunk to fit the monitor if necessary.

We do this twice, once for each monitor. Two virtual canvases are floating in memory. Then we simply set our gravity to 'center' again for the main canvas, set the background to 'none' again, and append the first image to the second.

If we don't set the gravity to center, the two images will have their uppermost edges aligned by default. A bit of trial and error shows that the whole wallpaper will be centred vertically on the desktop, so we use the gravity option to make sure that the individual wallpapers are also centred vertically. You can use this knowledge to do various other things with the images, in case you were not intending to use it as a wallpaper changer in the first place.

You can use the command convert <filename> <filename> +append to simply stick one image to the other, horizontally. We have replaced the <filename>s with virtual canvases and added a couple of controls to make sure the output is nice. The option -append sticks them together vertically.

Here you can see the complete script, including a stop file which you can create (touch ~/.nodesktopchange) in order to prevent the image magick happening. This is advisable if you're doing something important because the process is fairly processor-intensive.


#!/bin/bash
if [ -e $HOME/.nodesktopchange ]; then
    exit 0;
fi;

bgcnt=`wc -l $HOME/wallpapers-cache | cut -f1 -d" "`
bgnum=`expr $RANDOM % $bgcnt + 1`
selbg1=`head -n$bgnum $HOME/wallpapers-cache | tail -n1`

bgnum=`expr $RANDOM % $bgcnt + 1`
selbg2=`head -n$bgnum $HOME/wallpapers-cache | tail -n1`

fn='$HOME/wallpaper.png'
tmp_fn='$HOME/wallpaper.tmp.png'

convert \
    \( "$selbg1" -gravity center -resize '1280x1024>' -background none -extent '1280x1024' \) \
    \( "$selbg2" -gravity center -resize '1440x900>' -background none -extent '1440x900^' \) \
    -gravity center -background none +append "$tmp_fn"
mv "$tmp_fn" "$fn"

gconftool-2 -u /desktop/gnome/background/picture_filename 2>&1;
gconftool-2 -t string -s /desktop/gnome/background/picture_filename "$fn" 2>&1;

gconftool-2 -u /apps/compiz/plugins/cube/screen0/options/skydome_image 
gconftool-2 -t string -s /apps/compiz/plugins/cube/screen0/options/skydome_image "$fn"

logger "Changed background to - $selbg1, $selbg2"
exit 0

Note the addition of the compiz sections. This is supposed to change the skydome at the same time but it doesn't seem to have the desired effect. I left it in anyway for some reason.

The wallpaper-cache file is simply a text file containing a list of all the images available, meaning the script doesn't have to search the entire wallpapers directory for two images to use.

2010-04-08

jQuery Snippets You Should Know About pt I: toggle()

jQuery is the Javascript library to use these days. Ain't nothin' it can't do. In this series we will look at the plugins and core things it can do that make your own Javascript code nothing but one(ish)-liners.

This series was originally going to be entitled "jQuery *One-Liners* You Should Know About", but unfortunately one line of Javascript is what we call a mess, so we're going to allow ourselves a few lines.

Plus, the shortest version is only a line or two long, but many jQuery things can take a whole bunch of parameters and options, so yours could end up long. But, in spirit, it is one line; one function; a short piece of code.

Let's begin with toggle(). Here's what it does.

The JS for this is hideously simple:


  $('#togglebut').click(function(){
    $('#hiddendiv').toggle('fast');
  })

(I have cut out the $(function(){}) from here. Newcomers to jQuery should be aware of this function from the last time we talked about jQuery. It simply runs the function as soon as the document is ready to be modified.)

The HTML:


<input type="button" id="togglebut" value="Click">

<div id="hiddendiv" style="display:none; border: 1px solid grey;">
  <p>Lo and behold! A div!</p>
</div>

As you can see, the way I specified this behaviour was first to find the button using the jQuery CSS-style selector $('#togglebut'), and then to call the click function on that.

Remember that if your selector finds more than one element, the click function will be applied to them all. This can be either a hint or a warning, depending on what you want.

Anyway, the click function takes a function as its parameter. This parameter is the function that will be run when the item is clicked on. In our function, we have used the same selector style to find the div, and then called toggle on that. The parameter 'fast' is optional and determines the speed at which the div is toggled. You can use 'fast', 'slow' or any number of milliseconds as the duration.

This use of toggle can also be given a function as the second parameter, after the speed parameter. If you do, you will find that it is run when the object has been displayed. Observe:


$('#togglebut2').click(function(){
  $('#hiddendiv').toggle('fast', function() {
      alert("Toggled.");
    }
  );
});


<input type="button" id="togglebut2" value="Click">

<div id="hiddendiv2" style="display:none; border: 1px solid grey;">
  <p>Lo and behold! A div!</p>
</div>

All I did was change the HTML IDs so that the jQuery selectors found the second edition, and added the function to the toggle() call.

The other useful toggle

That is not all toggle does. If you provide it with multiple functions, it will run each function in turn every time it is clicked, until all the functions have been run, when it starts again.

Click me!

Let's look at the code for the above.

Again, note that this code is put in the document ready function, $(function(){}). We won't mention this again, as it is assumed that you know where you want it to go. Just because it is common that you will set up your click bindings as soon as possible, does not mean you don't want to set them up as the result of some other operation!


  $('#multitoggle').toggle(
    function() {
      $("p", $(this)).css('color', '#333');
    },
    function() {
      $("p", $(this)).css('color', '#666');
    },
    function() {
      $("p", $(this)).css('color', '#999');
    },
    function() {
      $("p", $(this)).css('color', '#CCC');
    }
  );

And the HTML:


  <div id="multitoggle" style="border: 1px solid grey">
  <p>Click me!</p>
  </div>

This edition of toggle only works for clicking, and can take as many functions as you fancy, cycling through them as you click.

It is at this point we lament a stylistic misfortune of Javascript. You see, in Javascript, functions are data types too. As we have seen, we can pass a whole function into another function as a parameter. It does not pass in the return value from the function: it passes in the function itself! Because this is such a powerful and useful tool, it is used a lot in jQuery, as we have seen. Callback functions and functions as a list of things to do, in order. The document ready function takes a function as a thing to do when the document is ready. So it is a shame that the syntax for creating a function is so verbose. Not only does it require the keyword 'function', but it has a formal parameter list, which, by the way, can be given parameters that your function expects.

This all culminates in jQuery code being very simple to write but a bit of an eyestrain to read because of the many levels of indentation it creates. But with the power it offers, your web page is plain old HTML without it.

2010-03-30

Style Switching with jQuery

Stylesheets seem to be a non-trivial problem with javascript. The problem I found was that even though HTML provisions for the "alternate stylesheet" value to the rel attribute, browsers do not tend to pay attention when this changes.

I therefore cobbled together a quick demonstration of how to change your site's skin with some simple jQuery. I wanted to do this so that I could have a work-in-progress stylesheet but easily switch to the live stylesheet so I could also see what new things will look like when they go live.

However, it is not beyond the realms of feasibility that you might actually just have more than one stylesheet and they all work and you'd like to allow the visitor to select their preferred skin without reloading the page.

Let's say you have two stylesheets called /static/main.css and /static/wip.css. The easiest way to allow the switch between these is to first set up the <link> for the main stylesheet, and then to duplicate the href to the stylesheet in another <link> tag that is specified as an alternate stylesheet. Set up the other as another alternate stylesheet, and give both alternate stylesheets a title:


<html>
    <head>
        <link rel="stylesheet" href="/static/main.css" />
        <link rel="alternate stylesheet" href="/static/wip.css" title="wip" />
        <link rel="alternate stylesheet" href="/static/main.css" title="live" />
    </head>

This is repeating yourself a bit, but if you think about it semantically, these are all actually alternate stylesheets, and one of them just happens to be the current stylesheet.

Next, I set up a single link to do the switching. You might prefer to have one link per stylesheet, or a link that toggles. Either way, give all the links the same class name so we can recognise them.


    <a href="#" rel="wip" class="ss-switch">Stylesheet</a>

Observe that the rel in the <a> matches the title in the link.

I made mine toggle since there are only two stylesheets. This same bit of jQuery should work regardless of how many links you use, and it will make the link you used switch back again.


    $(function() {
        $('a.ss-switch').click(function(){
            var curr_sheet = $('link[rel=stylesheet]').attr('href');
            var new_sheet =  $('link[title=' + $(this).attr('rel') + ']').attr('href');
            $('link[rel=stylesheet]').attr('href', new_sheet);
            $(this).attr('rel', $('link[href=' + curr_sheet + ']').attr('title');
            return false;
        });
    });

A description, then. Let's do it linewise.

Line 1 should be recognisable to anyone but those new to jQuery as the document-ready function. The function we run here ends on line 9, so we won't mention line 9 again.

This function is run when the document is ready to be played with - probably the most useful event in the page event cycle, and certainly the most useful feature of jQuery. (Getting the document ready event without jQuery is a nightmare, thanks to IE.)

Line 2 assigns a function to be run whenever a matching link is clicked. I have used the class ss-switch to denote such a link.

The jQuery selector a.ss-switch means the same as the CSS selector: all a elements with the class ss-switch. Change this if you want a different class name. Every matched element has its click handler set to the given function, which starts here and ends on line 8.

The magic of Javascript is that inside the click handler, the keyword this will refer to the element that we clicked on when this function is run. That means that it is possible to create this generic function and assign it to several elements: we can use this inside the function to find out at runtime which one we are actually using.

On line 3 we get the href of the current stylesheet. We'll use this later to find the title of this stylesheet in the alternate stylesheets. For this reason you should make sure that you have created an alternate stylesheet with the same href as the default one.

On line 4 we can see this funky combination of stuff. Briefly, this function finds the URL of the alternate stylesheet that matches the rel of the link we clicked.

Let's pick it apart. First we see $(this).attr('rel') buried deep in the middle. This will return the rel attribute of the link we clicked (this). We put the $() around it to make sure it is a jQuery object. It should be, but this construct will not do any harm. The rel of the href is inserted using string concatenation into 'link[title=foo]', which is then used in $() again, resulting in a jQuery handle on the alternate stylesheet whose title matches the rel of the link we clicked. On this we can call attr('href') to get the URL of the stylesheet we are changing to, and save this in the new_sheet variable.

On line 5, we set the href attribute of the stylesheet to be the new one. The effect of this should be immediately apparent, but be aware that it may cause a brief second where the browser has not yet got that stylesheet and the browser's default stylesheet shows through. You might want to consider using AJAX to load it, and then swap it out when you have done so. Or something.

Then on line 6 we reverse the idea that we had for line 4, and swap the rel of the link with the title of the stylesheet we are swapping from.

We use the curr_sheet variable we set up to find the <link> tag based on its href. There should be only one link tag with the same href as the original stylesheet, so it should be trivial to find it. We take the title tag of that link tag and set it to be the rel attribute of the link we clicked. That means that the next time we click the link, the whole process should go around again, except this time the other way around.

Most importantly, on line 7 we return false. This stops the browser following the link on the <a> tag in the first place. You can use this behaviour to set up a default for people without Javascript enabled: simply set instead of the # as the href some URL that will send the browser to the same page with the selected CSS as the default.


    <a href="?style=wip" rel="wip">WIP Stylesheet</a>

Brevitometer TM

tl;dr

moar infos

Posts on this page

About Podcats

A blog about things that are interesting. Alastair McGowan-Douglas and his wife Dee often stumble upon things on the internet that other people should know about and write it down so that then they do.

Podcats blogs

Advertisement

See our privacy policy.

Wishlist

If you like my blog and want to help, I would very much appreciate something from my Amazon wishlist!

If you don't like my blog and want to help, I would very much appreciate feedback to al@podcats.in. In fact, if you want to leave feedback of any sort, send it along!