Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ name: Run Checks

on: [ push, pull_request, workflow_dispatch ]

permissions:
contents: read

jobs:
checks:
runs-on: ubuntu-latest
Expand All @@ -11,25 +14,26 @@ jobs:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]

steps:
- uses: actions/checkout@v4
- name: Check out repository
uses: actions/checkout@v4

- name: Set up python ${{ matrix.python-version }}
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install Poetry
run: curl -sSL https://install.python-poetry.org | python3 -
run: python -m pip install poetry

- name: Configure Poetry
run: poetry config virtualenvs.in-project true

- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: .venv
key: venv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }}
key: venv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }}-${{ hashFiles('**/pyproject.toml') }}

- name: Install dependencies
run: poetry install
Expand Down
456 changes: 322 additions & 134 deletions poetry.lock

Large diffs are not rendered by default.

21 changes: 13 additions & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,28 @@ repository = "https://github.com/Iapetus-11/To-ASCII"
keywords = ["ascii", "color", "colors", "ascii-art", "video", "image", "nim"]

[tool.poetry.dependencies]
python = ">=3.10,<4.0"
numpy = "2.0"
opencv-python = "^4.10.0.84"
python = ">=3.10,<3.15"
numpy = [
{ version = ">=2.2.6,<2.3", python = "3.10" },
{ version = ">=2.4.4,<2.5", python = "3.11" },
{ version = "^2.5.3", python = ">=3.12" },
]
opencv-python = "^4.14.0.94"
colorama = "^0.4.6"
nimporter = { version = "^1.1.0", optional = true}
pydantic = "^1.9.1"
click = "^8.1.8"
nimporter = { version = "^1.1.0", optional = true }
setuptools = { version = "^84.0.0", optional = true }
pydantic = "^1.10.26"
click = "^8.5.0"

[tool.poetry.extras]
speedups = ["nimporter"]
speedups = ["nimporter", "setuptools"]
cli = ["click"]

[tool.poetry.scripts]
toascii = "toascii.cli:toascii_command"

[tool.poetry.group.dev.dependencies]
ruff = "^0.14.11"
ruff = ">=0.14.11,<0.15"

[build-system]
requires = ["poetry-core>=1.0.0"]
Expand Down
2 changes: 1 addition & 1 deletion toascii/converters/base_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def asciify_image(self, image: np.ndarray) -> str:

raise NotImplementedError

def calculate_dimensions(self, initial_width: int, initial_height: int) -> Tuple[int, int]:
def calculate_dimensions(self, initial_height: int, initial_width: int) -> Tuple[int, int]:
width = self.options.width
height = self.options.height

Expand Down
4 changes: 2 additions & 2 deletions toascii/converters/extensions/color_converter.nim
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ proc asciifyImage(imgPyo: PyObject, gradient: openArray[string], saturation: flo
elif saturation < -1: saturation = -1

result = ""
let gradientLen = gradient.len.float
let gradientLen = gradient.high.float
var lastColoramaCode = "-1"

var imgBuf: RawPyBuffer
imgPyo.getBuffer(imgBuf, PyBUF_WRITABLE or PyBuf_ND)
imgPyo.getBuffer(imgBuf, PyBUF_ND)
defer: imgBuf.release()

for rowIdx in 0 .. imgBuf.dimShape(0) - 1:
Expand Down
4 changes: 2 additions & 2 deletions toascii/converters/extensions/color_converter_nim.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import numpy as np

from ..color_converter import RGB_TO_ASCII_CODE, ColorConverter
from .extension_utils import build_extensions
from .extension_utils import build_extensions, validate_image_buffer

build_extensions()

Expand All @@ -14,7 +14,7 @@

class ColorConverterNim(ColorConverter):
def _asciify_image(self, image: np.ndarray) -> Generator[str, None, None]:
image = self._contrast(image)
image = self._contrast(validate_image_buffer(image))
yield color_converter.asciifyImage(
image, list(self.options.gradient), self.options.saturation
)
6 changes: 4 additions & 2 deletions toascii/converters/extensions/converter_utils.nim
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import std/math

import nimpy/[raw_buffers]

import nimpy_numpy
Expand Down Expand Up @@ -29,7 +31,7 @@ proc rgb2hsl*(c: Color): HslColor {.inline.} =
var h, s, l: float = 0.0

if delta == 0.0: h = 0.0
elif cMax == r: h = ((g - b) / delta) mod 6.0
elif cMax == r: h = floorMod((g - b) / delta, 6.0)
elif cMax == g: h = ((b - r) / delta) + 2.0
else: h = ((r - g) / delta) + 4.0

Expand All @@ -56,7 +58,7 @@ proc hsl2rgb*(c: HslColor): Color {.inline.} =
var
r, g, b: float = 0.0
c = (1.0 - abs(2 * l - 1.0)) * s
x = c * (1.0 - abs((h / 60.0) mod 2.0 - 1.0))
x = c * (1.0 - abs(floorMod(h / 60.0, 2.0) - 1.0))
m = l - c / 2.0

if (0 <= h and h < 60):
Expand Down
11 changes: 11 additions & 0 deletions toascii/converters/extensions/extension_utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pathlib

import nimporter
import numpy as np

_did_build_extensions = False

Expand All @@ -10,3 +11,13 @@ def build_extensions() -> None:
if not _did_build_extensions:
nimporter.build_nim_extensions(pathlib.Path(__file__).parent.resolve())
_did_build_extensions = True


def validate_image_buffer(image: np.ndarray) -> np.ndarray:
"""Return a buffer that is safe for the native converters to access."""
if image.dtype != np.uint8:
raise TypeError("image must use the uint8 dtype")
if image.ndim != 3 or image.shape[2] != 3 or image.shape[0] == 0 or image.shape[1] == 0:
raise ValueError("image must be a non-empty height x width x 3 array")

return np.ascontiguousarray(image)
4 changes: 2 additions & 2 deletions toascii/converters/extensions/grayscale_converter.nim
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import converter_utils


proc asciifyImage(imgPyO: PyObject, gradient: openArray[string]): string {.exportpy.} =
let gradientLen = gradient.len.float
let gradientLen = gradient.high.float
result = ""

var imgBuf: RawPyBuffer
imgPyo.getBuffer(imgBuf, PyBUF_WRITABLE or PyBuf_ND)
imgPyo.getBuffer(imgBuf, PyBUF_ND)
defer: imgBuf.release()

for rowIdx in 0 .. imgBuf.dimShape(0) - 1:
Expand Down
3 changes: 2 additions & 1 deletion toascii/converters/extensions/grayscale_converter_nim.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import numpy as np

from ..grayscale_converter import GrayscaleConverter
from .extension_utils import build_extensions
from .extension_utils import build_extensions, validate_image_buffer

build_extensions()

Expand All @@ -12,4 +12,5 @@

class GrayscaleConverterNim(GrayscaleConverter):
def _asciify_image(self, image: np.ndarray) -> Generator[str, None, None]:
image = validate_image_buffer(image)
yield grayscale_converter.asciifyImage(image, list(self.options.gradient))
7 changes: 5 additions & 2 deletions toascii/converters/extensions/html_color_converter.nim
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ proc asciifyImage(imgPyo: PyObject, gradient: openArray[string], saturation: flo
elif saturation < -1: saturation = -1

result = ""
let gradientLen = gradient.len.float
let gradientLen = gradient.high.float
var lastColor = ""

var imgBuf: RawPyBuffer
imgPyo.getBuffer(imgBuf, PyBUF_WRITABLE or PyBuf_ND)
imgPyo.getBuffer(imgBuf, PyBUF_ND)
defer: imgBuf.release()

for rowIdx in 0 .. imgBuf.dimShape(0) - 1:
Expand All @@ -36,3 +36,6 @@ proc asciifyImage(imgPyo: PyObject, gradient: openArray[string], saturation: flo
result &= gChar

result &= "<br>"

if lastColor != "":
result &= "</span>"
4 changes: 2 additions & 2 deletions toascii/converters/extensions/html_color_converter_nim.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import numpy as np

from ..html_color_converter import HtmlColorConverter
from .extension_utils import build_extensions
from .extension_utils import build_extensions, validate_image_buffer

build_extensions()

Expand All @@ -12,7 +12,7 @@

class HtmlColorConverterNim(HtmlColorConverter):
def _asciify_image(self, image: np.ndarray) -> Generator[str, None, None]:
image = self._contrast(image)
image = self._contrast(validate_image_buffer(image))
yield html_color_converter.asciifyImage(
image, list(self.options.gradient), self.options.saturation
)
Loading