Skip to content

Commit

Permalink
Merge 90ea4e3 into 9c2d137
Browse files Browse the repository at this point in the history
  • Loading branch information
SebCorbin committed Jan 27, 2024
2 parents 9c2d137 + 90ea4e3 commit 0c7f2dc
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 40 deletions.
16 changes: 16 additions & 0 deletions CHANGES
Expand Up @@ -2,6 +2,22 @@
CHANGELOG
=========

0.12 (2024-01-27)
==================

** Bug fixed **

- Draw single points

** New **

- Updated Github Actions.
- Dropped support for python < 3.8
- Dropped support for Django < 4.2
- Add support for pillow >= 10
- Updated jSignature to latest version


0.11 (2022-01-17)
==================

Expand Down
62 changes: 38 additions & 24 deletions jsignature/utils.py
Expand Up @@ -6,63 +6,77 @@
from itertools import chain
from PIL import Image, ImageDraw, ImageOps, __version__ as PIL_VERSION

AA = 5 # super sampling gor antialiasing
AA = 5 # super sampling for antialiasing


def draw_signature(data, as_file=False):
""" Draw signature based on lines stored in json_string.
`data` can be a json object (list in fact) or a json string
if `as_file` is True, a temp file is returned instead of Image instance
"""Draw signature based on lines stored in json_string.
`data` can be a json object (list in fact) or a json string
if `as_file` is True, a temp file is returned instead of Image instance
"""

def _remove_empty_pts(pt):
return {
'x': list(filter(lambda n: n is not None, pt['x'])),
'y': list(filter(lambda n: n is not None, pt['y']))
"x": list(filter(lambda n: n is not None, pt["x"])),
"y": list(filter(lambda n: n is not None, pt["y"])),
}

if type(data) is str:
if isinstance(data, str):
drawing = json.loads(data, object_hook=_remove_empty_pts)
elif type(data) is list:
elif isinstance(data, list):
drawing = data
else:
raise ValueError

# Compute box
min_width = int(round(min(chain(*[d['x'] for d in drawing])))) - 10
max_width = int(round(max(chain(*[d['x'] for d in drawing])))) + 10
padding = 10
min_width = int(round(min(chain(*[d["x"] for d in drawing])))) - padding
max_width = int(round(max(chain(*[d["x"] for d in drawing])))) + padding
width = max_width - min_width
min_height = int(round(min(chain(*[d['y'] for d in drawing])))) - 10
max_height = int(round(max(chain(*[d['y'] for d in drawing])))) + 10
min_height = int(round(min(chain(*[d["y"] for d in drawing])))) - padding
max_height = int(round(max(chain(*[d["y"] for d in drawing])))) + padding
height = max_height - min_height

# Draw image
im = Image.new("RGBA", (width * AA, height * AA))
draw = ImageDraw.Draw(im)
for line in drawing:
len_line = len(line['x'])
points = [
(
(line['x'][i] - min_width) * AA,
(line['y'][i] - min_height) * AA
for coords in drawing:
line_length = len(coords["x"])
if line_length == 1:
# This is a single point, convert to a circle of 2x2 AA pixels
draw.ellipse(
[
(
(coords["x"][0] - min_width) * AA,
(coords["y"][0] - min_height) * AA,
),
(
(coords["x"][0] - min_width + 2) * AA,
(coords["y"][0] - min_height + 2) * AA,
),
],
fill="#000",
)
for i in range(0, len_line)
]
draw.line(points, fill="#000", width=2 * AA)
else:
points = [
((coords["x"][i] - min_width) * AA, (coords["y"][i] - min_height) * AA)
for i in range(0, line_length)
]
draw.line(points, fill="#000", width=2 * AA)
im = ImageOps.expand(im)
# Smart crop
bbox = im.getbbox()
if bbox:
im.crop(bbox)

old_pil_version = int(PIL_VERSION.split('.')[0]) < 10
im.thumbnail(
(width, height),
Image.ANTIALIAS if old_pil_version else Image.LANCZOS
# Image.ANTIALIAS is replaced in PIL 10.0.0
Image.ANTIALIAS if int(PIL_VERSION.split(".")[0]) < 10 else Image.LANCZOS,
)

if as_file:
ret = im._dump(format='PNG')
ret = im._dump(format="PNG")
else:
ret = im

Expand Down
4 changes: 2 additions & 2 deletions setup.py
Expand Up @@ -5,7 +5,7 @@

setup(
name='django-jsignature',
version='0.11',
version='0.12',
author='Florent Lebreton',
author_email='florent.lebreton@makina-corpus.com',
url='https://github.com/fle/django-jsignature',
Expand All @@ -14,7 +14,7 @@
long_description=open(os.path.join(here, 'README.rst')).read() + '\n\n' +
open(os.path.join(here, 'CHANGES')).read(),
license='LPGL, see LICENSE file.',
install_requires=['Django>=4.2', 'pillow<9.1.0', 'pyquery>=1.4.2'],
install_requires=['Django>=4.2', 'pillow', 'pyquery>=1.4.2'],
packages=find_packages(exclude=['example_project*', 'tests']),
include_package_data=True,
zip_safe=False,
Expand Down
23 changes: 14 additions & 9 deletions tests/test_fields.py
@@ -1,17 +1,12 @@
import json

from django import forms
from django.test import SimpleTestCase
from django.core.exceptions import ValidationError

from jsignature.fields import JSignatureField
from jsignature.forms import JSignatureField as JSignatureFormField

try:
from django.utils import six

string_types = six.string_types
except ImportError:
string_types = str
from tests.models import JSignatureTestModel


class JSignatureFieldTest(SimpleTestCase):
Expand Down Expand Up @@ -46,15 +41,15 @@ def test_get_prep_value_correct_values_python(self):
f = JSignatureField()
val = [{"x": [1, 2], "y": [3, 4]}]
val_prep = f.get_prep_value(val)
self.assertIsInstance(val_prep, string_types)
self.assertIsInstance(val_prep, str)
self.assertEqual(val, json.loads(val_prep))

def test_get_prep_value_correct_values_json(self):
f = JSignatureField()
val = [{"x": [1, 2], "y": [3, 4]}]
val_str = '[{"x":[1,2], "y":[3,4]}]'
val_prep = f.get_prep_value(val_str)
self.assertIsInstance(val_prep, string_types)
self.assertIsInstance(val_prep, str)
self.assertEqual(val, json.loads(val_prep))

def test_get_prep_value_incorrect_values(self):
Expand All @@ -66,3 +61,13 @@ def test_formfield(self):
f = JSignatureField()
cls = f.formfield().__class__
self.assertTrue(issubclass(cls, JSignatureFormField))

def test_modelform_media(self):
class TestModelForm(forms.ModelForm):
class Meta:
model = JSignatureTestModel
fields = forms.ALL_FIELDS

form = TestModelForm()
self.assertIn('jSignature.min.js', str(form.media))
self.assertIn('django_jsignature.js', str(form.media))
27 changes: 22 additions & 5 deletions tests/test_filter.py
Expand Up @@ -4,16 +4,33 @@

from jsignature.templatetags.jsignature_filters import signature_base64

DUMMY_VALUE = [{"x": [205, 210], "y": [59, 63]},
{"x": [205, 207], "y": [67, 64]}]
DUMMY_VALUE = [{"x": [205, 210], "y": [59, 63]}, {"x": [205, 207], "y": [67, 64]}]
DUMMY_STR_VALUE = json.dumps(DUMMY_VALUE)


class TemplateFilterTest(SimpleTestCase):
def test_inputs_bad_type_value(self):
self.assertEqual(signature_base64(object()), '')
self.assertEqual(signature_base64(None), '')
self.assertEqual(signature_base64(object()), "")
self.assertEqual(signature_base64(None), "")

def test_outputs_as_base64(self):
output = signature_base64(DUMMY_STR_VALUE)
self.assertEqual(output, "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAcCAYAAACUJBTQAAAAuElEQVR4nO3TMQ4BQRSH8d8uR9AQN5Do9Bo3wB3cQCs6lULcwZkkGmeQSBQU+yZEVIxC7FfNvGTnm7fvP9TU/A0NFN8UPB5eflMwQjvWzZyCMiQ9XLFHP7eoCFEL2xCdMMkleMUMl5AtVN1km1GhShcMcQjRKmqNF9+8JSnd59HFDoPYf9xNuuUUZ8w/PfCZlK4OjqpfNI5aU6bHmWK6DsEm9llmkCjcO1mqopxqv0mK8O92UFPzPjdRMBNmBFDqcwAAAABJRU5ErkJggg==")
self.assertEqual(
output,
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAcCAYAAACUJBTQAAAAuElEQVR4nO3TMQ4BQRSH8d8uR9AQN5Do9Bo3wB3cQCs6lULcwZkkGmeQSBQU+yZEVIxC7FfNvGTnm7fvP9TU/A0NFN8UPB5eflMwQjvWzZyCMiQ9XLFHP7eoCFEL2xCdMMkleMUMl5AtVN1km1GhShcMcQjRKmqNF9+8JSnd59HFDoPYf9xNuuUUZ8w/PfCZlK4OjqpfNI5aU6bHmWK6DsEm9llmkCjcO1mqopxqv0mK8O92UFPzPjdRMBNmBFDqcwAAAABJRU5ErkJggg==",
)

def test_outputs_as_base64_with_singlepoints(self):
face = [
{"x": [117], "y": [88]},
{"x": [140], "y": [83]},
{
"x": [116, 121, 125, 129, 134, 139, 142, 145],
"y": [100, 100, 101, 102, 102, 102, 100, 97],
},
]
output = signature_base64(json.dumps(face))
self.assertEqual(
output,
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADEAAAAnCAYAAACmE6CaAAABmklEQVR4nO3XvUpcQRjG8d9xhYghhYFoIZgmbSCVQlIERAKpAl6GuYrchNeQFLmCRLGytdDCIpUEAmkNxIAfKWYGDrK6c9adXTeZP7zs7izn/ZiZ5505VCqVSuU/oIn2TzCVhbSTfjCxLO5Igzns4BuW49hMroPZMnllk1ahh2dYwUNcKbitSogv+VsSCmmPFaVkkKIrkCglvk46GDbAncRXkhxhT0R8JZiY+Eoy1QXcGx1UxkQjNJKppa23qdy6KekFbMbvI20iTR/rCWfNzAiCpQLm8VU4h97HsbFurWGLSofuPL4IBZzinYyOOOjEbqLDR/gcP69a/51gD/s4xp8+zw+iwblwJ/uIDfyKBezGAi4z/NwaAB4Lyd9mJ/iELbzQ7aL4EgfRzw+sx/Gs953cJe/hectpem4Br/Aaa8IlMfE9WlrNm+I3WI2/t/EBP2PMi8z8RsYi3sQk9vDb4NVLdoi3LV+dWmsX8d0k1kaYseuz/QRPB/hMN+EjnAmzf9nH19i43nK7MnQbLf2amVvMRGe/UqlUyvAXHBNDpT7g8gsAAAAASUVORK5CYII=",
)

0 comments on commit 0c7f2dc

Please sign in to comment.