Skip to content

Commit

Permalink
Add 4Gray support (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
adriankeenan authored Jan 10, 2025
1 parent 52ff20d commit 03d490b
Show file tree
Hide file tree
Showing 7 changed files with 42 additions and 19 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
**.pyc

# image storage when running locally
src/img.png
**/img.png
24 changes: 12 additions & 12 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,18 @@ cat image.jpg | curl --form image=@- --form resize=FIT --form background=WHITE h

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` | 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 |
| `dither` | Whether to enable dithering when converting image to mono chromatic. `true` or `1` to enable. | `true` | 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 ~5 seconds.

Display update will be skipped if the resulting image is the same as the last request (see `updated` field in the response).
| 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>`4GRAY` - uses `display_4Gray` in order to display an image in black, white and 2 shades of grey.<br>`PARTIAL` - uses `display_Partial` for incremental updates (which may cause ghosting). | `FAST` | No |
| `dither` | Whether to enable dithering when converting image to mono or 4 gray palette. `true` or `1` to enable. | `true` | 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 ~5 seconds.

Display update will be skipped if the resulting image is the same as the last request (see `updated` field in the response).

### Fetching the current image

Expand Down
Empty file removed src/epd_mock.py
Empty file.
12 changes: 11 additions & 1 deletion src/epd_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,13 @@ def display_clear(epd):


def display_img(epd, image: Image, mode: Mode):
if mode == Mode.PARTIAL:
if mode == Mode.FOUR_GRAY:
epd.init_4GRAY()
epd.display_4Gray(epd.getbuffer_4Gray(image))
elif mode == Mode.PARTIAL:
epd.init()
# display_Partial will leave ghosting from the previous images. Calling a second time clears this almost
# entirely.
for i in range(2):
epd.display_Partial(epd.getbuffer(image))
else:
Expand All @@ -46,3 +51,8 @@ def handle_epd_error(e: Exception) -> tuple[Response, int]:
else:
logging.error(f'Unexpected error occurred - {str(e)}')
return jsonify(message='Unexpected error occurred'), 500

def palette_4gray() -> list[int]:
# @see https://github.com/waveshareteam/e-Paper/blob/ecdd8cf7bab311e6e290c84c68d474deafb7ca8d/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epd4in26.py#L38
# Copied, rather than imported, to allow access without importing epd lib (eg when mocking)
return [0xff, 0xC0, 0x80, 0x00]
20 changes: 16 additions & 4 deletions src/img_utils.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from typing import Tuple
from PIL import Image

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


def resize_img(img: Image, dither: bool, rotation: Rotation, resize: Resize, background: BackgroundColour,
def resize_img(img: Image, mode: Mode, dither: bool, rotation: Rotation, resize: Resize, background: BackgroundColour,
display_res: Resolution) -> Image:
# Rotate
out_img = img.rotate(angle=rotation, expand=True)
Expand All @@ -22,13 +23,24 @@ def resize_img(img: Image, dither: bool, rotation: Rotation, resize: Resize, bac
scaled_image = out_img.resize(scaled_resolution)

dither_setting = Image.Dither.FLOYDSTEINBERG if dither else Image.Dither.NONE
scaled_image = scaled_image.convert('1', dither=dither_setting)

if mode == Mode.FOUR_GRAY:
# Palette is a flat array of each colour as RGB (3 elements), repeated 64 times in series to fill 256 slots
epd_palette = sum([[colour] * 3*64 for colour in palette_4gray()], [])
palette_image = Image.new("P", (16, 16), 0) # image size is arbitrary
palette_image.putpalette(epd_palette)
scaled_image = scaled_image.convert('RGB').quantize(palette=palette_image, dither=dither_setting)
else:
scaled_image = scaled_image.convert('1', dither=dither_setting)

# Output image mode is grayscale for 4gray and mono for everything else
image_mode = 'L' if mode == Mode.FOUR_GRAY else '1'

# Add scaled image to full size canvas
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('1', display_res, bg_colour)
canvas = Image.new(image_mode, display_res, bg_colour)
canvas.paste(scaled_image, (x, y))
return canvas

Expand Down
1 change: 1 addition & 0 deletions src/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ class BackgroundColour(StrEnum):
class Mode(StrEnum):
FAST = 'FAST'
PARTIAL = 'PARTIAL'
FOUR_GRAY = '4GRAY'
2 changes: 1 addition & 1 deletion src/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def show_image() -> tuple[Response, int]:
image_settings['image_resolution'] = [image.width, image.height]
logging.debug(f'Creating an image with the following settings: {json.dumps(image_settings)}')

image_to_display = resize_img(image, dither, rotate, resize, background, DISPLAY_RESOLUTION)
image_to_display = resize_img(image, mode, dither, rotate, resize, background, DISPLAY_RESOLUTION)

try:
update_image = image_changed(Image.open(IMG_PATH), image_to_display)
Expand Down

0 comments on commit 03d490b

Please sign in to comment.