Full Code of hbldh/hitherdither for AI

master 0f3bbc44595a cached
30 files
52.9 KB
16.3k tokens
58 symbols
1 requests
Download .txt
Repository: hbldh/hitherdither
Branch: master
Commit: 0f3bbc44595a
Files: 30
Total size: 52.9 KB

Directory structure:
gitextract_8y6y_8sa/

├── .coveragerc
├── .github/
│   ├── ISSUE_TEMPLATE.md
│   └── workflows/
│       ├── build_and_test.yml
│       └── pypi-publish.yml
├── .gitignore
├── LICENSE
├── MANIFEST.in
├── Pipfile
├── README.rst
├── hitherdither/
│   ├── __init__.py
│   ├── __version__.py
│   ├── data/
│   │   └── __init__.py
│   ├── diffusion.py
│   ├── exceptions.py
│   ├── math/
│   │   └── __init__.py
│   ├── ordered/
│   │   ├── __init__.py
│   │   ├── bayer.py
│   │   ├── cluster.py
│   │   └── yliluoma/
│   │       ├── __init__.py
│   │       ├── _algorithm_one.py
│   │       └── _utils.py
│   ├── palette.py
│   └── utils.py
├── requirements.txt
├── run.py
├── setup.py
└── tests/
    ├── __init__.py
    ├── conftest.py
    ├── test_bayer.py
    └── test_palette.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .coveragerc
================================================
[run]
branch = True
source = hitherdither
include = */hitherdither/*
omit =
    */setup.py

[report]
exclude_lines =
    except NameError
    except ImportError





================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
* bleak version:
* Python version:
* Operating System:
* BlueZ version (`bluetoothctl -v`) in case of Linux: 

### Description

Describe what you were trying to get done.
Tell us what happened, what went wrong, and what you expected to happen.

### What I Did

```
Paste the command(s) you ran and the output.
If there was a crash, please include the traceback here.
```


================================================
FILE: .github/workflows/build_and_test.yml
================================================
name: Build and Test

on:
    push:
        branches: [ master, develop ]
    pull_request:
        branches: [ master, develop ]

jobs:
    build_linux:
        name: "Build and test"
        runs-on: ${{ matrix.os }}
        strategy:
            matrix:
                os: [ubuntu-latest]
                python-version: [3.7, 3.8, 3.9, '3.10', '3.11']
        steps:
            -   uses: actions/checkout@v4
            -   name: Set up Python ${{ matrix.python-version }}
                uses: actions/setup-python@v4
                with:
                    python-version: ${{ matrix.python-version }}
            -   name: Upgrade pip. setuptools and wheel
                run: python -m pip install --upgrade pip setuptools wheel

            -   name: Install development dependencies
                run: pip install flake8 pytest pytest-cov
            -   name: Lint with flake8
                run: |
                    # stop the build if there are Python syntax errors or undefined names
                    flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
                    # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
                    flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
            -   name: Install package as editable
                run: pip install -e .
            -   name: Test with pytest
                run: |
                    pytest tests --junitxml=junit/test-results-${{ matrix.os }}-${{ matrix.python-version }}.xml --cov=com --cov-report=xml --cov-report=html
            -   name: Upload pytest test results
                uses: actions/upload-artifact@v3
                with:
                    name: pytest-results-${{ matrix.os }}-${{ matrix.python-version }}
                    path: junit/test-results-${{ matrix.os }}-${{ matrix.python-version }}.xml
                # Use always() to always run this step to publish test results when there are test failures
                if: ${{ always() }}


================================================
FILE: .github/workflows/pypi-publish.yml
================================================
# This workflows will upload a Python Package using Twine when a release is created
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries

name: Upload Python Package

on:
  release:
    types: [created]

jobs:
  deploy:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Set up Python
      uses: actions/setup-python@v2
      with:
        python-version: '3.x'
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install setuptools wheel twine
    - name: Build and publish
      env:
        TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
        TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
      run: |
        python setup.py sdist bdist_wheel
        twine upload dist/*


================================================
FILE: .gitignore
================================================
hitherdither/data/*.png
tests/astronaut.png
tests/rocket.jpg

# Created by .ignore support plugin (hsz.mobi)
### VisualStudio template
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.

# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates

# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs

# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/

# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/

# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*

# NUNIT
*.VisualState.xml
TestResult.xml

# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c

# DNX
project.lock.json
project.fragment.lock.json
artifacts/

*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc

# Chutzpah Test files
_Chutzpah*

# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb

# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap

# TFS 2012 Local Workspace
$tf/

# Guidance Automation Toolkit
*.gpState

# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user

# JustCode is a .NET coding add-in
.JustCode

# TeamCity is a build add-in
_TeamCity*

# DotCover is a Code Coverage Tool
*.dotCover

# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*

# MightyMoose
*.mm.*
AutoTest.Net/

# Web workbench (sass)
.sass-cache/

# Installshield output folder
[Ee]xpress/

# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html

# Click-Once directory
publish/

# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj

# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/

# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets

# Microsoft Azure Build Output
csx/
*.build.csdef

# Microsoft Azure Emulator
ecf/
rcf/

# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt

# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/

# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs

# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/

# RIA/Silverlight projects
Generated_Code/

# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm

# SQL Server files
*.mdf
*.ldf

# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings

# Microsoft Fakes
FakesAssemblies/

# GhostDoc plugin setting file
*.GhostDoc.xml

# Node.js Tools for Visual Studio
.ntvs_analysis.dat

# Visual Studio 6 build log
*.plg

# Visual Studio 6 workspace options file
*.opt

# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions

# Paket dependency manager
.paket/paket.exe
paket-files/

# FAKE - F# Make
.fake/

# JetBrains Rider
.idea/
*.sln.iml
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/

# Translations
*.mo
*.pot

# Django stuff:
local_settings.py

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# IPython Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# dotenv
.env

# virtualenv
venv/
ENV/

# Spyder project settings
.spyderproject

# Rope project settings
.ropeproject
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839

# User-specific stuff:
.idea/workspace.xml
.idea/tasks.xml
.idea/dictionaries
.idea/vcs.xml
.idea/jsLibraryMappings.xml

# Sensitive or high-churn files:
.idea/dataSources.ids
.idea/dataSources.xml
.idea/dataSources.local.xml
.idea/sqlDataSources.xml
.idea/dynamic.xml
.idea/uiDesigner.xml

# Gradle:
.idea/gradle.xml
.idea/libraries

# Mongo Explorer plugin:
.idea/mongoSettings.xml

## File-based project format:
*.iws

## Plugin-specific files:

# IntelliJ
/out/

# mpeltonen/sbt-idea plugin
.idea_modules/

# JIRA plugin
atlassian-ide-plugin.xml

# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
hitherdither/data/scenenodither.png


================================================
FILE: LICENSE
================================================
The MIT License

Copyright (c) 2020 Henrik Blidh

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.



================================================
FILE: MANIFEST.in
================================================
include LICENSE README.rst


================================================
FILE: Pipfile
================================================
[[source]]

url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"


[packages]

Pillow = ">=3.3.1"
numpy = ">=1.9.0"


[dev-packages]

pytest = "*"
twine = "*"


[requires]

python_version = "3.6"

================================================
FILE: README.rst
================================================
hitherdither
============

|Build Status| |Coverage Status|

A package inspired by [1]_, implementing dithering algorithms that can be used with 
`PIL/Pillow <https://pillow.readthedocs.io/en/3.3.x/>`_. 

Description
-----------

This module is a small extension to `PIL/Pillow <https://pillow.readthedocs.io/en/3.3.x/>`_, adding
a more managable palette object and several dithering algorithms:

* Error diffusion dithering
    - Floyd-Steinberg
    - Jarvis-Judice-Ninke
    - Stucki
    - Burkes
    - Sierra3 
    - Sierra2
    - Sierra-2-4A
    - Stevenson-Arce
    - Atkinson
* Standard ordered dithering
    - Bayer matrix
    - Cluster dot matrix
    - Arbitrary square threshold matrix (not implemented yet)
* Yliluoma's ordered dithering (see [1]_)
    - Algorithm 1 
    - Algorithm 2 (not implemented yet)
    - Algorithm 3 (not implemented yet)

The dithering algorithms are applicable for arbitrary palettes and for both
RGB and greyscale images.

Installation
------------

::

    pip install git+https://www.github.com/hbldh/hitherdither

Usage
-----

Bayer dithering using a median cut palette:

.. code:: python

   from PIL import Image
   import hitherdither

   img = Image.open('image.jpg')
   palette = hitherdither.palette.Palette.create_by_median_cut(img)
   img_dithered = hitherdither.ordered.bayer.bayer_dithering(
       img, palette, [256/4, 256/4, 256/4], order=8)

`Yliluoma's Algorithm 1 <http://bisqwit.iki.fi/story/howto/dither/jy/#YliluomaSOrderedDitheringAlgorithm 1>`_
using a predefined palette:

.. code:: python

   from PIL import Image
   import hitherdither

   palette = hitherdither.palette.Palette(
       [0x080000, 0x201A0B, 0x432817, 0x492910,
        0x234309, 0x5D4F1E, 0x9C6B20, 0xA9220F,
        0x2B347C, 0x2B7409, 0xD0CA40, 0xE8A077,
        0x6A94AB, 0xD5C4B3, 0xFCE76E, 0xFCFAE2]
   )

   img = Image.open('image.jpg')
   img_dithered = hitherdither.ordered.yliluoma.yliluomas_1_ordered_dithering(
       img, palette, order=8)

Tests
~~~~~

Tests can be run with `pytest <http://doc.pytest.org/en/latest/>`_:

.. code:: sh

    hbldh@devbox:~/Repos/hitherdither$ py.test tests
    ============================= test session starts ==============================
    platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1
    rootdir: /home/hbldh/Repos/hitherdither, inifile: 
    collected 13 items 

    tests/test_bayer.py ...
    tests/test_palette.py ..........

    ========================== 13 passed in 0.11 seconds ===========================

References
----------

.. [1] Joel Yliluoma's arbitrary-palette positional dithering algorithm (http://bisqwit.iki.fi/story/howto/dither/jy/)


.. |Build Status| image:: https://github.com/hbldh/hitherdither/workflows/Build%20and%20Test/badge.svg
   :target: https://github.com/hbldh/hitherdither/actions?query=workflow%3A%22Build+and+Test%22
   :alt: Build and Test
.. |Coverage Status| image:: https://coveralls.io/repos/github/hbldh/hitherdither/badge.svg?branch=master
   :target: https://coveralls.io/github/hbldh/hitherdither?branch=master


================================================
FILE: hitherdither/__init__.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import absolute_import

from . import data
from . import math
from . import ordered
from . import diffusion
from . import palette
from . import utils
from .__version__ import __version__, version


================================================
FILE: hitherdither/__version__.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
__version__.py
-----------

:copyright: 2017-05-10 by hbldh <henrik.blidh@nedomkull.com>

"""

from __future__ import division
from __future__ import print_function
from __future__ import absolute_import

# Version information.
__version__ = "0.1.7"
version = __version__  # backwards compatibility name


================================================
FILE: hitherdither/data/__init__.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-

try:
    import pathlib2 as pathlib
except ImportError:
    import pathlib

try:
    from urllib import urlopen
except ImportError:
    from urllib.request import urlopen

from PIL import Image


def scene():
    """Chrono Cross PNG image used in Yliluoma's web page.

    :return: The PIL image of the Chrono Cross scene.

    """
    image_path = pathlib.Path(__file__).resolve().parent.joinpath("scene.png")
    image_url = "http://bisqwit.iki.fi/jutut/kuvat/ordered_dither/scene.png"
    return _image(image_path, image_url)


def scene_undithered():
    """Chrono Cross PNG image rendered directly with specified palette.

    :return: The PIL image of the undithered Chrono Cross scene.

    """
    return _image(
        pathlib.Path(__file__).resolve().parent.joinpath("scenenodither.png"),
        "http://bisqwit.iki.fi/jutut/kuvat/ordered_dither/scenenodither.png",
    )


def scene_bayer0():
    """Chrono Cross PNG image dithered using ordered Bayer matrix method.

    :return: The PIL image of the ordered Bayer matrix dithered
        Chrono Cross scene.

    """
    return _image(
        pathlib.Path(__file__).resolve().parent.joinpath("scenebayer0.png"),
        "http://bisqwit.iki.fi/jutut/kuvat/ordered_dither/scenebayer0.png",
    )


def _image(pth, url):
    """Load image specified in ``path``. If not present,
    fetch it from ``url`` and store locally.

    :param str or :class:`~pathlib.Path` pth:
    :param str url: URL from where to fetch the image.
    :return: The :class:`~PIL.Image` requested.

    """
    if pth.exists():
        return Image.open(str(pth))
    else:
        r = urlopen(url)
        with open(str(pth), "wb") as f:
            f.write(r.read())
        return _image(pth, url)


def palette():
    return [
        0x080000,
        0x201A0B,
        0x432817,
        0x492910,
        0x234309,
        0x5D4F1E,
        0x9C6B20,
        0xA9220F,
        0x2B347C,
        0x2B7409,
        0xD0CA40,
        0xE8A077,
        0x6A94AB,
        0xD5C4B3,
        0xFCE76E,
        0xFCFAE2,
    ]


================================================
FILE: hitherdither/diffusion.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
:mod:`diffusion`
=======================

.. moduleauthor:: hbldh <henrik.blidh@swedwise.com>
Created on 2016-09-12, 11:34

"""

from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import absolute_import

import numpy as np

_DIFFUSION_MAPS = {
    "floyd-steinberg": (
        (1, 0, 7 / 16),
        (-1, 1, 3 / 16),
        (0, 1, 5 / 16),
        (1, 1, 1 / 16),
    ),
    "atkinson": (
        (1, 0, 1 / 8),
        (2, 0, 1 / 8),
        (-1, 1, 1 / 8),
        (0, 1, 1 / 8),
        (1, 1, 1 / 8),
        (0, 2, 1 / 8),
    ),
    "jarvis-judice-ninke": (
        (1, 0, 7 / 48),
        (2, 0, 5 / 48),
        (-2, 1, 3 / 48),
        (-1, 1, 5 / 48),
        (0, 1, 7 / 48),
        (1, 1, 5 / 48),
        (2, 1, 3 / 48),
        (-2, 2, 1 / 48),
        (-1, 2, 3 / 48),
        (0, 2, 5 / 48),
        (1, 2, 3 / 48),
        (2, 2, 1 / 48),
    ),
    "stucki": (
        (1, 0, 8 / 42),
        (2, 0, 4 / 42),
        (-2, 1, 2 / 42),
        (-1, 1, 4 / 42),
        (0, 1, 8 / 42),
        (1, 1, 4 / 42),
        (2, 1, 2 / 42),
        (-2, 2, 1 / 42),
        (-1, 2, 2 / 42),
        (0, 2, 4 / 42),
        (1, 2, 2 / 42),
        (2, 2, 1 / 42),
    ),
    "burkes": (
        (1, 0, 8 / 32),
        (2, 0, 4 / 32),
        (-2, 1, 2 / 32),
        (-1, 1, 4 / 32),
        (0, 1, 8 / 32),
        (1, 1, 4 / 32),
        (2, 1, 2 / 32),
    ),
    "sierra3": (
        (1, 0, 5 / 32),
        (2, 0, 3 / 32),
        (-2, 1, 2 / 32),
        (-1, 1, 4 / 32),
        (0, 1, 5 / 32),
        (1, 1, 4 / 32),
        (2, 1, 2 / 32),
        (-1, 2, 2 / 32),
        (0, 2, 3 / 32),
        (1, 2, 2 / 32),
    ),
    "sierra2": (
        (1, 0, 4 / 16),
        (2, 0, 3 / 16),
        (-2, 1, 1 / 16),
        (-1, 1, 2 / 16),
        (0, 1, 3 / 16),
        (1, 1, 2 / 16),
        (2, 1, 1 / 16),
    ),
    "sierra-2-4a": (
        (1, 0, 2 / 4),
        (-1, 1, 1 / 4),
        (0, 1, 1 / 4),
    ),
}


def error_diffusion_dithering(image, palette, method="floyd-steinberg", order=2):
    """Perform image dithering by error diffusion method.

    .. note:: Error diffusion is totally unoptimized and therefore very slow.
        It is included more as a reference implementation than as a useful
        method.

    Reference:
        http://bisqwit.iki.fi/jutut/kuvat/ordered_dither/error_diffusion.txt

    Quantization error of *current* pixel is added to the pixels
    on the right and below according to the formulas below.
    This works nicely for most static pictures, but causes
    an avalanche of jittering artifacts if used in animation.

    Floyd-Steinberg:

              *  7
           3  5  1      / 16

    Jarvis-Judice-Ninke:

              *  7  5
        3  5  7  5  3
        1  3  5  3  1   / 48

    Stucki:

              *  8  4
        2  4  8  4  2
        1  2  4  2  1   / 42

    Burkes:

              *  8  4
        2  4  8  4  2   / 32


    Sierra3:

              *  5  3
        2  4  5  4  2
           2  3  2      / 32

    Sierra2:

              *  4  3
        1  2  3  2  1   / 16

    Sierra-2-4A:

              *  2
           1  1         / 4

    Stevenson-Arce:

                      *   .  32
        12   .   26   .  30   .  16
        .   12    .  26   .  12   .
        5    .   12   .  12   .   5    / 200

    Atkinson:

              *   1   1    / 8
          1   1   1
              1

    :param :class:`PIL.Image` image: The image to apply error
        diffusion dithering to.
    :param :class:`~hitherdither.colour.Palette` palette: The palette to use.
    :param str method: The error diffusion map to use.
    :param int order: Metric parameter ``ord`` to send to
        :method:`numpy.linalg.norm`.
    :return: The error diffusion dithered PIL image of type
        "P" using the input palette.

    """
    ni = np.array(image, "float")

    diff_map = _DIFFUSION_MAPS.get(method.lower())

    for y in range(ni.shape[0]):
        for x in range(ni.shape[1]):
            old_pixel = ni[y, x]
            old_pixel[old_pixel < 0.0] = 0.0
            old_pixel[old_pixel > 255.0] = 255.0
            new_pixel = palette.pixel_closest_colour(old_pixel, order)
            quantization_error = old_pixel - new_pixel
            ni[y, x] = new_pixel
            for dx, dy, diffusion_coefficient in diff_map:
                xn, yn = x + dx, y + dy
                if (0 <= xn < ni.shape[1]) and (0 <= yn < ni.shape[0]):
                    ni[yn, xn] += quantization_error * diffusion_coefficient
    return palette.create_PIL_png_from_rgb_array(np.array(ni, "uint8"))


================================================
FILE: hitherdither/exceptions.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
exceptions
-----------

:copyright: 2017-05-10 by hbldh <henrik.blidh@nedomkull.com>

"""

from __future__ import division
from __future__ import print_function
from __future__ import absolute_import


class HitherDitherError(Exception):
    pass


class PaletteCouldNotBeCreatedError(Exception):
    pass


================================================
FILE: hitherdither/math/__init__.py
================================================


================================================
FILE: hitherdither/ordered/__init__.py
================================================
from . import bayer
from . import yliluoma
from . import cluster


================================================
FILE: hitherdither/ordered/bayer.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
bayer_dithering
-----------

:copyright: 2016-09-09 by hbldh <henrik.blidh@nedomkull.com>

"""

from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import absolute_import

import numpy as np


def B(n, transposed=False):
    """Get the Bayer matrix with side of length ``n``.

    Will only work if ``n`` is a power of 2.

    Reference: http://caca.zoy.org/study/part2.html

    :param int n: Power of 2 side length of matrix.
    :return: The Bayer matrix.

    """
    return (1 + I(n, transposed)) / (1 + (n * n))


def I(n, transposed=False):
    """Get the index matrix with side of length ``n``.

    Will only work if ``n`` is a power of 2.

    Reference: http://caca.zoy.org/study/part2.html

    :param int n: Power of 2 side length of matrix.
    :param bool transposed:
    :return: The index matrix.

    """
    if n == 0:
        return np.array([[0, 0], [0, 0]], "int")
    if n == 2:
        if transposed:
            return np.array([[0, 3], [2, 1]], "int")
        else:
            return np.array([[0, 2], [3, 1]], "int")
    else:
        smaller_I = I(n >> 1, transposed)
        if transposed:
            return np.bmat(
                [
                    [4 * smaller_I, 4 * smaller_I + 3],
                    [4 * smaller_I + 2, 4 * smaller_I + 1],
                ]
            )
        else:
            return np.bmat(
                [
                    [4 * smaller_I, 4 * smaller_I + 2],
                    [4 * smaller_I + 3, 4 * smaller_I + 1],
                ]
            )


def bayer_dithering(image, palette, thresholds, order=8):
    """Render the image using the ordered Bayer matrix dithering pattern.

    :param :class:`PIL.Image` image: The image to apply
        Bayer ordered dithering to.
    :param :class:`~hitherdither.colour.Palette` palette: The palette to use.
    :param thresholds: Thresholds to apply dithering at.
    :param int order: The size of the Bayer matrix.
    :return:  The Bayer matrix dithered PIL image of type "P"
        using the input palette.

    """
    bayer_matrix = B(order)
    ni = np.array(image, "uint8")
    thresholds = np.array(thresholds, "uint8")
    xx, yy = np.meshgrid(range(ni.shape[1]), range(ni.shape[0]))
    xx %= order
    yy %= order
    factor_threshold_matrix = np.expand_dims(bayer_matrix[yy, xx], axis=2) * thresholds
    new_image = ni + factor_threshold_matrix
    return palette.create_PIL_png_from_rgb_array(new_image)


================================================
FILE: hitherdither/ordered/cluster.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
bayer_dithering
-----------

:copyright: 2016-09-09 by hbldh <henrik.blidh@nedomkull.com>

"""

from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import absolute_import

import numpy as np

_CLUSTER_DOT_MATRICES = {
    4: np.array([[12, 5, 6, 13], [4, 0, 1, 7], [11, 3, 2, 8], [15, 10, 9, 14]], "float")
    / 16.0,
    8: np.array(
        [
            [24, 10, 12, 26, 35, 47, 49, 37],
            [8, 0, 2, 14, 45, 59, 61, 51],
            [22, 6, 4, 16, 43, 57, 63, 53],
            [30, 20, 18, 28, 33, 41, 55, 39],
            [34, 46, 48, 36, 25, 11, 13, 27],
            [44, 57, 60, 50, 9, 1, 3, 15],
            [42, 56, 62, 52, 23, 7, 5, 17],
            [32, 40, 54, 38, 31, 21, 19, 29],
        ],
        "float",
    )
    / 64.0,
    (5, 3): np.array([[9, 3, 0, 6, 12], [10, 4, 1, 7, 13], [11, 5, 2, 8, 14]], "float")
    / 15.0,
}


def cluster_dot_dithering(image, palette, thresholds, order=4):
    """Render the image using the ordered Bayer matrix dithering pattern.

    Reference: http://caca.zoy.org/study/part2.html

    :param :class:`PIL.Image` image: The image to apply the
        ordered dithering to.
    :param :class:`~hitherdither.colour.Palette` palette: The palette to use.
    :param thresholds: Thresholds to apply dithering at.
    :param int order: The size of the Bayer matrix.
    :return:  The Bayer matrix dithered PIL image of type "P"
        using the input palette.

    """

    cluster_dot_matrix = _CLUSTER_DOT_MATRICES.get(order)
    if cluster_dot_matrix is None:
        raise NotImplementedError("Only order 4 and 8 is implemented as of yet.")
    ni = np.array(image, "uint8")
    thresholds = np.array(thresholds, "uint8")
    xx, yy = np.meshgrid(range(ni.shape[1]), range(ni.shape[0]))
    xx %= order
    yy %= order
    factor_threshold_matrix = (
        np.expand_dims(cluster_dot_matrix[yy, xx], axis=2) * thresholds
    )
    new_image = ni + factor_threshold_matrix
    return palette.create_PIL_png_from_rgb_array(new_image)


================================================
FILE: hitherdither/ordered/yliluoma/__init__.py
================================================
from ._algorithm_one import yliluomas_1_ordered_dithering


================================================
FILE: hitherdither/ordered/yliluoma/_algorithm_one.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
algorithm_one
-----------

:copyright: 2016-09-12 by hbldh <henrik.blidh@nedomkull.com>

"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import numpy as np

from ._utils import color_compare, CCIR_LUMINOSITY
from ..bayer import I


def _get_mixing_plan_matrix(palette, order=8):
    mixing_matrix = []
    colours = {}
    colour_component_distances = []

    nn = order * order
    for i in range(len(palette)):
        for j in range(i, len(palette)):
            for ratio in range(0, nn):
                if i == j and ratio != 0:
                    break
                # Determine the two component colors.
                c_mix = _colour_combine(palette, i, j, ratio / nn)
                hex_colour = palette.rgb2hex(*c_mix.tolist())
                colours[hex_colour] = (i, j, ratio / nn)
                mixing_matrix.append(c_mix)

                c1 = np.array(palette[i], "int")
                c2 = np.array(palette[j], "int")
                cmpval = (
                    color_compare(c1, c2)
                    * 0.1
                    * (np.abs((ratio / float(nn)) - 0.5) + 0.5)
                )
                colour_component_distances.append(cmpval)

    mixing_matrix = np.array(mixing_matrix)
    colour_component_distances = np.array(colour_component_distances)

    for c in mixing_matrix:
        assert palette.rgb2hex(*c.tolist()) in colours

    return mixing_matrix, colours, colour_component_distances


def _colour_combine(palette, i, j, ratio):
    c1, c2 = np.array(palette[i], "int"), np.array(palette[j], "int")
    return np.array(c1 + ratio * (c2 - c1), "uint8")


def _improved_mixing_error_fcn(
    colour, mixing_matrix, colour_component_distances, luma_mat=None
):
    """Compares two colours using the Psychovisual model.

    The simplest way to adjust the psychovisual model is to
    add some code that considers the difference between the
    two pixel values that are being mixed in the dithering
    process, and penalizes combinations that differ too much.

    Wikipedia has an entire article about the topic of comparing
    two color values. Most of the improved color comparison
    functions are based on the CIE colorspace, but simple
    improvements can be done in the RGB space too. Such a simple
    improvement is shown below. We might call this RGBL, for
    luminance-weighted RGB.

    :param :class:`numpy.ndarray` colour: The colour to estimate error to.
    :param :class:`numpy.ndarray` mixing_matrix: The rgb
        values of mixed colours.
    :param :class:`numpy.ndarray` colour_component_distances: The colour
        distance of the mixed colours.
    :return: :class:`numpy.ndarray`

    """
    colour = np.array(colour, "int")
    if luma_mat is None:
        luma_mat = mixing_matrix.dot(CCIR_LUMINOSITY / 1000.0 / 255.0)
    luma_colour = colour.dot(CCIR_LUMINOSITY) / (255.0 * 1000.0)
    luma_diff_squared = (luma_mat - luma_colour) ** 2
    diff_colour_squared = ((colour - mixing_matrix) / 255.0) ** 2
    cmpvals = diff_colour_squared.dot(CCIR_LUMINOSITY) / 1000.0
    cmpvals *= 0.75
    cmpvals += luma_diff_squared
    cmpvals += colour_component_distances
    return cmpvals


def yliluomas_1_ordered_dithering(image, palette, order=8):
    """A dithering method that weighs in color combinations of palette.

    N.B. tri-tone dithering is not implemented.

    :param :class:`PIL.Image` image: The image to apply
        Bayer ordered dithering to.
    :param :class:`~hitherdither.colour.Palette` palette: The palette to use.
    :param int order: The Bayer matrix size to use.
    :return:  The dithered PIL image of type "P" using the input palette.

    """
    bayer_matrix = I(order, transposed=True) / 64.0
    ni = np.array(image, "uint8")
    xx, yy = np.meshgrid(range(ni.shape[1]), range(ni.shape[0]))
    factor_matrix = bayer_matrix[yy % order, xx % order]

    # Prepare all precalculated mixed colours and their respective
    mixing_matrix, colour_map, colour_component_distances = _get_mixing_plan_matrix(
        palette
    )
    mixing_matrix = np.array(mixing_matrix, "int")
    luma_mat = mixing_matrix.dot(CCIR_LUMINOSITY / 1000.0 / 255.0)

    color_matrix = np.zeros(ni.shape[:2], dtype="uint8")
    for x, y in zip(np.nditer(xx), np.nditer(yy)):

        min_index = np.argmin(
            _improved_mixing_error_fcn(
                ni[y, x, :], mixing_matrix, colour_component_distances, luma_mat
            )
        )
        closest_mix_colour = mixing_matrix[min_index, :].tolist()
        closest_mix_hexcolour = palette.rgb2hex(*closest_mix_colour)
        plan = colour_map.get(closest_mix_hexcolour)
        color_matrix[y, x] = plan[1] if (factor_matrix[y, x] < plan[-1]) else plan[0]

    return palette.create_PIL_png_from_closest_colour(color_matrix)


def _evaluate_mixing_error(
    desired_colour,
    mixed_colour,
    component_colour_1,
    component_colour_2,
    ratio,
    component_colour_compare_value=None,
):
    """Compare colours and weigh in component difference.

    double EvaluateMixingError(int r,int g,int b,
                               int r0,int g0,int b0,
                               int r1,int g1,int b1,
                               int r2,int g2,int b2,
                               double ratio)
    {
        return ColorCompare(r,g,b, r0,g0,b0)
             + ColorCompare(r1,g1,b1, r2,g2,b2) * 0.1
             * (fabs(ratio-0.5)+0.5);
    }


    :param desired_colour:
    :param mixed_colour:
    :param component_colour_1:
    :param component_colour_2:
    :param ratio:
    :param component_colour_compare_value:
    :return:

    """
    if component_colour_compare_value is None:
        return color_compare(desired_colour, mixed_colour) + (
            color_compare(component_colour_1, component_colour_2)
            * 0.1
            * (np.abs(ratio - 0.5) + 0.5)
        )
    else:
        return (
            color_compare(desired_colour, mixed_colour) + component_colour_compare_value
        )


================================================
FILE: hitherdither/ordered/yliluoma/_utils.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
_utils
-----------

:copyright: 2016-09-23 by hbldh <henrik.blidh@nedomkull.com>

"""

from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import absolute_import

import numpy as np

# CCIR 601 luminosity
CCIR_LUMINOSITY = np.array([299.0, 587.0, 114.0])


def color_compare(c1, c2):
    """Compare the difference of two RGB values, weigh by CCIR 601 luminosity

    double ColorCompare(int r1,int g1,int b1, int r2,int g2,int b2)
    {
        double luma1 = (r1*299 + g1*587 + b1*114) / (255.0*1000);
        double luma2 = (r2*299 + g2*587 + b2*114) / (255.0*1000);
        double lumadiff = luma1-luma2;
        double diffR = (r1-r2)/255.0, diffG = (g1-g2)/255.0, diffB = (b1-b2)/255.0;
        return (diffR*diffR*0.299 + diffG*diffG*0.587 + diffB*diffB*0.114)*0.75
             + lumadiff*lumadiff;
    }

    :return: float

    """
    luma_diff = c1.dot(CCIR_LUMINOSITY) / (255.0 * 1000.0) - c2.dot(CCIR_LUMINOSITY) / (
        255.0 * 1000.0
    )
    diff_col = (c1 - c2) / 255.0
    return ((diff_col ** 2).dot(CCIR_LUMINOSITY / 1000.0) * 0.75) + (luma_diff ** 2)


================================================
FILE: hitherdither/palette.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
palette
-----------

:copyright: 2016-09-09 by hbldh <henrik.blidh@nedomkull.com>

"""

from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import absolute_import

import numpy as np
from PIL import Image
from PIL.ImagePalette import ImagePalette

from hitherdither.exceptions import PaletteCouldNotBeCreatedError

try:
    string_type = basestring
except NameError:
    string_type = str


def hex2rgb(h):
    if isinstance(h, string_type):
        return hex2rgb(int(h[1:] if h.startswith("#") else h, 16))
    return (h >> 16) & 0xFF, (h >> 8) & 0xFF, h & 0xFF


def rgb2hex(r, g, b):
    return (r << 16) + (g << 8) + b


def _get_all_present_colours(im):
    """Returns a dict of RGB colours present.

    N.B. Do not use this except for testing purposes.

    Reference: http://stackoverflow.com/a/4643911

    :param im: The image to get number of colours in.
    :type im: :class:`~PIL.Image.Image`
    :return: A dict of contained RGB colours as keys.
    :rtype: dict

    """
    from collections import defaultdict

    by_color = defaultdict(int)
    for pixel in im.getdata():
        by_color[pixel] += 1
    return by_color


class Palette(object):
    """The :mod:`~hitherdither` implementation of a colour palette.

    Can be instantiated in from colour specifications in the following forms:

    - ``uint8`` numpy array of size ``[N x 3]``
    - ``uint8`` numpy array of size ``[3N]``
    - :class:`~PIL.ImagePalette.ImagePalette`
    - :class:`~PIL.Image.Image`
    - list of hex values
    - list of RGB tuples

    """

    def __init__(self, data):
        if isinstance(data, np.ndarray):
            if data.ndim == 1:
                self.colours = data.reshape((3, len(data) // 3))
            else:
                self.colours = data
            self.hex = [rgb2hex(*colour) for colour in data]
        elif isinstance(data, ImagePalette):
            _tmp = np.frombuffer(data.palette, "uint8")
            self.colours = _tmp.reshape((3, len(_tmp) // 3))
            self.hex = [rgb2hex(*colour) for colour in data]
        elif isinstance(data, Image.Image):
            if data.palette is None:
                raise PaletteCouldNotBeCreatedError(
                    "Image of mode {0} has no PIL palette. "
                    "Make sure it is of mode P.".format(data.mode)
                )
            _colours = data.getcolors()
            _n_colours = len(_colours)
            _tmp = np.array(data.getpalette())[: 3 * _n_colours]
            self.colours = _tmp.reshape((3, len(_tmp) // 3)).T
            self.hex = [rgb2hex(*colour) for colour in self]
        elif isinstance(data, (list, tuple)):
            if isinstance(data[0], string_type):
                # Assume hex strings
                self.hex = data
                self.colours = np.array([hex2rgb(c) for c in data])
            elif isinstance(data[0], int):
                # Assume hex values
                self.hex = data  # TODO: Convert to hex string.
                self.colours = np.array([hex2rgb(c) for c in data])
            else:
                # Assume RGB tuples
                self.colours = np.array(data)
                self.hex = [rgb2hex(*colour) for colour in data]

    def __iter__(self):
        for colour in self.colours:
            yield colour

    def __len__(self):
        return self.colours.shape[0]

    def __getitem__(self, item):
        if isinstance(item, int):
            return self.colours[item, :]
        else:
            raise IndexError("Can only reference colours by integer values.")

    def render(self, colours):
        return np.array(np.take(self.colours, colours, axis=0), "uint8")

    def image_distance(self, image, order=2):
        ni = np.array(image, "float")
        distances = np.zeros((ni.shape[0], ni.shape[1], len(self)), "float")
        for i, colour in enumerate(self):
            distances[:, :, i] = np.linalg.norm(ni - colour, ord=order, axis=2)
        return distances

    def image_closest_colour(self, image, order=2):
        return np.argmin(self.image_distance(image, order=order), axis=2)

    def pixel_distance(self, pixel, order=2):
        return np.array([np.linalg.norm(pixel - colour, ord=order) for colour in self])

    def pixel_closest_colour(self, pixel, order=2):
        return self.colours[
            np.argmin(self.pixel_distance(pixel, order=order)), :
        ].copy()

    @classmethod
    def create_by_kmeans(cls, image):
        raise NotImplementedError()

    @classmethod
    def create_by_median_cut(cls, image, n=16, dim=None):
        img = np.array(image)
        # Create pixel buckets to simplify sorting and splitting.
        if img.ndim == 3:
            pixels = img.reshape((img.shape[0] * img.shape[1], img.shape[2]))
        elif img.ndim == 2:
            pixels = img.reshape((img.shape[0] * img.shape[1], 1))

        def median_cut(p, dim=None):
            """Median cut method.

            Reference:
            https://en.wikipedia.org/wiki/Median_cut

            :param p: The pixel array to split in two.
            :return: Two numpy arrays, split by median cut method.
            """
            if dim is not None:
                sort_dim = dim
            else:
                mins = p.min(axis=0)
                maxs = p.max(axis=0)
                sort_dim = np.argmax(maxs - mins)

            argument = np.argsort(p[:, sort_dim])
            p = p[argument, :]
            m = np.median(p[:, sort_dim])
            split_mask = p[:, sort_dim] >= m
            return [p[~split_mask, :].copy(), p[split_mask, :].copy()]

        # Do actual splitting loop.
        bins = [
            pixels,
        ]
        while len(bins) < n:
            new_bins = []
            for bin in bins:
                if len(bin) != 0:
                    new_bins += median_cut(bin, dim)
            bins = new_bins

        # Average over pixels in each bin to create
        colours = np.array(
            [np.array(bin.mean(axis=0).round(), "uint8") for bin in bins], "uint8"
        )
        return cls(colours)

    def create_PIL_png_from_closest_colour(self, cc):
        """Create a ``P`` PIL image with this palette.

        Avoids the PIL dithering in favour of our own.

        Reference: http://stackoverflow.com/a/29438149

        :param :class:`numpy.ndarray` cc: A ``[M x N]`` array with integer
            values representing palette colour indices to build image from.
        :return: A :class:`PIL.Image.Image` image of mode ``P``.

        """
        pa_image = Image.new("P", cc.shape[::-1])
        pa_image.putpalette(self.colours.flatten().tolist())
        im = Image.fromarray(np.array(cc, "uint8")).im.convert("P", 0, pa_image.im)
        try:
            # Pillow >= 4
            return pa_image._new(im)
        except AttributeError:
            # Pillow < 4
            return pa_image._makeself(im)

    def create_PIL_png_from_rgb_array(self, img_array):
        """Create a ``P`` PIL image from a RGB image with this palette.

        Avoids the PIL dithering in favour of our own.

        Reference: http://stackoverflow.com/a/29438149

        :param :class:`numpy.ndarray` img_array: A ``[M x N x 3]`` uint8
            array representing RGB colours.
        :return: A :class:`PIL.Image.Image` image of mode ``P`` with colours
            available in this palette.

        """
        cc = self.image_closest_colour(img_array, order=2)
        pa_image = Image.new("P", cc.shape[::-1])
        pa_image.putpalette(self.colours.flatten().tolist())
        im = Image.fromarray(np.array(cc, "uint8")).im.convert("P", 0, pa_image.im)
        try:
            # Pillow >= 4
            return pa_image._new(im)
        except AttributeError:
            # Pillow < 4
            return pa_image._makeself(im)

    @staticmethod
    def hex2rgb(x):
        return hex2rgb(x)

    @staticmethod
    def rgb2hex(r, g, b):
        return rgb2hex(r, g, b)


================================================
FILE: hitherdither/utils.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
:mod:`utils`
=======================

.. moduleauthor:: hbldh <henrik.blidh@swedwise.com>
Created on 2016-09-12, 09:50

"""

from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import absolute_import


import numpy as np
from PIL import Image


def np2pil(img):
    return Image.fromarray(np.array(img, "uint8"))


def pil2np(img):
    return np.array(img, "uint8")


================================================
FILE: requirements.txt
================================================
Pillow>=3.3.1
numpy>=1.9.0


================================================
FILE: run.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
:mod:`run`
=======================

.. moduleauthor:: hbldh <henrik.blidh@nedomkull.com>
Created on 2016-09-12, 09:44

"""

from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import absolute_import

import numpy as np

from hitherdither import data
from hitherdither.palette import Palette
from hitherdither.diffusion import error_diffusion_dithering
from hitherdither.ordered import yliluoma
import hitherdither.utils

# Fetch the example image and the palette from Yliluoma's page.
s = data.scene()
p = Palette(hitherdither.data.palette())

p2 = Palette.create_by_median_cut(s)

# Map raw image to the palette
closest_colour = p.image_closest_colour(s, order=2)
# Render the undithered image with only colours in
# the palette as a RGB numpy array.
undithered_image = p.render(closest_colour)
# Create a PIL Image of mode "P" from the palette colour index matrix.
s_png = p.create_PIL_png_from_closest_colour(closest_colour)
s_png.show()

#print(np.linalg.norm(undithered_image - np.array(s_png.convert("RGB"))))

# Render an Yliluoma algorithm 1 image.
yliluoma1_image = yliluoma.yliluomas_1_ordered_dithering(
    s, p, order=8)
yliluoma1_image.resize(np.array(yliluoma1_image.size) * 4).show()
#yliluoma1_image.show()



================================================
FILE: setup.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Note: To use the 'upload' functionality of this file, you must:
#   $ pip install twine

import io
import os
import sys
from shutil import rmtree

from setuptools import find_packages, setup, Command

# Package meta-data.
NAME = 'hitherdither'
DESCRIPTION = 'Dithering algorithms for arbitrary palettes in PIL'
URL = 'https://github.com/hbldh/hitherdither'
EMAIL = 'henrik.blidh@nedomkull.com'
AUTHOR = 'Henrik Blidh'

# What packages are required for this module to be executed?
REQUIRED = [
   'Pillow>=3.3.1',
   'numpy>=1.9.0',
   'pathlib2;python_version<"3"'
],


here = os.path.abspath(os.path.dirname(__file__))

with io.open(os.path.join(here, 'README.rst'), encoding='utf-8') as f:
    long_description = '\n' + f.read()

# Load the package's __version__.py module as a dictionary.
about = {}
with open(os.path.join(here, NAME, '__version__.py')) as f:
    exec(f.read(), about)


class UploadCommand(Command):
    """Support setup.py upload."""

    description = 'Build and publish the package.'
    user_options = []

    @staticmethod
    def status(s):
        """Prints things in bold."""
        print('\033[1m{0}\033[0m'.format(s))

    def initialize_options(self):
        pass

    def finalize_options(self):
        pass

    def run(self):
        try:
            self.status('Removing previous builds…')
            rmtree(os.path.join(here, 'dist'))
        except OSError:
            pass

        self.status('Building Source and Wheel (universal) distribution…')
        os.system('{0} setup.py sdist bdist_wheel --universal'.format(sys.executable))

        self.status('Uploading the package to PyPi via Twine…')
        os.system('twine upload dist/*')

        sys.exit()


# Where the magic happens:
setup(
    name=NAME,
    version=about['__version__'],
    description=DESCRIPTION,
    long_description=long_description,
    author=AUTHOR,
    author_email=EMAIL,
    url=URL,
    packages=find_packages(exclude=('tests',)),
    install_requires=REQUIRED,
    include_package_data=True,
    license='MIT',
    classifiers=[
        'License :: OSI Approved :: MIT License',
        'Programming Language :: Python',
        'Programming Language :: Python :: 2',
        'Programming Language :: Python :: 2.6',
        'Programming Language :: Python :: 2.7',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.3',
        'Programming Language :: Python :: 3.4',
        'Programming Language :: Python :: 3.5',
        'Programming Language :: Python :: 3.6',
        'Operating System :: OS Independent',
        'Development Status :: 4 - Beta',
        'Intended Audience :: Developers',
    ],
    # $ setup.py publish support.
    cmdclass={
        'upload': UploadCommand,
    },
)


================================================
FILE: tests/__init__.py
================================================


================================================
FILE: tests/conftest.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
tools
-----------

:copyright: 2017-05-10 by hbldh <henrik.blidh@nedomkull.com>

"""

from __future__ import division
from __future__ import print_function
from __future__ import absolute_import

import pytest
try:
    import pathlib2 as pathlib
except ImportError:
    import pathlib

from hitherdither.data import _image


@pytest.fixture(scope='session')
def test_png():
    p = pathlib.Path(__file__).parent.joinpath('astronaut.png')
    url = 'https://raw.githubusercontent.com/scikit-image/scikit-image/master/skimage/data/astronaut.png'
    i = _image(p, url)
    return i


@pytest.fixture(scope='session')
def test_jpeg():
    p = pathlib.Path(__file__).parent.joinpath('rocket.jpg')
    url = 'https://raw.githubusercontent.com/scikit-image/scikit-image/master/skimage/data/rocket.jpg'
    i = _image(p, url)
    return i


================================================
FILE: tests/test_bayer.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
:mod:`test_bayer`
=======================

.. moduleauthor:: hbldh <henrik.blidh@nedomkull.com>
Created on 2016-09-12, 13:35

"""

from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import absolute_import

import pytest
import numpy as np

from hitherdither.ordered import bayer


_BAYER_MATRICES = {
    2: (1 / 5.) * np.array([
        [1, 3],
        [4, 2]]
    ),
    3: (1 / 10.) * np.array([
        [1, 8, 4],
        [7, 6, 3],
        [5, 2, 9]]
    ),
    4: (1 / 17.) * np.array(
        [[1, 9, 3, 11],
         [13, 5, 15, 7],
         [4, 12, 2, 10],
         [16, 8, 14, 6]]
    ),
    8: 1 / 65. * np.array([
        [1, 49, 13, 61, 4, 52, 16, 64],
        [33, 17, 45, 29, 36, 20, 48, 32],
        [9, 57, 5, 53, 12, 60, 8, 56],
        [41, 25, 37, 21, 44, 28, 40, 24],
        [3, 51, 15, 63, 2, 50, 14, 62],
        [35, 19, 47, 31, 34, 18, 46, 30],
        [11, 59, 7, 55, 10, 58, 6, 54],
        [43, 27, 39, 23, 42, 26, 38, 22]]
    ).T
}

@pytest.mark.parametrize("order", [2,4,8])
def test_bayer(order):
    np.testing.assert_allclose(bayer.B(order, False), _BAYER_MATRICES.get(order))




================================================
FILE: tests/test_palette.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
:mod:`test_palette`
=======================

.. moduleauthor:: hbldh <henrik.blidh@nedomkull.com>
Created on 2016-09-13, 09:38

"""

from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import absolute_import

import pytest
import numpy as np

from hitherdither import palette
from hitherdither.exceptions import PaletteCouldNotBeCreatedError
from hitherdither.data import scene, scene_bayer0, scene_undithered


@pytest.mark.parametrize(
    "hex_colour, rgb_colour",
    (
        ("#ffffff", (255, 255, 255)),
        ("#abcdef", (171, 205, 239)),
        ("#012345", (1, 35, 69)),
        (0x82F698, (130, 246, 152)),
        ("0x82f698", (130, 246, 152)),
    ),
)
def test_hex2rgb(hex_colour, rgb_colour):
    assert palette.hex2rgb(hex_colour) == rgb_colour


@pytest.mark.parametrize(
    "hex_colour, rgb_colour",
    (
        ("#ffffff", (255, 255, 255)),
        ("#abcdef", (171, 205, 239)),
        ("#012345", (1, 35, 69)),
        (0x82F698, (130, 246, 152)),
        ("0x82f698", (130, 246, 152)),
    ),
)
def test_rgb2hex(hex_colour, rgb_colour):
    try:
        if isinstance(hex_colour, int):
            hc = hex_colour
        else:
            hc = int(hex_colour, 16)
    except:
        hc = int(hex_colour[1:], 16)
    assert palette.rgb2hex(*rgb_colour) == hc


@pytest.mark.parametrize(
    "input_data, n_colours",
    (
        (
            [
                np.array((255, 255, 255)),
                np.array((171, 205, 239)),
                np.array((1, 35, 69)),
                np.array((130, 246, 152)),
            ],
            4,
        ),
        ([(255, 255, 255), (171, 205, 239), (1, 35, 69), (130, 246, 152)], 4),
        (
            np.array(
                [
                    (255, 255, 255),
                    (171, 205, 239),
                    (1, 35, 69),
                    (130, 246, 152),
                    (0, 0, 0),
                ]
            ),
            5,
        ),
        (
            [
                "#ff21ee",
                "#123456",
                "#abcdef",
                "#000000",
            ],
            4,
        ),
        (
            [
                0xFF21EE,
                0x123456,
                0xABCDEF,
                0x000000,
            ],
            4,
        ),
    ),
)
def test_create(input_data, n_colours):
    p = palette.Palette(input_data)
    if isinstance(n_colours, tuple):
        # JPEG gets 80 colours in Python 2.7.9 and 3.4,
        # 82 in Python 2.7.12 and 3.5, 3.6...
        assert len(p) in n_colours
        assert len([c for c in p]) in n_colours
    else:
        assert len(p) == n_colours
        assert len([c for c in p]) == n_colours


def test_create_png(test_png):
    n_colours = 104
    p = palette.Palette(test_png.convert("P"))
    if isinstance(n_colours, tuple):
        # JPEG gets 80 colours in Python 2.7.9 and 3.4,
        # 82 in Python 2.7.12 and 3.5, 3.6...
        assert len(p) in n_colours
        assert len([c for c in p]) in n_colours
    else:
        assert len(p) == n_colours
        assert len([c for c in p]) == n_colours


def test_create_jpg(test_jpeg):
    n_colours = (80, 82)
    p = palette.Palette(test_jpeg.convert("P"))
    if isinstance(n_colours, tuple):
        # JPEG gets 80 colours in Python 2.7.9 and 3.4,
        # 82 in Python 2.7.12 and 3.5, 3.6...
        assert len(p) in n_colours
        assert len([c for c in p]) in n_colours
    else:
        assert len(p) == n_colours
        assert len([c for c in p]) == n_colours


def test_create_bayer0():
    n_colours = 16
    p = palette.Palette(scene_bayer0())
    if isinstance(n_colours, tuple):
        # JPEG gets 80 colours in Python 2.7.9 and 3.4,
        # 82 in Python 2.7.12 and 3.5, 3.6...
        assert len(p) in n_colours
        assert len([c for c in p]) in n_colours
    else:
        assert len(p) == n_colours
        assert len([c for c in p]) == n_colours


def test_create_bayer0():
    n_colours = 16
    p = palette.Palette(scene_undithered())
    if isinstance(n_colours, tuple):
        # JPEG gets 80 colours in Python 2.7.9 and 3.4,
        # 82 in Python 2.7.12 and 3.5, 3.6...
        assert len(p) in n_colours
        assert len([c for c in p]) in n_colours
    else:
        assert len(p) == n_colours
        assert len([c for c in p]) == n_colours


def test_create_fails_1(test_png):
    with pytest.raises(PaletteCouldNotBeCreatedError):
        p = palette.Palette(test_png)


def test_create_fails_2(test_jpeg):
    with pytest.raises(PaletteCouldNotBeCreatedError):
        p = palette.Palette(test_jpeg)


def test_create_fails_3(test_jpeg):
    with pytest.raises(PaletteCouldNotBeCreatedError):
        p = palette.Palette(test_jpeg.convert("L"))


def test_create_fails_4(test_jpeg):
    with pytest.raises(PaletteCouldNotBeCreatedError):
        p = palette.Palette(scene())
Download .txt
gitextract_8y6y_8sa/

├── .coveragerc
├── .github/
│   ├── ISSUE_TEMPLATE.md
│   └── workflows/
│       ├── build_and_test.yml
│       └── pypi-publish.yml
├── .gitignore
├── LICENSE
├── MANIFEST.in
├── Pipfile
├── README.rst
├── hitherdither/
│   ├── __init__.py
│   ├── __version__.py
│   ├── data/
│   │   └── __init__.py
│   ├── diffusion.py
│   ├── exceptions.py
│   ├── math/
│   │   └── __init__.py
│   ├── ordered/
│   │   ├── __init__.py
│   │   ├── bayer.py
│   │   ├── cluster.py
│   │   └── yliluoma/
│   │       ├── __init__.py
│   │       ├── _algorithm_one.py
│   │       └── _utils.py
│   ├── palette.py
│   └── utils.py
├── requirements.txt
├── run.py
├── setup.py
└── tests/
    ├── __init__.py
    ├── conftest.py
    ├── test_bayer.py
    └── test_palette.py
Download .txt
SYMBOL INDEX (58 symbols across 13 files)

FILE: hitherdither/data/__init__.py
  function scene (line 17) | def scene():
  function scene_undithered (line 28) | def scene_undithered():
  function scene_bayer0 (line 40) | def scene_bayer0():
  function _image (line 53) | def _image(pth, url):
  function palette (line 71) | def palette():

FILE: hitherdither/diffusion.py
  function error_diffusion_dithering (line 100) | def error_diffusion_dithering(image, palette, method="floyd-steinberg", ...

FILE: hitherdither/exceptions.py
  class HitherDitherError (line 16) | class HitherDitherError(Exception):
  class PaletteCouldNotBeCreatedError (line 20) | class PaletteCouldNotBeCreatedError(Exception):

FILE: hitherdither/ordered/bayer.py
  function B (line 19) | def B(n, transposed=False):
  function I (line 33) | def I(n, transposed=False):
  function bayer_dithering (line 70) | def bayer_dithering(image, palette, thresholds, order=8):

FILE: hitherdither/ordered/cluster.py
  function cluster_dot_dithering (line 40) | def cluster_dot_dithering(image, palette, thresholds, order=4):

FILE: hitherdither/ordered/yliluoma/_algorithm_one.py
  function _get_mixing_plan_matrix (line 22) | def _get_mixing_plan_matrix(palette, order=8):
  function _colour_combine (line 57) | def _colour_combine(palette, i, j, ratio):
  function _improved_mixing_error_fcn (line 62) | def _improved_mixing_error_fcn(
  function yliluomas_1_ordered_dithering (line 100) | def yliluomas_1_ordered_dithering(image, palette, order=8):
  function _evaluate_mixing_error (line 140) | def _evaluate_mixing_error(

FILE: hitherdither/ordered/yliluoma/_utils.py
  function color_compare (line 22) | def color_compare(c1, c2):

FILE: hitherdither/palette.py
  function hex2rgb (line 28) | def hex2rgb(h):
  function rgb2hex (line 34) | def rgb2hex(r, g, b):
  function _get_all_present_colours (line 38) | def _get_all_present_colours(im):
  class Palette (line 59) | class Palette(object):
    method __init__ (line 73) | def __init__(self, data):
    method __iter__ (line 109) | def __iter__(self):
    method __len__ (line 113) | def __len__(self):
    method __getitem__ (line 116) | def __getitem__(self, item):
    method render (line 122) | def render(self, colours):
    method image_distance (line 125) | def image_distance(self, image, order=2):
    method image_closest_colour (line 132) | def image_closest_colour(self, image, order=2):
    method pixel_distance (line 135) | def pixel_distance(self, pixel, order=2):
    method pixel_closest_colour (line 138) | def pixel_closest_colour(self, pixel, order=2):
    method create_by_kmeans (line 144) | def create_by_kmeans(cls, image):
    method create_by_median_cut (line 148) | def create_by_median_cut(cls, image, n=16, dim=None):
    method create_PIL_png_from_closest_colour (line 195) | def create_PIL_png_from_closest_colour(self, cc):
    method create_PIL_png_from_rgb_array (line 217) | def create_PIL_png_from_rgb_array(self, img_array):
    method hex2rgb (line 242) | def hex2rgb(x):
    method rgb2hex (line 246) | def rgb2hex(r, g, b):

FILE: hitherdither/utils.py
  function np2pil (line 22) | def np2pil(img):
  function pil2np (line 26) | def pil2np(img):

FILE: setup.py
  class UploadCommand (line 40) | class UploadCommand(Command):
    method status (line 47) | def status(s):
    method initialize_options (line 51) | def initialize_options(self):
    method finalize_options (line 54) | def finalize_options(self):
    method run (line 57) | def run(self):

FILE: tests/conftest.py
  function test_png (line 25) | def test_png():
  function test_jpeg (line 33) | def test_jpeg():

FILE: tests/test_bayer.py
  function test_bayer (line 52) | def test_bayer(order):

FILE: tests/test_palette.py
  function test_hex2rgb (line 35) | def test_hex2rgb(hex_colour, rgb_colour):
  function test_rgb2hex (line 49) | def test_rgb2hex(hex_colour, rgb_colour):
  function test_create (line 105) | def test_create(input_data, n_colours):
  function test_create_png (line 117) | def test_create_png(test_png):
  function test_create_jpg (line 130) | def test_create_jpg(test_jpeg):
  function test_create_bayer0 (line 143) | def test_create_bayer0():
  function test_create_bayer0 (line 156) | def test_create_bayer0():
  function test_create_fails_1 (line 169) | def test_create_fails_1(test_png):
  function test_create_fails_2 (line 174) | def test_create_fails_2(test_jpeg):
  function test_create_fails_3 (line 179) | def test_create_fails_3(test_jpeg):
  function test_create_fails_4 (line 184) | def test_create_fails_4(test_jpeg):
Condensed preview — 30 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (58K chars).
[
  {
    "path": ".coveragerc",
    "chars": 164,
    "preview": "[run]\nbranch = True\nsource = hitherdither\ninclude = */hitherdither/*\nomit =\n    */setup.py\n\n[report]\nexclude_lines =\n   "
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "chars": 371,
    "preview": "* bleak version:\n* Python version:\n* Operating System:\n* BlueZ version (`bluetoothctl -v`) in case of Linux: \n\n### Descr"
  },
  {
    "path": ".github/workflows/build_and_test.yml",
    "chars": 2052,
    "preview": "name: Build and Test\n\non:\n    push:\n        branches: [ master, develop ]\n    pull_request:\n        branches: [ master, "
  },
  {
    "path": ".github/workflows/pypi-publish.yml",
    "chars": 865,
    "preview": "# This workflows will upload a Python Package using Twine when a release is created\n# For more information see: https://"
  },
  {
    "path": ".gitignore",
    "chars": 6375,
    "preview": "hitherdither/data/*.png\ntests/astronaut.png\ntests/rocket.jpg\n\n# Created by .ignore support plugin (hsz.mobi)\n### VisualS"
  },
  {
    "path": "LICENSE",
    "chars": 1074,
    "preview": "The MIT License\n\nCopyright (c) 2020 Henrik Blidh\n\nPermission is hereby granted, free of charge, to any person obtaining "
  },
  {
    "path": "MANIFEST.in",
    "chars": 27,
    "preview": "include LICENSE README.rst\n"
  },
  {
    "path": "Pipfile",
    "chars": 213,
    "preview": "[[source]]\n\nurl = \"https://pypi.python.org/simple\"\nverify_ssl = true\nname = \"pypi\"\n\n\n[packages]\n\nPillow = \">=3.3.1\"\nnump"
  },
  {
    "path": "README.rst",
    "chars": 3074,
    "preview": "hitherdither\n============\n\n|Build Status| |Coverage Status|\n\nA package inspired by [1]_, implementing dithering algorith"
  },
  {
    "path": "hitherdither/__init__.py",
    "chars": 369,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nfrom __future__ import division\nfrom __future__ import print_function\nfro"
  },
  {
    "path": "hitherdither/__version__.py",
    "chars": 354,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"\n__version__.py\n-----------\n\n:copyright: 2017-05-10 by hbldh <henrik.bl"
  },
  {
    "path": "hitherdither/data/__init__.py",
    "chars": 2110,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\ntry:\n    import pathlib2 as pathlib\nexcept ImportError:\n    import pathli"
  },
  {
    "path": "hitherdither/diffusion.py",
    "chars": 4701,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"\n:mod:`diffusion`\n=======================\n\n.. moduleauthor:: hbldh <hen"
  },
  {
    "path": "hitherdither/exceptions.py",
    "chars": 356,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"\nexceptions\n-----------\n\n:copyright: 2017-05-10 by hbldh <henrik.blidh@"
  },
  {
    "path": "hitherdither/math/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "hitherdither/ordered/__init__.py",
    "chars": 65,
    "preview": "from . import bayer\nfrom . import yliluoma\nfrom . import cluster\n"
  },
  {
    "path": "hitherdither/ordered/bayer.py",
    "chars": 2563,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"\nbayer_dithering\n-----------\n\n:copyright: 2016-09-09 by hbldh <henrik.b"
  },
  {
    "path": "hitherdither/ordered/cluster.py",
    "chars": 2120,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"\nbayer_dithering\n-----------\n\n:copyright: 2016-09-09 by hbldh <henrik.b"
  },
  {
    "path": "hitherdither/ordered/yliluoma/__init__.py",
    "chars": 58,
    "preview": "from ._algorithm_one import yliluomas_1_ordered_dithering\n"
  },
  {
    "path": "hitherdither/ordered/yliluoma/_algorithm_one.py",
    "chars": 6152,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"\nalgorithm_one\n-----------\n\n:copyright: 2016-09-12 by hbldh <henrik.bli"
  },
  {
    "path": "hitherdither/ordered/yliluoma/_utils.py",
    "chars": 1201,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"\n_utils\n-----------\n\n:copyright: 2016-09-23 by hbldh <henrik.blidh@nedo"
  },
  {
    "path": "hitherdither/palette.py",
    "chars": 8078,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"\npalette\n-----------\n\n:copyright: 2016-09-09 by hbldh <henrik.blidh@ned"
  },
  {
    "path": "hitherdither/utils.py",
    "chars": 490,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"\n:mod:`utils`\n=======================\n\n.. moduleauthor:: hbldh <henrik."
  },
  {
    "path": "requirements.txt",
    "chars": 27,
    "preview": "Pillow>=3.3.1\nnumpy>=1.9.0\n"
  },
  {
    "path": "run.py",
    "chars": 1349,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"\n:mod:`run`\n=======================\n\n.. moduleauthor:: hbldh <henrik.bl"
  },
  {
    "path": "setup.py",
    "chars": 2821,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n# Note: To use the 'upload' functionality of this file, you must:\n#   $ p"
  },
  {
    "path": "tests/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/conftest.py",
    "chars": 882,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"\ntools\n-----------\n\n:copyright: 2017-05-10 by hbldh <henrik.blidh@nedom"
  },
  {
    "path": "tests/test_bayer.py",
    "chars": 1239,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"\n:mod:`test_bayer`\n=======================\n\n.. moduleauthor:: hbldh <he"
  },
  {
    "path": "tests/test_palette.py",
    "chars": 4995,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"\n:mod:`test_palette`\n=======================\n\n.. moduleauthor:: hbldh <"
  }
]

About this extraction

This page contains the full source code of the hbldh/hitherdither GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 30 files (52.9 KB), approximately 16.3k tokens, and a symbol index with 58 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!