diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 11df2c9..08618a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,20 +15,20 @@ concurrency: jobs: release-flag: - if: contains(github.event.head_commit.message, '!!release') + if: contains(github.event.head_commit.message, 'gha:release') runs-on: ubuntu-latest steps: - uses: actions/checkout@master pypi-flag: - if: contains(github.event.head_commit.message, '!!pypi') + if: contains(github.event.head_commit.message, 'gha:pypi') runs-on: ubuntu-latest steps: - uses: actions/checkout@master master-flag: - if: contains(github.event.head_commit.message, '!!master') + if: contains(github.event.head_commit.message, 'gha:master') runs-on: ubuntu-latest steps: - uses: actions/checkout@master @@ -41,7 +41,7 @@ jobs: strategy: matrix: os: [ ubuntu-latest, macos-latest, windows-latest ] - python-version: [ 3.7, '3.11' ] + python-version: [ '3.8', '3.11', '3.12' ] steps: - uses: actions/checkout@v2 @@ -93,7 +93,8 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.11.2' + #python-version: '3.11.2' + python-version: '3.12' - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/ci.yml.bak b/.github/workflows/ci.yml.bak new file mode 100644 index 0000000..8ebb41f --- /dev/null +++ b/.github/workflows/ci.yml.bak @@ -0,0 +1,185 @@ +name: test-and-deploy + +on: + push: + branches: [ dev ] + paths-ignore: + - '**.md' + - '**.txt' + +concurrency: + # subsequently queued workflow run will interrupt previous runs + group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' + cancel-in-progress: true + +jobs: + + release-flag: + if: contains(github.event.head_commit.message, '!!release') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + + pypi-flag: + if: contains(github.event.head_commit.message, '!!pypi') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + + + master-flag: + if: contains(github.event.head_commit.message, '!!master') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + + test: + timeout-minutes: 15 + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ ubuntu-latest, macos-latest, windows-latest ] + python-version: [ 3.7, '3.11.0-rc.2' ] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install --upgrade setuptools distlib + pip install -e . + pip install -r requirements.txt + - name: Lint + run: | + python3 ./do.py lint + - name: Run unit tests + run: | + python3 ./do.py test + - name: Run pkg tests + # важно, чтобы в случае Windows это был действительно python, а + # не python3. Иначе вызовы самого-себя в качестве дочернего процесса + # приведут к ошибкам с "ненайденными файлами". Ненайденный - вероятно + # сам пайтоне внутри venv + run: | + python ./do.py test-pkg + + to-staging: + needs: [test, build-exe] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Merge current -> staging + uses: devmasx/merge-branch@v1.3.1 + with: + type: now + target_branch: staging + github_token: ${{ github.token }} + + build-exe: + #needs: [ to-staging ] + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ ubuntu-latest, macos-latest, windows-latest ] + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.8.10' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install --upgrade setuptools distlib + pip install -e . + pip install -r requirements.txt + - name: Build + run: python ./do.py build + - name: Run exe (test A) + run: dist/img2texture --version + - name: Run exe (test B) + run: dist/img2texture docs/1_orion_src.jpg texture.tmp.jpg + - name: Store Exe as artifact + uses: actions/upload-artifact@v3 + with: + name: binary_from_${{ matrix.os }} + path: dist/* + retention-days: 3 + + to-master: + # if the commit message was "publish", copy the tested code + # to "master" branch and create GitHub release + + needs: [ to-staging, master-flag ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + # UPDATE MASTER BRANCH + - name: Merge to master branch + uses: devmasx/merge-branch@v1.3.1 + with: + type: now + target_branch: master + github_token: ${{ github.token }} + + to-pypi: + needs: [ to-master, pypi-flag ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + # ADD PYPI RELEASE + - 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_USR }} + TWINE_PASSWORD: ${{ secrets.PYPI_PWD }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* + + + to-github-release: + needs: [ build-exe, to-staging, release-flag ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Get previously built artifacts + uses: actions/download-artifact@v3 + with: + path: downloaded_artifacts + + - name: Create distributable archives + run: | + wget -c https://github.com/rtmigo/exe2dist/releases/latest/download/exe2dist_linux_amd64.tgz -O - | tar -xz + ./exe2dist img2texture 'downloaded_artifacts/*/*' dist + + - name: Get the project version + run: | + echo "::set-output name=VER::$(python setup.py --version)" + id: version + + - name: Publish GitHub release + id: publish_github_release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ steps.version.outputs.VER }} + files: ./dist/* + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/linux_build.yml.bak b/.github/workflows/linux_build.yml.bak new file mode 100644 index 0000000..b40ad3c --- /dev/null +++ b/.github/workflows/linux_build.yml.bak @@ -0,0 +1,40 @@ +name: build-dev + +on: + push: + branches: [ dev ] + paths-ignore: + - '**.md' + - '**.txt' + +concurrency: + # subsequently queued workflow run will interrupt previous runs + group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' + cancel-in-progress: true + +jobs: + + + build-exe: + #needs: [ to-staging ] + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.8' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e . + pip install -r requirements.txt + - name: Pip list + run: pip list | tail -n +3 | awk '{print $1}' | xargs pip show | grep -E 'Location:|Name:' | cut -d ' ' -f 2 | paste -d ' ' - - | awk '{print $2 "/" tolower($1)}' | xargs du -sh 2> /dev/null | sort -hr + - name: Build + run: python ./do.py build + - name: Run exe (test A) + run: dist/img2texture --version + - name: Run exe (test B) + run: dist/img2texture docs/1_orion_src.jpg texture.tmp.jpg diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9b904f6 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ +# 1.1.2 :: 2023-10-06 + +- Pillow version requirement raised to version 10.0.1+ to fix recent [security vulnerabilities](https://snyk.io/blog/critical-webp-0-day-cve-2023-4863/) +- Python version requirement raised to 3.8 (was 3.7) — as requred by Pillow 10 +- Added CI tests for Python 3.12 +- Executables are now built with Python 3.12 \ No newline at end of file diff --git a/do.py b/do.py index 9180baa..7940aaf 100644 --- a/do.py +++ b/do.py @@ -30,6 +30,7 @@ def _test(): def test_pkg(): with Package() as pkg: pkg.run_shell_code('img2texture --version') + # running console_scripts defined in setup.py pkg.run_shell_code('img2texture --help') pkg.run_shell_code('img2texture', expected_return_code=2) diff --git a/img2texture/__init__.py b/img2texture/__init__.py index f010c6b..e21c85a 100644 --- a/img2texture/__init__.py +++ b/img2texture/__init__.py @@ -1,2 +1,5 @@ +# SPDX-FileCopyrightText: (c) 2021 Artёm iG +# SPDX-License-Identifier: MIT + from ._texturizing import file_to_seamless, image_to_seamless, img2tex from ._cli import cli \ No newline at end of file diff --git a/img2texture/__main__.py b/img2texture/__main__.py index 7c4a768..13fe7cb 100644 --- a/img2texture/__main__.py +++ b/img2texture/__main__.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: (c) 2021 Artёm iG +# SPDX-License-Identifier: MIT + from . import cli if __name__ == "__main__": diff --git a/img2texture/_cli.py b/img2texture/_cli.py index 34be64e..2385af1 100644 --- a/img2texture/_cli.py +++ b/img2texture/_cli.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: (c) 2021 Artёm iG +# SPDX-License-Identifier: MIT + import argparse import os import sys @@ -120,8 +123,8 @@ def cli(): # preventing exception "Image.DecompressionBombError: Image size (324000000 # pixels) exceeds limit of 178956970 pixels, could be decompression bomb DOS - # attack". In case of CLI we are not expecting attacks. Just processing - # large files + # attack". In case of CLI we are not expecting attacks: CLI user probably + # converting their own images. So we'll turn off this check for CLI Image.MAX_IMAGE_PIXELS = None try: diff --git a/img2texture/_common.py b/img2texture/_common.py deleted file mode 100644 index 66aee17..0000000 --- a/img2texture/_common.py +++ /dev/null @@ -1,3 +0,0 @@ -from PIL import Image - - diff --git a/img2texture/_constants.py b/img2texture/_constants.py index ae1e0c6..3cffc7e 100644 --- a/img2texture/_constants.py +++ b/img2texture/_constants.py @@ -1,6 +1,6 @@ -__version__ = "1.1.0" +__version__ = "1.1.2" -__copyright__ = "(c) Artem iG " +__copyright__ = "(c) Arte:m iG " __license__ = "MIT" -__build_timestamp__ = "2022-10-12 05:30:19" +__build_timestamp__ = "2022-10-12 05:30:20" diff --git a/img2texture/_texturizing.py b/img2texture/_texturizing.py index a186467..4911474 100644 --- a/img2texture/_texturizing.py +++ b/img2texture/_texturizing.py @@ -1,11 +1,12 @@ # SPDX-FileCopyrightText: (c) 2021 Artёm iG # SPDX-License-Identifier: MIT + import warnings from math import floor from pathlib import Path from typing import Tuple, Union -from ._common import Image # importing with tweaked options +from PIL import Image # importing with tweaked options # todo Find a way to add dithering noise to 8-bit grading @@ -19,12 +20,12 @@ # # So all colors are 8 bit now. Maybe we should find a way to add some random # noise to out gradient. But Pillow will not create noise, we need to generate -# it pixel-by-pixel, and probably not in native Python +# it pixel-by-pixel, and probably not in Python (numpy could do this) def horizontal_gradient_256_scaled(size: Tuple[int, int], reverse=True) -> Image: - gradient = Image.new('L', (256, 1), color=None) + gradient = Image.new('L', (256, 1), color=None) # type: ignore for x in range(256): if reverse: gradient.putpixel((x, 0), x) @@ -35,7 +36,7 @@ def horizontal_gradient_256_scaled(size: Tuple[int, int], def vertical_gradient_256_scaled(size: Tuple[int, int], reverse=True) -> Image: - gradient = Image.new('L', (1, 256), color=None) + gradient = Image.new('L', (1, 256), color=None) # type: ignore for x in range(256): if reverse: gradient.putpixel((0, x), x) @@ -88,7 +89,8 @@ def _bottom_stripe_image(self): 0, self.src_height - self.vertical_stripe_height, self.src_width, self.src_height)) - def _to_rgba(self, image: Image) -> Image: + @staticmethod + def _to_rgba(image: Image) -> Image: if image.mode != 'RGBA': converted = image.convert('RGBA') assert converted is not None @@ -129,8 +131,10 @@ def img2tex(src: Path, dst: Path, pct=0.25): stacklevel=2) file_to_seamless(src, dst, overlap=pct) + Overlap = Union[float, Tuple[float, float]] + def file_to_seamless(src: Path, dst: Path, overlap: Overlap = 0.25) -> None: """Reads image from `src` file, converts it to seamless tile and saves to `dst` file.""" @@ -149,8 +153,7 @@ def image_to_seamless(src: Image, overlap: Overlap = 0.25) -> Image: return result -def _float_or_index(dynamic: Overlap, - idx: int) -> float: +def _float_or_index(dynamic: Overlap, idx: int) -> float: if isinstance(dynamic, float): return dynamic else: diff --git a/img2texture/_tiling.py b/img2texture/_tiling.py index 25cfd68..b803d0c 100644 --- a/img2texture/_tiling.py +++ b/img2texture/_tiling.py @@ -3,7 +3,7 @@ from pathlib import Path -from ._common import Image # importing with tweaked options +from PIL import Image def tile(source: Path, target: Path, diff --git a/setup.py b/setup.py index dbc321a..3a8015d 100644 --- a/setup.py +++ b/setup.py @@ -30,8 +30,8 @@ def load_module_dict(filename: str) -> dict: packages=find_packages(include=[name, f'{name}.*']), package_data={name: ["py.typed"]}, - python_requires='>=3.7, <4', - install_requires=["pillow>=9.2, <10"], + python_requires='>=3.8, <4', # 3.8 for Pillow 10 + install_requires=["pillow>=10.0.1"], # fixing libwebp CVE-2023-4863 description="Command line utility for converting images to seamless tiles.", long_description=readme, @@ -53,6 +53,7 @@ def load_module_dict(filename: str) -> dict: "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Environment :: Console", "Typing :: Typed", "Topic :: Scientific/Engineering :: Image Processing",