Dithering for clocks, etc

Posted on
  • Just experimented with the image converter and was impressed by the results with only 3 bit color using error diffusion. And e.g. what clocks would be possible. Both are based on existing watch faces. The analog one could easily be created. For the first one individual images for the shadowed numbers would be needed.


    2 Attachments

    • 20240622_140433.jpg
    • 20240622_140538.jpg
  • That looks really neat! There are actually some options that might help you to do it without having to have separate images for each colour (or dither/non-dithered):

    • When given a 1 or 2 bit unpaletted image, g.drawImage will use the background + foreground colors - so using a pre-dithered black and white image will then allow you to draw that out in whatever colors you want (or fg+bg the same to have no dither). Using 2 bit allows you to get black+white, plus another color used to signal transparency.
    • It might be possible to use g.drawImages to layer up the number as well as your dither pattern
  • This looks really awesome!

  • Thank you @andiohn for your feedback. If anyone is interested (in which of the two?), I could try to create clocks.

    @Gordon thank you for your inspiration.

    There are actually some options that might help you to do it without having to have separate images for each colour

    It worked really well and looks almost the same: I use a 2 bit image with four colors. One color for the border of the number, one for the shadow at the top, one color for the inside of the number and one to be transparent. So it is possible to configure all color combinations that the users are interested in.

    But what's the best way to change the colors on the fly? I don't think the suggestions from above work when we want to support mix colors and also transparency is needed because the numbers overlap.

    I hoped there is some kind of callback that I could use when I pixel will be painted, but setCallback doesn't work for the existing "g".

    Scanning the mapped image from storage and use g.setPixel seems very slow; I guess because on every call the display get's updated.

    Currently the best approach seems copying the image from storage to make it editable, changing the pixels and then drawing it. But this is three loops (copying, modifying, drawing) where one should be enough.

  • Ahh, ok - so you can use FG/BG colors in Graphics if you just want 2 colours (even in 2 bit images), but then color 0=bg, 3=fg, and 1 and 2 are blends between.

    You can use g.drawImages even with just one image: https://www.espruino.com/Reference#l_Gra­phics_drawImages

    And in that, you can override the palette:

    var pal = new Uint16Array([
      g.toColor("#000"), 
      g.toColor("#F00"),
      g.toColor("#0F0"),
      g.toColor("#00F")]);
    g.drawImages([{
      x : x, y: y,
      image : img,
      palette : pal
    }]);
    

    or if you want to just use drawImage, when you're using the https://www.espruino.com/Image+Converter­ select Image Object as the output type, then
    you can set img.palette = new Uint16Array(...) as in the example above.

    Ideally you want to avoid intercepting any setPixel/etc call as it'll be slow enough it'll cause you real problems :)

  • With your help I now have only one image per number and can colorize the border and inside with every possible color that can be represented with 2x2 dithering.

    Take a look at the number 9 I attached. It consists only of the 8 bangle js 2 colors. The background is red and will be set to transparent, the shadow has a color of it's own. The border and the inside each consist of 3 different colors. The image is then converted with the espruino online converter to 3 bit image object with nearest color.

    Then only the base64 encoded buffer is transformed to binary and copied to the watch. That seemed to be the only way to reference it from flash memory but still be able to set the palette:

    var img9 = 
    {
      width : 98, height : 100, bpp : 3,
      transparent: 4,
      buffer : require("Storage").read("9.bin")
    };
    
    var palBottom = new Uint16Array(E.toArrayBuffer(E.toFlatStri­ng(new Uint16Array([
      g.toColor("#000"),
      g.toColor("#000"),
      g.toColor("#F00"),
      g.toColor("#FF0"),
      g.toColor("#00F"),
      g.toColor("#000"),
      g.toColor("#FF0"),
      g.toColor("#000"),
      ]).buffer)));
    

    Also take a look at the array conversion for the palette. This was the only way to create a flat string, because using it without the conversion on a 8 color pallete like this:

    var palBottom = new Uint16Array([
      g.toColor("#000"),
      g.toColor("#000"),
      g.toColor("#F00"),
      g.toColor("#FF0"),
      g.toColor("#00F"),
      g.toColor("#000"),
      g.toColor("#FF0"),
      g.toColor("#000"),
      ]);
    

    will give the error XXX when drawing the image:

    Uncaught Error: Palette specified, but must be a flat Uint16Array of 2,4,8,16,256 elements
     at line 12 col 28
      g.drawImage(imgUR, 75, 77);
    

    2 Attachments

    • 9.png
    • screenshot.png
  • That's really strange about the palette - I'd have thought Uint16Array would have automatically used a flat string in that case, but glad you got it sorted!

    Then only the base64 encoded buffer is transformed to binary and copied to the watch. That seemed to be the only way to reference it from flash memory

    Actually if you just use the base64 text with atob("...") then when uploaded from the app loader, the strings are 'pretokenised' by default and will be kept in flash (inside the JS file) without you having to worry!

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

Dithering for clocks, etc

Posted by Avatar for Chriz @Chriz

Actions