HiPi
Perl Modules for Raspberry Pi
Version 0.92 - released 28 March 2024

HiPi::Interface::EPaper

This module provides an interface to several small form factor E-Paper / E-Ink displays.

It uses HiPi::Device::SPI as a backend.

Currently supported displays are:

IMPORTANT: Using incorrect setup and update parameters with E-Paper displays can cause permanent damage to the display. You use the modules at your own risk. I have spent considerable time, offort and testing to ensure that the modules follow the manufacturer / suppliers example code and datasheet specifications. However, I cannot guarantee that I have done this correctly and the code may still damage your display.

The module only supports display types that have been available for testing during development.

If you are not familiar with E-Paper displays and their limitations, please read the E-Paper Display Notes first.

Examples

Methods

Creates a new instance of the module class

use HiPi qw( :epaper );
use HiPi::Interface::EPaper;
my $epd = HiPi::Interface::EPaper->new(
    type => EPD_WS_2_13_250_X_122_A
);

Constants are provided for each type of display that the module supports.

  • EPD_WS_1_54_200_X_200_A      (Waveshare 1.54 inch black, white with partial update)
  • EPD_WS_1_54_200_X_200_B      (Waveshare 1.54 inch red, black, white)
  • EPD_WS_1_54_152_X_152_C      (Waveshare 1.54 inch yellow, black, white)
  • EPD_WS_2_13_250_X_122_A      (Waveshare 2.13 inch black, white with partial update)
  • EPD_WS_2_13_212_X_104_B      (Waveshare 2.13 inch red, black, white)
  • EPD_PIMORONI_INKY_PHAT_V2      (Pimoroni Inky pHAT red, black, white)
  • EPD_WS_2_90_296_X_128_A      (Waveshare 2.9 inch black, white with partial update)
  • EPD_WS_2_90_296_X_128_B      (Waveshare 2.9 inch red, black, white)

IMPORTANT: You must use the correct type for your display in the constructor. Passing the wrong type may permanently damage your display. Be careful!

Rotation

In all of the supported displays the width or x axis is always the shorter dimension. For example, the Waveshare 2.9 inch displays have a width of 128 pixels and a height of 296 pixels.

For most applications of these small form factor wide displays the logical width in the application is the longer dimension. To change the orientation of the display pass the required rotation in the constructor.

Constants are provided to define the rotation required.

  • EPD_ROTATION_0
  • EPD_ROTATION_90
  • EPD_ROTATION_180
  • EPD_ROTATION_270

The default rotation for 'long' or 'wide' displays is EPD_ROTATION_90. For square displays it is EPD_ROTATION_0

Passing a rotation that does not match your drawing code is harmless. Attempting to draw to pixels outside the display area is ignored. Use rotation to orient your display according to its mounting in your project.

use HiPi qw( :epaper );
use HiPi::Interface::EPaper;
my $epd = HiPi::Interface::EPaper->new(
    type     => EPD_WS_2_13_250_X_122_A,
    rotation => EPD_ROTATION_270
);

SPI Interface and Pin Settings

Each of the supported display types has default settings that use CE0 of the SPI0 device. (Only the MOSI, SCLK and CEO pins from the Pi are connected - the displays are write only so the MISO pin is unconnected).

The displays also use three additional pins for DC, RESET and BUSY functions.

Waveshare Display Defaults

     DC PIN      GPIO 25      RPI PIN 22
     RESET PIN      GPIO 17      RPI PIN 11
     BUSY PIN      GPIO 24      RPI PIN 18

Pimoroni Inky pHAT Defaults

     DC PIN      GPIO 22      RPI PIN 15
     RESET PIN      GPIO 27      RPI PIN 13
     BUSY PIN      GPIO 17      RPI PIN 11

If you connect your display differently you can override the defaults in the constructor.

use HiPi qw( :epaper :rpi );
use HiPi::Interface::EPaper;
my $epd = HiPi::Interface::EPaper->new(
    type       => EPD_WS_2_13_250_X_122_A,
    devicename => '/dev/spidev0.1',
    dc_pin     => RPI_PIN_38,
    reset_pin  => RPI_PIN_40,
    busy_pin   => RPI_PIN_36, 
);

The modules reset your display automatically when initialised. You can also reset the display in your own code. Note that this doesn't clear your local buffer containing your current bitmap. To clear that call clear_buffer

$epd->display_reset()

All drawing methods store data in a buffer. They are not written to the display until you call display_update.

The method uses the current update mode as set by set_update_mode( $mode ). By default and always on displays that do not support partial update, the current mode is EPD_UPD_MODE_FULL (full).

In the default full update mode the displays take some time to refresh and will flicker. The refresh time varies between 2 and 30 seconds depending on the display type.

If the current mode is EPD_UPD_MODE_PARTIAL, a partial update is used against the whole of the display area.

$epd->display_update()

The manufacturers recommendation for E-Paper displays is that they are placed into sleep or power-off mode between refreshes. You should therefore ensure that your code calls display_sleep after each refresh or before your code exits.

Once display_sleep has been called, display_reset() must be called before the display will accept any other commands.

While in sleep mode the module will not maintain the state of the busy pin.

$epd->display_sleep()

This method sets the current update mode for displays that support partial updates.

Available constants for mode are:

  • EPD_UPD_MODE_FULL      (default)
  • EPD_UPD_MODE_PARTIAL

use HiPi qw( :epaper );
use HiPi::Interface::EPaper;
my $epd = HiPi::Interface::EPaper->new(
    type              => EPD_WS_2_13_250_X_122_A,
    skip_sleep_onexit => 1,
);

# want to do partial update on an area 80 pixels
# wide by 24 pixels high in the centre of this display

# do initial drawing
.....
$epd->display_update;
# now set partial mode
$epd->set_update_mode( EPD_UPD_MODE_PARTIAL );
# first update the whole display with a partial update
$epd->display_update;

my $pctx = $epd->create_partial_context( 80, 40 );
$pctx->draw_text(0,0, 'Raspberry Pi', 'SansEPD15');

# update the partial area at x = 85, y = 41
$epd->display_update_partial( 85, 41, $pctx);

See also: Partial Update Example

For displays that support partial updates, update the area of the display at top left logical coordinate $x, $y using partial context $context.

To perform a partial update, create a HiPi::Interface::EPaper::PartialContext using the create_partial_context method. Write to the PartialContext, and update the display with the partial context.

my $part_ctx = $epd->create_partial_context( $width, $height );
# draw to partial context
.......
# write partial update to display
$epd->display_partial_update( $x, $y, $part_ctx );

The currently supported display types that allow partial updates are:

  • EPD_WS_1_54_200_X_200_A      (Waveshare 1.54 inch black, white with partial update)
  • EPD_WS_2_13_250_X_122_A      (Waveshare 2.13 inch black, white with partial update)
  • EPD_WS_2_90_296_X_128_A      (Waveshare 2.9 inch black, white with partial update)

See also: Partial Update Example

HiPi::Interface::EPaper supports partial updates using the HiPi::Interface::EPaper::PartialContext class. The create_partial_context method returns a new instance of this class.

The $width and $height parameters define the size of the partial context. Partial updates use byte boundaries to define the area to update so the $width and $height parameters should be muliples of 8 pixels. If they are not, the size of the partial context will be extended so that its logical width and logical height are multiples of 8 pixels.

use HiPi qw( :epaper );
use HiPi::Interface::EPaper;
my $epd = HiPi::Interface::EPaper->new(
    type              => EPD_WS_2_13_250_X_122_A,
    skip_sleep_onexit => 1,
);

# want to do partial update on an area 80 pixels
# wide by 24 pixels high in the centre of this display

# do initial drawing
.....
$epd->display_update;
# now set partial mode
$epd->set_update_mode( EPD_UPD_MODE_PARTIAL );
# first update the whole display with a partial update
$epd->display_update;

my $pctx = $epd->create_partial_context( 80, 40 );
$pctx->draw_text(0,0, 'Raspberry Pi', 'SansEPD15');

# update the partial area at x = 85, y = 41
$epd->display_update_partial( 85, 41, $pctx);

See also: Partial Update Example

The module writes all drawing operations to a local buffer that you write to the display using display_update. You can clear all pixels set in the buffer using clear_buffer.

$epd->clear_buffer();
# optionally send to display
$epd->display_update()

Where displays support a colour in addition to black, you set the colour for all drawing operations using the set_pen() method.

use HiPi qw( :epaper );
...
            
$epd->set_pen( EPD_BLACK_PEN );
$epd->draw_text(0,0, 'This is black text', 'SansEPD15');
$epd->set_pen( EPD_RED_PEN );
$epd->draw_text(0,20, 'This is red text', 'SansEPD15');

The module provides the following constants

  • EPD_BLACK_PEN
  • the following are all equivalent for the alternate colour in a tri colour display but all are provided for convenience
  • EPD_RED_PEN
  • EPD_YELLOW_PEN
  • EPD_COLOUR_PEN
  • EPD_COLOR_PEN

Where a display only supports black / white specifying EPD_RED_PEN or equivalent has no effect.

Set invert_pen to set pixels you write using the drawing methods to 'off' rather than on. This is useful to draw items as 'off' over areas you may have previously set 'on'

# draw a filled rectangle
$epd->draw_rectangle(10,20, 60, 40, 1);
$epd->invert_pen(1);
# write some text over the rectangle
$epd->draw_text(12, 25, 'Hello World', 'SansEPD15');
# restore normal pen
$epd->invert_pen(0);

Returns the logical width. All drawing methods use logical coordinates. logical_width returns the width of the drawing area adjusted for its rotation.

For example, if using a 296 x 128 display

     rotation      logical width
     EPD_ROTATION_0      128
     EPD_ROTATION_90      296
     EPD_ROTATION_180      128
     EPD_ROTATION_270      296
my $width = $epd->logical_width;

Returns the logical height. All drawing methods use logical coordinates. logical_height returns the height of the drawing area adjusted for its rotation.

For example, if using a 296 x 128 display

     rotation      logical height
     EPD_ROTATION_0      296
     EPD_ROTATION_90      128
     EPD_ROTATION_180      296
     EPD_ROTATION_270      128
my $height = $epd->logical_height;

Instead of writing to the buffer directly, you can use drawing methods on a HiPi::Graphics::DrawingContext which can then be written to the buffer using draw_context.

You can draw the same context on the buffer many times. You can rotate all the pixels in a context as you write it to the buffer.

my $ctx = $epd->create_context();
$ctx->draw_text(0,0,'Hello World'. 'SansEPD15');
# write it to the buffer at position 10,20 and rotate it 90 degrees.
$epd->draw_context( 10, 20, $ctx->rotated_context( 90, 0, 0) );
# write it to the buffer at position 100,30 and rotate it 180 degrees.
$epd->draw_context( 100, 30, $ctx->rotated_context( 180, 0, 0 ) );

Draw an arc.

  • $x,$y is the origin of the arc
  • $rw and $rh are the horizontal ( $rw - width ) and vertical ( $rh - height ) radius to use when drawing the arc. If $rw and $rh are equal, you will draw the arc of a circle
  • $start and $end denote the start and end of the arc in degrees. See the diagram below
  • $join is optional. If specified ( $join == 1 ) lines are drawn from the origin to the end points of the arc
  • $fill is optional. If specified ( $fill == 1 ) the arc is filled.

# draw 20 degree arc of a circle at x = 63, y = 15, 10 pixel radius
$epd->draw_arc(63, 15, 10, 10, 350, 10);

# same arc, but draw lines from origin to edges
$epd->draw_arc(63, 15, 10, 10, 350, 10, 1);

# same arc, but filled
$epd->draw_arc(63, 15, 10, 10, 350, 10, 0, 1);

Draw an array of 1 / 0 values as a bitmap.

  • $x, $y is the origin to draw at
  • $bitarray is a reference to an array defining the points
  • $fill is optional but if specified, bits that are 0 in the array will be explicitly written to the buffer. If fill is not specified, only bits that are 1 are explicitly written. Using $fill is usefull if you want to overwrite an area of the buffer that has previously been written to.

# Draw a 'raspberry' starting at x = 10, y = 15
my $bitarray =  [ 
    [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
    [ 0, 0, 0, 1, 1, 1, 1, 0, 0, 0,   0, 0, 0, 1, 1, 1, 1, 0, 0, 0 ],
    [ 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,   0, 0, 1, 0, 0, 0, 0, 1, 0, 0 ],
    [ 0, 0, 1, 0, 1, 1, 0, 0, 1, 0,   0, 1, 0, 0, 1, 1, 0, 1, 0, 0 ],
    [ 0, 0, 1, 0, 0, 0, 1, 0, 1, 0,   0, 1, 0, 1, 0, 0, 0, 1, 0, 0 ],
    
    [ 0, 0, 0, 1, 0, 0, 0, 0, 0, 1,   1, 0, 0, 0, 0, 0, 1, 0, 0, 0 ],
    [ 0, 0, 0, 0, 1, 1, 1, 0, 0, 0,   0, 0, 0, 1, 1, 1, 0, 0, 0, 0 ],
    [ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,   0, 0, 0, 1, 0, 0, 0, 0, 0, 0 ],
    [ 0, 0, 0, 0, 1, 1, 0, 0, 0, 1,   1, 0, 0, 0, 1, 1, 0, 0, 0, 0 ],
    [ 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,   0, 1, 0, 0, 0, 0, 1, 0, 0, 0 ],
    
    [ 0, 0, 1, 0, 0, 1, 1, 0, 0, 1,   1, 0, 0, 1, 1, 0, 0, 1, 0, 0 ],
    [ 0, 0, 1, 0, 1, 0, 0, 1, 0, 0,   0, 0, 1, 0, 0, 1, 0, 1, 0, 0 ],
    [ 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,   0, 0, 1, 0, 0, 1, 0, 0, 1, 0 ],
    [ 0, 1, 0, 0, 0, 1, 1, 0, 0, 1,   1, 0, 0, 1, 1, 0, 0, 0, 1, 0 ],
    [ 0, 1, 0, 0, 0, 0, 0, 0, 1, 0,   0, 1, 0, 0, 0, 0, 0, 0, 1, 0 ],
    
    [ 0, 1, 0, 0, 1, 1, 0, 0, 1, 0,   0, 1, 0, 0, 1, 1, 0, 0, 1, 0 ],
    [ 0, 0, 1, 0, 1, 0, 1, 0, 0, 1,   1, 0, 0, 1, 0, 1, 0, 1, 0, 0 ],
    [ 0, 0, 1, 0, 0, 1, 1, 0, 0, 0,   0, 0, 0, 1, 1, 0, 0, 1, 0, 0 ],
    [ 0, 0, 0, 1, 0, 0, 0, 0, 0, 1,   1, 0, 0, 0, 0, 0, 1, 0, 0, 0 ],
    [ 0, 0, 0, 0, 1, 1, 0, 0, 1, 0,   0, 1, 0, 0, 1, 1, 0, 0, 0, 0 ],
    
    [ 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,   0, 0, 1, 1, 0, 0, 0, 0, 0, 0 ],
    [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,   1, 1, 0, 0, 0, 0, 0, 0, 0, 0 ],
    [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
];

$epd->draw_bit_array( 10, 15, $bitarray );

Draw a circle at origin $x $y using radius $r. If $fill is specified, fill the circle

$epd->draw_circle( 15, 15, 10 );

Write a drawing context to the buffer.

Instead of writing to the buffer directly, you can use drawing methods on a HiPi::Graphics::DrawingContext which can then be written to the buffer using draw_context.

You can draw the same context on the buffer many times. You can rotate all the pixels in a context as you write it to the buffer.

my $ctx = $epd->create_context();
$ctx->draw_text(0,0,'Hello World'. 'SansEPD15');

# write it to the buffer at position 10,20 and rotate it 90 degrees.
$epd->draw_context( 10, 20, $ctx->rotated_context( 90, 0, 0) );

# write it to the buffer at position 100,30 and rotate it 180 degrees.
$epd->draw_context( 100, 30, $ctx->rotated_context( 180, 0, 0) );

Draw an ellipse.

  • $x,$y is the origin of the ellipse
  • $rw and $rh are the horizontal ( $rw - width ) and vertical ( $rh - height ) radius to use when drawing the $ellipse. If $rw and $rh are equal, you will draw a circle
  • $fill is optional. If specified ( $fill == 1 ) the ellipse is filled.

$epd->draw_ellipse(20, 15, 10, 7);

Draw a line between $x1,$y1 and $x2,$y2. $endpoint denotes whether the pixel at $x2,$y2 should be written. The default value for $endpoint is 1 ( true ).

$epd->draw_line( 0, 0, 127, 31);

Set a single pixel to 1 (on) or 0 (off).

$x, $y define the pixel to write. $on can be 1 or 0 ( default 1 )

# set pixel at 0,0 to 'on'
$epd->draw_pixel( 0, 0 );
            
# set same pixel to 'off'
$epd->draw_pixel( 0, 0, 0 );

Draw a polygon

  • $vertices is a reference to an array of points representing the vertices of the polygon. If the final point is not equal to the first point, the polygon will be closed anyway.
  • $fill may be optionally set to fill the polygon.
$epd->draw_polygon( [ [41,32],[63,60],[84,32],[78,53],[46,53] ] );
# draw it filled
$epd->draw_polygon( [ [41,32],[63,60],[84,32],[78,53],[46,53] ], 1 );

Draw a rectangle

  • $x1, $y1 is the top left of the rectangle
  • $x2, $y2 is the bottom right of the rectangle
  • $fill can be optionally be specified ( 1 ) to fill the rectangle

$epd->draw_rectangle( 10, 15, 30, 20 );

Draw a rectangle with rounded corners

  • $x1, $y1 is the top left of the rectangle
  • $x2, $y2 is the bottom right of the rectangle
  • $r is the radius to use for the arc that will form the rounded corners. The default value is 4.
  • $fill can be optionally be specified ( 1 ) to fill the rectangle

$epd->draw_rounded_rectangle( 10, 15, 30, 20, 5 );

Draw text to the buffer

  • $x, $y is the origin for drawing the text. The origin is in the top left corner of the text.
  • $text is the text string to be written
  • $font can either be a string naming one of the builtin fonts, or a reference to a font object. The default font is Mono10

The method returns the width and height of the text as written to the buffer

my $y = 0;
my( $w, $h ) = $epd->draw_text(0, $y, 'Hello World', 'Sans12');
$y += $h;
( $w, $h ) = $epd->draw_text(0, $y, 'Raspberry Pi', 'Sans12');
$y += $h;
( $w, $h ) = $epd->draw_text(0, $y, 'HiPi Perl', 'Sans12');

Several builtin fonts are provided.

EPD fonts are included intended for use with E-Paper displays but you may use any of the bitmap font types with both OLED and EPaper displays. THE EPD fonts include all the Latin-1 Supplement characters.

  • SansEPD15, SansEPD19, SansEPD23, SansEPD28, SansEPD31, SansEPD38, SansEPD50, SansEPD76, SansEPD102
  • MonoEPD15, MonoEPD19, MonoEPD23, MonoEPD28, MonoEPD31, MonoEPD38, MonoEPD50, MonoEPD76, MonoEPD102
  • SerifEPD15, SerifEPD19, SerifEPD23, SerifEPD28, SerifEPD31, SerifEPD38, SerifEPD50, SerifEPD76, SerifEPD102

The standard fonts include the printable ascii characters and the 'degree' character ( U+00B0 )

  • Sans10, Sans12, Sans14, Sans15, Sans19, Sans20, Sans26, Sans33
  • Mono10, Mono12, Mono14, Mono15, Mono19, Mono20, Mono26, Mono33
  • Serif10, Serif12, Serif14, Serif15, Serif19, Serif20, Serif26, Serif33

There are also extended fonts that include all the Latin-1 Supplement characters

  • SansExtended11, SansExtended13, SansExtended15, SansExtended17, SansExtended21, SansExtended23, SansExtended30, SansExtended38
  • MonoExtended11, MonoExtended13, MonoExtended15, MonoExtended17, MonoExtended21, MonoExtended23, MonoExtended30, MonoExtended38
  • SerifExtended11, SerifExtended13, SerifExtended15, SerifExtended17, SerifExtended21, SerifExtended23, SerifExtended30, SerifExtended38

The fonts are derived from the Bitstream Vera family.

It is expected that many users will need characters outside the builtin range. HiPi includes tools to create your own bitmapped fonts in the same way the builtins were created.

See: Creating Bitmap Fonts

Get the width and height of the text, without writing to the buffer. This can be helpful to work out where to position the text.

# Write the text to the centre of the OLED
my( $w, $h ) = $epd->get_text_extents( 'Raspberry Pi', 'SansEPD15' );
# centre of text
my $x = int(0.5 + ($epd->logical_width - $w) / 2);
my $y = int(0.5 + ($epd->logical_height - $h) / 2);
$epd->draw_text( $x, $y, 'Raspberry Pi', 'SansEPD15' ); 

On initial use you may check that your constructor and connections are correct by drawing a simple startup screen.

use HiPi qw( :epaper :rpi );
use HiPi::Interface::EPaper;
my $epd = HiPi::Interface::EPaper->new(
    type       => EPD_WS_2_13_250_X_122_A,
);

$epd->draw_logo;
$epd->display_update;

Examples

Rotated Text

Use a drawing context to rotate text or other graphics.

#!/usr/bin/perl
use strict;
use warnings;

use HiPi qw( :epaper :rpi );
use HiPi::Interface::EPaper;
my $epd = HiPi::Interface::EPaper->new(
    type     => EPD_PIMORONI_INKY_PHAT_V2,
    
);
    
$epd->clear_buffer;

my $ctx = $epd->create_context;

my( $w, $h) = $ctx->draw_text(0,0,'Raspberry Pi', 'SansEPD28');

# Draw all raspberrys & perls in centre

# centre of text
my $cx = int( 0.5 + $w / 2);
my $cy = int( 0.5 + $h / 2);

# draw top line centered
{
    my $x = int(0.5 + ($epd->logical_width - $w) / 2);
    my $y = 0;
    $epd->draw_context( $x, $y, $ctx->rotated_context( 0, 0, 0) );
}

# draw bottom line rotated through 180 about its centre
{
    my $x = int(0.5 + ($epd->logical_width - $w) / 2);
    my $y = $epd->logical_height - $h -1;
    $epd->draw_context( $x, $y, $ctx->rotated_context( 180, $cx, $cy) );
}

$ctx->clear_context;

( $w, $h) = $ctx->draw_text(0,0,'Perl', 'SansEPD28');
$cx = int( 0.5 + $w / 2);
$cy = int( 0.5 + $h / 2);

# Perl in red , if available, otherwise will be black
$epd->set_pen( EPD_RED_PEN );
# Perl right
{
    my $x = $epd->logical_width -1;
    my $y = int( 0.5 + ($epd->logical_height - $w) / 2);
    $epd->draw_context( $x, $y, $ctx->rotated_context( 90, 0, 0 ) );
}

# Perl left
{
    my $x = 0;
    my $y = int( 0.5 + ($w + $epd->logical_height) / 2);
    $epd->draw_context( $x, $y, $ctx->rotated_context( -90, 0, 0 ) );
}
$epd->display_update;
$epd->display_sleep;
1;

Drawing Using Bitmap Images

Any bitmap image loaded from file or created in memory can be used to draw to the display.

Use the image manipulation module of your choice then query the indiviual pixel values to decide which to write to the display.

This example uses Image::Imlib2 to load a red / black image to a display that supports red / black colour.

You could use any functions the image library offers, such as text drawing with TrueType fonts, before applying your filter to the image and writing to the display.

#!/usr/bin/perl
use strict;
use warnings;
use HiPi qw( :epaper );

use HiPi::Interface::EPaper;
use Image::Imlib2;

my $epd = HiPi::Interface::EPaper->new(
    type => EPD_PIMORONI_INKY_PHAT_V2,
);

my $backgroundfile = '/resources/pimoroni/InkyPhat-212x104.png';
my $image = Image::Imlib2->load( $backgroundfile );
my $h = $image->height;
my $w = $image->width;

for (my $y = 0; $y < $h; $y ++) {
        
    for ( my $x = 0; $x < $w; $x ++) {
        my $pen = undef;
        my($r,$g,$b,$a) = $image->query_pixel($x, $y);
        # filter for black pixels
        if( $r < 50 && $g < 50 && $b < 50 && $a == 255 ) {
            $epd->set_pen(EPD_BLACK_PEN);
            $epd->draw_pixel($x, $y);
        # filter for red pixels
        } elsif( $r > 200 && $g < 50 && $b < 50 && $a == 255 ) {
            $epd->set_pen(EPD_RED_PEN);
            $epd->draw_pixel($x, $y);
        }
    }
}

$epd->display_update;
$epd->display_sleep;

1;

Partial Updates

The example loads the main image from a file. Partial updates are done whenever the minute changes. A full update is done whenever the hour changes. Display specifications suggest displays are fully updated periodically.

#!/usr/bin/perl
use strict;
use warnings;
use HiPi qw( :epaper );

use HiPi::Interface::EPaper;
use Image::Imlib2;

my $epd = HiPi::Interface::EPaper->new(
    type => EPD_WS_2_90_296_X_128_A,
);

# initialise with background bitmap
# populate both display memory areas
# by updating in full mode & partial mode
update_background();
$epd->set_update_mode(EPD_UPD_MODE_PARTIAL);
$epd->display_update();

my $displayed_min = '';
my $displayed_hour = '';
$epd->display_sleep();

while( 1 ) {
    my ($sec, $min, $hour) = localtime(time);
    if( $hour ne $displayed_hour ) {
        # full update once an hour
        # wake up display
        $epd->display_reset;
        $epd->set_update_mode(EPD_UPD_MODE_FULL);
        update_background();
        $epd->set_update_mode(EPD_UPD_MODE_PARTIAL);
        $epd->display_update();
        # and partial
        my $datestamp = sprintf('%02d:%02d', $hour, $min);
        partial_update($datestamp);
        $displayed_hour = $hour;
        $displayed_min = $min;
        # send display to sleep
        $epd->display_sleep;
    } elsif( $min ne $displayed_min ) {
        # wake up display
        $epd->display_reset;
        $epd->set_update_mode(EPD_UPD_MODE_PARTIAL);
        my $datestamp = sprintf('%02d:%02d', $hour, $min);
        partial_update($datestamp);
        $displayed_min = $min;
        # send display to sleep
        $epd->display_sleep;
    }
    sleep 1;
}

sub partial_update {
    my $timestamp = shift;
    
    # we already know the fixed coordinates
    # of the area we want to partial update
    
    my $fixed_x = 88;
    my $fixed_y = 72;
    my $fixed_w = 120;
    my $fixed_h = 32;
    
    # from all our epd font sizes, select the largest that
    # allows the text to fit in the partial update area
    
    my $timefont;
    my ($tw, $th);
    
    my $ctx = $epd->create_partial_context( $fixed_w, $fixed_h );
    # our dimensions are multiples of 8, but if they were not,
    # the following adjustment would be necessary
    $fixed_w = $ctx->logical_width;
    $fixed_h = $ctx->logical_height;
    
    for my $size ( qw( 102 76 50 38 31 28 23 19 15) ) {
        $timefont = 'MonoEPD' . $size;
        ($tw, $th) = $ctx->get_text_extents( $timestamp, $timefont );
        last if $tw < $fixed_w && $th < $fixed_h; 
    }
    
    # get the text in the centre of the partial area
    my $tx = int( 0.5 + ($fixed_w - $tw ) / 2);
    my $ty = int( 0.5 + ($fixed_h - $th ) / 2);
    
    $ctx->draw_text( $tx, $ty, $timestamp, $timefont);
    $epd->display_partial_update( $fixed_x, $fixed_y, $ctx);
    return;
}


sub update_background {
    $epd->clear_buffer;
    my $backgroundfile = '/resources/hipi/clock296.png';
    my $image = Image::Imlib2->load( $backgroundfile );
    my $h = $image->height;
    my $w = $image->width;
    
    for (my $y = 0; $y < $h; $y ++) {
            
        for ( my $x = 0; $x < $w; $x ++) {
            my($r,$g,$b,$a) = $image->query_pixel($x, $y);
            if( $r < 50 && $g < 50 && $b < 50 && $a == 255 ) {
                $epd->draw_pixel($x, $y);
            }
        }
    }
    
    $epd->display_update;
}

1;