Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add grayscale support for B/W displays #166

Open
ThomasR opened this issue Feb 1, 2025 · 5 comments
Open

Add grayscale support for B/W displays #166

ThomasR opened this issue Feb 1, 2025 · 5 comments
Labels
enhancement New feature or request

Comments

@ThomasR
Copy link

ThomasR commented Feb 1, 2025

Is your feature request related to a problem? Please describe.
I have a 2.7 inch Waveshare HAT with a resolution of 264x176. On such a small screen, fonts are mostly hard to read without antialiasing.

Example with PaperPi: https://i0.wp.com/dronebotworkshop.com/wp-content/uploads/2022/02/paperpi-weather.png

Describe the solution you'd like
The display supports 4 shades of gray, as can be seen in https://github.com/waveshareteam/e-Paper/blob/master/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epd2in7.py#L436.

This also seems to apply to other models.

To use it, one must create a 4 color image with palette #000000, #808080, #c0c0c0, #ffffff, and call init_4Gray, getBuffer_4Gray and display_4Gray instead of the methods without the _4Gray suffix.

Describe alternatives you've considered
More dithering in black & white doesn't help.

Additional context

For comparison, I converted the same input image to B/W and 4 colors, and the difference is significant:
 

Input image here

@ThomasR ThomasR added the enhancement New feature or request label Feb 1, 2025
@ThomasR ThomasR changed the title Add grayscale support for B/W HATs Add grayscale support for B/W displays Feb 1, 2025
@txoof
Copy link
Owner

txoof commented Feb 1, 2025

This is something I've wanted to do for a while, but will take some overhaul of the underlying epdlib that manages all of the display interface. Once that's done, it would be relatively easy to add into PaperPi. Right now there are three classes of devices that are supported by epdlib: HD, 1 Bit and 7 Color. The HD screens all support grayscale, but the oddballs like the 2in7 aren't supported (yet).

The basic problems is that the 2in7 and others have oddball methods that require a lot of extra code to manage for writing in grayscale and also for managing the other color layers. The Screen.writeEPD() method would need to be adjusted to handle additional write functions loaded from the waveshare drivers.

Unfortunately I'm pretty swamped with other commitments right now and don't have access to my 2in7 display for testing. If you're interested in tackling the epdlib side of things, I can offer some support and guidance, but I can't do much right now.

PRs are always welcome!

@ThomasR
Copy link
Author

ThomasR commented Feb 1, 2025

I'll see what I can do. My python skills are very basic so I can't promise anything.

@txoof Can you please confirm the following?

  1. There is nothing to do for HD displays
  2. HD displays are exactly the ones without any Waveshare drivers (as per this code)?

@txoof
Copy link
Owner

txoof commented Feb 1, 2025

You're on the right track. The HD displays are managed with another, non-waveshare driver altogether.

The _load_non_hd method is where all the various types of waveshare drivers are setup. Upon further investigation on lines 514..520, I see that I was starting to implement this. The display mode for your type of screen is inferred to support grayscale because the display_args_spec has > 2 parameters. This typically implies that it's a grayscale screen.

To test if this works, create an epdlib.Screen instance and set it up with the appropriate 2in7 driver and then push a grayscale image to it and see what happens. If that works, then the problem is in PaperPi not recognizing that the display is grayscale and falling back to 1 bit images.

If you need some hints on setting up a screen object and pushing images to it, look at the main() function at the bottom. It has a fairly complete implementation of all the steps.

Looking at the blame, it looks like it's been at least 4 years since I thought about this, so I'm just going by comments and the logic there and that might be...wrong.

Let me know what you get to and I'll try to give some more pointers.

@ThomasR
Copy link
Author

ThomasR commented Feb 1, 2025

Looking at the Waveshare catalog, these are the device classes:

  1. black/white only
  2. black/white with 4 gray levels
  3. black/white with 16 gray levels (HD only)
  4. black/white/red
  5. black/white/yellow
  6. black/white/red/yellow
  7. 7 color
  8. full color (HD only)

The display mode for your type of screen is inferred to support grayscale because the display_args_spec has > 2 parameters. This typically implies that it's a grayscale screen.

Not quite. This indicates a screen from class 4/5/6.
The class 2 models have 2 parameters in the display method, and come with an additional display_4Gray method, also with 2 parameters.

Should be pretty straight-forward to implement for that case. Let me look into it.

edit

What I get from the driver code:

Class Waveshare driver methods
1. black/white only display(bw_image)
2. black/white with 4 gray levels display(bw_image), or, at your choice,
display_4Gray(gray4_image) (Colors need to be reduced explicitly)
3. black/white with 16 gray levels -
4. black/white/red display(bw_image, red_image)
5. black/white/yellow display(bw_image, yellow_image)
6. black/white/red/yellow display(bwry_image) (Colors will be reduced in getbuffer)
7. 7-color display(color7_image) (Colors will be reduced in getbuffer)
8. full Color -

@ThomasR
Copy link
Author

ThomasR commented Feb 2, 2025

There are some adustments needed in PaperPi

PaperPi/paperpi/paperpi.py

Lines 682 to 691 in 224a7cb

# force to one-bit mode for non HD and non-color screens
# screens that have mode 1 are always one bit
if screen.mode == '1':
one_bit = True
# if color is true or the display is HD: one_bit == False
elif config['main'].get('color', False) or config['main'].get('display_type', 'none').lower() == 'hd':
one_bit = False
else:
# fall back to one bit
one_bit = True

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants