Skip to content

Commit

Permalink
Merge pull request #78 from schemen/dev
Browse files Browse the repository at this point in the history
Add Background Color change + bugfixes
  • Loading branch information
schemen authored May 22, 2022
2 parents 11366eb + 34cb09f commit 343dfe0
Show file tree
Hide file tree
Showing 10 changed files with 168 additions and 60 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## 1.8.0(2022-05-18)

### New
* Add the possibility to change the creature background color. Default remains white.
* Added color selection tools to the classic builder as well as the Quickbuilder

### Improvements
* Transparent PNGs have their transparency replaced with the chosen background color.
* Applied a fix where enumeration doesn't apply correctly in the Quickbuilder and sometimes in the classic builder

## 1.7.0(2022-05-18)

### New
Expand Down
2 changes: 1 addition & 1 deletion paperminis/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
__version__ = '1.7.0'
__version__ = '1.8.0'
VERSION = __version__ # synonym
5 changes: 4 additions & 1 deletion paperminis/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,14 @@ class CreatureModifyForm(forms.ModelForm):

class Meta:
model = Creature
fields = ['name', 'show_name', 'img_url', 'size', 'position', 'color']
fields = ['name', 'show_name', 'img_url', 'size', 'position', 'background_color', 'color']

def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user') # To get request.user. Do not use kwargs.pop('user', None) due to potential security hole
# self.method = kwargs.pop('method') # To get create or update
super(CreatureModifyForm, self).__init__(*args, **kwargs)
self.fields['color'].required = False
self.fields['background_color'].required = False
self.fields['position'].required = False

def clean(self):
Expand Down Expand Up @@ -252,3 +253,5 @@ class QuickCreateCreatureForm(forms.Form):
name = forms.CharField(max_length=100, required=False)
size = forms.ChoiceField(choices=CREATURE_SIZE_CHOICES, initial="M")
quantity = forms.IntegerField(max_value=100, min_value=1, required=False, initial=1)
color = forms.ChoiceField(choices=COLOR_CHOICES, initial=DARKGRAY)
background_color = forms.ChoiceField(choices=COLOR_CHOICES, initial=WHITE)
53 changes: 29 additions & 24 deletions paperminis/generate_minis.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def load_settings(self,
def build_all_and_pdf(self):
if self.enumerate:
# if enumerate is true, settings are always loaded
self.creature_counter = Counter([c.name for c in self.creatures])
self.creature_counter = Counter([c.id for c in self.creatures])
self.creature_counter = {key: val for key, val in self.creature_counter.items() if val > 1}

self.minis = []
Expand Down Expand Up @@ -287,48 +287,49 @@ def build_mini(self, creature):
m_img = download_image(creature.img_url)

# fix grayscale images

try:
if len(m_img.shape) == 2:
m_img = cv.cvtColor(m_img, cv.COLOR_GRAY2RGB)
except:
return 'Image could not be found or loaded.'

# replace alpha channel with white for pngs (with fix for grayscale images)
# set the creature backrgound color
background_color = [int(creature.background_color[i:i + 2], 16) for i in (4, 2, 0)]

# replace alpha channel with backrgound color for pngs (with fix for grayscale images)
if m_img.shape[2] == 4:
alpha_channel = m_img[:, :, 3]
mask = (alpha_channel == 0)
mask = np.dstack((mask, mask, mask))
bmask = (alpha_channel == 0)
color = m_img[:, :, :3]
color[mask] = 255
color[bmask] = background_color
m_img = color

# get Textbox height
namebox_height = n_img.shape[0]

# find optimal size of image
# leave 1 pixel on each side for black border
# leave 1 pixel on each side for border
# Fit width
if m_img.shape[1] > width - 2:
f = (width - 2) / m_img.shape[1]
m_img = cv.resize(m_img, (0, 0), fx=f, fy=f)
white_vert = np.zeros((m_img.shape[0], 1, 3), np.uint8) + 255
white_vert = np.zeros((m_img.shape[0], 1, 3), np.uint8) + background_color
m_img = np.concatenate((white_vert, m_img, white_vert), axis=1)

# Fit height
if m_img.shape[0] > (max_height - 2 - namebox_height):
f = (max_height - 2 - namebox_height) / m_img.shape[0]
m_img = cv.resize(m_img, (0, 0), fx=f, fy=f)
white_horiz = np.zeros((1, m_img.shape[1], 3), np.uint8) + 255
white_horiz = np.zeros((1, m_img.shape[1], 3), np.uint8) + background_color
m_img = np.concatenate((white_horiz, m_img, white_horiz), axis=0)

if m_img.shape[1] < width:
diff = width - m_img.shape[1]
left = np.floor_divide(diff, 2)
right = left
if diff % 2 == 1: right += 1
m_img = np.concatenate((np.zeros((m_img.shape[0], left, 3), np.uint8) + 255, m_img,
np.zeros((m_img.shape[0], right, 3), np.uint8) + 255), axis=1)
m_img = np.concatenate((np.zeros((m_img.shape[0], left, 3), np.uint8) + background_color, m_img,
np.zeros((m_img.shape[0], right, 3), np.uint8) + background_color), axis=1)
# Handle creature positioning
if self.fixed_height:
if m_img.shape[0] < (min_height - namebox_height):
Expand All @@ -337,12 +338,12 @@ def build_mini(self, creature):
bottom = top
if diff % 2 == 1: bottom += 1
if creature.position == Creature.WALKING:
m_img = np.concatenate((np.zeros((diff, m_img.shape[1], 3), np.uint8) + 255, m_img), axis=0)
m_img = np.concatenate((np.zeros((diff, m_img.shape[1], 3), np.uint8) + background_color, m_img), axis=0)
elif creature.position == Creature.HOVERING:
m_img = np.concatenate((np.zeros((top, m_img.shape[1], 3), np.uint8) + 255, m_img,
np.zeros((bottom, m_img.shape[1], 3), np.uint8) + 255), axis=0)
m_img = np.concatenate((np.zeros((top, m_img.shape[1], 3), np.uint8) + background_color, m_img,
np.zeros((bottom, m_img.shape[1], 3), np.uint8) + background_color), axis=0)
elif creature.position == Creature.FLYING:
m_img = np.concatenate((m_img, np.zeros((diff, m_img.shape[1], 3), np.uint8) + 255), axis=0)
m_img = np.concatenate((m_img, np.zeros((diff, m_img.shape[1], 3), np.uint8) + background_color), axis=0)
else:
return 'Position setting is invalid. Chose Walking, Hovering or Flying.'
else:
Expand All @@ -352,17 +353,21 @@ def build_mini(self, creature):
bottom = np.floor_divide(diff, 3)
if diff % 2 == 1: bottom += 1
if creature.position == Creature.WALKING:
m_img = np.concatenate((np.zeros((top, m_img.shape[1], 3), np.uint8) + 255, m_img), axis=0)
m_img = np.concatenate((np.zeros((top, m_img.shape[1], 3), np.uint8) + background_color, m_img), axis=0)
elif creature.position == Creature.HOVERING:
m_img = np.concatenate((np.zeros((top, m_img.shape[1], 3), np.uint8) + 255, m_img,
np.zeros((top, m_img.shape[1], 3), np.uint8) + 255), axis=0)
m_img = np.concatenate((np.zeros((top, m_img.shape[1], 3), np.uint8) + background_color, m_img,
np.zeros((top, m_img.shape[1], 3), np.uint8) + background_color), axis=0)
elif creature.position == Creature.FLYING:
m_img = np.concatenate((m_img, np.zeros((bottom, m_img.shape[1], 3), np.uint8) + 255), axis=0)
m_img = np.concatenate((m_img, np.zeros((bottom, m_img.shape[1], 3), np.uint8) + background_color), axis=0)
else:
return 'Position setting is invalid. Chose Walking, Hovering or Flying.'

# draw border
cv.rectangle(m_img, (0, 0), (m_img.shape[1] - 1, m_img.shape[0] - 1), (0, 0, 0), thickness=1)
m_img = np.ascontiguousarray(m_img, dtype=np.uint8)
# draw border, ensure there is a white border with black background
if creature.background_color == Creature.BLACK:
cv.rectangle(m_img, (0, 0), (m_img.shape[1] - 1, m_img.shape[0] - 1), (255, 255, 255), thickness=1)
else:
cv.rectangle(m_img, (0, 0), (m_img.shape[1] - 1, m_img.shape[0] - 1), (0, 0, 0), thickness=1)

## flipped miniature image
m_img_flipped = np.flip(m_img, 0)
Expand Down Expand Up @@ -408,9 +413,9 @@ def build_mini(self, creature):
return 'Invalid base shape. Choose square, hexagon or circle.'

# enumerate
if self.enumerate and creature.name in self.creature_counter:
if self.enumerate and creature.id in self.creature_counter:
# print(creature.name, self.creature_counter[creature.name])
text = str(self.creature_counter[creature.name])
text = str(self.creature_counter[creature.id])
textsize = cv.getTextSize(text, self.font, enum_size, enum_width)[0]
x_margin = b_img.shape[1] - textsize[0]
y_margin = b_img.shape[0] - textsize[1]
Expand All @@ -425,7 +430,7 @@ def build_mini(self, creature):
textY = np.floor_divide(demi_base + textsize[1], 2)
cv.putText(b_img, text, (textX, textY), self.font, enum_size, enum_color, enum_width, cv.LINE_AA)

self.creature_counter[creature.name] -= 1
self.creature_counter[creature.id] -= 1

## construct full miniature
img = np.concatenate((m_img, n_img, b_img), axis=0)
Expand Down
18 changes: 18 additions & 0 deletions paperminis/migrations/0021_creature_background_color.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.13 on 2022-05-20 08:36

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('paperminis', '0020_auto_20220503_1646'),
]

operations = [
migrations.AddField(
model_name='creature',
name='background_color',
field=models.CharField(choices=[('228b22', 'Green'), ('aa3939', 'Red'), ('005b96', 'Blue'), ('d3d3d3', 'Light Gray'), ('a9a9a9', 'Dark Gray'), ('000000', 'Black'), ('ffffff', 'White')], default='ffffff', max_length=6),
),
]
1 change: 1 addition & 0 deletions paperminis/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ class Creature(models.Model):
size = models.CharField(max_length=1, choices=CREATURE_SIZE_CHOICES, default=MEDIUM)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
color = models.CharField(max_length=6, choices=COLOR_CHOICES, default=DARKGRAY)
background_color = models.CharField(max_length=6, choices=COLOR_CHOICES, default=WHITE)
position = models.CharField(max_length=50, choices=POSITION_CHOICES, default=WALKING)
show_name = models.BooleanField(default=True)
from_ddb = models.BooleanField(default=False)
Expand Down
26 changes: 17 additions & 9 deletions paperminis/utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from itertools import count
import json
from fractions import Fraction
from functools import lru_cache
from urllib.request import Request, urlopen
from dataclasses import dataclass
from dataclasses import dataclass, field


import cv2 as cv
import numpy as np
Expand Down Expand Up @@ -87,16 +89,17 @@ def handle_json(f, user):
return skip


@dataclass(init=False)
@dataclass()
class QuickCreature:
"""Class for a creature."""
name: str
size: float
quantity: int
position: str
img_url: str
color: str
show_name: bool = True
name: str = field(init=False)
size: float = field(init=False)
quantity: int = field(init=False)
position: str = field(init=False)
img_url: str = field(init=False)
color: str = field(init=False)
show_name: bool = field(default=True, init=False)
id: int = field(default_factory=count().__next__, init=False)


def quick_validate_creature(var):
Expand Down Expand Up @@ -140,6 +143,11 @@ def quick_validate_creature(var):
color = var.get("color", "d3d3d3")
creature.color = color

# background color
# TODO Implement color validation
background_color = var.get("background_color", "ffffff")
creature.background_color = background_color

# show_name
if creature.name:
creature.show_name = True
Expand Down
14 changes: 13 additions & 1 deletion templates/paperminis/creature_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,19 @@ <h4 class="card-title">{{ creature.name }}</h4>
<p><a href="{{ creature.img_url }}" target="_blank">{{ creature.img_url }}</a></p>
</div>
</div>
<div class="row">
<div class="col-md-4">
<span><strong>Background Color:</strong></span>
</div>
<div class="col-md-8">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" style="background-color: #{{ creature.background_color }}">&nbsp;</span>
<span class="input-group-text">{{ creature.get_background_color_display }}</span>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<span><strong>Base Color:</strong></span>
Expand All @@ -59,7 +72,6 @@ <h4 class="card-title">{{ creature.name }}</h4>
<span class="input-group-text" style="background-color: #{{ creature.color }}">&nbsp;</span>
<span class="input-group-text">{{ creature.get_color_display }}</span>
</div>
{# <input type="text" class="form-control" placeholder="Username" aria-label="Username" aria-describedby="basic-addon1">#}
</div>
</div>
</div>
Expand Down
19 changes: 19 additions & 0 deletions templates/paperminis/creature_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
var x = document.getElementById("id_color").value;
document.getElementById("color_label").style.backgroundColor = "#"+x;
}
function update_background_color() {
var x = document.getElementById("id_background_color").value;
document.getElementById("background_color_label").style.backgroundColor = "#"+x;
}
</script>
{% load widget_tweaks %}
{% load auth_extras %}
Expand Down Expand Up @@ -91,6 +95,21 @@ <h4 class="card-title">{% if object %}Edit Creature{% else %}Add new Creature{%
</select>
</div>
{# </div>#}
<div class="form-group">
{{ form.background_color.errors }}
<label for="{{ form.background_color.id_for_label }}">Background Color:</label>
<div class="input-group mb-3">
<div class="input-group-prepend">
<label id="background_color_label" class="input-group-text" for="background_color_picker" style="background-color: #{{ form.background_color.initial }}">&nbsp;</label>
</div>
<select name="background_color" id="id_background_color" class="custom-select" onchange="update_background_color()">
{% for hex, col in form.background_color.field.choices %}
<option class="form-control" {% if hex == form.background_color.initial %}selected{% endif %} value="{{ hex }}">{{ col }}</option>
{% endfor %}
</select>

</div>
</div>
<div class="form-group">
{{ form.color.errors }}
<label for="{{ form.color.id_for_label }}">Base Color:</label>
Expand Down
Loading

0 comments on commit 343dfe0

Please sign in to comment.