You can find the video recording here: https://www.youtube.com/watch?v=lYoHh19RNy4
Heiko Behrens and Matthew Hungerford present advanced programming techniques for Pebble. This presentation focused on graphics techniques including run-time dithering, offline dithering, pixel manipulations, and frame-buffer drawing.
This talk featured the amiga boing ball dithering demo.
Day 1 - Video 3B
5. LAYER STACK
Layers are added to the
layer_stack from back to front
starting from the window layer.
!
Layers are then rendered from
back to front into the application
frame buffer.
Window
6. THE COMPOSITOR
Example of a non-fullscreen application (144x152):
Application
frame buffer
Compositor
frame buffer
Final
display
14. Dither is an intentionally applied form of noise used to
randomize quantization error, preventing large-scale
patterns such as color banding in images.
http://en.wikipedia.org/wiki/Dither
256 shades of gray
16 shades of gray + nearest color
16 shades of gray + random noise
16 shades of gray + ordered dithering
15. 256 shades of gray
16 SHADES OF GRAY
16 shades of gray + nearest color
16 shades of gray + random noise
16 shades of gray + ordered dithering
BLACK AND WHITE
black & white + nearest color
black & white + noise
black & white + ordered dithering
22. BETTER BITMAPS (GIMP/PHOTOSHOP)
Resize to desired size (<144x168) using sinc filter
Convert image to grayscale
Modify brightness/contrast to sharpen image
Posterize to 3 colors (if dithering to gray_50) or 2
colors for black and white.
Undo-Redo brightness/contrast & posterize till
desired effect
Convert image to black and white with ordered
(positional) dithering
35. DITHERED ANIMATIONS (APNG)
ImageMagick scripting
!
Ordered dithering (gray_50)
!
(a)PNG compression for 200 frames
!
Resource loading and drawing at high FPS
(10 FPS in this example)
49. ORDERED DITHERING (BAYER MATRIX)
threshold_map_4x4 =
foreach y
foreach x
oldpixel := pixel[x][y] + threshold_map_4x4[x mod 4][y mod 4]
newpixel := find_closest_palette_color(oldpixel)
pixel[x][y] := newpixel
ORDERED DITHERING BLACK & WHITE
foreach y
foreach x
newpixel := pixel[x][y] > threshold_map_8x8[x mod 8][y mod 8] ? white : black;
pixel[x][y] := newpixel
57. DOING FAST(!) LIVE DITHERING
GBitmap
* graphics_capture_frame_buffer(GContext* ctx)
Gives you a GBitmap to represent the current frame buffer
bool graphics_release_frame_buffer(GContext* ctx, GBitmap* buffer)
…and locks out any other graphic function until you release it
58. GBITMAP RECAP
0
4
8
12
16
29px
4 bytes per row
5px
unused
unused
unused
unused
unused
Fragment of 8 pixels in a row, stored
in 8 bits or 1 byte (with value 0x5C). 0 1 0 1 1 1 0 0
Fragment of 8 pixels in a row, stored
in 8 bits or 1 byte (with value 0x5C). 0 1 0 1 1 1 0 0
GBitmap
typedef struct {
void *addr;
uint16_t row_size_bytes;
// ...
GRect bounds;
} GBitmap;
uint8_t *byte_offset = (uint8_t*)bmp->addr
;
byte_offset += x / 8;
byte_offset *= y * bmp->row_size_bytes;
59. DOING FAST(!) LIVE DITHERING
static void update_proc_frame_buffer(Layer *layer, GContext *ctx) {
GBitmap *fb = graphics_capture_frame_buffer(ctx);
!
uint8_t *row = (uint8_t *)fb->addr;
!
row += fb->bounds.origin.y * fb->row_size_bytes;
!
!
!
!
!
!
!
!
!
!
!
!
!
!
const int16_t h = fb->bounds.size.h;
for (int16_t y = fb->bounds.origin.y; y < h; y++) {
uint8_t row_gray_value = (uint8_t)((gray_top * (h - y) + y * gray_bottom) / h);
!
!
!
!
uint8_t row_gray_dither_pattern = d i t h e r _ p a t t e r n _ 8 (y, row_gray_value);
row += fb->row_size_bytes;
}
!
!
!
graphics_release_frame_buffer(ctx, fb);
}
60. RENDER 8 PIXELS AT ONCE
static uint8_t (int16_t y, uint8_t gray) {
return color_for_gray(0, y, gray) << 7 |
color_for_gray(1, y, gray) << 6 |
color_for_gray(2, y, gray) << 5 |
color_for_gray(3, y, gray) << 4 |
color_for_gray(4, y, gray) << 3 |
color_for_gray(5, y, gray) << 2 |
color_for_gray(6, y, gray) << 1 |
color_for_gray(7, y, gray) << 0;
}
dither_pattern_8
61. DOING FAST(!) LIVE DITHERING
static void update_proc_frame_buffer(Layer *layer, GContext *ctx) {
GBitmap *fb = graphics_capture_frame_buffer(ctx);
!
uint8_t *row = (uint8_t *)fb->addr;
!
row += fb->bounds.origin.y * fb->row_size_bytes;
!
!
!
!
!
!
!
!
!
!
!
!
!
!
const int16_t h = fb->bounds.size.h;
for (int16_t y = fb->bounds.origin.y; y < h; y++) {
uint8_t row_gray_value = (uint8_t)((gray_top * (h - y) + y * gray_bottom) / h);
!
!
!
!
uint8_t row_gray_dither_pattern = d i t h e r _ p a t t e r n _ 8 (y, row_gray_value);
memset(row, row_gray_dither_pattern, fb->row_size_bytes);
row += fb->row_size_bytes;
}
!
!
!
graphics_release_frame_buffer(ctx, fb);
}