robweber / omni-epd

An EPD (electronic paper display) class abstraction to simplify communications across multiple display types.
GNU General Public License v3.0
83 stars 19 forks source link

Replace hitherdither with didder #72

Closed missionfloyd closed 2 years ago

missionfloyd commented 2 years ago

As the title implies, this replaces hitherdither with didder, which runs faster on a pi 1 than hitherdither does on a pi4.

Supported dithering algorithms: Ordered Error-Diffusion
ClusteredDot4x4 Simple2D
ClusteredDotDiagonal8x8 FloydSteinberg
Vertical5x3 FalseFloydSteinberg
Horizontal3x5 JarvisJudiceNinke
ClusteredDotDiagonal6x6 Atkinson
ClusteredDotDiagonal8x8_2 Stucki
ClusteredDotDiagonal16x16 Burkes
ClusteredDot6x6 Sierra
ClusteredDotSpiral5x5 TwoRowSierra
ClusteredDotHorizontalLine SierraLite
ClusteredDotVerticalLine StevenPigeon
ClusteredDot8x8
ClusteredDot6x6_2
ClusteredDot6x6_3
ClusteredDotDiagonal8x8_3

It also supports random noise, bayer. and custom matrices.

Additional Options (descriptions from https://github.com/makeworld-the-better-one/didder/blob/main/MANPAGE.md):

dither_strength Set the strength of dithering. This will affect every command except random. Decimal format is -1.0 to 1.0, and percentage format is -100% or 100%. The range is not limited. A zero value will be ignored. Defaults to 100%, meaning that the dithering is applied at full strength.

dither_serpentine Enable serpentine dithering, which "snakes" back and forth when moving down the image, instead of going left-to-right each time. Default: False

dither_args For dither=bayer: Requires two arguments, for the X and Y dimension of the matrix. They can be separated by a space, comma, or x. Both arguments must be a power of two, with the exception of: 3x5, 5x3, and 3x3. Default: 4x4

For dither=random: Accepts two arguments (min and max) for RGB or grayscale, or six (min/max for each channel) to control each RGB channel. Arguments can be separated by commas or spaces. Default: -0.5,0.5

For dither=customordered: JSON of a custom matrix, or a path to JSON file for your custom matrix.

The JSON format (whether inline or in a file) looks like the below. The matrix must be "rectangular", meaning each array must have the same length. More information how to use a custom matrix can be found here: https://pkg.go.dev/github.com/makeworld-the-better-one/dither/v2#OrderedDitherMatrix

{
  "matrix": [
    [12, 5, 6, 13],
    [4, 0, 1, 7],
    [11, 3, 2, 8],
    [15, 10, 9, 14]
  ],
  "max": 16
}

For dither=customdiffusion: JSON of a custom matrix, or a path to JSON file for your custom matrix. Zero values can be used to represent pixels that have already been processed. The current pixel is assumed to be the right-most zero value in the top row. Example:

[
  [0, 0, 0.4375],
  [0.1875, 0.3125, 0.0625]
]

These can also go in omni-epd.ini like this:

dither_args={"matrix": [[12, 5, 6, 13], [4, 0, 1, 7], [11, 3, 2, 8], [15, 10, 9, 14]], "max": 16}

or like this:

dither_args={
              "matrix": [
                [12, 5, 6, 13],
                [4, 0, 1, 7],
                [11, 3, 2, 8],
                [15, 10, 9, 14]
              ],
              "max": 16
            }

One remaining issue is how to handle the didder requirement. It's a commandline program written in go, and isn't in apt. There are binaries here. The linux_arm* builds work on the various raspberry pis. I've been putting it in ~/.local/bin

As such, this isn't quite ready. Or maybe there's some other tool that would work better.

robweber commented 2 years ago

This is super interesting. I agree that the distribution and install are probably the biggest hurdles but the overall usefulness is way better than hitherdither. Using pure Python to do this kind of stuff is definitely a performance hit.

I haven't done too much digging but it is possible to bundle library files within a Python wheel right? Generally I think it's done with C files but maybe that is an option?

missionfloyd commented 2 years ago

I worked out how to include it in the package. I've included the armv6 build for now, it should work on all raspberry pi models.

It is a bit slower than the arm64 build, though. Testing with floyd-steinberg at 800x480 with 3 colors got me a difference of 0.6 seconds on a pi 4, and 1.6 seconds on a pi zero 2 (3 vs 3.6 seconds / 7 vs 8.6 seconds).

robweber commented 2 years ago

I'm excited to try this out. Glad you could get it bundled with the library without extra work.

I did notice that didder is GPLv3. Since we're bundling it I think that means we need to, at the least, disclose the source somewhere for others. Maybe a reference in the README somewhere? Something like Acknowledgements towards the end with a link to the project?

missionfloyd commented 2 years ago

I wasn't sure how to word it. Feel free to change it.

robweber commented 2 years ago

I played around with a bunch of these and they all are working great. Much faster as well. I like how you did this in a memory buffer since it does require running that separate didder executable. I couldn't notice any real difference when applying the dither vs not using it at all on my screen.

I also couldn't get it to crash out so that's good. No edge case I could think of broke it. I think after merging to take your top examples with the explanations and adding them to to the Wiki instead of the README directly? I think having full page with the dithering explanations is better than jamming it all in there. For those interested in really playing with it they can dive in. Just using the defaults resulted in some cool images.

missionfloyd commented 2 years ago

It is a bit much to put in the readme.

robweber commented 2 years ago

I added a wiki paging using a lot of what you had written above. Hopefully it's easy to follow. If anything is blatantly wrong please correct. I'll update the README to point to this as well.

https://github.com/robweber/omni-epd/wiki/Image-Dithering-Options