Skip to content

Commit

Permalink
revert 4GRAY
Browse files Browse the repository at this point in the history
  • Loading branch information
adriankeenan committed Dec 29, 2024
1 parent 4b4d70c commit ace5747
Show file tree
Hide file tree
Showing 5 changed files with 27 additions and 37 deletions.
16 changes: 8 additions & 8 deletions readme.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# rpi-epaper-api

A rest API for setting the display image on a [Waveshare 4.26](https://www.waveshare.com/4.26inch-e-paper-hat.htm)
A rest API for setting the display image on a [Waveshare 4.26"](https://www.waveshare.com/4.26inch-e-paper-hat.htm)
eink HAT connected to a Raspberry Pi. Supports scaling and rotating the input image. Endpoints are also provided for
fetching display contents and clearing the display.

Expand Down Expand Up @@ -37,13 +37,13 @@ curl --form [email protected] --form resize=FIT --form background=WHITE http://r

Data should be sent form-encoded.

| Field | Description | Default | Required |
|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|----------|
| `image` | Image file to display. Any image type supported by the Python PIL library should work. | N/A | Yes |
| `mode` | Colour mode used to display the image on the e-ink display.<br>`MONO` - each pixel is either on or off. Uses dithering.<br>`4GRAY` - each pixel is either on, dark gray, light gray or off. | MONO | No |
| `resize` | `FIT` Resize keeping aspect ratio, without cropping<br>`CROP` Resize keeping aspect ratio, with cropping<br>`STRETCH` fill display, ignoring aspect ratio<br>`NONE` Display without any scaling, pixels drawn 1:1. | `FIT` | No |
| `rotate` | Number of degrees to rotate counter-clockwise, supports increments of 90 degrees. | 0 | No |
| `background` | Background colour to use if the image doesn't fill the display. Either `WHITE` or `BLACK`. | `WHITE` | No |
| Field | Description | Default | Required |
|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|----------|
| `image` | Image file to display. Any image type supported by the Python PIL library should work. | N/A | Yes |
| `mode` | Display mode, corresponding to the `display_XXX` method on the `epd` object.<br>`FAST` - uses `display_Fast` for a complete screen refresh.<br>`PARTIAL` - uses `display_Partial` for incremental updates (which may cause ghosting). | `FAST` | No |
| `resize` | `FIT` Resize keeping aspect ratio, without cropping<br>`CROP` Resize keeping aspect ratio, with cropping<br>`STRETCH` fill display, ignoring aspect ratio<br>`NONE` Display without any scaling, pixels drawn 1:1. | `FIT` | No |
| `rotate` | Number of degrees to rotate counter-clockwise, supports increments of 90 degrees. | 0 | No |
| `background` | Background colour to use if the image doesn't fill the display. Either `WHITE` or `BLACK`. | `WHITE` | No |

Expect this call to take ~4-11 seconds.

Expand Down
21 changes: 12 additions & 9 deletions src/epd_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,24 @@
from filelock import Timeout as FileLockTimeout, FileLock

from models import Mode
from sys import path

path.append('lib')
# noinspection PyUnresolvedReferences
from waveshare_epd import epd4in26


def get_epd():
return epd4in26.EPD()


def display_clear(epd):
epd.init()
epd.Clear()
epd.sleep()


def display_img(epd, image: Image, mode: Mode):
if mode == Mode.FOUR_GRAY:
epd.init_4GRAY()
epd.display_4Gray(epd.getbuffer_4Gray(image))
if mode == Mode.PARTIAL:
epd.init()
for i in range(2):
Expand All @@ -25,22 +32,18 @@ def display_img(epd, image: Image, mode: Mode):
epd.display_Fast(epd.getbuffer(image))
epd.sleep()


def handle_epd_error(e: Exception) -> tuple[Response, int]:
if isinstance(e, IOError):
logging.error(f'IOError on display write: {str(e)}')
return jsonify(message='Unexpected error'), 500
elif isinstance(e, KeyboardInterrupt):
logging.info('Display write interrupted due to KeyboardInterrupt')
#epd4in26.epdconfig.module_exit(cleanup=True)
epd4in26.epdconfig.module_exit(cleanup=True)
return jsonify(message='Cancelled'), 500
elif isinstance(e, FileLockTimeout):
logging.info('Could not obtain device lock')
return jsonify(message='Device busy, please wait for current request to complete'), 409
else:
logging.error(f'Unexpected error occurred - {str(e)}')
return jsonify(message='Unexpected error occurred'), 500

def get_display_colour_palette() -> list[int]:
# https://github.com/waveshareteam/e-Paper/blob/ecdd8cf7bab311e6e290c84c68d474deafb7ca8d/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epd4in26.py#L38
colours = [0xff, 0xC0, 0x80, 0x00]
return sum([[colour] * 3 for colour in colours], [])
13 changes: 3 additions & 10 deletions src/img_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from PIL import Image

from models import Resolution, Rotation, Resize, BackgroundColour, Mode
from epd_utils import get_display_colour_palette


def resize_img(img: Image, mode: Mode, rotation: Rotation, resize: Resize, background: BackgroundColour,
Expand All @@ -23,18 +22,11 @@ def resize_img(img: Image, mode: Mode, rotation: Rotation, resize: Resize, backg
scaled_image = out_img.resize(scaled_resolution)

# Add scaled image to full size canvas
img_mode = 'RGB' if mode == Mode.FOUR_GRAY else '1'
bg_colour = 255 if background == BackgroundColour.WHITE else 0
x = int((display_res.width - scaled_image.width) / 2)
y = int((display_res.height - scaled_image.height) / 2)
canvas = Image.new(img_mode, display_res, bg_colour)
canvas = Image.new('1', display_res, bg_colour)
canvas.paste(scaled_image, (x, y))
if mode == Mode.FOUR_GRAY:
palette_img = Image.new('P', (16, 16))
palette_img.putpalette(get_display_colour_palette() * 32)
palette_img.load()
canvas = canvas.quantize(palette=palette_img)

return canvas


Expand All @@ -44,5 +36,6 @@ def get_resize_scale(img: Image, crop: bool, display_res: Resolution) -> Tuple[i
scale = max(width_scale, height_scale) if crop else min(width_scale, height_scale)
return int(img.width * scale), int(img.height * scale)


def image_changed(existing_image: Image, new_image: Image) -> bool:
return list(new_image.getdata()) != list(existing_image.getdata())
return list(new_image.getdata()) != list(existing_image.getdata())
3 changes: 1 addition & 2 deletions src/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,5 @@ class BackgroundColour(StrEnum):


class Mode(StrEnum):
MONO = 'MONO'
FOUR_GRAY = '4GRAY'
FAST = 'FAST'
PARTIAL = 'PARTIAL'
11 changes: 3 additions & 8 deletions src/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,14 @@
from models import Resolution, Rotation, Resize, BackgroundColour, Mode

from img_utils import resize_img, image_changed
from epd_utils import handle_epd_error, display_clear, display_img

from sys import path
path.append('lib')
# noinspection PyUnresolvedReferences
from waveshare_epd import epd4in26
from epd_utils import handle_epd_error, display_clear, display_img, get_epd

IMG_PATH = 'img.png'
DISPLAY_RESOLUTION = Resolution(800, 480)

logging.basicConfig(level=logging.DEBUG)

epd = epd4in26.EPD()
epd = get_epd()

app = Flask(__name__)

Expand Down Expand Up @@ -65,7 +60,7 @@ def show_image() -> tuple[Response, int]:
return jsonify(message=f'"background" invalid, must be one of {", ".join([x for x in BackgroundColour])}'), 422

try:
mode = Mode(request.form.get('mode', Mode.MONO.value))
mode = Mode(request.form.get('mode', Mode.FAST.value))
except ValueError:
return jsonify(message=f'"mode" invalid, must be one of {", ".join([x for x in Mode])}'), 422

Expand Down

0 comments on commit ace5747

Please sign in to comment.