Dial Clock

This is another Academic Project by Jiju Thomas Mathew

The story behind making this

An eccentric and wild walk through dark woods till found the light!

Took almost 4 hours to complete this. With deep diving into multiple tools, the list is quite long. NodeJs http-server, Firefox browser and developer tools, VScode, PHP, Google, Leonardo AI, Gimp, ImageMagick, FFmpeg and Xubuntu Shell are the ones. Will try to summarize step by step.

The start was to get a basic image and from Google search images picked one which was quite impressive, Using GIMP stripped the image down into dial, hour handle, minute handle, and there was no seconds handle, hence it was manually created in GIMP. using selection tools rectangle, circle and bucket fill and finally export to gif.

From the images created the dial is stationery and the animation effect is needed for the other three, which I chose to use CSS sprites. From basic calculations, the hour handle needed to rotate and show along with minute movement. This evaluated to 12 hours 360o one hour will rotate 30 o. Hence for that to smooth with minute movement. Thus it was decided to attach an identifier based on the integer value of half of the minutes passed, since 60 minutes is one hour and in that time hour needle must rotate 30o. This evaluates to 30 x 12 = 360 sprite images with each rotation at 1o. This was a herculean task if done manually. My DevOps instinct, and automation thoughts started a wild journey and finally found the light.

      seq 0 360 | while read r; do fn=$(printf 'img_%04d.png' $r); convert ../hour.png -background none -rotate $r -geometry 368x368 $fn ;done
    

To explain, seq 0 360 > generates numbers and this is piped into bash do while loop, with each number in variable r, the file name with pattern img_XXXX.png is generated using printf, and ImageMagick tool convert picks 368x368 pixel first image hour.png rotates it for r o effectively getting 1o increment in each converted file here it is implied that ImageMagick should use no background, with -background none and ensured that image size is at 368x368, with -geometry. This generated the 360 images, now what, ofcourse to stack this into a sprite somehow there should be an easy way out. It turned out to be a gigantic rock. But stubborn me, will not admit defeat. Imagine a horizontally stacked image 1,32,480 pixels wide and 368 pixels tall which was at 2.5 MB (24,58,574 bytes) in size. Not recommended for a css sprite. Solution, split it into six sprites one each for a 2-hour span. and define it by checking the hour part of date object using Date.getHours()

The actual stack generation turned out to be tough as the command found from various online references viz: +/- append option for ImageMagick convert utility, was failing on my system. Might be that I am still using an old version. Here my thoughts went a bit far and checked if it was possible using ffmpeg. Finally yes with a multiple pass, each image could be overlayed onto a full-size blank base and that output needs to be used for the next image base. The next large stone to be dug up

    cp ../mask.png /dev/shm/
    seq 0 59 | while read r; do ox=$(($r * 368)); fn=$(printf 'img_%04d.png' $r); ffmpeg -hide_banner -i /dev/shm/mask.png -i $fn -filter_complex "[0:v][1] overlay=${ox}:0" /dev/shm/out.png; mv -vf /dev/shm/out.png /dev/shm/mask.png ;done
    convert /dev/shm/mask.png ../hour-sprite-0.gif
  

Basically, a mask.png was created using GIMP with dimensions 22,080x368 ie 60 images of dimension 368x368. This was stored in the parent directory of the working directory. Hence line 1 initializes the blank base image as the first one. seq generates numbers and piped to do while loop. calculate the offset-x with sequence x 368 and define the filename from sequence using printf, ffmpeg is passed complex filter overlay x:y as offset-x:0 making sure each image is placed towards the right of its previous one, stacking the images horizontally. This handles one image at a time and creates the out.png which is in the loop itself renamed to mask.png to be used as the base in the next pass of the loop.

The above snippet was run with a batch of 60 images changing the seq params (60 - 119, 120 - 179, 180 - 239, 240-299, 300 - 359), one each for a 2-hour segment. Each time care was taken to update the final convert output file name as 1,2,3,4,5.

YouTube video while above long running command was ongoing in my Asus TUF A17, taken on SamsungGalaxy M14 5G

For seconds and minutes the same above method was used with only 60 images by using seq 0 59 and the rotate loop with second.png and minute.png and then the ffmpeg loop to combine and create the corresponding sprite.png finally had to use GIMP to convert png to gif for a bit of file size savings 376.4 KB to 100 KB in the minute-sprite and similar in others too.

Time to generate the css definitions, and after considering many options finally picked up php to get the job done.


function exportHour(){

  $h = range(0,11);
  $m = range(0,30);
  $iid = 0;

  foreach( $h as $j ) {
    foreach( $m as $n ) {
      echo ' .hr-', ($j === 0 ? '12' : $j ) , '-' , $n , '{';

      $ofs = $iid * 368;

      $ofs = ($ofs > 22080) ? ( $ofs % 22080 ) : $ofs;

      if($ofs > 0){
        echo  ' background-position: -', $ofs, 'px 0;';
      }else{
        echo  ' background-position: 0 0;';
      }
    echo '}'."\n";
    $iid++;
    }
  }

}

Explanation of the above code is a bit too complicated. At the start two ranges defined for h and m, as referenced above hour have 360 positions on one cycle of 12 hours. hence current hour and a half of minutes is considered as one frame. with the two step loop of nested foreach this is accomplished. Whereas my first image which was img_0000.png was with 0o rotation, hence 0 is forced to 12 $iid represents image id from the original shell loop so that starts from 0 and extends up to 360 in this situation. but I had split the enormous sprite into 6 manageable blocks each of 22080 pixels and hence the $iiid * 368 is adjusted with multiples of 22080 using modulus taking remainder after removing multiples of 22080 to find the corresponding offset-x the output of this function is copied into the linked css.


  /**
  * @tag String
  *  tag can be ' .sec-' or ' .min-' 
  */

 function exportOther($tag){
 
   $m = range(0,59);
 
     foreach( $m as $n ) {
       echo $tag, $n , '{';
 
       $ofs = $n * 368;
 
       if($ofs > 0){
         echo  ' background-position: -', $ofs, 'px 0;';
       }else{
         echo  ' background-position: 0 0;';
       }
     echo '}'."\n";
     }
 
 }
 

Since seconds and minutes were simple and straight with 60 frames each placed horizontally in the stack into a single sprite the css tags and background-position definitions were simple and straight. The output of the above function with tag values as in the comment was generated and copied into the css linked to this document.

The HTML and JavaScript was fair and simple once the sprite was created, and the source is not obfuscated. If interested you are welcome to take a look. Also few of my other activities and social media as well as profiles are linked herewith,

Reached here and now I wanted to spruce up the clock dial inline with my imagination and loaded up Leonardo AI to ask for a clock face. Prompt written
Render an image of a distressed circular wooden clock dial from 1940.
The rendered image had the hour and minute hands also, took it into GIMP and did the manipulation with the clone, smudge, brush, erase and spray tools. Then exported to gif to link onto this page. The Clock is completed.

  • GitHub: github logo
  • TopTal: toptal.com logo
  • Freelancer: freelancer.com logo
  • LinkedIn: Follow on LinkedIn
  • YouTube: YouTube logo
  • QR Code generator with enough macros to generate mobile cam scannable QR codes embedding direct call to phone, chat to whatsapp without saving number, upi payment QR with embedded custom image like business logo, Linked in follow url and more in the wish list.
  • World Clock, Tired of juggling multiple time zones? My project makes it easy to track time across the globe. Try it now and stay organized!
  • Browser Based Text Editor, text editor which constantly updates the total character count and provides find and replace with regular expressions was my need and developed it with few curated prompts into Google Gemini.