Skip to content

Commit

Permalink
Merge 6f4aaae into master
Browse files Browse the repository at this point in the history
  • Loading branch information
github-actions[bot] authored Apr 26, 2023
2 parents 2599581 + 6f4aaae commit 9b42bd6
Show file tree
Hide file tree
Showing 16 changed files with 157 additions and 90 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
strategy:
matrix:
os: [ ubuntu-latest, macos-latest, windows-latest ]
python-version: [ 3.7, '3.11.0-rc.2' ]
python-version: [ 3.7, '3.11' ]

steps:
- uses: actions/checkout@v2
Expand Down Expand Up @@ -93,7 +93,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.8.10'
python-version: '3.11.2'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2021 Artsiom iG <github.com/rtmigo>
Copyright (c) 2021 Artёm iG <github.com/rtmigo>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,27 @@ img2texture source.jpg seamless.jpg --tile

All the samples on this page were created with `--tile`.

# Use programmatically

If you don't need CLI but need to create seamless image in your own program:

```python3
from PIL import Image
from img2texture import image_to_seamless

# load PIL image
src_image = Image.open("/path/to/source.png")

# convert to seamless PIL image
result_image = image_to_seamless(src_image, overlap=0.1)

# save
result_image.save("/path/to/result.png")
```

`overlap=0.1` means 10%, and `overlap=(0.1, 0.2)` means 10% horizontal, 20% vertical.

# License

Copyright © 2021 [Artsiom iG](https://github.com/rtmigo).
Copyright © 2021 [Artёm iG](https://github.com/rtmigo).
Released under the [MIT License](LICENSE).
2 changes: 1 addition & 1 deletion img2texture/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from ._texturizing import img2tex
from ._texturizing import file_to_seamless, image_to_seamless, img2tex
from ._cli import cli
12 changes: 10 additions & 2 deletions img2texture/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
from enum import Enum
from pathlib import Path

from PIL import Image

import img2texture._constants as constants
from img2texture import img2tex
from img2texture import img2tex, file_to_seamless
from ._tiling import tile


Expand Down Expand Up @@ -116,13 +118,19 @@ def tile_filename(texture: Path) -> Path:
def cli():
args = ParsedArgs()

# 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
Image.MAX_IMAGE_PIXELS = None

try:
if args.target.exists():
if not confirm(f"File '{args.target.name}' exists. Overwrite?"):
sys.exit(3)
os.remove(args.target)

img2tex(args.source, args.target, pct=args.overlap_pct)
file_to_seamless(args.source, args.target, overlap=args.overlap_pct)

if args.tile:
tile_src = args.target if args.mode != Mode.none else args.source
Expand Down
5 changes: 1 addition & 4 deletions img2texture/_common.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
from PIL import Image

# preventing Image.DecompressionBombError: Image size (324000000 pixels)
# exceeds limit of 178956970 pixels, could be decompression bomb DOS attack.
# We just open a large file and are not afraid of it
Image.MAX_IMAGE_PIXELS = None

4 changes: 2 additions & 2 deletions img2texture/_constants.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
__version__ = "1.0.6"
__version__ = "1.1.0"

__copyright__ = "(c) Artsiom iG <[email protected]>"
__copyright__ = "(c) Artem iG <[email protected]>"
__license__ = "MIT"
__build_timestamp__ = "2022-10-12 05:30:19"

71 changes: 48 additions & 23 deletions img2texture/_texturizing.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# SPDX-FileCopyrightText: (c) 2021 Artsiom iG <github.com/rtmigo>
# SPDX-FileCopyrightText: (c) 2021 Artёm iG <github.com/rtmigo>
# SPDX-License-Identifier: MIT
import warnings
from math import floor
from pathlib import Path
from typing import Tuple
from typing import Tuple, Union

from ._common import Image # importing with tweaked options

Expand Down Expand Up @@ -78,14 +79,14 @@ def _left_stripe_image(self):
(0, 0, self.horizontal_stripe_width, self.src_height))

def _right_stripe_image(self):
return self.source.crop(
(self.src_width - self.horizontal_stripe_width, 0,
self.src_width, self.src_height))
return self.source.crop((
self.src_width - self.horizontal_stripe_width, 0, self.src_width,
self.src_height))

def _bottom_stripe_image(self):
return self.source.crop(
(0, self.src_height - self.vertical_stripe_height,
self.src_width, self.src_height))
return self.source.crop((
0, self.src_height - self.vertical_stripe_height, self.src_width,
self.src_height))

def _to_rgba(self, image: Image) -> Image:
if image.mode != 'RGBA':
Expand All @@ -102,13 +103,10 @@ def make_seamless_h(self) -> Image:
overlay = Image.new('RGBA', size=self.source.size, color=0x00)
overlay.paste(stripe, box=(0, 0))

comp = Image.alpha_composite(self._to_rgba(self.source),
overlay)
comp = Image.alpha_composite(self._to_rgba(self.source), overlay)

comp = comp.crop((0,
0,
comp.size[0] - self.horizontal_stripe_width,
comp.size[1]))
comp = comp.crop(
(0, 0, comp.size[0] - self.horizontal_stripe_width, comp.size[1]))
return comp

def make_seamless_v(self) -> Image:
Expand All @@ -119,22 +117,49 @@ def make_seamless_v(self) -> Image:
overlay = Image.new('RGBA', size=self.source.size, color=0x00)
overlay.paste(stripe, box=(0, 0))

comp = Image.alpha_composite(self._to_rgba(self.source),
overlay)
comp = Image.alpha_composite(self._to_rgba(self.source), overlay)

comp = comp.crop((0,
0,
comp.size[0],
comp.size[1] - self.vertical_stripe_height))
comp = comp.crop(
(0, 0, comp.size[0], comp.size[1] - self.vertical_stripe_height))
return comp


def img2tex(src: Path, dst: Path, pct=0.25):
mixer1 = Mixer(Image.open(src), pct=pct)
warnings.warn("Replaced by `file_to_seamless`", DeprecationWarning,
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."""
image_to_seamless(Image.open(src), overlap=overlap).save(dst)


def image_to_seamless(src: Image, overlap: Overlap = 0.25) -> Image:
"""Converts `PIL.Image` to seamless `PIL.Image`."""
mixer1 = Mixer(src, pct=_horizontal_overlap(overlap))
result = mixer1.make_seamless_h()

mixer2 = Mixer(result, pct=pct)
mixer2 = Mixer(result, pct=_vertical_overlap(overlap))
result = mixer2.make_seamless_v()
if result.mode != "RGB":
result = result.convert("RGB")
result.save(dst)
return result


def _float_or_index(dynamic: Overlap,
idx: int) -> float:
if isinstance(dynamic, float):
return dynamic
else:
return dynamic[idx]


def _horizontal_overlap(overlaps: Overlap) -> float:
return _float_or_index(overlaps, 0)


def _vertical_overlap(overlaps: Overlap) -> float:
return _float_or_index(overlaps, 1)
2 changes: 1 addition & 1 deletion img2texture/_tiling.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: (c) 2021 Artsiom iG <github.com/rtmigo>
# SPDX-FileCopyrightText: (c) 2021 Artёm iG <github.com/rtmigo>
# SPDX-License-Identifier: MIT

from pathlib import Path
Expand Down
Empty file added img2texture/py.typed
Empty file.
11 changes: 11 additions & 0 deletions sample/programmatic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from PIL import Image
from img2texture import image_to_seamless

# load PIL image
src_image = Image.open("/path/to/source.png")

# convert to seamless PIL image
result_image = image_to_seamless(src_image, overlap=0.1)

# save
result_image.save("/path/to/result.png")
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@ def load_module_dict(filename: str) -> dict:
name=name,
version=constants['__version__'],

author="Artsiom iG",
author="Artёm iG",
author_email="[email protected]",
url='https://github.com/rtmigo/img2texture#readme',

packages=find_packages(include=[name, f'{name}.*']),
package_data={name: ["py.typed"]},

python_requires='>=3.7, <4',
install_requires=["pillow>=9.2, <10"],
Expand Down
2 changes: 1 addition & 1 deletion tests/helpers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: (c) 2021 Artsiom iG <github.com/rtmigo>
# SPDX-FileCopyrightText: (c) 2021 Artёm iG <github.com/rtmigo>
# SPDX-License-Identifier: MIT


Expand Down
2 changes: 1 addition & 1 deletion tests/test_make_tiles.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: (c) 2021 Artsiom iG <github.com/rtmigo>
# SPDX-FileCopyrightText: (c) 2021 Artёm iG <github.com/rtmigo>
# SPDX-License-Identifier: MIT


Expand Down
50 changes: 0 additions & 50 deletions tests/test_my_texturize.py

This file was deleted.

55 changes: 55 additions & 0 deletions tests/test_texturize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# SPDX-FileCopyrightText: (c) 2021 Artёm iG <github.com/rtmigo>
# SPDX-License-Identifier: MIT


import unittest
from pathlib import Path

from PIL import Image

from img2texture._texturizing import horizontal_gradient_256_scaled, Mixer, \
file_to_seamless
from img2texture._tiling import tile
from tests.helpers import temp_file_path, file_md5


class TestGradient(unittest.TestCase):
def test(self):
w = 100
h = 50
result = horizontal_gradient_256_scaled((100, 50))
self.assertEqual(result.size[0], w)
self.assertEqual(result.size[1], h)

outfile = temp_file_path("gradient_h_1.png")
result.save(outfile)
self.assertEqual(file_md5(outfile), 'b135a054bc325a37df404b141f512567')


class TestToSeamless(unittest.TestCase):
def test_overlap_tuple(self):
src = Path(__file__).parent / "data" / "sand.png"
dst = temp_file_path("sand-tx-tuple.png")
dst_tiled = temp_file_path("sand-tx-tuple-tiled.png")

file_to_seamless(src, dst, overlap=(0.4, 0.3))
tile(dst, dst_tiled)

# without RGB conversion it was c9c4d278498050e99c1df3994efc3bcd
self.assertEqual(file_md5(dst), '696749d1337dc3d9e4021e9b8852a6e1')

def test_overlap_float(self):
src = Path(__file__).parent / "data" / "sand.png"
dst = temp_file_path("sand-tx-float.png")
dst_tiled = temp_file_path("sand-tx-float-tiled.png")

file_to_seamless(src, dst, overlap=0.4)
tile(dst, dst_tiled)

# without RGB conversion it was c9c4d278498050e99c1df3994efc3bcd
self.assertEqual(file_md5(dst), 'c957847053bc9f7747369f8a3ae091d2')


if __name__ == "__main__":
# TestGradient().test()
TestToSeamless().test_overlap_tuple()

0 comments on commit 9b42bd6

Please sign in to comment.