[Git][debian-gis-team/pydecorate][upstream] New upstream version 0.3.3

Antonio Valentino (@antonio.valentino) gitlab at salsa.debian.org
Sun Mar 6 08:21:04 GMT 2022



Antonio Valentino pushed to branch upstream at Debian GIS Project / pydecorate


Commits:
8af1fef6 by Antonio Valentino at 2022-03-06T07:14:15+00:00
New upstream version 0.3.3
- - - - -


27 changed files:

- + .bandit
- .git_archival.txt
- + .github/workflows/ci.yaml
- + .github/workflows/deploy-sdist.yaml
- .gitignore
- + .pre-commit-config.yaml
- − .travis.yml
- CHANGELOG.md
- MANIFEST.in
- README.rst
- RELEASING.md
- − appveyor.yml
- + continuous_integration/environment.yaml
- + doc/source/_static/.gitkeep
- doc/source/conf.py
- doc/source/index.rst
- doc/source/usage.rst
- pydecorate/__init__.py
- pydecorate/decorator.py
- pydecorate/decorator_agg.py
- pydecorate/decorator_base.py
- + pydecorate/tests/__init__.py
- + pydecorate/tests/test_decorator_agg.py
- test.py → pydecorate/tests/test_legacy.py
- pyproject.toml
- + setup.cfg
- setup.py


Changes:

=====================================
.bandit
=====================================
@@ -0,0 +1,3 @@
+[bandit]
+skips: B506
+exclude: pydecorate/tests


=====================================
.git_archival.txt
=====================================
@@ -1 +1 @@
-ref-names: HEAD -> master, tag: v0.3.1
+ref-names: HEAD -> main, tag: v0.3.3


=====================================
.github/workflows/ci.yaml
=====================================
@@ -0,0 +1,131 @@
+name: CI
+# https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#concurrency
+# https://docs.github.com/en/developers/webhooks-and-events/events/github-event-types#pullrequestevent
+concurrency:
+  group: ${{ github.workflow }}-${{ github.event.number }}-${{ github.event.type }}
+  cancel-in-progress: true
+
+on: [push, pull_request]
+
+jobs:
+  lint:
+    name: lint and style checks
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout source
+        uses: actions/checkout at v2
+      - name: Set up Python
+        uses: actions/setup-python at v2
+        with:
+          python-version: 3.9
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install flake8 flake8-docstrings flake8-debugger flake8-bugbear pytest
+      - name: Install Pydecorate
+        run: |
+          pip install -e .
+      - name: Run linting
+        run: |
+          flake8 pydecorate/
+
+  website:
+    name: build website
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout source
+        uses: actions/checkout at v2
+        with:
+          fetch-depth: 0
+
+      - name: Set up Python
+        uses: actions/setup-python at v2
+        with:
+          python-version: 3.9
+
+      - name: Install Pydecorate
+        shell: bash -l {0}
+        run: |
+          pip install -e .[docs]
+
+      - name: Run Sphinx Build
+        shell: bash -l {0}
+        run: |
+          cd doc; \
+          make html SPHINXOPTS="-W"
+
+  test:
+    runs-on: ${{ matrix.os }}
+    continue-on-error: ${{ matrix.experimental }}
+    needs: [lint]
+    strategy:
+      fail-fast: true
+      matrix:
+        os: ["windows-latest", "ubuntu-latest", "macos-latest"]
+        python-version: ["3.7", "3.10"]
+        experimental: [false]
+        include:
+          - python-version: "3.10"
+            os: "ubuntu-latest"
+            experimental: true
+
+    env:
+      PYTHON_VERSION: ${{ matrix.python-version }}
+      OS: ${{ matrix.os }}
+      UNSTABLE: ${{ matrix.experimental }}
+      ACTIONS_ALLOW_UNSECURE_COMMANDS: true
+
+    steps:
+      - name: Checkout source
+        uses: actions/checkout at v2
+
+#      - name: Set up Python
+#        uses: actions/setup-python at v2
+#        with:
+#          python-version: "${{ matrix.python-version }}"
+
+      - name: Setup Conda Environment
+        uses: conda-incubator/setup-miniconda at v2
+        with:
+          miniforge-variant: Mambaforge
+          miniforge-version: latest
+          use-mamba: true
+          python-version: "${{ matrix.python-version }}"
+          environment-file: continuous_integration/environment.yaml
+          activate-environment: test-environment
+
+      - name: Install unstable dependencies
+        if: matrix.experimental == true
+        shell: bash -l {0}
+        run: |
+          python -m pip install \
+          --index-url https://pypi.anaconda.org/scipy-wheels-nightly/simple/ \
+          --trusted-host pypi.anaconda.org \
+          --no-deps --pre --upgrade \
+          numpy;
+
+      - name: Install pydecorate
+        shell: bash -l {0}
+        run: |
+          python -m pip install -e . --no-deps
+
+      - name: Run unit tests
+        shell: bash -l {0}
+        run: |
+          pytest --cov=pydecorate pydecorate/tests
+
+      - name: Coveralls Parallel
+        uses: AndreMiras/coveralls-python-action at develop
+        with:
+          flag-name: run-${{ matrix.test_number }}
+          parallel: true
+        if: runner.os == 'Linux'
+
+  coveralls:
+    needs: [test]
+    runs-on: ubuntu-latest
+    steps:
+      - name: Coveralls Finished
+        uses: AndreMiras/coveralls-python-action at develop
+        with:
+          parallel-finished: true


=====================================
.github/workflows/deploy-sdist.yaml
=====================================
@@ -0,0 +1,27 @@
+name: Deploy sdist
+
+on:
+  release:
+    types:
+      - published
+
+jobs:
+  test:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout source
+        uses: actions/checkout at v2
+
+      - name: Create sdist
+        shell: bash -l {0}
+        run: |
+          python -m pip install -U build pip
+          python -m build --sdist
+
+      - name: Publish package to PyPI
+        if: github.event.action == 'published'
+        uses: pypa/gh-action-pypi-publish at v1.4.1
+        with:
+          user: __token__
+          password: ${{ secrets.pypi_password }}


=====================================
.gitignore
=====================================
@@ -67,4 +67,3 @@ nosetests.xml
 
 # vi / vim swp files
 *.swp
-


=====================================
.pre-commit-config.yaml
=====================================
@@ -0,0 +1,47 @@
+exclude: '^$'
+fail_fast: false
+repos:
+  - repo: https://github.com/psf/black
+    rev: 22.1.0
+    hooks:
+      - id: black
+        language_version: python3
+        exclude: versioneer.py
+        args:
+          - --target-version=py38
+  - repo: https://github.com/PyCQA/flake8
+    rev: 4.0.1
+    hooks:
+      - id: flake8
+        additional_dependencies: [flake8-docstrings, flake8-debugger, flake8-bugbear, mccabe]
+        args: [--max-complexity, "10"]
+  - repo: https://github.com/pre-commit/pre-commit-hooks
+    rev: v4.1.0
+    hooks:
+      - id: trailing-whitespace
+      - id: end-of-file-fixer
+      - id: check-yaml
+        args: [--unsafe]
+  - repo: https://github.com/PyCQA/bandit
+    rev: '1.7.2' # Update me!
+    hooks:
+      - id: bandit
+        args: [--ini, .bandit]
+  - repo: https://github.com/pre-commit/mirrors-mypy
+    rev: 'v0.931'  # Use the sha / tag you want to point at
+    hooks:
+      - id: mypy
+        additional_dependencies:
+          - types-docutils
+          - types-pkg-resources
+          - types-PyYAML
+          - types-requests
+  - repo: https://github.com/pycqa/isort
+    rev: 5.10.1
+    hooks:
+      - id: isort
+        language_version: python3
+ci:
+  # To trigger manually, comment on a pull request with "pre-commit.ci autofix"
+  autofix_prs: false
+  skip: [bandit]


=====================================
.travis.yml deleted
=====================================
@@ -1,42 +0,0 @@
-language: python
-env:
-  global:
-  - NUMPY_VERSION=stable
-  - MAIN_CMD='python setup.py'
-  - CONDA_DEPENDENCIES='sphinx pillow coveralls coverage codecov trollimage aggdraw
-    pytest pytest-cov'
-  - PIP_DEPENDENCIES=''
-  - SETUP_XVFB=False
-  - EVENT_TYPE='push pull_request'
-  - SETUP_CMD='test'
-  - CONDA_CHANNELS='conda-forge'
-  - CONDA_CHANNEL_PRIORITY='strict'
-matrix:
-  include:
-  - env: PYTHON_VERSION=3.8
-    os: linux
-    language: generic
-  - env: PYTHON_VERSION=3.7
-    os: linux
-    language: generic
-  - env: PYTHON_VERSION=3.8
-    os: osx
-    language: generic
-install:
-- git clone --depth 1 git://github.com/astropy/ci-helpers.git
-- source ci-helpers/travis/setup_conda.sh
-- pip install --no-deps -e .
-script:
-- pytest --cov=pydecorate test.py
-after_success:
-- if [[ $PYTHON_VERSION == 3.8 ]]; then coveralls; codecov; fi
-deploy:
-  - provider: pypi
-    user: dhoese
-    password:
-      secure: PoMrJlgTJRzDvqDRs5H/k/1nkqada+zfiUTUXZEvulebkAVCu3Sj945fNQVsgq3Ha0hlyttuNGyJbLf8KUZIX9y9OJr3TuP253hQx+Ghe6scH5SFHrSj6s4ROinc9HeZfwzGlObZoeV042aSgf77O/ocGUiA54f2ZLYa8U15w9A=
-    distributions: sdist
-    skip_existing: true
-    on:
-      tags: true
-      repo: pytroll/pydecorate


=====================================
CHANGELOG.md
=====================================
@@ -1,3 +1,31 @@
+## Version 0.3.3 (2022/02/17)
+
+### Pull Requests Merged
+
+#### Bugs fixed
+
+* [PR 19](https://github.com/pytroll/pydecorate/pull/19) - Add missing setuptools dependency
+
+In this release 1 pull request was closed.
+
+
+## Version 0.3.2 (2022/02/17)
+
+### Issues Closed
+
+* [Issue 17](https://github.com/pytroll/pydecorate/issues/17) - pydecorate.DecoratorAGG: How to put text/colorbar outside of image and control width of colorbar.
+
+In this release 1 issue was closed.
+
+### Pull Requests Merged
+
+#### Bugs fixed
+
+* [PR 18](https://github.com/pytroll/pydecorate/pull/18) - Fix colorbar creation when limits are inverted
+
+In this release 1 pull request was closed.
+
+
 ## Version 0.3.1 (2020/04/06)
 
 ### Pull Requests Merged


=====================================
MANIFEST.in
=====================================
@@ -3,4 +3,4 @@ include docs/Makefile
 recursive-include pydecorate/fonts *
 include README.rst
 include LICENSE.txt
-include pydecorate/version.py
\ No newline at end of file
+include pydecorate/version.py


=====================================
README.rst
=====================================
@@ -1,14 +1,11 @@
 pydecorate
 ==========
 
-.. image:: https://travis-ci.org/pytroll/pydecorate.svg?branch=master
-    :target: https://travis-ci.org/pytroll/pydecorate
+.. image:: https://github.com/pytroll/pydecorate/workflows/CI/badge.svg?branch=main
+    :target: https://github.com/pytroll/pydecorate/actions?query=workflow%3A%22CI%22
 
-.. image:: https://ci.appveyor.com/api/projects/status/61r5wqu2j2ay07ns/branch/master?svg=true
-    :target: https://ci.appveyor.com/project/pytroll/pydecorate/branch/master
-
-.. image:: https://coveralls.io/repos/github/pytroll/pydecorate/badge.svg?branch=master
-    :target: https://coveralls.io/github/pytroll/pydecorate?branch=master
+.. image:: https://coveralls.io/repos/github/pytroll/pydecorate/badge.svg?branch=main
+    :target: https://coveralls.io/github/pytroll/pydecorate?branch=main
 
 Pydecorate is a package for decorating PIL images with logos, texts, and color
 scales.


=====================================
RELEASING.md
=====================================
@@ -1,37 +1,45 @@
 # Releasing Pydecorate
 
-1. checkout master
+1. checkout main branch
 2. pull from repo
 3. run the unittests
-4. run `loghub`.  Replace <github username> and <previous version> with proper
-   values.  To get the previous version run `git tag` and select the most
-   recent with highest version number.
+4. run `loghub` and update the `CHANGELOG.md` file:
 
-```
-loghub pytroll/pydecorate -u <github username> -st v<previous version> -plg bug "Bugs fixed" -plg enhancement "Features added" -plg documentation "Documentation changes" -plg backwards-incompatibility "Backwards incompatible changes"
-```
+   ```
+   loghub pytroll/pydecorate --token $LOGHUB_GITHUB_TOKEN -st v<previous version> -plg bug "Bugs fixed" -plg enhancement "Features added" -plg documentation "Documentation changes" -plg backwards-incompatibility "Backward incompatible changes" -plg refactor "Refactoring"
+   ```
 
-This command will create a CHANGELOG.temp file which need to be added
-to the top of the CHANGELOG.md file.  The same content is also printed
-to terminal, so that can be copy-pasted, too.  Remember to update also
-the version number to the same given in step 5. Don't forget to commit
-CHANGELOG.md!
+   This uses a `LOGHUB_GITHUB_TOKEN` environment variable. This must be created
+   on GitHub and it is recommended that you add it to your `.bashrc` or
+   `.bash_profile` or equivalent.
 
-5. Create a tag with the new version number, starting with a 'v', eg:
+   This command will create a CHANGELOG.temp file which need to be added
+   to the top of the CHANGLOG.md file.  The same content is also printed
+   to terminal, so that can be copy-pasted, too.  Remember to update also
+   the version number to the same given in step 5. Don't forget to commit
+   CHANGELOG.md!
 
-```
-git tag -a v<new version> -m "Version <new version>"
-```
+5. Create a tag with the new version number, starting with a 'v', eg:
 
-For example if the previous tag was `v0.9.0` and the new release is a
-patch release, do:
+   ```
+   git tag -a v<new version> -m "Version <new version>"
+   ```
 
-```
-git tag -a v0.9.1 -m "Version 0.9.1"
-```
+   For example if the previous tag was `v0.9.0` and the new release is a
+   patch release, do:
 
-See [semver.org](http://semver.org/) on how to write a version number.
+   ```
+   git tag -a v0.9.1 -m "Version 0.9.1"
+   ```
 
+   See [semver.org](http://semver.org/) on how to write a version number..
 
 6. push changes to github `git push --follow-tags`
-7. Verify travis tests passed and deployed sdist and wheel to PyPI
+7. Verify github action unittests passed.
+8. Create a "Release" on GitHub by going to
+   https://github.com/pytroll/pydecorate/releases and clicking "Draft a new release".
+   On the next page enter the newly created tag in the "Tag version" field,
+   "Version X.Y.Z" in the "Release title" field, and paste the markdown from
+   the changelog (the portion under the version section header) in the
+   "Describe this release" box. Finally click "Publish release".
+9. Verify the GitHub actions for deployment succeed and the release is on PyPI.


=====================================
appveyor.yml deleted
=====================================
@@ -1,39 +0,0 @@
-environment:
-  global:
-    PYTHON: "C:\\conda"
-    MINICONDA_VERSION: "latest"
-    CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\ci-helpers\\appveyor\\windows_sdk.cmd"
-    CONDA_DEPENDENCIES: "sphinx pillow coverage trollimage aggdraw pytest pytest-cov"
-    PIP_DEPENDENCIES: ""
-    CONDA_CHANNELS: "conda-forge"
-
-  matrix:
-    - PYTHON: "C:\\Python38_64"
-      PYTHON_VERSION: "3.8"
-      PYTHON_ARCH: "64"
-      NUMPY_VERSION: "stable"
-
-install:
-    - "git clone --depth 1 git://github.com/astropy/ci-helpers.git"
-    - "powershell ci-helpers/appveyor/install-miniconda.ps1"
-    - "conda activate test"
-    - "pip install --no-deps -e ."
-
-build: false  # Not a C# project, build stuff at the test step instead.
-
-test_script:
-  # Build the compiled extension and run the project tests
-  - "%CMD_IN_ENV% pytest --cov=pydecorate test.py"
-
-after_test:
-  # If tests are successful, create a whl package for the project.
-  - "%CMD_IN_ENV% python setup.py bdist_wheel bdist_wininst"
-  - ps: "ls dist"
-
-artifacts:
-  # Archive the generated wheel package in the ci.appveyor.com build report.
-  - path: dist\*
-
-#on_success:
-#  - TODO: upload the content of dist/*.whl to a public wheelhouse
-#


=====================================
continuous_integration/environment.yaml
=====================================
@@ -0,0 +1,10 @@
+name: test-environment
+channels:
+  - conda-forge
+dependencies:
+  - numpy
+  - pillow
+  - aggdraw
+  - pytest
+  - pytest-cov
+  - trollimage


=====================================
doc/source/_static/.gitkeep
=====================================


=====================================
doc/source/conf.py
=====================================
@@ -10,208 +10,214 @@
 #
 # All configuration values have a default; values that are commented out
 # serve to show the default.
+"""Sphinx configuration file for pydecorate documentation."""
 
 # To generate apidoc modules:
 #     sphinx-apidoc -f -T -o source/api ../pydecorate ../pydecorate/tests
+from __future__ import annotations
 
 import os
 import sys
+
 from pkg_resources import get_distribution
 
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
-sys.path.insert(0, os.path.abspath('../../'))
+sys.path.insert(0, os.path.abspath("../../"))
 
 # -- General configuration -----------------------------------------------------
 
 # If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
+# needs_sphinx = '1.0'
 
 # Add any Sphinx extension module names here, as strings. They can be extensions
 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest']
+extensions = ["sphinx.ext.autodoc", "sphinx.ext.doctest"]
 
 # Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
 
 # The suffix of source filenames.
-source_suffix = '.rst'
+source_suffix = ".rst"
 
 # The encoding of source files.
-#source_encoding = 'utf-8-sig'
+# source_encoding = 'utf-8-sig'
 
 # The master toctree document.
-master_doc = 'index'
+master_doc = "index"
 
 # General information about the project.
-project = u'Pydecorate'
-copyright = u'2013, Hrobjartur Thorsteinsson'
+project = "Pydecorate"
+copyright = "2013, Hrobjartur Thorsteinsson"
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
 # built documents.
 #
 # get version using setuptools-scm
-release = get_distribution('pydecorate').version
+release = get_distribution("pydecorate").version
 # The full version, including alpha/beta/rc tags.
 # for example take major/minor
-version = '.'.join(release.split('.')[:2])
+version = ".".join(release.split(".")[:2])
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
-#language = None
+# language = None
 
 # There are two options for replacing |today|: either, you set today to some
 # non-false value, then it is used:
-#today = ''
+# today = ''
 # Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
+# today_fmt = '%B %d, %Y'
 
 # List of patterns, relative to source directory, that match files and
 # directories to ignore when looking for source files.
-exclude_patterns = []
+exclude_patterns: list[str] = []
 
 # The reST default role (used for this markup: `text`) to use for all documents.
-#default_role = None
+# default_role = None
 
 # If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
+# add_function_parentheses = True
 
 # If true, the current module name will be prepended to all description
 # unit titles (such as .. function::).
-#add_module_names = True
+# add_module_names = True
 
 # If true, sectionauthor and moduleauthor directives will be shown in the
 # output. They are ignored by default.
-#show_authors = False
+# show_authors = False
 
 # The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = "sphinx"
 
 # A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
+# modindex_common_prefix = []
 
 
 # -- Options for HTML output ---------------------------------------------------
 
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
-html_theme = 'default'
+html_theme = "default"
 
 # Theme options are theme-specific and customize the look and feel of a theme
 # further.  For a list of options available for each theme, see the
 # documentation.
-#html_theme_options = {}
+# html_theme_options = {}
 
 # Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
+# html_theme_path = []
 
 # The name for this set of Sphinx documents.  If None, it defaults to
 # "<project> v<release> documentation".
-#html_title = None
+# html_title = None
 
 # A shorter title for the navigation bar.  Default is the same as html_title.
-#html_short_title = None
+# html_short_title = None
 
 # The name of an image file (relative to this directory) to place at the top
 # of the sidebar.
-#html_logo = None
+# html_logo = None
 
 # The name of an image file (within the static path) to use as favicon of the
 # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
 # pixels large.
-#html_favicon = None
+# html_favicon = None
 
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css"..
-html_static_path = ['_static']
+html_static_path = ["_static"]
 
 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 # using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
+# html_last_updated_fmt = '%b %d, %Y'
 
 # If true, SmartyPants will be used to convert quotes and dashes to
 # typographically correct entities.
-#html_use_smartypants = True
+# html_use_smartypants = True
 
 # Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
+# html_sidebars = {}
 
 # Additional templates that should be rendered to pages, maps page names to
 # template names.
-#html_additional_pages = {}
+# html_additional_pages = {}
 
 # If false, no module index is generated.
-#html_domain_indices = True
+# html_domain_indices = True
 
 # If false, no index is generated.
-#html_use_index = True
+# html_use_index = True
 
 # If true, the index is split into individual pages for each letter.
-#html_split_index = False
+# html_split_index = False
 
 # If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
+# html_show_sourcelink = True
 
 # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
+# html_show_sphinx = True
 
 # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
+# html_show_copyright = True
 
 # If true, an OpenSearch description file will be output, and all pages will
 # contain a <link> tag referring to it.  The value of this option must be the
 # base URL from which the finished HTML is served.
-#html_use_opensearch = ''
+# html_use_opensearch = ''
 
 # This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
+# html_file_suffix = None
 
 # Output file base name for HTML help builder.
-htmlhelp_basename = 'Pydecoratedoc'
+htmlhelp_basename = "Pydecoratedoc"
 
 
 # -- Options for LaTeX output --------------------------------------------------
 
-latex_elements = {
-# The paper size ('letterpaper' or 'a4paper').
-#'papersize': 'letterpaper',
-
-# The font size ('10pt', '11pt' or '12pt').
-#'pointsize': '10pt',
-
-# Additional stuff for the LaTeX preamble.
-#'preamble': '',
+latex_elements: dict[str, str] = {
+    # The paper size ('letterpaper' or 'a4paper').
+    # 'papersize': 'letterpaper',
+    # The font size ('10pt', '11pt' or '12pt').
+    # 'pointsize': '10pt',
+    # Additional stuff for the LaTeX preamble.
+    # 'preamble': '',
 }
 
 # Grouping the document tree into LaTeX files. List of tuples
 # (source start file, target name, title, author, documentclass [howto/manual]).
 latex_documents = [
-  ('index', 'Pydecorate.tex', u'Pydecorate Documentation',
-   u'Hrobjartur Thorsteinsson', 'manual'),
+    (
+        "index",
+        "Pydecorate.tex",
+        "Pydecorate Documentation",
+        "Hrobjartur Thorsteinsson",
+        "manual",
+    ),
 ]
 
 # The name of an image file (relative to this directory) to place at the top of
 # the title page.
-#latex_logo = None
+# latex_logo = None
 
 # For "manual" documents, if this is true, then toplevel headings are parts,
 # not chapters.
-#latex_use_parts = False
+# latex_use_parts = False
 
 # If true, show page references after internal links.
-#latex_show_pagerefs = False
+# latex_show_pagerefs = False
 
 # If true, show URL addresses after external links.
-#latex_show_urls = False
+# latex_show_urls = False
 
 # Documents to append as an appendix to all manuals.
-#latex_appendices = []
+# latex_appendices = []
 
 # If false, no module index is generated.
-#latex_domain_indices = True
+# latex_domain_indices = True
 
 
 # -- Options for manual page output --------------------------------------------
@@ -219,12 +225,11 @@ latex_documents = [
 # One entry per manual page. List of tuples
 # (source start file, name, description, authors, manual section).
 man_pages = [
-    ('index', 'pydecorate', u'Pydecorate Documentation',
-     [u'Hrobjartur Thorsteinsson'], 1)
+    ("index", "pydecorate", "Pydecorate Documentation", ["Hrobjartur Thorsteinsson"], 1)
 ]
 
 # If true, show URL addresses after external links.
-#man_show_urls = False
+# man_show_urls = False
 
 
 # -- Options for Texinfo output ------------------------------------------------
@@ -233,16 +238,22 @@ man_pages = [
 # (source start file, target name, title, author,
 #  dir menu entry, description, category)
 texinfo_documents = [
-  ('index', 'Pydecorate', u'Pydecorate Documentation',
-   u'Hrobjartur Thorsteinsson', 'Pydecorate', 'One line description of project.',
-   'Miscellaneous'),
+    (
+        "index",
+        "Pydecorate",
+        "Pydecorate Documentation",
+        "Hrobjartur Thorsteinsson",
+        "Pydecorate",
+        "One line description of project.",
+        "Miscellaneous",
+    ),
 ]
 
 # Documents to append as an appendix to all manuals.
-#texinfo_appendices = []
+# texinfo_appendices = []
 
 # If false, no module index is generated.
-#texinfo_domain_indices = True
+# texinfo_domain_indices = True
 
 # How to display URL addresses: 'footnote', 'no', or 'inline'.
-#texinfo_show_urls = 'footnote'
+# texinfo_show_urls = 'footnote'


=====================================
doc/source/index.rst
=====================================
@@ -33,4 +33,3 @@ Indices and tables
 * :ref:`genindex`
 * :ref:`modindex`
 * :ref:`search`
-


=====================================
doc/source/usage.rst
=====================================
@@ -34,7 +34,7 @@ demonstration logos:
 
   >>> dc.add_logo("logos/pytroll_light_big.png",height=80.0)
   >>> dc.add_logo("logos/NASA_Logo.gif")
-  >>> 
+  >>>
   >>> img.show()
 
 .. image:: images/logo_image.png
@@ -49,7 +49,7 @@ To add text, you could do:
   >>> dc.add_text("MSG SEVIRI\nThermal blue marble\n1/1/1977 00:00")
   >>> dc.add_logo("logos/pytroll_light_big.png")
   >>> dc.add_logo("logos/NASA_Logo.gif")
-  >>> 
+  >>>
   >>> img.show()
 
 .. image:: images/text_and_logo_image.png
@@ -74,7 +74,7 @@ scales and legends (DRAFT)
 ++++++++++++++++++++++++++
 
 PyDecorate can work with trollimage colormap objects to add colour scales.
-To add some logos along with a standard scale feature based on the 'rdbu' 
+To add some logos along with a standard scale feature based on the 'rdbu'
 scale from trollmap one might do as follows,
 
   >>> from trollimage.colormap import rdbu
@@ -83,14 +83,14 @@ scale from trollmap one might do as follows,
   >>> dc.add_logo("logos/pytroll_light_big.png")
   >>> dc.add_logo("logos/NASA_Logo.gif")
   >>> dc.add_scale(rdbu,extend=True)
-  
+
 .. image:: images/logo_and_scale1.png
     :width: 400px
     :align: center
 
 Note that the extend=True option sets the scale feature to extend to the full
 available space. Without this option the scale will inherit the previous width,
-or one might pass a specific width as argument. See more on this topic in the 
+or one might pass a specific width as argument. See more on this topic in the
 Styles section below.
 
 Off course the placement of the logos and scale feature is very flexible..
@@ -137,7 +137,7 @@ can be changed.
 alignment
 +++++++++
 
-Continuing from the previous example, 
+Continuing from the previous example,
 we can align the cursor to the bottom-right corner, by executing
 
   >>> dc.align_right()
@@ -242,5 +242,3 @@ to be repeated on successive calls,
 .. image:: images/style_retention.png
     :width: 400px
     :align: center
-
-


=====================================
pydecorate/__init__.py
=====================================
@@ -1,19 +1,19 @@
-# pydecorate - python module for labelling 
-# and adding colour scales to images
-# 
-#Copyright (C) 2011  Hrobjartur Thorsteinsson
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
 #
-#This program is free software: you can redistribute it and/or modify
-#it under the terms of the GNU General Public License as published by
-#the Free Software Foundation, either version 3 of the License, or
-#(at your option) any later version.
+# Copyright (C) 2011  Hrobjartur Thorsteinsson
 #
-#This program is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#GNU General Public License for more details.
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
 #
-#You should have received a copy of the GNU General Public License
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 try:
@@ -22,4 +22,4 @@ except ImportError:
     # package is not installed
     pass
 
-from pydecorate.decorator_agg import DecoratorAGG
+from pydecorate.decorator_agg import DecoratorAGG  # noqa


=====================================
pydecorate/decorator.py
=====================================
@@ -1,12 +1,12 @@
-# pydecorate - python module for labelling
-# and adding colour scales to images
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
 #
 # Copyright (C) 2011  Hrobjartur Thorsteinsson
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
-#(at your option) any later version.
+# (at your option) any later version.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -15,25 +15,49 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""PIL-based image decoration class."""
 
-try:
-    from PIL import ImageDraw
-except ImportError:
-    print("ImportError: Missing module: ImageDraw")
+from PIL import ImageDraw, ImageFont
 
 from .decorator_base import DecoratorBase
 
 
 class Decorator(DecoratorBase):
+    """PIL-based image decoration class."""
 
-    def add_scale(self, color_def, font=None, size=None, fill='black',
-                  outline=None, outline_width=1, bg='white', extend=False, unit='', margins=None, minortick=0.0,
-                  nan_color=(0, 0, 0), nan_check_color=(1, 1, 1), nan_check_size=0):
-        """ Todo
-        """
-        self._add_scale(color_def, font=font, size=size, fill=fill,
-                        outline=outline, outline_widht=outline_width, bg=bg, extend=extend, unit=unit, margins=margins, minortick=minortick,
-                        nan_color=nan_color, nan_check_color=nan_check_color, nan_check_size=nan_check_size)
+    def add_scale(
+        self,
+        color_def,
+        font=None,
+        size=None,
+        fill="black",
+        outline=None,
+        outline_width=1,
+        bg="white",
+        extend=False,
+        unit="",
+        margins=None,
+        minortick=0.0,
+        nan_color=(0, 0, 0),
+        nan_check_color=(1, 1, 1),
+        nan_check_size=0,
+    ):
+        self._add_scale(
+            color_def,
+            font=font,
+            size=size,
+            fill=fill,
+            outline=outline,
+            outline_widht=outline_width,
+            bg=bg,
+            extend=extend,
+            unit=unit,
+            margins=margins,
+            minortick=minortick,
+            nan_color=nan_color,
+            nan_check_color=nan_check_color,
+            nan_check_size=nan_check_size,
+        )
 
     def _load_default_font(self):
         return ImageFont.load_default()
@@ -45,6 +69,5 @@ class Decorator(DecoratorBase):
         self._add_logo(logo_path, **kwargs)
 
     def _get_canvas(self, image):
-        """Returns PIL image object
-        """
+        """Return PIL image object."""
         return ImageDraw.Draw(image)


=====================================
pydecorate/decorator_agg.py
=====================================
@@ -1,12 +1,12 @@
-# pydecorate - python module for labelling
-# and adding colour scales to images
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
 #
-# Copyright (C) 2011, 2016  Hrobjartur Thorsteinsson
+# Copyright (C) 2011  Hrobjartur Thorsteinsson
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
-#(at your option) any later version.
+# (at your option) any later version.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -15,23 +15,29 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""Aggdraw-based image decoration class."""
+
+import aggdraw
+from pkg_resources import resource_filename
+
 from pydecorate.decorator_base import DecoratorBase
 
+
 class DecoratorAGG(DecoratorBase):
+    """Aggdraw-based image decoration class."""
 
     def add_scale(self, colormap, **kwargs):
         self._add_scale(colormap, **kwargs)
 
     def _load_default_font(self):
-        import aggdraw
-        from pkg_resources import resource_filename
-        font_path = resource_filename('pydecorate.fonts', 'DejaVuSerif.ttf')
-        return aggdraw.Font('black', font_path, size=16)
+        font_path = resource_filename("pydecorate.fonts", "DejaVuSerif.ttf")
+        return aggdraw.Font("black", font_path, size=16)
 
     def _load_font(self):
-        import aggdraw
         try:
-            return aggdraw.Font(self.style['line'], self.style['font'], self.style['font_size'])
+            return aggdraw.Font(
+                self.style["line"], self.style["font"], self.style["font_size"]
+            )
         except IOError:
             raise
 
@@ -42,23 +48,28 @@ class DecoratorAGG(DecoratorBase):
         self._add_logo(logo_path, **kwargs)
 
     def _get_canvas(self, image):
-        """Returns AGG image object
-        """
-        import aggdraw
+        """Return AGG image object."""
         return aggdraw.Draw(image)
 
     def _finalize(self, draw):
-        """Flush the AGG image object
-        """
+        """Flush the AGG image object."""
         draw.flush()
 
-    def _draw_text_line(self, draw, xy, text, font, fill='black'):
+    def _draw_text_line(self, draw, xy, text, font, fill="black"):
         draw.text(xy, text, font)
 
-    def _draw_rectangle(self, draw, xys, outline='white', bg='white', bg_opacity=255, outline_width=1, outline_opacity=255, **kwargs):
-        import aggdraw
-        pen = aggdraw.Pen(
-            outline, width=outline_width, opacity=outline_opacity)
+    def _draw_rectangle(
+        self,
+        draw,
+        xys,
+        outline="white",
+        bg="white",
+        bg_opacity=255,
+        outline_width=1,
+        outline_opacity=255,
+        **kwargs,
+    ):
+        pen = aggdraw.Pen(outline, width=outline_width, opacity=outline_opacity)
         brush = aggdraw.Brush(bg, opacity=bg_opacity)
         # draw bg and outline
         # bg unaliased (otherwise gaps between successive bgs)
@@ -77,41 +88,34 @@ class DecoratorAGG(DecoratorBase):
             draw.rectangle(xys, pen, None)
 
     def _draw_line(self, draw, xys, **kwargs):
-        import aggdraw
-        if kwargs['line'] is None:
+        if kwargs["line"] is None:
             pen = None
         else:
             pen = aggdraw.Pen(
-                kwargs['line'], width=kwargs['line_width'], opacity=kwargs['line_opacity'])
+                kwargs["line"],
+                width=kwargs["line_width"],
+                opacity=kwargs["line_opacity"],
+            )
         xys_straight = [item for t in xys for item in t]
         draw.line(xys_straight, pen)
 
-    def _draw_polygon(self, draw, xys, outline=None, fill='white', fill_opacity=255, outline_width=1, outline_opacity=255):
-        import aggdraw
+    def _draw_polygon(
+        self,
+        draw,
+        xys,
+        outline=None,
+        fill="white",
+        fill_opacity=255,
+        outline_width=1,
+        outline_opacity=255,
+    ):
         if outline is None:
             pen = None
         else:
-            pen = aggdraw.Pen(
-                outline, width=outline_width, opacity=outline_opacity)
+            pen = aggdraw.Pen(outline, width=outline_width, opacity=outline_opacity)
         if fill is None:
             brush = None
         else:
             brush = aggdraw.Brush(fill, opacity=fill_opacity)
         xys_straight = [item for t in xys for item in t]
         draw.polygon(xys_straight, pen, brush)
-
-
-#########################################
-# float list generator
-def _frange(x, y, jump):
-    while x < y:
-        yield x
-        x += jump
-
-
-def frange(x, y, jump):
-    return [p for p in _frange(x, y, jump)]
-
-
-class ShapeFileError(Exception):
-    pass


=====================================
pydecorate/decorator_base.py
=====================================
@@ -1,12 +1,12 @@
-# pydecorate - python module for labelling
-# and adding colour scales to images
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
 #
-# Copyright (C) 2011, 2016  Hrobjartur Thorsteinsson
+# Copyright (C) 2011  Hrobjartur Thorsteinsson
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
-#(at your option) any later version.
+# (at your option) any later version.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -15,166 +15,157 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""Base class and utilities for decorating images."""
 
-import numpy as np
+import copy
 
-try:
-    from PIL import Image, ImageFont
-except ImportError:
-    print("ImportError: Missing PIL image objects")
+import numpy as np
+from PIL import Image
 
 try:
-    from PIL import ImageDraw
+    from trollimage.image import Image as TImage
 except ImportError:
-    print("ImportError: Missing module: ImageDraw")
+    TImage = None
 
 # style dictionary defines default options
 # some only used by aggdraw version of the decorator
 default_style_dict = {
-    'cursor': [0, 0],
-    'margins': [5, 5],
-    'height': 60,
-    'width': 60,
-    'propagation': [1, 0],
-    'newline_propagation': [0, 1],
-    'alignment': [0.0, 0.0],
-    'bg': 'white',
-    'bg_opacity': 127,
-    'line': "black",
-    'line_width': 1,
-    'line_opacity': 255,
-    'outline': None,
-    'outline_width': 1,
-    'outline_opacity': 255,
-    'fill': 'black',
-    'fill_opacity': 255,
-    'font': None,
-    'font_size': 16,
-    'start_border': [0, 0],
-    'extend': False,
-    'tick_marks': 1.0,
-    'minor_tick_marks': 0.5,
-    'unit': None
+    "cursor": [0, 0],
+    "margins": [5, 5],
+    "height": 60,
+    "width": 60,
+    "propagation": [1, 0],
+    "newline_propagation": [0, 1],
+    "alignment": [0.0, 0.0],
+    "bg": "white",
+    "bg_opacity": 127,
+    "line": "black",
+    "line_width": 1,
+    "line_opacity": 255,
+    "outline": None,
+    "outline_width": 1,
+    "outline_opacity": 255,
+    "fill": "black",
+    "fill_opacity": 255,
+    "font": None,
+    "font_size": 16,
+    "start_border": [0, 0],
+    "extend": False,
+    "tick_marks": 1.0,
+    "minor_tick_marks": 0.5,
+    "unit": None,
 }
 
 
 class DecoratorBase(object):
+    """Base class for drawing decorations on an Image."""
 
     def __init__(self, image):
-        """
-        Probably users only want to instantiate DecoratorAgg or the Decorator implementations.
-        DecoratorBase is a base class outlining common operations and interface for the Decorator (PIL drawing engine) and DecoratorAgg (Aggdraw drawing engine)
-        """
+        """Initialize decorator and create handle to provided image object."""
         self.image = image
-
-        import copy
         self.style = copy.deepcopy(default_style_dict)
 
     def set_style(self, **kwargs):
         self.style.update(kwargs)
-        self.style['cursor'] = list(self.style['cursor'])
+        self.style["cursor"] = list(self.style["cursor"])
 
     def _finalize(self, draw):
-        """Do any need finalization of the drawing
-        """
+        """Do any needed finalization of the drawing."""
         pass
 
     def write_vertically(self):
         # top-right
-        if self.style['alignment'][0] == 1.0 and self.style['alignment'][1] == 0.0:
-            self.style['propagation'] = [0, 1]
-            self.style['newline_propagation'] = [-1, 0]
+        if self.style["alignment"][0] == 1.0 and self.style["alignment"][1] == 0.0:
+            self.style["propagation"] = [0, 1]
+            self.style["newline_propagation"] = [-1, 0]
         # bottom-right
-        elif self.style['alignment'][0] == 1.0 and self.style['alignment'][1] == 1.0:
-            self.style['propagation'] = [0, -1]
-            self.style['newline_propagation'] = [-1, 0]
+        elif self.style["alignment"][0] == 1.0 and self.style["alignment"][1] == 1.0:
+            self.style["propagation"] = [0, -1]
+            self.style["newline_propagation"] = [-1, 0]
         # bottom-left
-        elif self.style['alignment'][0] == 0.0 and self.style['alignment'][1] == 1.0:
-            self.style['propagation'] = [0, -1]
-            self.style['newline_propagation'] = [1, 0]
+        elif self.style["alignment"][0] == 0.0 and self.style["alignment"][1] == 1.0:
+            self.style["propagation"] = [0, -1]
+            self.style["newline_propagation"] = [1, 0]
         # top-left and other alignments
         else:
-            self.style['propagation'] = [0, 1]
-            self.style['newline_propagation'] = [1, 0]
+            self.style["propagation"] = [0, 1]
+            self.style["newline_propagation"] = [1, 0]
 
     def write_horizontally(self):
         # top-right
-        if self.style['alignment'][0] == 1.0 and self.style['alignment'][1] == 0.0:
-            self.style['propagation'] = [-1, 0]
-            self.style['newline_propagation'] = [0, 1]
+        if self.style["alignment"][0] == 1.0 and self.style["alignment"][1] == 0.0:
+            self.style["propagation"] = [-1, 0]
+            self.style["newline_propagation"] = [0, 1]
         # bottom-right
-        elif self.style['alignment'][0] == 1.0 and self.style['alignment'][1] == 1.0:
-            self.style['propagation'] = [-1, 0]
-            self.style['newline_propagation'] = [0, -1]
+        elif self.style["alignment"][0] == 1.0 and self.style["alignment"][1] == 1.0:
+            self.style["propagation"] = [-1, 0]
+            self.style["newline_propagation"] = [0, -1]
         # bottom-left
-        elif self.style['alignment'][0] == 0.0 and self.style['alignment'][1] == 1.0:
-            self.style['propagation'] = [1, 0]
-            self.style['newline_propagation'] = [0, -1]
+        elif self.style["alignment"][0] == 0.0 and self.style["alignment"][1] == 1.0:
+            self.style["propagation"] = [1, 0]
+            self.style["newline_propagation"] = [0, -1]
         # top-left and other alignments
         else:
-            self.style['propagation'] = [1, 0]
-            self.style['newline_propagation'] = [0, 1]
+            self.style["propagation"] = [1, 0]
+            self.style["newline_propagation"] = [0, 1]
 
     def align_bottom(self):
-        if self.style['alignment'][1] != 1.0:
-            self.style['alignment'][1] = 1.0
-            self.style['newline_propagation'][
-                1] = -self.style['newline_propagation'][1]
-            self.style['propagation'][1] = -self.style['propagation'][1]
+        if self.style["alignment"][1] != 1.0:
+            self.style["alignment"][1] = 1.0
+            self.style["newline_propagation"][1] = -self.style["newline_propagation"][1]
+            self.style["propagation"][1] = -self.style["propagation"][1]
             self.home()
 
     def align_top(self):
-        if self.style['alignment'][1] != 0.0:
-            self.style['alignment'][1] = 0.0
-            self.style['newline_propagation'][
-                1] = -self.style['newline_propagation'][1]
-            self.style['propagation'][1] = -self.style['propagation'][1]
+        if self.style["alignment"][1] != 0.0:
+            self.style["alignment"][1] = 0.0
+            self.style["newline_propagation"][1] = -self.style["newline_propagation"][1]
+            self.style["propagation"][1] = -self.style["propagation"][1]
             self.home()
 
     def align_right(self):
-        if self.style['alignment'][0] != 1.0:
-            self.style['alignment'][0] = 1.0
-            self.style['propagation'][0] = -self.style['propagation'][0]
-            self.style['newline_propagation'][
-                0] = -self.style['newline_propagation'][0]
+        if self.style["alignment"][0] != 1.0:
+            self.style["alignment"][0] = 1.0
+            self.style["propagation"][0] = -self.style["propagation"][0]
+            self.style["newline_propagation"][0] = -self.style["newline_propagation"][0]
             self.home()
 
     def align_left(self):
-        if self.style['alignment'][0] != 0.0:
-            self.style['alignment'][0] = 0.0
-            self.style['propagation'][0] = -self.style['propagation'][0]
-            self.style['newline_propagation'][
-                0] = -self.style['newline_propagation'][0]
+        if self.style["alignment"][0] != 0.0:
+            self.style["alignment"][0] = 0.0
+            self.style["propagation"][0] = -self.style["propagation"][0]
+            self.style["newline_propagation"][0] = -self.style["newline_propagation"][0]
             self.home()
 
     def home(self):
-        self.style['cursor'][0] = int(
-            self.style['alignment'][0] * self.image.size[0])
-        self.style['cursor'][1] = int(
-            self.style['alignment'][1] * self.image.size[1])
+        self.style["cursor"][0] = int(self.style["alignment"][0] * self.image.size[0])
+        self.style["cursor"][1] = int(self.style["alignment"][1] * self.image.size[1])
 
     def rewind(self):
-        if self.style['newline_propagation'][0] == 0:
-            self.style['cursor'][0] = int(
-                self.style['alignment'][0] * self.image.size[0])
-        if self.style['newline_propagation'][1] == 0:
-            self.style['cursor'][1] = int(
-                self.style['alignment'][1] * self.image.size[1])
+        if self.style["newline_propagation"][0] == 0:
+            self.style["cursor"][0] = int(
+                self.style["alignment"][0] * self.image.size[0]
+            )
+        if self.style["newline_propagation"][1] == 0:
+            self.style["cursor"][1] = int(
+                self.style["alignment"][1] * self.image.size[1]
+            )
 
     def new_line(self):
+        """Set position of next decoration under the previously added decorations."""
         # set new line
-        self.style['cursor'][
-            0] += self.style['newline_propagation'][0] * self.style['width']
-        self.style['cursor'][
-            1] += self.style['newline_propagation'][1] * self.style['height']
+        self.style["cursor"][0] += (
+            self.style["newline_propagation"][0] * self.style["width"]
+        )
+        self.style["cursor"][1] += (
+            self.style["newline_propagation"][1] * self.style["height"]
+        )
         # rewind
         self.rewind()
 
     def _step_cursor(self):
-        self.style['cursor'][
-            0] += self.style['propagation'][0] * self.style['width']
-        self.style['cursor'][1] += self.style['propagation'][1] * \
-            self.style['height']
+        self.style["cursor"][0] += self.style["propagation"][0] * self.style["width"]
+        self.style["cursor"][1] += self.style["propagation"][1] * self.style["height"]
 
     # def start_border(self):
     #    self.style['start_border'] = list( self.style['cursor'] )
@@ -189,7 +180,7 @@ class DecoratorBase(object):
     #    self._finalize(draw)
 
     def _draw_polygon(self, draw, xys, **kwargs):
-        draw.polygon(xys, fill=kwargs['fill'], outline=kwargs['outline'])
+        draw.polygon(xys, fill=kwargs["fill"], outline=kwargs["outline"])
 
     def _get_canvas(self, img):
         raise NotImplementedError("Derived class implements this.")
@@ -197,11 +188,10 @@ class DecoratorBase(object):
     def _load_default_font(self):
         raise NotImplementedError("Derived class implements this.")
 
-    def _draw_text(self, draw, xy, txt, font, fill='black', align='cc', dry_run=False, **kwargs):
-        """
-        Elementary text draw routine,
-        with alignment. Returns text size.
-        """
+    def _draw_text(
+        self, draw, xy, txt, font, fill="black", align="cc", dry_run=False, **kwargs
+    ):
+        """Elementary text draw routine, with alignment. Returns text size."""
         # check for font object
         if font is None:
             font = self._load_default_font()
@@ -211,13 +201,13 @@ class DecoratorBase(object):
 
         # align text position
         x, y = xy
-        if align[0] == 'c':
+        if align[0] == "c":
             x -= tw / 2.0
-        elif align[0] == 'r':
+        elif align[0] == "r":
             x -= tw
-        if align[1] == 'c':
+        if align[1] == "c":
             y -= th / 2.0
-        elif align[1] == 'r':
+        elif align[1] == "r":
             y -= th
 
         # draw the text
@@ -227,23 +217,23 @@ class DecoratorBase(object):
         return tw, th
 
     def _check_align(self):
-        if 'align' in self.style:
-            if 'top_bottom' in self.style['align']:
-                if self.style['align']['top_bottom'] == 'top':
+        if "align" in self.style:
+            if "top_bottom" in self.style["align"]:
+                if self.style["align"]["top_bottom"] == "top":
                     self.align_top()
-                elif self.style['align']['top_bottom'] == 'bottom':
+                elif self.style["align"]["top_bottom"] == "bottom":
                     self.align_bottom()
-            if 'left_right' in self.style['align']:
-                if self.style['align']['left_right'] == 'left':
+            if "left_right" in self.style["align"]:
+                if self.style["align"]["left_right"] == "left":
                     self.align_left()
-                elif self.style['align']['left_right'] == 'right':
+                elif self.style["align"]["left_right"] == "right":
                     self.align_right()
 
     def _get_current_font(self):
-        if self.style['font'] is None:
-            self.style['font'] = self._load_default_font()
-        elif isinstance(self.style['font'], str):
-            self.style['font'] = self._load_font()
+        if self.style["font"] is None:
+            self.style["font"] = self._load_default_font()
+        elif isinstance(self.style["font"], str):
+            self.style["font"] = self._load_font()
         else:
             pass  # assume self.style['font'] has already been assigned as Font obj. FIXME
 
@@ -262,36 +252,34 @@ class DecoratorBase(object):
         x_size, y_size = self.image.size
 
         # split text into newlines '\n'
-        txt_nl = txt.split('\n')
+        txt_nl = txt.split("\n")
 
         # current xy and margins
-        x = self.style['cursor'][0]
-        y = self.style['cursor'][1]
-        mx = self.style['margins'][0]
-        my = self.style['margins'][1]
-        prev_width = self.style['width']
-        prev_height = self.style['height']
+        x = self.style["cursor"][0]
+        y = self.style["cursor"][1]
+        mx = self.style["margins"][0]
+        my = self.style["margins"][1]
+        # prev_width = self.style["width"]
+        prev_height = self.style["height"]
 
         # calculate text space
-        tw, th = draw.textsize(txt_nl[0], self.style['font'])
+        tw, th = draw.textsize(txt_nl[0], self.style["font"])
         for t in txt_nl:
-            w, tmp = draw.textsize(t, self.style['font'])
+            w, tmp = draw.textsize(t, self.style["font"])
             if w > tw:
                 tw = w
         hh = len(txt_nl) * th
 
         # set height/width for subsequent draw operations
         if prev_height < int(hh + 2 * my):
-            self.style['height'] = int(hh + 2 * my)
-        self.style['width'] = int(tw + 2 * mx)
+            self.style["height"] = int(hh + 2 * my)
+        self.style["width"] = int(tw + 2 * mx)
 
         # draw base
-        px = (self.style['propagation'][0] +
-              self.style['newline_propagation'][0])
-        py = (self.style['propagation'][1] +
-              self.style['newline_propagation'][1])
+        px = self.style["propagation"][0] + self.style["newline_propagation"][0]
+        py = self.style["propagation"][1] + self.style["newline_propagation"][1]
         x1 = x + px * (tw + 2 * mx)
-        y1 = y + py * self.style['height']
+        y1 = y + py * self.style["height"]
         self._draw_rectangle(draw, [x, y, x1, y1], **self.style)
 
         # draw
@@ -299,11 +287,16 @@ class DecoratorBase(object):
             pos_x = x + mx
             pos_y = y + i * th + my
             if py < 0:
-                pos_y += py * self.style['height']
+                pos_y += py * self.style["height"]
             if px < 0:
-                pos_x += px * self.style['width']
+                pos_x += px * self.style["width"]
             self._draw_text_line(
-                draw, (pos_x, pos_y), txt_nl[i], self.style['font'], fill=self.style['fill'])
+                draw,
+                (pos_x, pos_y),
+                txt_nl[i],
+                self.style["font"],
+                fill=self.style["fill"],
+            )
 
         # update cursor
         self._step_cursor()
@@ -311,19 +304,19 @@ class DecoratorBase(object):
         # finalize
         self._finalize(draw)
 
-    def _draw_text_line(self, draw, xy, text, font, fill='black'):
+    def _draw_text_line(self, draw, xy, text, font, fill="black"):
         draw.text(xy, text, font=font, fill=fill)
 
     def _draw_line(self, draw, xys, **kwargs):
         # inconvenient to use fill for a line so swapped def.
-        draw.line(xys, fill=kwargs['line'])
+        draw.line(xys, fill=kwargs["line"])
 
     def _draw_rectangle(self, draw, xys, **kwargs):
         # adjust extent of rectangle to draw up to but not including xys[2/3]
         xys[2] -= 1
         xys[3] -= 1
-        if kwargs['bg'] or kwargs['outline']:
-            draw.rectangle(xys, fill=kwargs['bg'], outline=kwargs['outline'])
+        if kwargs["bg"] or kwargs["outline"]:
+            draw.rectangle(xys, fill=kwargs["bg"], outline=kwargs["outline"])
 
     def _add_logo(self, logo_path, **kwargs):
         # synchronize kwargs into style
@@ -331,17 +324,17 @@ class DecoratorBase(object):
         self._check_align()
 
         # current xy and margins
-        x = self.style['cursor'][0]
-        y = self.style['cursor'][1]
+        x = self.style["cursor"][0]
+        y = self.style["cursor"][1]
 
-        mx = self.style['margins'][0]
-        my = self.style['margins'][1]
+        mx = self.style["margins"][0]
+        my = self.style["margins"][1]
 
         # draw object
         draw = self._get_canvas(self.image)
 
         # get logo image
-        logo = Image.open(logo_path, "r").convert('RGBA')
+        logo = Image.open(logo_path, "r").convert("RGBA")
 
         # default size is _line_size set by previous draw operation
         # else do not resize
@@ -350,24 +343,22 @@ class DecoratorBase(object):
 
         # default logo sizes ...
         # use previously set line_size
-        if self.style['propagation'][0] != 0:
-            ny = self.style['height']
+        if self.style["propagation"][0] != 0:
+            ny = self.style["height"]
             nyi = int(round(ny - 2 * my))
             nxi = int(round(nyi / aspect))
-            nx = (nxi + 2 * mx)
-        elif self.style['propagation'][1] != 0:
-            nx = self.style['width']
+            nx = nxi + 2 * mx
+        elif self.style["propagation"][1] != 0:
+            nx = self.style["width"]
             nxi = int(round(nx - 2 * mx))
             nyi = int(round(nxi * aspect))
-            ny = (nyi + 2 * my)
+            ny = nyi + 2 * my
 
         logo = logo.resize((nxi, nyi), resample=Image.ANTIALIAS)
 
         # draw base
-        px = (self.style['propagation'][0] +
-              self.style['newline_propagation'][0])
-        py = (self.style['propagation'][1] +
-              self.style['newline_propagation'][1])
+        px = self.style["propagation"][0] + self.style["newline_propagation"][0]
+        py = self.style["propagation"][1] + self.style["newline_propagation"][1]
         box = [x, y, x + px * nx, y + py * ny]
         self._draw_rectangle(draw, box, **self.style)
 
@@ -375,13 +366,12 @@ class DecoratorBase(object):
         self._finalize(draw)
 
         # paste logo
-        box = [x + px * mx, y + py * my, x + px *
-               mx + px * nxi, y + py * my + py * nyi]
+        box = [x + px * mx, y + py * my, x + px * mx + px * nxi, y + py * my + py * nyi]
         self._insert_RGBA_image(logo, box)
 
         # update cursor
-        self.style['width'] = int(nx)
-        self.style['height'] = int(ny)
+        self.style["width"] = int(nx)
+        self.style["height"] = int(ny)
         self._step_cursor()
 
     def _add_scale(self, colormap, title=None, **kwargs):
@@ -389,42 +379,34 @@ class DecoratorBase(object):
         self.set_style(**kwargs)
 
         # sizes, current xy and margins
-        x = self.style['cursor'][0]
-        y = self.style['cursor'][1]
-        mx = self.style['margins'][0]
-        my = self.style['margins'][1]
+        x = self.style["cursor"][0]
+        y = self.style["cursor"][1]
+        mx = self.style["margins"][0]
+        my = self.style["margins"][1]
         x_size, y_size = self.image.size
 
         # horizontal/vertical?
-        is_vertical = False
-        if self.style['propagation'][1] != 0:
-            is_vertical = True
-
+        is_vertical = self.style["propagation"][1] != 0
         # left/right?
-        is_right = False
-        if self.style['alignment'][0] == 1.0:
-            is_right = True
-
+        is_right = self.style["alignment"][0] == 1.0
         # top/bottom?
-        is_bottom = False
-        if self.style['alignment'][1] == 1.0:
-            is_bottom = True
+        is_bottom = self.style["alignment"][1] == 1.0
 
         # adjust new size based on extend (fill space) style,
-        if self.style['extend']:
-            if self.style['propagation'][0] == 1:
-                self.style['width'] = (x_size - x)
-            elif self.style['propagation'][0] == -1:
-                self.style['width'] = x
-            if self.style['propagation'][1] == 1:
-                self.style['height'] = (y_size - y)
-            elif self.style['propagation'][1] == -1:
-                self.style['height'] = y
+        if self.style["extend"]:
+            if self.style["propagation"][0] == 1:
+                self.style["width"] = x_size - x
+            elif self.style["propagation"][0] == -1:
+                self.style["width"] = x
+            if self.style["propagation"][1] == 1:
+                self.style["height"] = y_size - y
+            elif self.style["propagation"][1] == -1:
+                self.style["height"] = y
 
         # set scale spacer for units and other
         x_spacer = 0
         y_spacer = 0
-        if self.style['unit']:
+        if self.style["unit"]:
             if is_vertical:
                 y_spacer = 40
             else:
@@ -437,35 +419,22 @@ class DecoratorBase(object):
         self._get_current_font()
 
         # draw base
-        px = (self.style['propagation'][0] +
-              self.style['newline_propagation'][0])
-        py = (self.style['propagation'][1] +
-              self.style['newline_propagation'][1])
-        x1 = x + px * self.style['width']
-        y1 = y + py * self.style['height']
+        px = self.style["propagation"][0] + self.style["newline_propagation"][0]
+        py = self.style["propagation"][1] + self.style["newline_propagation"][1]
+        x1 = x + px * self.style["width"]
+        y1 = y + py * self.style["height"]
         self._draw_rectangle(draw, [x, y, x1, y1], **self.style)
 
         # scale dimensions
-        scale_width = self.style['width'] - 2 * mx - x_spacer
-        scale_height = self.style['height'] - 2 * my - y_spacer
+        scale_width = self.style["width"] - 2 * mx - x_spacer
+        scale_height = self.style["height"] - 2 * my - y_spacer
 
         # generate color scale image obj inset by margin size mx my,
-        from trollimage.image import Image as TImage
-
-        #### THIS PART TO BE INGESTED INTO A COLORMAP FUNCTION ####
+        # TODO: THIS PART TO BE INGESTED INTO A COLORMAP FUNCTION
         minval, maxval = colormap.values[0], colormap.values[-1]
-        
-        if is_vertical:
-            linedata = np.ones(
-                (scale_width, 1)) * np.arange(minval, maxval, float(maxval - minval) / scale_height)
-            linedata = linedata.transpose()
-        else:
-            linedata = np.ones(
-                (scale_height, 1)) * np.arange(minval, maxval, float(maxval - minval) / scale_width)
-
-        timg = TImage(linedata, mode="L")
-        timg.colorize(colormap)
-        scale = timg.pil_image()
+        scale = _create_colorbar_image(
+            colormap, minval, maxval, scale_height, scale_width, is_vertical
+        )
         ###########################################################
 
         # finalize (must be before paste)
@@ -477,100 +446,287 @@ class DecoratorBase(object):
 
         # reload draw object
         draw = self._get_canvas(self.image)
+        self._draw_colorbar_ticks(
+            draw,
+            minval,
+            maxval,
+            is_vertical,
+            scale_width,
+            scale_height,
+            px,
+            py,
+            mx,
+            my,
+            x,
+            y,
+        )
+
+        # draw unit and/or power if set
+        if self.style["unit"]:
+            self._add_colorbar_units(
+                draw,
+                self.style["unit"],
+                is_vertical,
+                is_right,
+                is_bottom,
+                x,
+                y,
+                mx,
+                my,
+                scale_width,
+                scale_height,
+                x_spacer,
+                y_spacer,
+            )
+
+        if title:
+            self._add_colorbar_title(
+                draw,
+                title,
+                is_vertical,
+                is_right,
+                is_bottom,
+                x,
+                y,
+                mx,
+                my,
+                scale_width,
+                scale_height,
+            )
 
-        # draw tick marks
-        val_steps = _round_arange2(minval, maxval, self.style['tick_marks'])
-        minor_steps = _round_arange(
-            minval, maxval, self.style['minor_tick_marks'])
+        # finalize
+        self._finalize(draw)
 
-        ffra, fpow = _optimize_scale_numbers(
-            minval, maxval, self.style['tick_marks'])
+    def _draw_colorbar_ticks(
+        self,
+        draw,
+        minval,
+        maxval,
+        is_vertical,
+        scale_width,
+        scale_height,
+        px,
+        py,
+        mx,
+        my,
+        x,
+        y,
+    ):
+        val_steps = _round_arange2(minval, maxval, self.style["tick_marks"])
+        minor_steps = _round_arange(minval, maxval, self.style["minor_tick_marks"])
+
+        ffra, fpow = _optimize_scale_numbers(minval, maxval, self.style["tick_marks"])
         form = "%" + "." + str(ffra) + "f"
-        last_x = x + px * mx
-        last_y = y + py * my
         ref_w, ref_h = self._draw_text(
-            draw, (0, 0), form % (val_steps[0]), dry_run=True, **self.style)
+            draw, (0, 0), form % (val_steps[0]), dry_run=True, **self.style
+        )
 
         if is_vertical:
-            # major
-            offset_start = val_steps[0] - minval
-            offset_end = val_steps[-1] - maxval
-            y_steps = py * (val_steps - minval - offset_start -
-                            offset_end) * scale_height / (maxval - minval) + y + py * my
-            y_steps = y_steps[::-1]
-            for i, ys in enumerate(y_steps):
-                self._draw_line(
-                    draw, [(x + px * mx, ys), (x + px * (mx + scale_width / 3.0), ys)], **self.style)
-                if abs(ys - last_y) > ref_h:
-                    self._draw_text(
-                        draw, (x + px * (mx + 2 * scale_width / 3.0), ys), (form % (val_steps[i])).strip(), **self.style)
-                    last_y = ys
-            # minor
-            y_steps = py * (minor_steps - minval) * \
-                scale_height / (maxval - minval) + y + py * my
-            y_steps = y_steps[::-1]
-            for i, ys in enumerate(y_steps):
-                self._draw_line(
-                    draw, [(x + px * mx, ys), (x + px * (mx + scale_width / 6.0), ys)], **self.style)
+            self._draw_vertical_colorbar_ticks(
+                draw,
+                minval,
+                maxval,
+                val_steps,
+                scale_width,
+                scale_height,
+                px,
+                py,
+                mx,
+                my,
+                x,
+                y,
+                minor_steps,
+                ref_h,
+                form,
+            )
         else:
-            # major
-            x_steps = px * (val_steps - minval) * \
-                scale_width / (maxval - minval) + x + px * mx
-            for i, xs in enumerate(x_steps):
-                self._draw_line(
-                    draw, [(xs, y + py * my), (xs, y + py * (my + scale_height / 3.0))], **self.style)
-                if abs(xs - last_x) > ref_w:
-                    self._draw_text(
-                        draw, (xs, y + py * (my + 2 * scale_height / 3.0)), (form % (val_steps[i])).strip(), **self.style)
-                    last_x = xs
-            # minor
-            x_steps = px * (minor_steps - minval) * \
-                scale_width / (maxval - minval) + x + px * mx
-            for i, xs in enumerate(x_steps):
-                self._draw_line(
-                    draw, [(xs, y + py * my), (xs, y + py * (my + scale_height / 6.0))], **self.style)
-
-        # draw unit and/or power if set
-        if self.style['unit']:
-            # calculate position
-            if is_vertical:
-                if is_right:
-                    x_ = x - mx - scale_width / 2.0
-                else:
-                    x_ = x + mx + scale_width / 2.0
-                y_ = y + my + scale_height + y_spacer / 2.0
+            self._draw_horizontal_colorbar_ticks(
+                draw,
+                minval,
+                maxval,
+                val_steps,
+                scale_width,
+                scale_height,
+                px,
+                py,
+                mx,
+                my,
+                x,
+                y,
+                minor_steps,
+                ref_w,
+                form,
+            )
+
+    def _draw_vertical_colorbar_ticks(
+        self,
+        draw,
+        minval,
+        maxval,
+        val_steps,
+        scale_width,
+        scale_height,
+        px,
+        py,
+        mx,
+        my,
+        x,
+        y,
+        minor_steps,
+        ref_h,
+        form,
+    ):
+        # major
+        offset_start = val_steps[0] - minval
+        offset_end = val_steps[-1] - maxval
+        y_steps = (
+            py
+            * (val_steps - minval - offset_start - offset_end)
+            * scale_height
+            / (maxval - minval)
+            + y
+            + py * my
+        )
+        y_steps = y_steps[::-1]
+        last_y = y + py * my
+        for i, ys in enumerate(y_steps):
+            self._draw_line(
+                draw,
+                [(x + px * mx, ys), (x + px * (mx + scale_width / 3.0), ys)],
+                **self.style,
+            )
+            if abs(ys - last_y) > ref_h:
+                self._draw_text(
+                    draw,
+                    (x + px * (mx + 2 * scale_width / 3.0), ys),
+                    (form % (val_steps[i])).strip(),
+                    **self.style,
+                )
+                last_y = ys
+        # minor
+        y_steps = (
+            py * (minor_steps - minval) * scale_height / (maxval - minval) + y + py * my
+        )
+        y_steps = y_steps[::-1]
+        for ys in y_steps:
+            self._draw_line(
+                draw,
+                [(x + px * mx, ys), (x + px * (mx + scale_width / 6.0), ys)],
+                **self.style,
+            )
+
+    def _draw_horizontal_colorbar_ticks(
+        self,
+        draw,
+        minval,
+        maxval,
+        val_steps,
+        scale_width,
+        scale_height,
+        px,
+        py,
+        mx,
+        my,
+        x,
+        y,
+        minor_steps,
+        ref_w,
+        form,
+    ):
+        # major
+        x_steps = (
+            px * (val_steps - minval) * scale_width / (maxval - minval) + x + px * mx
+        )
+        last_x = x + px * mx
+        for i, xs in enumerate(x_steps):
+            self._draw_line(
+                draw,
+                [(xs, y + py * my), (xs, y + py * (my + scale_height / 3.0))],
+                **self.style,
+            )
+            if abs(xs - last_x) > ref_w:
+                self._draw_text(
+                    draw,
+                    (xs, y + py * (my + 2 * scale_height / 3.0)),
+                    (form % (val_steps[i])).strip(),
+                    **self.style,
+                )
+                last_x = xs
+        # minor
+        x_steps = (
+            px * (minor_steps - minval) * scale_width / (maxval - minval) + x + px * mx
+        )
+        for xs in x_steps:
+            self._draw_line(
+                draw,
+                [(xs, y + py * my), (xs, y + py * (my + scale_height / 6.0))],
+                **self.style,
+            )
+
+    def _add_colorbar_units(
+        self,
+        draw,
+        unit,
+        is_vertical,
+        is_right,
+        is_bottom,
+        x,
+        y,
+        mx,
+        my,
+        scale_width,
+        scale_height,
+        x_spacer,
+        y_spacer,
+    ):
+        if is_vertical:
+            if is_right:
+                x_ = x - mx - scale_width / 2.0
             else:
-                x_ = x + mx + scale_width + x_spacer / 2.0
-                if is_bottom:
-                    y_ = y - my - scale_height / 2.0
-                else:
-                    y_ = y + my + scale_height / 2.0
-            # draw marking
-            self._draw_text(draw, (x_, y_), self.style['unit'], **self.style)
-
-        if title:
-            # check for font object
-            self._get_current_font()
-
-            # calculate position
-            tw, th = draw.textsize(title, self.style['font'])
-            if is_vertical:
-                # TODO: Rotate the text?
-                if is_right:
-                    x = x - mx - scale_width - tw
-                else:
-                    x = x + mx + scale_width + tw
-                y = y + my + scale_height / 2.0
+                x_ = x + mx + scale_width / 2.0
+            y_ = y + my + scale_height + y_spacer / 2.0
+        else:
+            x_ = x + mx + scale_width + x_spacer / 2.0
+            if is_bottom:
+                y_ = y - my - scale_height / 2.0
             else:
-                x = x + mx + scale_width / 2.0
-                if is_bottom:
-                    y = y - my - scale_height - th
-                else:
-                    y = y + my + scale_height + th
-            self._draw_text(draw, (x, y), title, **self.style)
+                y_ = y + my + scale_height / 2.0
+        # draw marking
+        self._draw_text(draw, (x_, y_), unit, **self.style)
+
+    def _add_colorbar_title(
+        self,
+        draw,
+        title,
+        is_vertical,
+        is_right,
+        is_bottom,
+        x,
+        y,
+        mx,
+        my,
+        scale_width,
+        scale_height,
+    ):
+        # check for font object
+        self._get_current_font()
 
-        # finalize
-        self._finalize(draw)
+        # calculate position
+        tw, th = draw.textsize(title, self.style["font"])
+        if is_vertical:
+            # TODO: Rotate the text?
+            if is_right:
+                x = x - mx - scale_width - tw
+            else:
+                x = x + mx + scale_width + tw
+            y = y + my + scale_height / 2.0
+        else:
+            x = x + mx + scale_width / 2.0
+            if is_bottom:
+                y = y - my - scale_height - th
+            else:
+                y = y + my + scale_height + th
+        self._draw_text(draw, (x, y), title, **self.style)
 
     def _form_xy_box(self, box):
         newbox = box + []
@@ -591,13 +747,37 @@ class DecoratorBase(object):
         self.image.paste(comp, box)
 
 
+def _create_colorbar_image(
+    colormap, minval, maxval, scale_height, scale_width, is_vertical
+):
+    if TImage is None:
+        raise ImportError(
+            "Missing 'trollimage' dependency. Required colorbar creation."
+        )
+
+    if is_vertical:
+        linedata = np.ones((scale_width, 1)) * np.arange(
+            minval, maxval, float(maxval - minval) / scale_height
+        )
+        linedata = linedata.transpose()
+    else:
+        linedata = np.ones((scale_height, 1)) * np.arange(
+            minval, maxval, float(maxval - minval) / scale_width
+        )
+
+    timg = TImage(linedata, mode="L")
+    timg.colorize(colormap)
+    return timg.pil_image()
+
+
 def _round_arange(val_min, val_max, dval):
-    """
-    Returns an array of values in the range from valmin to valmax
-    but with stepping, dval. This is similar to numpy.arange except
+    """Get an array of values in the range from valmin to valmax but with stepping by dval.
+
+    This is similar to numpy.arange except
     the values must be rounded to the nearest multiple of dval.
     """
-    vals = np.arange(val_min, val_max, dval)
+    delta = val_max - val_min
+    vals = np.arange(val_min, val_max, np.sign(delta) * dval)
     round_vals = vals - vals % dval
     if round_vals[0] < val_min:
         round_vals = round_vals[1:]
@@ -605,25 +785,26 @@ def _round_arange(val_min, val_max, dval):
 
 
 def _round_arange2(val_min, val_max, dval):
-    """
-    Returns an array of values in the range from valmin to valmax
-    but with stepping, dval. This is similar to numpy.linspace except
+    """Get an array of values in the range from valmin to valmax with stepping by dval.
+
+    This is similar to numpy.linspace except
     the values must be rounded to the nearest multiple of dval.
     The difference to _round_arange is that the return values include
-    also the bounary value val_max.
+    also the boundary value val_max.
     """
     val_min_round = val_min + (dval - val_min % dval) % dval
     val_max_round = val_max - val_max % dval
-    n_points = int((val_max_round - val_min_round) / dval) + 1
+    n_points = int(abs(val_max_round - val_min_round) / dval) + 1
     vals = np.linspace(val_min_round, val_max_round, num=n_points)
 
     return vals
 
 
 def _optimize_scale_numbers(minval, maxval, dval):
-    """
-    find a suitable number format, A and B in "%A.Bf" and power if numbers are large
-    for display of scale numbers.
+    """Find a suitable number format for display of scale numbers.
+
+    Returns:
+        A and B in "%A.Bf" and power if numbers are large.
     """
     ffra = 1
     # no fractions, so turn off remainder


=====================================
pydecorate/tests/__init__.py
=====================================


=====================================
pydecorate/tests/test_decorator_agg.py
=====================================
@@ -0,0 +1,68 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2022 Pydecorate developers
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""Tests for the aggdraw-based decorator."""
+
+import numpy as np
+import pytest
+from PIL import Image
+from trollimage.colormap import rdbu
+
+from pydecorate import DecoratorAGG
+
+
+ at pytest.mark.parametrize(
+    "orientation_func_name", ["write_vertically", "write_horizontally"]
+)
+ at pytest.mark.parametrize(
+    "align_func_name", ["align_top", "align_bottom", "align_left", "align_right"]
+)
+ at pytest.mark.parametrize("clims", [(-90, 10), (10, -90)])
+def test_colorbar(tmp_path, orientation_func_name, align_func_name, clims):
+    fn = tmp_path / "test_colorbar.png"
+    img = Image.fromarray(np.zeros((200, 100, 3), dtype=np.uint8))
+    dc = DecoratorAGG(img)
+    getattr(dc, align_func_name)()
+    getattr(dc, orientation_func_name)()
+    cmap = rdbu.set_range(*clims, inplace=False)
+    dc.add_scale(cmap, extend=True, tick_marks=5.0, line_opacity=100, unit="K")
+    img.save(fn)
+
+    # check results
+    output_img = Image.open(fn)
+    arr = np.array(output_img)
+    _assert_colorbar_orientation_alignment(arr, orientation_func_name, align_func_name)
+
+
+def _assert_colorbar_orientation_alignment(
+    img_arr, orientation_func_name, align_func_name
+):
+    cbar_size = 60
+    if orientation_func_name == "write_vertically":
+        if align_func_name in ("align_left", "align_top", "align_bottom"):
+            assert np.unique(img_arr[:, :cbar_size]).size >= 100
+            np.testing.assert_allclose(img_arr[:, cbar_size:], 0)
+        else:
+            assert np.unique(img_arr[:, -cbar_size:]).size >= 100
+            np.testing.assert_allclose(img_arr[:, :-cbar_size], 0)
+    else:
+        if align_func_name in ("align_top", "align_left", "align_right"):
+            assert np.unique(img_arr[:cbar_size, :]).size >= 100
+            np.testing.assert_allclose(img_arr[-cbar_size:, :], 0)
+        else:
+            assert np.unique(img_arr[-cbar_size:, :]).size >= 100
+            np.testing.assert_allclose(img_arr[:cbar_size, :], 0)


=====================================
test.py → pydecorate/tests/test_legacy.py
=====================================
@@ -6,44 +6,50 @@ NOTE: These aren't proper unit tests
 
 """
 
+import os
+
+REPOS_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
+FONTS_ROOT = os.path.join(REPOS_ROOT, "pydecorate", "fonts")
+DEJAVU_FONT = os.path.join(FONTS_ROOT, "DejaVuSerif.ttf")
+
 
 def test_style_retention():
+    # import aggdraw
     from PIL import Image
-    from pydecorate import DecoratorAGG
-    import aggdraw
     from trollimage.colormap import rdbu
 
-    font = aggdraw.Font("navy", "pydecorate/fonts/DejaVuSerif.ttf", size=20)
-    font_scale = aggdraw.Font("black", "pydecorate/fonts/DejaVuSerif.ttf", size=12)
+    from pydecorate import DecoratorAGG
+
+    # font = aggdraw.Font("navy", DEJAVU_FONT, size=20)
+    # font_scale = aggdraw.Font("black", DEJAVU_FONT, size=12)
 
     rdbu.colors = rdbu.colors[::-1]
     rdbu.set_range(-90, 10)
 
-    img = Image.open('BMNG_clouds_201109181715_areaT2.png')
+    img = Image.open(os.path.join(REPOS_ROOT, "BMNG_clouds_201109181715_areaT2.png"))
     dc = DecoratorAGG(img)
 
+    # dc.write_vertically()
+    # dc.add_logo("logos/pytroll_light_big.png")
+    # dc.add_logo("logos/NASA_Logo.gif",margins=[10,10],bg='yellow')
+    # dc.add_logo("logos/pytroll_light_big.png")
+    # font = aggdraw.Font("blue", DEJAVU_FONT, size=16)
+    # dc.add_text("Some text",font=font)
 
-    #dc.write_vertically()
-    #dc.add_logo("logos/pytroll_light_big.png")
-    #dc.add_logo("logos/NASA_Logo.gif",margins=[10,10],bg='yellow')
-    #dc.add_logo("logos/pytroll_light_big.png")
-    font = aggdraw.Font("blue", "pydecorate/fonts/DejaVuSerif.ttf", size=16)
-    #dc.add_text("Some text",font=font)
-
-
-    #dc.align_right()
-    dc.add_scale(rdbu, extend=True, tick_marks=5.0, line_opacity=100, unit='K')
+    # dc.align_right()
+    dc.add_scale(rdbu, extend=True, tick_marks=5.0, line_opacity=100, unit="K")
 
-    #dc.align_bottom()
-    #dc.add_scale(rdbu, extend=True, tick_marks=2.0, line_opacity=100, width=60)
+    # dc.align_bottom()
+    # dc.add_scale(rdbu, extend=True, tick_marks=2.0, line_opacity=100, width=60)
 
-    #dc.align_right()
-    #dc.write_vertically()
+    # dc.align_right()
+    # dc.write_vertically()
     dc.align_bottom()
-    dc.add_scale(rdbu, extend=True, tick_marks=5.0, line_opacity=100, unit='K')
+    dc.add_scale(rdbu, extend=True, tick_marks=5.0, line_opacity=100, unit="K")
 
-    #dc.align_left()
-    #dc.add_scale(rdbu, extend=True, font=font_scale, tick_marks=2.0, minor_tick_marks=1.0, line_opacity=100, width=60, unit='K')
+    # dc.align_left()
+    # dc.add_scale(rdbu, extend=True, font=font_scale, tick_marks=2.0, minor_tick_marks=1.0,
+    #              line_opacity=100, width=60, unit='K')
 
     # img.show()
     img.save("style_retention.png")
@@ -132,9 +138,3 @@ def test_style_retention():
     #
     #
     # img.show()
-
-
-if __name__ == "__main__":
-    import sys
-    import pytest
-    sys.exit(pytest.main())


=====================================
pyproject.toml
=====================================
@@ -2,4 +2,12 @@
 requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]>=3.4", "setuptools_scm_git_archive"]
 
 [tool.setuptools_scm]
-write_to = "pydecorate/version.py"
\ No newline at end of file
+write_to = "pydecorate/version.py"
+
+[tool.isort]
+sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"]
+profile = "black"
+skip_gitignore = true
+default_section = "THIRDPARTY"
+known_first_party = "pydecorate"
+line_length = 120


=====================================
setup.cfg
=====================================
@@ -0,0 +1,10 @@
+[flake8]
+max-line-length = 120
+ignore = D102,D103,D104,D107,W503
+exclude =
+    pydecorate/version.py
+
+[coverage:run]
+relative_files = True
+omit =
+    pydecorate/version.py


=====================================
setup.py
=====================================
@@ -1,25 +1,23 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-
-# Copyright (c) 2013, 2016 Adam Dybbroe
-
-# Author(s):
-
-#   Hrobjartur Thorsteinsson <thorsteinssonh at gmail.com>
-
+#
+# Copyright (c) 2013 Pydecorate developers
+#
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
-
+#
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU General Public License for more details.
-
+#
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""Package building definition and script."""
 
+import sys
 
 from setuptools import setup
 
@@ -27,6 +25,7 @@ try:
     # HACK: https://github.com/pypa/setuptools_scm/issues/190#issuecomment-351181286
     # Stop setuptools_scm from including all repository files
     import setuptools_scm.integration
+
     setuptools_scm.integration.find_files = lambda _: []
 except ImportError:
     pass
@@ -34,32 +33,39 @@ except ImportError:
 with open("./README.rst", "r") as fd:
     long_description = fd.read()
 
+tests_require = ["pytest", "pytest-cov", "trollimage"]
+if sys.platform.startswith("win"):
+    tests_require.append("freetype-py")
 
-setup(name='pydecorate',
-      description='Decorating PIL images: logos, texts, pallettes',
-      long_description=long_description,
-      author='Hrobjartur Thorsteinsson',
-      author_email='thorsteinssonh at gmail.com',
-      classifiers=["Development Status :: 4 - Beta",
-                   "Intended Audience :: Science/Research",
-                   "License :: OSI Approved :: GNU General Public License v3 " +
-                   "or later (GPLv3+)",
-                   "Operating System :: OS Independent",
-                   "Programming Language :: Python",
-                   "Topic :: Scientific/Engineering"],
-      url="https://github.com/pytroll/pydecorate",
-      license='GPLv3',
-      packages=['pydecorate'],
-      include_package_data=True,
-      package_data={'pydecorate': ['fonts/*.ttf']},
-      # Project should use reStructuredText, so ensure that the docutils get
-      # installed or upgraded on the target machine
-      install_requires=['pillow', 'aggdraw'],
-      setup_requires=['setuptools_scm', 'setuptools_scm_git_archive'],
-      scripts=[],
-      data_files=[],
-      # test_suite="",
-      tests_require=['pytest'],
-      python_requires='>=3.6',
-      use_scm_version={'write_to': 'pydecorate/version.py'},
-      zip_safe=False)
+setup(
+    name="pydecorate",
+    description="Decorating PIL images: logos, texts, pallettes",
+    long_description=long_description,
+    long_description_content_type="text/x-rst",
+    author="Hrobjartur Thorsteinsson",
+    author_email="thorsteinssonh at gmail.com",
+    classifiers=[
+        "Development Status :: 4 - Beta",
+        "Intended Audience :: Science/Research",
+        "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
+        "Operating System :: OS Independent",
+        "Programming Language :: Python",
+        "Topic :: Scientific/Engineering",
+    ],
+    url="https://github.com/pytroll/pydecorate",
+    license="GPLv3+",
+    packages=["pydecorate"],
+    include_package_data=True,
+    package_data={"pydecorate": ["fonts/*.ttf"]},
+    install_requires=["pillow", "aggdraw", "numpy", "setuptools"],
+    setup_requires=["setuptools_scm", "setuptools_scm_git_archive"],
+    scripts=[],
+    data_files=[],
+    python_requires=">=3.7",
+    extras_require={
+        "tests": tests_require,
+        "docs": ["sphinx", "sphinx_rtd_theme", "sphinxcontrib-apidoc", "trollimage"],
+    },
+    use_scm_version={"write_to": "pydecorate/version.py"},
+    zip_safe=False,
+)



View it on GitLab: https://salsa.debian.org/debian-gis-team/pydecorate/-/commit/8af1fef61c1d8426a2af0e3c4def6db66f27b571

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/pydecorate/-/commit/8af1fef61c1d8426a2af0e3c4def6db66f27b571
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20220306/6801e8e1/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list