Thumbnail Generator

Generating thumbnails with Ecore-Evas is simple, but may not be so trivial to new comers so here we present a simple example on how to do it properly.

We provide a thumbnail library and client-server using DBus to make it much easier and faster (and non-blocking), see Ethumb.

Theory

  • Evas provides evas_object_image_save() api that will save image data to given file. Notice that the saved data is not scaled to current object size (as in evas_object_resize(o, w, h) but rather the current image data size (as in evas_object_image_size_set(o, w, h)).
  • If one wants to save the scaled image, he should use as image data the already scaled image.
  • One can use as image data the pixels from ecore_evas_buffer, what is rendered in this child canvas will show as image data on the parent canvas. This is automated with the ecore_evas_object_image_new() call that will create an image object that pixels are already set to be read from a child canvas.
  • In order to have pixels painted to image data buffer, you need the child canvas to render. To do so you should damage the whole canvas area with evas_damage_rectangle_add(), this process is also known as "expose". Without this evas_render() will be void since it would believe canvas user is not willing to see updates.

Code

  • main() just setup libraries and check parameters.
  • image_load() builds the to-be-thumbnailed scene, here we create a rectangle to serve as border and then an image resized with the same aspect as the original. Nothing really interesting, at least in the point of view of a thumbnailer.
  • thumb_gen() is the tricky machinery, except by cache configuration that can be skipped, everything else is very important to be there and in the same order!
#include <stdlib.h>
#include <stdio.h>
#include <Evas.h>
#include <Ecore.h>
#include <Ecore_Evas.h>

#define BORDER_COLOR 255, 255, 255, 255
#define BORDER 1
#define WIDTH 256
#define HEIGHT 256
#define FLAGS "quality=85"

static int
image_load(Evas *e, const char *input, Evas_Coord *rw, Evas_Coord *rh)
{
   Evas_Object *img, *border;
   int error;
   Evas_Coord w, h;

   border = evas_object_rectangle_add(e);
   evas_object_color_set(border, BORDER_COLOR);

   img = evas_object_image_add(e);
   if ((!border) || (!img))
     {
        fputs("ERROR: could not create source objects.\n", stderr);
        return -1;
     }

   /* request image to be loaded close to WIDTHxHEIGHT
    * this is a hint that some loaders like JPEG may use to speed up
    */
   evas_object_image_load_size_set(img, WIDTH, HEIGHT);
   evas_object_image_file_set(img, input, NULL);

   error = evas_object_image_load_error_get(img);
   if (error != EVAS_LOAD_ERROR_NONE)
     {
        fprintf(stderr, "ERROR: could not load image '%s': %d\n", input, error);
        return -1;
     }

   /* get the original image size, as in the 'input' file. */
   evas_object_image_size_get(img, &w, &h);
   if ((w <= 0) || (h <= 0))
     return -1;

   if (w >= h)
     {
        h = h * (WIDTH / (double)w);
        w = WIDTH;
     }
   else
     {
        w = w * (HEIGHT / (double)h);
        h = HEIGHT;
     }

   evas_object_move(border, 0, 0);
   evas_object_resize(border, w + BORDER * 2, h + BORDER * 2);
   evas_object_show(border);

   evas_object_move(img, BORDER, BORDER);
   evas_object_resize(img, w, h);
   evas_object_image_fill_set(img, 0, 0, w, h);
   evas_object_show(img);

   *rw = w + BORDER * 2;
   *rh = h + BORDER * 2;
   return 0;
}

static int
thumb_gen(const char *input, const char *output)
{
   Ecore_Evas *ee, *sub_ee;
   Evas *e, *sub_e;
   Evas_Object *o;
   Evas_Coord w, h;
   int r;

   /* ecore_evas so we get an evas so we can create objects.
    * size does not matter here.
    */
   ee = ecore_evas_buffer_new(1, 1);
   e = ecore_evas_get(ee);
   if (!e)
     {
        fputs("ERROR: could not create ecore evas buffer\n", stderr);
        return -1;
     }

   evas_image_cache_set(e, 0);
   evas_font_cache_set(e, 0);

   o = ecore_evas_object_image_new(ee);
   if (!o)
     {
        fputs("ERROR: could not create sub ecore evas buffer\n", stderr);
        goto error;
     }

   /* magic that comes from ecore_evas_object_image_new() */
   sub_ee = evas_object_data_get(o, "Ecore_Evas");
   sub_e = ecore_evas_get(sub_ee);

   evas_image_cache_set(sub_e, 0);
   evas_font_cache_set(sub_e, 0);

   if (image_load(sub_e, input, &w, &h) != 0)
     {
        fputs("ERROR: could not load input image.\n", stderr);
        goto error;
     }

   /* set buffer size to the size of returned object */
   evas_object_image_size_set(o, w, h);
   ecore_evas_resize(sub_ee, w, h);

   /* force area to be painted */
   evas_damage_rectangle_add(sub_e, 0, 0, w, h);
   evas_render(sub_e);

   /* pixels are painted, just need to save and free canvas (and children) */
   r = evas_object_image_save(o, output, NULL, FLAGS);
   ecore_evas_free(ee);

   if (!r)
     {
        fputs("ERROR: could not save image.\n", stderr);
        return -1;
     }

   return 0;

 error:
   ecore_evas_free(ee);
   return -1;
}

int
main(int argc, char *argv[])
{
   int r;

   if (argc < 3)
     {
        fprintf(stderr, "usage: %s <infile> <outfile>\n", argv[0]);
        return -1;
     }

   evas_init();
   ecore_init();
   ecore_evas_init();

   r = thumb_gen(argv[1], argv[2]);

   ecore_evas_shutdown();
   ecore_shutdown();
   evas_shutdown();

   return r;
}