PushPinImageField is a Django form field that is a sub-class of an ImageField. The field accepts an image to upload and based on certain settings, notably the size of the resulting image, the sizes and colors of 3 different borders, as well as the color of the push pin, a re-sized image is created with a colorized push pin in the top-center.
A live demo of the field as well as more detailed usage instructions are available as a blog entry.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 | from PIL import Image
from django.db.models.fields.files import ImageField, ImageFieldFile
from django.core.files.base import ContentFile
import os
import math
import re
import colorsys
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
class PushPinImageFieldFile(ImageFieldFile):
def hex_to_rgb(self, value):
value = value.lstrip('#')
lv = len(value)
return tuple(int(value[i:i+lv/3], 16) for i in range(0, lv, lv/3))
def max_size(self, width, height):
ratio = float(width) / height
if width > self.field.max_width:
width = self.field.max_width
height = int(math.floor(width / ratio))
if height > self.field.max_height:
height = self.field.max_height
width = int(math.floor(height * ratio))
return width, height
def crop_to_fixed(self, img, width, height):
"""
Returns a scaled down and cropped version of the image based on the provided size values.
"""
ratio = float(img.size[0]) / img.size[1]
w = img.size[0]
h = img.size[1]
if (float(w) / width) >= (float(h) / height):
new_h = height
new_w = int(round(height * ratio))
else:
new_w = width
new_h = int(round(width / ratio))
img = img.resize((new_w, new_h), Image.ANTIALIAS)
if (new_w, new_h) != (width, height): # crop needed
if new_w > width:
start_x = int(round(float(new_w - width) / 2))
img = img.crop((start_x, 0, start_x + width, new_h))
elif new_w < width:
img = img.resize((width, new_h), Image.ANTIALIAS)
if new_h > height:
start_y = int(round(float(new_h - height) / 2))
img = img.crop((0, start_y, new_w, start_y + height))
elif new_h < height:
img = img.resize((new_w, height), Image.ANTIALIAS)
return img
def save(self, name, content, save=True):
push_pin_im = Image.open(self.field.push_pin_path)
new_content = StringIO()
content.file.seek(0)
in_image = Image.open(content.file)
# if fixed size given, max will be ignored
if self.field.fixed_width > 0 and self.field.fixed_height > 0:
in_width, in_height = self.field.fixed_width, self.field.fixed_height
else: # get adjusted height and width values
in_width, in_height = self.max_size(in_image.size[0], in_image.size[1])
# adjust further based on borders and stroke
new_in_height = in_height - (self.field.border[0] * 2)
new_in_width = in_width - (self.field.border[0] * 2)
# scale or crop the input image
if self.field.fixed_width or self.field.fixed_height:
scaled_in_image = self.crop_to_fixed(in_image, new_in_width, new_in_height)
else:
scaled_in_image = in_image.resize((new_in_width, new_in_height), Image.ANTIALIAS)
new_im = Image.new('RGB', (in_width, in_height))
# outer stroke
if self.field.outer_border_stroke[0]:
new_im.paste(self.hex_to_rgb(self.field.outer_border_stroke[1]), (0, 0, in_width, in_height))
# the main border
if self.field.border[0]:
new_im.paste(self.hex_to_rgb(self.field.border[1]), (self.field.outer_border_stroke[0], self.field.outer_border_stroke[0],
in_width - self.field.outer_border_stroke[0],
in_height - self.field.outer_border_stroke[0]
))
offset_x = float(in_width - new_in_width) / 2
offset_y = float(in_height - new_in_height) / 2
more_offset_x = 0
if offset_x % 1:
more_offset_x = 1
more_offset_y = 0
if offset_y % 1:
more_offset_y = 1
offset_x = int(math.floor(offset_x))
offset_y = int(math.floor(offset_y))
# inner stroke
if self.field.inner_border_stroke[0]:
new_im.paste(self.hex_to_rgb(self.field.inner_border_stroke[1]), (
offset_x - self.field.inner_border_stroke[0],
offset_y - self.field.inner_border_stroke[0],
in_width - (offset_x + more_offset_x) + self.field.inner_border_stroke[0],
in_height - (offset_y + more_offset_y) + self.field.inner_border_stroke[0]
))
# paste the scaled-down and centered input image
new_im.paste(scaled_in_image, (
offset_x,
offset_y,
offset_x + new_in_width,
offset_y + new_in_height
))
# colorize the push pin
push_pin_im = push_pin_im.convert('RGBA')
pixdata = push_pin_im.load()
rgb_push_pin = self.hex_to_rgb(self.field.push_pin_color)
push_pin_hsv = colorsys.rgb_to_hsv(float(rgb_push_pin[0])/255, float(rgb_push_pin[1])/255,
float(rgb_push_pin[2])/255)
push_pin_hue = push_pin_hsv[0]
push_pin_s = push_pin_hsv[1]
highest_s = list(colorsys.rgb_to_hsv(float(pixdata[16, 20][0])/255, float(pixdata[16, 20][1])/255,
float(pixdata[16, 20][2])/255))[1]
s_offset = push_pin_s - highest_s
for y in xrange(push_pin_im.size[1]):
for x in xrange(push_pin_im.size[0]):
hsv = list(colorsys.rgb_to_hsv(float(pixdata[x, y][0])/255, float(pixdata[x, y][1])/255,
float(pixdata[x, y][2])/255))
if hsv[0]>0:
hsv[0] = push_pin_hue
hsv[1] += s_offset
new_rgb = colorsys.hsv_to_rgb(hsv[0], hsv[1], hsv[2])
pixdata[x, y] = (int(round(new_rgb[0] * 255)), int(round(new_rgb[1] * 255)), int(round(new_rgb[2] * 255)), pixdata[x, y][3])
# paste in the push pin
pin_x = int(round(float(in_width - push_pin_im.size[1]) / 2)) + 8
new_im.paste(push_pin_im, (
pin_x,
1,
pin_x + push_pin_im.size[0],
1 + push_pin_im.size[1]
), push_pin_im)
new_im.save(new_content, format=self.field.format, quality=100)
new_content = ContentFile(new_content.getvalue())
super(PushPinImageFieldFile, self).save(name, new_content, save)
class PushPinImageField(ImageField):
attr_class = PushPinImageFieldFile
@staticmethod
def is_valid_hex_color(value):
if not value.startswith('#') or len(value) != 7 or not re.match('^[A-F0-9]*$', value[1:7].upper()):
return False
else:
return True
def check_color(self, value, field):
if not PushPinImageField.is_valid_hex_color(value):
raise ValueError('The color value for the %s field must contain a valid 6 digit hex color value' \
' and must start with a hash(#) character. Short-hand notation (e.g. #FFF) is' \
' not acceptable.' % field)
def __init__(self, push_pin_path, max_width=150, max_height=100, fixed_width=0, fixed_height=0,
push_pin_color='#ffff33', format='PNG', border=(0, '#ffffff'), inner_border_stroke=(0,'#000000'),
outer_border_stroke=(0, '#000000'), *args, **kwargs):
self.check_color(push_pin_color, 'push_pin_color')
self.check_color(border[1], 'border')
self.check_color(inner_border_stroke[1], 'inner_border_stroke')
self.check_color(outer_border_stroke[1], 'outer_border_stroke')
if inner_border_stroke[0] + outer_border_stroke[0] > border[0]:
raise ValueError('The sum of the sizes of the innner_border_stroke and outer_border_stroke' \
' cannot exceed the size of the border.')
self.push_pin_path = push_pin_path
self.max_width = max_width
self.max_height = max_height
self.fixed_width = fixed_width
self.fixed_height = fixed_height
self.push_pin_color = push_pin_color
self.format = format
self.border = border
self.inner_border_stroke = inner_border_stroke
self.outer_border_stroke = outer_border_stroke
super(PushPinImageField, self).__init__(*args, **kwargs)
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 1 week ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 2 weeks ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 6 months ago
Comments
Please login first before commenting.