Mask Effect for EffectLayer for Pebble

I’ve written before about EffectLayer library for Pebble smartwatch I’ve been working on. The idea is – user places the layer over screen and that layer applies an effect to screen content.

I’ve started with a few basic effects (invert, mirror) but since then several more developers joined the project adding more cool features. Ron added 90° rotation, zoom and lens effect. Gregoire Sage added cool blur effect. LeFauve not only added FPS effect, but also optimized the library to run the effects in a very efficient way: now effect can be defined as a function (even user defined function!) and that function passed as a parameter to effect_layer_add_effect method along with parameters for that effect.

I, for my part, contributed “mask” effect. What it does is essentially lets you show parts of background image thru user defined mask, creating a feel of transparency.

mask

Effect works in two modes: full mode, where user supplies mask color, background color, mask image (or, if masking by text, text, its font and alignment), and background image. Full mode effect is demonstrated by these images (text mask and bitmap mask respectfully). And it works on classic “Aplite” Pebble too.

Mask Second mode is onscreen: user supplies just mask color and background image and specifies background color as GColorClear. In this mode EffectLayer uses whatever is already drawn on the screen in mask color as the mask to show background image through. Onscreen mode effect is demonstrated by this image (which is actually watchface “Simple Striped” published to Pebble appstore) It also works both on classic Pebble and Pebble Time.

The way Mask effect behaves is determined by parameter passed to the effect_mask function. And here is how type of parameter is defined:

typedef struct {
  GBitmap*  bitmap_mask; // bitmap used for mask (when masking by bitmap)
  GBitmap*  bitmap_background; // bitmap to show thru mask
  GColor    mask_color; //color of the mask
  GColor    background_color; // color of the background
  char*     text; // text used for mask (when when masking by text)
  GFont     font; // font used for text mask;
  GTextOverflowMode text_overflow; // overflow used for text mask;
  GTextAlignment  text_align; // alignment used for text masks
} EffectMask;

Knowing this structure, if you want to define full mode effect, you can specify properties something like this:

static EffectMask mask;

mask.text = NULL;
mask.bitmap_mask = gbitmap_create_with_resource(RESOURCE_ID_MASK);
mask.mask_color = GColorWhite;
mask.background_color = GColorBlack;
mask.bitmap_background = gbitmap_create_with_resource(RESOURCE_ID_BACKGROUND);

In this example we’re using bitmap mask (so we’re specifying NULL for text and creating mask bitmap from recourse). Mask image should be of white color (which we also specify via mask.mask_color property). We also specify color of the background and bitmap for background. With this parameter defined EffectLayer will be drawn in black color and will show background image thru shape defined by mask bitmap. Text mask works in similar matter, only instead of specifying bitmap mask, you set it to NULL and define text string, font and alignment instead.

To define onscreen mode effect parameter will look like this

mask.text = NULL;
mask.bitmap_mask = NULL;
mask.mask_color = GColorWhite;
mask.background_color = GColorClear;
mask.bitmap_background = gbitmap_create_with_resource(RESOURCE_ID_BACKGROUND);

As you can see here we define neither text bitmap nor background bitmap, and specify transparent color for layer background. In this mode, whatever is already drawn on the screen in mask color (white in this case) will act as a mask for background image your supplied.

Once the parameter defined to add mask effect to your screen is pretty straightforward:

EffectLayer* effect_layer;

effect_layer = effect_layer_create(GRect(0,0,144,168));
effect_layer_add_effect(effect_layer, effect_mask, &mask);
layer_add_child(window_get_root_layer(window), effect_layer_get_layer(effect_layer));

Here we create EffectLayer (making it take full screen 144×168), adding mask effect with defined mask parameter and adding the layer to main window.

If you want to pick under the hood how effect_mask works, you’re in luck, here it is:

void effect_mask(GContext* ctx, GRect position, void* param) {
  GColor temp_pixel;  
  EffectMask *mask = (EffectMask *)param;

  //drawing background
  if (!GColorEq(mask->background_color, GColorClear)) {
    graphics_context_set_fill_color(ctx, mask->background_color);
    graphics_context_set_text_color(ctx, mask->mask_color);
    graphics_fill_rect(ctx, GRect(0, 0, position.size.w, position.size.h), 0, GCornerNone); 
  }
  
  if (mask->text) {//if text mask is used - drawing text
     graphics_draw_text(ctx, mask->text, mask->font, GRect(0, 0, position.size.w, position.size.h), mask->text_overflow, mask->text_align, NULL);
  } else if (mask->bitmap_mask) { // othersise - bitmap mask is used
     graphics_draw_bitmap_in_rect(ctx, mask->bitmap_mask, GRect(0, 0, position.size.w, position.size.h));
  }
    
  //capturing framebuffer bitmap
  GBitmap *fb = graphics_capture_frame_buffer(ctx);
  uint8_t *bitmap_data =  gbitmap_get_data(fb);
  int bytes_per_row = gbitmap_get_bytes_per_row(fb);
  
  //capturing background bitmap
  uint8_t *bg_bitmap_data =  gbitmap_get_data(mask->bitmap_background);
  int bg_bytes_per_row = gbitmap_get_bytes_per_row(mask->bitmap_background);
    
  //looping thru layer replacing mask with bg bitmap
  for (int y = 0; y < position.size.h; y++)
     for (int x = 0; x < position.size.w; x++) {
       temp_pixel = (GColor)get_pixel(bitmap_data, bytes_per_row, y + position.origin.y, x + position.origin.x);
       if (GColorEq(temp_pixel, mask->mask_color))
         set_pixel(bitmap_data, bytes_per_row, y + position.origin.y, x + position.origin.x, get_pixel(bg_bitmap_data, bg_bytes_per_row, y + position.origin.y, x + position.origin.x));
  }
  
  graphics_release_frame_buffer(ctx, fb);
  
}

In the function first we retrieve passed mask parameter in Line 03, then in Lines 06-10 check, if background color is passed and if it is – fill layer with this color (and set text color, in case we use text mask). Lines 12-16 check what kind of mask is used: for text one it draws text, for bitmap – draws bitmap. And if neither is passed – no mask is drawn. Lines 19-25 capture both screen image and background bitmap supplied by users as raw data and finally Lines 27-33 loop thru layer coordinates replacing pixels of mask color with pixels from background bitmap.

Don’t forget to destroy the bitmaps you created in your code cleanup section (window_unload, deinit etc.)


UPDATE AUG-21-2015 <– As of this version Mask Effect can use multicolor array for mask. So above's mask.mask_color becomes mask_mask_colors. Example usage assignment:

#ifdef PBL_COLOR
   mask.mask_colors = malloc(sizeof(GColor)*4);
   mask.mask_colors[0] = GColorWhite;
   mask.mask_colors[1] = GColorBlue;
   mask.mask_colors[2] = GColorRed;
   mask.mask_colors[3] = GColorClear;
#else
   mask.mask_colors = malloc(sizeof(GColor)*2);
   mask.mask_colors[0] = GColorWhite;
   mask.mask_colors[1] = GColorClear;
#endif

Here you allocate size of your array (plus one element, because array always have to terminate with GColorClear as last element) and assign colors. And don’t forget to free what you malloc’ed at the end.


Useful links

2 replies on “Mask Effect for EffectLayer for Pebble”

  1. Thanks for making this awesome library! It makes my life a lot easier 🙂

  2. @Kristof thanks for using it 🙂 It’s really fun to make it, hopefully we will have proper documentation soon.

Leave a Reply

Your email address will not be published. Required fields are marked *