Skip to content

Commit

Permalink
feat: add generate_matrices_list() helper method
Browse files Browse the repository at this point in the history
- add docstrings to ErrorDiffusers and OrderedDitherers dataclass fields
- add `simple-parsing` as a runtime requirement
- update READMEs and TODO
- add keywords in `pyproject.toml`
  • Loading branch information
tfuxu committed Sep 9, 2023
1 parent 2d166d6 commit 194a7ae
Show file tree
Hide file tree
Showing 9 changed files with 251 additions and 36 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- `MatrixUtils` helper class with `generate_matrices_list()` method
- Docstrings for `ErrorDiffusers` and `OrderedDitherers` dataclass fields

## [0.2.1] - 2023-09-04

Nothing has changed, this is just a PyPI release.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ If you want support for a format which isn't currently on that list, submit a fe

- If the palette is grayscale, the input image should be converted to grayscale first to get accurate results.
- All the `[][]uint` matrices are supposed to be applied with `PixelMapperFromMatrix`.
- You can generate a list of available dither matrices with their display names using `MatrixUtils().generate_matrices_list()` method.

## Notes:

Expand Down
1 change: 1 addition & 0 deletions README_PyPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ If you want support for a format which isn't currently on that list, submit a fe

- If the palette is grayscale, the input image should be converted to grayscale first to get accurate results.
- All the `[][]uint` matrices are supposed to be applied with `PixelMapperFromMatrix`.
- You can generate a list of available dither matrices with their display names using `MatrixUtils().generate_matrices_list()` method.

## Notes:

Expand Down
5 changes: 3 additions & 2 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

### Fundamentals

- [ ] Finalize README and make a project description
- [x] Finalize README and make a project description
- [x] Write functions to load and save image files
- [x] Write helper for creating color.RGBA objects, to avoid nil dereferencing
- [x] Write helper for creating palette slice, as Gopy doesn't have abstraction for slice of objects
Expand All @@ -17,7 +17,8 @@
- [ ] ~~Support for image manipulation (ideally support native Python libraries, like PIL)~~ There isn't any sufficient way to transport binary data between Go and Python yet. Library users will simply have to apply additional effects before dithering

### Multi-arch support:
- [ ] Be able to generate wheels for multiple architectures and platforms
- [x] Be able to generate wheels for multiple architectures and platforms
- [ ] Add support for Windows

<details>
<summary>Here's how this will be structured:</summary>
Expand Down
1 change: 1 addition & 0 deletions dither_go/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@

from .wrapper import *
from .matrices import *
from .utils.matrices import *
from .exceptions import DitherGoError, InvalidColorError
226 changes: 193 additions & 33 deletions dither_go/matrices.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,59 +3,219 @@

from dataclasses import dataclass

from dither_go import ErrorDiffusionMatrix, OrderedDitherMatrix
from dither_go.bindings import dither


# TODO: Add docstrings to variables (you can do this by putting docstring under the var declaration)

@dataclass(frozen=True)
class ErrorDiffusers:
class ErrorDiffusers: # pylint: disable=R0902,C0103
"""
Error diffusion matrices
"""

Simple2D = dither.Simple2D()
Simple2D: ErrorDiffusionMatrix = dither.Simple2D()
"""Simple 2D"""

FloydSteinberg: ErrorDiffusionMatrix = dither.FloydSteinberg()
"""Floyd-Steinberg"""

FalseFloydSteinberg: ErrorDiffusionMatrix = dither.FalseFloydSteinberg()
"""False Floyd-Steinberg"""

FloydSteinberg = dither.FloydSteinberg()
FalseFloydSteinberg = dither.FalseFloydSteinberg()
Stucki: ErrorDiffusionMatrix = dither.Stucki()
"""Stucki"""

Stucki = dither.Stucki()
Burkes = dither.Burkes()
Atkinson = dither.Atkinson()
JarvisJudiceNinke = dither.JarvisJudiceNinke()
Burkes: ErrorDiffusionMatrix = dither.Burkes()
"""Burkes"""

Sierra = dither.Sierra()
Sierra2 = dither.Sierra2()
Sierra2_4A = dither.Sierra2_4A()
Sierra3 = dither.Sierra3()
SierraLite = dither.SierraLite()
TwoRowSierra = dither.TwoRowSierra()
Atkinson: ErrorDiffusionMatrix = dither.Atkinson()
"""Atkinson"""

StevenPigeon = dither.StevenPigeon()
JarvisJudiceNinke: ErrorDiffusionMatrix = dither.JarvisJudiceNinke()
"""Jarvis, Judice & Ninke"""

Sierra: ErrorDiffusionMatrix = dither.Sierra()
"""Sierra"""

Sierra2: ErrorDiffusionMatrix = dither.Sierra2()
"""
Sierra2
Sierra2 is another name for Two-Row Sierra.
"""

Sierra2_4A: ErrorDiffusionMatrix = dither.Sierra2_4A()
"""
Sierra2-4A
Sierra2_4A (usually written as Sierra2-4A) is another name for Sierra Lite.
"""

Sierra3: ErrorDiffusionMatrix = dither.Sierra3()
"""
Sierra3
Sierra3 is another name for the original Sierra matrix.
"""

SierraLite: ErrorDiffusionMatrix = dither.SierraLite()
"""Sierra Lite"""

TwoRowSierra: ErrorDiffusionMatrix = dither.TwoRowSierra()
"""Two-Row Sierra"""

StevenPigeon: ErrorDiffusionMatrix = dither.StevenPigeon()
"""Pigeon"""


@dataclass(frozen=True)
class OrderedDitherers:
class OrderedDitherers: # pylint: disable=R0902,C0103
"""
Ordered dither matrices
"""

ClusteredDot4x4 = dither.ClusteredDot4x4()
ClusteredDot6x6 = dither.ClusteredDot6x6()
ClusteredDot6x6_2 = dither.ClusteredDot6x6_2()
ClusteredDot6x6_3 = dither.ClusteredDot6x6_3()
ClusteredDot8x8 = dither.ClusteredDot8x8()
ClusteredDot4x4: OrderedDitherMatrix = dither.ClusteredDot4x4()
"""
Clustered-Dot 4x4
ClusteredDotDiagonal6x6 = dither.ClusteredDotDiagonal6x6()
ClusteredDotDiagonal8x8 = dither.ClusteredDotDiagonal8x8()
ClusteredDotDiagonal8x8_2 = dither.ClusteredDotDiagonal8x8_2()
ClusteredDotDiagonal8x8_3 = dither.ClusteredDotDiagonal8x8_3()
ClusteredDotDiagonal16x16 = dither.ClusteredDotDiagonal16x16()
Comes from http://caca.zoy.org/study/part2.html
It is not diagonal, so the dots form a grid.
"""

Horizontal3x5 = dither.Horizontal3x5()
Vertical5x3 = dither.Vertical5x3()
ClusteredDot6x6: OrderedDitherMatrix = dither.ClusteredDot6x6()
"""
Clustered-Dot 6x6
Clustered-Dot 6x6 comes from Figure 5.9 of the book "Digital Halftoning" by
Robert Ulichney. It can represent "37 levels of gray". It is not diagonal.
"""

ClusteredDot6x6_2: OrderedDitherMatrix = dither.ClusteredDot6x6_2()
"""
Clustered-Dot 6x6-2
Clustered-Dot 6x6-2 comes from https://archive.is/71e9G. On the webpage it is
called "central white point" while Clustered-Dot 6x6 is called "clustered dots".
It is nearly identical to Clustered-Dot 6x6.
"""

ClusteredDot6x6_3: OrderedDitherMatrix = dither.ClusteredDot6x6_3()
"""
Clustered-Dot 6x6-3
ClusteredDotSpiral5x5 = dither.ClusteredDotSpiral5x5()
Clustered-Dot 6x6-3 comes from https://archive.is/71e9G. On the webpage it is
called "balanced centered point".
It is nearly identical to Clustered-Dot 6x6.
"""

ClusteredDot8x8: OrderedDitherMatrix = dither.ClusteredDot8x8()
"""
Clustered-Dot 8x8
Clustered-Dot 8x8 comes from Figure 1.5 of the book "Modern Digital Halftoning, Second Edition",
by Daniel L. Lau and Gonzalo R. Arce.
It is like Clustered-Dot Diagonal 8x8, but is not diagonal. It can represent "65 gray-levels".
"""

ClusteredDotDiagonal6x6: OrderedDitherMatrix = dither.ClusteredDotDiagonal6x6()
"""
Clustered-Dot Diagonal 6x6
Clustered-Dot Diagonal 6x6 comes from Figure 5.4 of the book "Digital Halftoning" by
Robert Ulichney.
In the book it's called ``M = 3``. It can represent "19 levels of gray".
Its dimensions are 6x6, but as a diagonal matrix it is 7x7. It is called
"Diagonal" because the resulting dot pattern is at a 45 degree angle.
"""

ClusteredDotHorizontalLine = dither.ClusteredDotHorizontalLine()
ClusteredDotVerticalLine = dither.ClusteredDotVerticalLine()
ClusteredDotDiagonal8x8: OrderedDitherMatrix = dither.ClusteredDotDiagonal8x8()
"""
Clustered-Dot Diagonal 8x8
Comes from http://caca.zoy.org/study/part2.html
They say it "mimics the halftoning techniques used by newspapers". It is called
"Diagonal" because the resulting dot pattern is at a 45 degree angle.
"""

ClusteredDotDiagonal8x8_2: OrderedDitherMatrix = dither.ClusteredDotDiagonal8x8_2()
"""
Clustered-Dot Diagonal 8x8-2
Clustered-Dot Diagonal 8x8-2 comes from Figure 5.4 of the book "Digital Halftoning" by
Robert Ulichney.
In the book it's called ``M = 4``. It can represent "33 levels of gray".
Its dimensions are 8x8, but as a diagonal matrix it is 9x9. It is called
"Diagonal" because the resulting dot pattern is at a 45 degree angle.
It is almost identical to Clustered-Dot Diagonal 8x8, but it's worse because it can
represent fewer gray levels. There is not much point in using it.
"""

ClusteredDotDiagonal8x8_3: OrderedDitherMatrix = dither.ClusteredDotDiagonal8x8_3()
"""
Clustered-Dot Diagonal 8x8-3
Clustered-Dot Diagonal 8x8-3 comes from https://archive.is/71e9G. On the webpage
it is called "diagonal ordered matrix with balanced centered points".
It is almost identical to Clustered-Dot Diagonal 8x8, but worse because it can
represent fewer gray levels. There is not much point in using it.
It is called "Diagonal" because the resulting dot pattern is at a 45 degree angle.
"""

ClusteredDotDiagonal16x16: OrderedDitherMatrix = dither.ClusteredDotDiagonal16x16()
"""
Clustered-Dot Diagonal 16x16
Clustered-Dot Diagonal 16x16 comes from Figure 5.4 of the book "Digital Halftoning" by
Robert Ulichney.
In the book it's called ``M = 8``. It can represent "129 levels of gray".
Its dimensions are 16x16, but as a diagonal matrix it is 17x17. It is called
"Diagonal" because the resulting dot pattern is at a 45 degree angle.
"""

Horizontal3x5: OrderedDitherMatrix = dither.Horizontal3x5()
"""
Horizontal 3x5
Horizontal 3x5 is a custom rotated version of Vertical 5x3.
"""

Vertical5x3: OrderedDitherMatrix = dither.Vertical5x3()
"""
Vertical 5x3
Comes from http://caca.zoy.org/study/part2.html
They say it "creates artistic vertical line artifacts".
"""

ClusteredDotSpiral5x5: OrderedDitherMatrix = dither.ClusteredDotSpiral5x5()
"""
Clustered-Dot Spiral 5x5
Clustered-Dot Spiral 5x5 comes from Figure 5.13 of the book "Digital Halftoning" by
Robert Ulichney. It can represent "26 levels of gray". Its dimensions are 5x5.
Instead of alternating dark and light dots like the other clustered-dot
matrices, the dark parts grow to fill the area.
"""

ClusteredDotHorizontalLine: OrderedDitherMatrix = dither.ClusteredDotHorizontalLine()
"""
Clustered-Dot Horizontal Line
Clustered-Dot Horizontal Line comes from Figure 5.13 of the book "Digital Halftoning" by
Robert Ulichney. It can represent "37 levels of gray". Its dimensions are 6x6.
It "clusters pixels about horizontal lines".
"""

ClusteredDotVerticalLine: OrderedDitherMatrix = dither.ClusteredDotVerticalLine()
"""
Clustered-Dot Vertical Line
Clustered-Dot Vertical Line is a custom rotated version of Clustered-Dot Horizontal Line.
"""
46 changes: 46 additions & 0 deletions dither_go/utils/matrices.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Copyright 2023, tfuxu <https://github.com/tfuxu>
# SPDX-License-Identifier: GPL-3.0-or-later

from dataclasses import asdict
from typing import List, Tuple

from simple_parsing.docstring import get_attribute_docstring

from dither_go import ErrorDiffusionMatrix, OrderedDitherMatrix
from dither_go.matrices import ErrorDiffusers, OrderedDitherers


class MatrixUtils: # pylint: disable=R0903
"""
A class for error diffusion and ordered dithering matrices utility methods.
"""

def __init__(self):
pass

def generate_matrices_list(self) -> Tuple[List[Tuple[str, ErrorDiffusionMatrix]], List[Tuple[str, OrderedDitherMatrix]]]:
"""
Generates a list of available matrices in library with human-readable names.
:returns: Two lists consisting of error diffusion and ordered dithering matrices with their display names.
:rtype: Tuple[List[Tuple[str, ErrorDiffusionMatrix]], List[Tuple[str, OrderedDitherMatrix]]]
"""

error_diffusers = asdict(ErrorDiffusers())
ordered_ditherers = asdict(OrderedDitherers())

error_matrices = []
for key in error_diffusers:
field_docstring = get_attribute_docstring(ErrorDiffusers, key).docstring_below
display_name = field_docstring.strip().partition("\n")[0]

error_matrices.append((display_name, error_diffusers[key]))

ordered_matrices = []
for key in ordered_ditherers:
field_docstring = get_attribute_docstring(OrderedDitherers, key).docstring_below
display_name = field_docstring.strip().partition("\n")[0]

ordered_matrices.append((display_name, ordered_ditherers[key]))

return error_matrices, ordered_matrices
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ name = "dither-go"
version = "0.2.1"
authors = [{name = "tfuxu"}]
description = "A fast and the most correct image dithering library"
keywords = ["golang", "bindings", "dithering", "processing"]
readme = "README_PyPI.md"
requires-python = ">=3.8"
classifiers = [
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
pybindgen
simple-parsing

0 comments on commit 194a7ae

Please sign in to comment.