summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEvgeny Fadeev <evgeny.fadeev@gmail.com>2010-04-03 17:19:33 -0400
committerEvgeny Fadeev <evgeny.fadeev@gmail.com>2010-04-03 17:19:33 -0400
commit9911fa367920ada19b22db51ed787d38ec1499ed (patch)
tree465489acfce2cd9d02b43551110284a0e5701656
parent982640e574fd633d5afe3a498c296b96ce66a65d (diff)
downloadaskbot-9911fa367920ada19b22db51ed787d38ec1499ed.tar.gz
askbot-9911fa367920ada19b22db51ed787d38ec1499ed.tar.bz2
askbot-9911fa367920ada19b22db51ed787d38ec1499ed.zip
added color blending for counters, fixed questions and user questions views, better humanization of counters
-rwxr-xr-xforum/skins/default/media/style/style.css12
-rw-r--r--forum/skins/default/templates/index.html2
-rw-r--r--forum/skins/default/templates/question_counter_widget.html47
-rw-r--r--forum/skins/default/templates/question_list.html51
-rw-r--r--forum/skins/default/templates/questions.html88
-rw-r--r--forum/skins/default/templates/user_stats.html4
-rw-r--r--forum/skins/default/templates/users_questions.html45
-rwxr-xr-xforum/templatetags/extra_filters.py17
-rwxr-xr-xforum/templatetags/extra_tags.py62
-rw-r--r--forum/utils/colors.py24
-rw-r--r--forum/utils/functions.py5
-rwxr-xr-xforum/views/users.py22
-rw-r--r--forum_modules/grapefruit.py1973
-rwxr-xr-xsettings_local.py.dist31
14 files changed, 2207 insertions, 176 deletions
diff --git a/forum/skins/default/media/style/style.css b/forum/skins/default/media/style/style.css
index 1a203164..93c979ce 100755
--- a/forum/skins/default/media/style/style.css
+++ b/forum/skins/default/media/style/style.css
@@ -518,9 +518,12 @@ blockquote {
.short-summary .counts {
float: right;
margin-top: 4px;
+ margin-right: 10px;
+ margin-left: 5px;
}
.short-summary .counts .item-count {
+ padding-top:4px;
font-size: 17px;
font-weight: 800;
}
@@ -1691,6 +1694,7 @@ ins .post-tag {
text-align: center;
}
+/*
.narrow .votes {
background: #EEEEEE none repeat scroll 0 0;
float: left;
@@ -1703,21 +1707,26 @@ ins .post-tag {
-khtml-border-radius: 5px;
-webkit-border-radius: 5px;
}
+*/
+/*
.narrow .summary {
width: 600px;
display: inline-block;
}
+*/
.narrow .summary h3 {
padding: 0px;
margin: 0px;
}
+/* todo: delete commented out stuff */
+/*
.narrow .views {
height: 42px;
float: left;
- margin: 0 7px 0 0; /*padding:5px 0 5px 4px;*/
+ margin: 0 7px 0 0; *//*padding:5px 0 5px 4px;*//*
padding: 5px;
width: 46px;
text-align: center;
@@ -1726,6 +1735,7 @@ ins .post-tag {
-webkit-border-radius: 5px;
color: #777;
}
+*/
.narrow .status {
float: left;
diff --git a/forum/skins/default/templates/index.html b/forum/skins/default/templates/index.html
index c2f36c9c..8a885dd4 100644
--- a/forum/skins/default/templates/index.html
+++ b/forum/skins/default/templates/index.html
@@ -32,7 +32,9 @@
</div>
</div>
<!-- ???? -->
+<div id="listA">
{% include "question_list.html" %}
+</div>
{% endblock %}
{% block sidebar %}
diff --git a/forum/skins/default/templates/question_counter_widget.html b/forum/skins/default/templates/question_counter_widget.html
new file mode 100644
index 00000000..5b35651f
--- /dev/null
+++ b/forum/skins/default/templates/question_counter_widget.html
@@ -0,0 +1,47 @@
+{% load extra_filters %}
+{% load i18n %}
+<div class="counts">
+ <div class="votes"
+ style="background:{{votes_bg}};color:{{votes_fg}}"
+ title="{% trans "Please decide if you like this question or not by voting" %}">
+ {% spaceless %}
+ <div class="item-count">
+ {{question.score|humanize_counter}}
+ </div>
+ <div>
+ {% blocktrans count question.score as cnt %}
+ vote
+ {% plural %}
+ votes
+ {% endblocktrans %}
+ </div>
+ {% endspaceless %}
+ </div >
+ {% comment %}
+ <div {% if question.answer_accepted %}title="{% trans "this answer has been accepted to be correct" %}"{% endif %} class="status {% if question.answer_accepted %}answered-accepted{% endif %} {% ifequal question.answer_count 0 %}unanswered{% endifequal %}{% ifnotequal question.answer_count 0 %}answered{% endifnotequal %}">
+ {% endcomment %}
+ <div class="votes" style="background:{{answers_bg}};color:{{answers_fg}}">
+ <div class="item-count">{{question.answer_count|humanize_counter}}</div>
+ {% spaceless %}
+ <div>
+ {% blocktrans count question.answer_count as cnt %}
+ answer
+ {% plural %}
+ answers
+ {% endblocktrans %}
+ </div>
+ {% endspaceless %}
+ </div>
+ <div class="views" style="background:{{views_bg}};color:{{views_fg}}">
+ {% spaceless %}
+ <div class="item-count">{{question.view_count|humanize_counter}}</div>
+ <div>
+ {% blocktrans count question.view_count as cnt %}
+ view
+ {% plural %}
+ views
+ {% endblocktrans %}
+ </div>
+ {% endspaceless %}
+ </div>
+</div>
diff --git a/forum/skins/default/templates/question_list.html b/forum/skins/default/templates/question_list.html
index d822a267..53633691 100644
--- a/forum/skins/default/templates/question_list.html
+++ b/forum/skins/default/templates/question_list.html
@@ -4,39 +4,20 @@
{% load extra_filters %}
{% load extra_tags %}
-<div id="listA">
- {% for question in questions.object_list %}
- {% cache 60 question_in_list question.id %}
- <div class="short-summary">
- <div class="counts">
- <div class="votes">
- <div class="item-count">{{question.score|intcomma}}</div>
- <div>{% trans "votes" %}</div>
- </div >
- <div {% if question.answer_accepted %}title="{% trans "this answer has been accepted to be correct" %}"{% endif %} class="status {% if question.answer_accepted %}answered-accepted{% endif %} {% ifequal question.answer_count 0 %}unanswered{% endifequal %}{% ifnotequal question.answer_count 0 %}answered{% endifnotequal %}">
- <div class="item-count">{{question.answer_count|intcomma}}</div>
- <div>{% trans "answers" %}</div>
- </div>
- <div class="views">
- <div class="item-count">{{question.view_count|cnprog_intword|safe}}</div>
- <div>{% trans "views" %}</div>
- </div>
- </div>
-
- <h2><a title="{{question.summary}}" href="{% url question id=question.id %}{{question.title|slugify}}">{{question.title}}</a></h2>
- <div class="userinfo">
- <span class="relativetime" title="{{question.last_activity_at}}">{% diff_date question.last_activity_at %}</span>
- {% if question.last_activity_by %}
- <a href="{% url user_profile question.last_activity_by.id question.last_activity_by.username|slugify %}">{{ question.last_activity_by }}</a> {% get_score_badge question.last_activity_by %}
- {% endif %}
- </div>
-
- <div class="tags">
- {% for tag in question.tagname_list %}
- <a href="{% url tag_questions tag|urlencode %}" title="{% trans "see questions tagged" %} '{{ tag }}' {% trans "using tags" %}" rel="tag">{{ tag }}</a>
- {% endfor %}
- </div>
- </div>
- {% endcache %}
- {% endfor %}
+{% for question in questions.object_list %}
+<div class="short-summary">
+ {% question_counter_widget question %}
+ <h2><a title="{{question.summary}}" href="{% url question id=question.id %}{{question.title|slugify}}">{{question.title}}</a></h2>
+ <div class="userinfo">
+ <span class="relativetime" title="{{question.last_activity_at}}">{% diff_date question.last_activity_at %}</span>
+ {% if question.last_activity_by %}
+ <a href="{% url user_profile question.last_activity_by.id question.last_activity_by.username|slugify %}">{{ question.last_activity_by }}</a> {% get_score_badge question.last_activity_by %}
+ {% endif %}
+ </div>
+ <div class="tags">
+ {% for tag in question.tagname_list %}
+ <a href="{% url tag_questions tag|urlencode %}" title="{% trans "see questions tagged" %} '{{ tag }}' {% trans "using tags" %}" rel="tag">{{ tag }}</a>
+ {% endfor %}
+ </div>
</div>
+{% endfor %}
diff --git a/forum/skins/default/templates/questions.html b/forum/skins/default/templates/questions.html
index a271f03d..366727d1 100644
--- a/forum/skins/default/templates/questions.html
+++ b/forum/skins/default/templates/questions.html
@@ -51,93 +51,7 @@
</div>
</div>
<div id="listA">
- {% for question in questions.object_list %}
- <div class="qstA"
- {% if request.user.is_authenticated %}
- {% if question.interesting_score > 0 %}
- style="background:#ffff99;"
- {% else %}
- {% if not request.user.hide_ignored_questions %}
- {% if question.ignored_score > 0 %}
- style="background:#f3f3f3;"
- {% endif %}
- {% endif %}
- {% endif %}
- {% endif %}
- >
- <h2>
- <a href="{{ question.get_absolute_url }}">{{ question.get_question_title }}</a>
- </h2>
- <div class="stat">
- <table>
- <tr>
- <td><span class="num">{{ question.answer_count|intcomma }}</span> </td>
- <td><span class="num">{{ question.score|intcomma }}</span> </td>
- <td><span class="num">{{ question.view_count|cnprog_intword|safe }}</span> </td>
- </tr>
- <tr>
- <td><span class="unit">{% trans "answers" %}</span></td>
- <td><span class="unit">{% trans "votes" %}</span></td>
- <td><span class="unit">{% trans "views" %}</span></td>
- </tr>
- </table>
- </div>
-
- <div class="summary">
- {{ question.summary }}...
- </div>
-
- {% ifequal tab_id 'active'%}
- {% if question.wiki and settings.WIKI_ON %}
- <span class="from wiki">{% trans "community wiki" %}</span>
- <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
- {% else %}
- <div class="from">
- {% comment %}{% gravatar question.last_activity_by 24 %}{% endcomment %}
- <span class="author"><a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a></span>
- <span class="score">{% get_score_badge question.last_activity_by %} </span>
- <span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
- </div>
- {% endif %}
- {% else %}
- {% if question.wiki and settings.WIKI_ON %}
- <span class="from wiki">{% trans "community wiki" %}</span>
- <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
- {% else %}
- <div class="from">
- {% comment %}{% gravatar question.author 24 %}{% endcomment %}
- {% if question.last_activity_at != question.added_at %}
- {% if question.author.id != question.last_activity_by.id %}
- {% trans "Posted:" %}
- <span class="author"><a href="{{ question.author.get_profile_url }}">{{ question.author }}</a></span>
- <span class="score">{% get_score_badge question.author %} </span>
- / {% trans "Updated:" %}
- <span class="author"><a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a></span>
- <span class="score">{% get_score_badge question.last_activity_by %} </span>
- <span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
- {% else %}
- {% trans "Updated:" %}
- <span class="author"><a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a></span>
- <span class="score">{% get_score_badge question.last_activity_by %} </span>
- <span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
- {% endif %}
- {% else %}
- {% trans "Posted:" %}
- <span class="author"><a href="{{ question.author.get_profile_url }}">{{ question.author }}</a></span>
- <span class="score">{% get_score_badge question.author %} </span>
- <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
- {% endif %}
- </div>
- {% endif %}
- {% endifequal %}
-
- <div class="tags">
- {% for tag in question.tagname_list %}
- <a href="{% url tag_questions tag|urlencode %}" title="{% trans "see questions tagged" %}'{{ tag }}'{% trans "using tags" %}" rel="tag">{{ tag }}</a>
- {% endfor %}
- </div>
- </div>
- {% endfor %}
+ {% include "question_list.html" %}
{% if searchtitle %}
{% if questions_count == 0 %}
<p class="evenMore" style="padding-top:30px;text-align:center;">
diff --git a/forum/skins/default/templates/user_stats.html b/forum/skins/default/templates/user_stats.html
index 5ad1d71b..1f462581 100644
--- a/forum/skins/default/templates/user_stats.html
+++ b/forum/skins/default/templates/user_stats.html
@@ -33,8 +33,8 @@
<a title="{{answered_question.summary|collapse}}"
href="{% url question answered_question.id %}{{answered_question.title|slugify}}#{{answered_question.answer_id}}">
<span class="answer-votes {% if answered_question.accepted %}answered-accepted{% endif %}"
- title="{% blocktrans with answered_question.vote_count as vote_count %}the answer has been voted for {{ vote_count }} times{% endblocktrans %} {% if answered_question.accepted %}{% trans "this answer has been selected as correct" %}{%endif%}">
- {{ answered_question.vote_count }}
+ title="{% blocktrans with answered_question.answer_score as answer_score %}the answer has been voted for {{ answer_score }} times{% endblocktrans %} {% if answered_question.accepted %}{% trans "this answer has been selected as correct" %}{%endif%}">
+ {{ answered_question.answer_score }}
</span>
</a>
<div class="answer-link">
diff --git a/forum/skins/default/templates/users_questions.html b/forum/skins/default/templates/users_questions.html
index 6ebc4833..be6aaf7d 100644
--- a/forum/skins/default/templates/users_questions.html
+++ b/forum/skins/default/templates/users_questions.html
@@ -24,38 +24,21 @@
{% else %}
<div class="favorites-empty"> </div>
{% endif %}
- <div id="question-summary-{{question.id}}" class="question-summary narrow">
- <div class="stats">
- <div class="votes">
- <span class="vote-count-post">{{question.vote_count|intcomma}}</span>
- {% trans "votes" %}
- </div >
- <div title="{% if question.answer_accepted %}{% trans "this answer has been accepted to be correct" %}{% endif %}" class="status {% if question.answer_accepted %}answered-accepted{% endif %} {% ifequal question.answer_count 0 %}unanswered{% endifequal %}{% ifnotequal question.answer_count 0 %}answered{% endifnotequal %}">
- <span class="answer-count-post">{{question.answer_count|intcomma}}</span>
- {% trans "answers" %}
- </div>
- <div class="views">
- <span class="views-count-post">{{question.view_count|cnprog_intword|safe}}</span>
- {% trans "views" %}
- </div>
+ <div id="question-summary-{{question.id}}" class="short-summary narrow">
+ {% question_counter_widget question %}
+ <h2><a title="{{question.summary}}" href="{% url question id=question.id %}{{question.title|slugify}}">{{question.title}}</a></h2>
+ <div class="tags">
+ {% convert2tagname_list question %}
+ {% for tag in question.tagnames %}
+ <!--todo - move trans below to blocktrans -->
+ <a href="{% url tag_questions tag|urlencode %}" title="{% trans "see questions tagged" %} '{{ tag }}' {% trans "using tags" %}" rel="tag">{{ tag }}</a>
+ {% endfor %}
</div>
- <div class="summary">
- <div class="question-title">
- <a title="{{question.summary}}" href="{% url question id=question.id %}{{question.title|slugify}}">{{question.title}}</a>
- </div>
- <div class="tags">
- {% convert2tagname_list question %}
- {% for tag in question.tagnames %}
- <!--todo - move trans below to blocktrans -->
- <a href="{% url tag_questions tag|urlencode %}" title="{% trans "see questions tagged" %} '{{ tag }}' {% trans "using tags" %}" rel="tag">{{ tag }}</a>
- {% endfor %}
- </div>
- <div class="started">
- <span class="relativetime" title="{{question.last_activity_at}}">{% diff_date question.last_activity_at %}</span>
- {% if question.la_username %}
- <a href="{% url users %}{{question.la_user_id}}/{{question.la_username}}">{{question.la_username}}</a> {% get_score_badge_by_details question.la_user_reputation question.la_user_gold question.la_user_silver question.la_user_bronze%}
- {% endif %}
- </div>
+ <div class="started">
+ <span class="relativetime" title="{{question.last_activity_at}}">{% diff_date question.last_activity_at %}</span>
+ {% if question.la_username %}
+ <a href="{% url users %}{{question.la_user_id}}/{{question.la_username}}">{{question.la_username}}</a> {% get_score_badge_by_details question.la_user_reputation question.la_user_gold question.la_user_silver question.la_user_bronze%}
+ {% endif %}
</div>
</div>
<br clear="all"/>
diff --git a/forum/templatetags/extra_filters.py b/forum/templatetags/extra_filters.py
index 3644fdc3..b687889f 100755
--- a/forum/templatetags/extra_filters.py
+++ b/forum/templatetags/extra_filters.py
@@ -1,5 +1,6 @@
from django import template
from forum import auth
+from forum_modules.grapefruit import Color
import logging
register = template.Library()
@@ -86,8 +87,22 @@ def cnprog_intword(number):
try:
if 1000 <= number < 10000:
string = str(number)[0:1]
- return "<span class=""thousand"">%sk</span>" % string
+ return '<span class="thousand">%sk</span>' % string
else:
return number
except:
return number
+
+@register.filter
+def humanize_counter(number):
+ if number == 0:
+ return 'no'
+ elif number >= 1000:
+ number = number/1000
+ s = '%.1f' % number
+ if s.endswith('.0'):
+ return s[:-2] + 'k'
+ else:
+ return s + 'k'
+ else:
+ return str(number)
diff --git a/forum/templatetags/extra_tags.py b/forum/templatetags/extra_tags.py
index 52f231fe..382d19f0 100755
--- a/forum/templatetags/extra_tags.py
+++ b/forum/templatetags/extra_tags.py
@@ -17,6 +17,8 @@ from django.template.defaulttags import url as default_url
from django.template.defaultfilters import slugify
from django.core.urlresolvers import reverse
from forum import skins
+from forum.utils import colors
+from forum.utils.functions import get_from_dict_or_object
register = template.Library()
@@ -36,14 +38,10 @@ def gravatar(user, size):
This tag can accept a User object, or a dict containing the
appropriate values.
"""
- try:
- gravatar = user['gravatar']
- username = user['username']
- user_id = user['id']
- except (TypeError, AttributeError, KeyError):
- gravatar = user.gravatar
- username = user.username
- user_id = user.id
+ #todo: rewrite using get_from_dict_or_object
+ gravatar = get_from_dict_or_object(user, 'gravatar')
+ username = get_from_dict_or_object(user, 'username')
+ user_id = get_from_dict_or_object(user, 'id')
slug = slugify(username)
user_profile_url = reverse('user_profile', kwargs={'id':user_id,'slug':slug})
return mark_safe(GRAVATAR_TEMPLATE % {
@@ -389,4 +387,52 @@ def fullmedia(url):
path = media(url)
return "%s%s" % (domain, path)
+@register.inclusion_tag("question_counter_widget.html")
+def question_counter_widget(question):
+
+ view_count = get_from_dict_or_object(question,'view_count')
+ answer_count = get_from_dict_or_object(question,'answer_count')
+ vote_count = get_from_dict_or_object(question,'score')
+ answer_accepted = get_from_dict_or_object(question,'answer_accepted')
+
+ #background and foreground colors for each item
+ (views_fg, views_bg) = colors.get_counter_colors(
+ view_count,
+ max = settings.VIEW_COUNTER_EXPECTED_MAXIMUM,
+ zero_bg = settings.COLORS_VIEW_COUNTER_EMPTY_BG,
+ zero_fg = settings.COLORS_VIEW_COUNTER_EMPTY_FG,
+ min_bg = settings.COLORS_VIEW_COUNTER_MIN_BG,
+ min_fg = settings.COLORS_VIEW_COUNTER_MIN_FG,
+ max_bg = settings.COLORS_VIEW_COUNTER_MAX_BG,
+ max_fg = settings.COLORS_VIEW_COUNTER_MAX_FG,
+ )
+
+ (answers_fg, answers_bg) = colors.get_counter_colors(
+ answer_count,
+ max = settings.ANSWER_COUNTER_EXPECTED_MAXIMUM,
+ zero_bg = settings.COLORS_ANSWER_COUNTER_EMPTY_BG,
+ zero_fg = settings.COLORS_ANSWER_COUNTER_EMPTY_FG,
+ min_bg = settings.COLORS_ANSWER_COUNTER_MIN_BG,
+ min_fg = settings.COLORS_ANSWER_COUNTER_MIN_FG,
+ max_bg = settings.COLORS_ANSWER_COUNTER_MAX_BG,
+ max_fg = settings.COLORS_ANSWER_COUNTER_MAX_FG,
+ )
+ if answer_accepted:
+ #todo: maybe recalculate the foreground color too
+ answers_bg = settings.COLORS_ANSWER_COUNTER_ACCEPTED_BG
+ answers_fg = settings.COLORS_ANSWER_COUNTER_ACCEPTED_FG
+
+ (votes_fg, votes_bg) = colors.get_counter_colors(
+ vote_count,
+ max = settings.VOTE_COUNTER_EXPECTED_MAXIMUM,
+ zero_bg = settings.COLORS_VOTE_COUNTER_EMPTY_BG,
+ zero_fg = settings.COLORS_VOTE_COUNTER_EMPTY_FG,
+ min_bg = settings.COLORS_VOTE_COUNTER_MIN_BG,
+ min_fg = settings.COLORS_VOTE_COUNTER_MIN_FG,
+ max_bg = settings.COLORS_VOTE_COUNTER_MAX_BG,
+ max_fg = settings.COLORS_VOTE_COUNTER_MAX_FG,
+ )
+
+ #returns a dictionary with keys like 'votes_bg', etc
+ return locals()
diff --git a/forum/utils/colors.py b/forum/utils/colors.py
new file mode 100644
index 00000000..8ab092d7
--- /dev/null
+++ b/forum/utils/colors.py
@@ -0,0 +1,24 @@
+from forum_modules.grapefruit import Color
+import math
+
+def get_counter_colors(count, max=10, empty_bg='white', empty_fg='black',
+ zero_bg='white', zero_fg='black',
+ min_bg='white', min_fg='black',
+ max_bg='white', max_fg='black'
+ ):
+ if count == 0:
+ return zero_fg, zero_bg
+
+ if count > max:
+ blend_factor = 0
+ else:
+ #todo deal with negative counts properly
+ blend_factor = 1 - math.fabs(float(count)/float(max))
+
+ max_fg_color = Color.NewFromHtml(max_fg)
+ fg = Color.NewFromHtml(min_fg).Blend(max_fg_color, blend_factor)
+
+ max_bg_color = Color.NewFromHtml(max_bg)
+ bg = Color.NewFromHtml(min_bg).Blend(max_bg_color, blend_factor)
+
+ return fg.html, bg.html
diff --git a/forum/utils/functions.py b/forum/utils/functions.py
new file mode 100644
index 00000000..671ddc2c
--- /dev/null
+++ b/forum/utils/functions.py
@@ -0,0 +1,5 @@
+def get_from_dict_or_object(object,key):
+ try:
+ return object[key]
+ except:
+ return getattr(object,key)
diff --git a/forum/views/users.py b/forum/views/users.py
index 7915ed6d..245a1f85 100755
--- a/forum/views/users.py
+++ b/forum/views/users.py
@@ -146,7 +146,7 @@ def user_stats(request, user_id, user_view):
user = get_object_or_404(User, id=user_id)
questions = Question.objects.extra(
select={
- 'vote_count' : 'question.score',
+ 'score' : 'question.score',
'favorited_myself' : 'SELECT count(*) FROM favorite_question f WHERE f.user_id = %s AND f.question_id = question.id',
'la_user_id' : 'auth_user.id',
'la_username' : 'auth_user.username',
@@ -159,8 +159,8 @@ def user_stats(request, user_id, user_view):
tables=['question', 'auth_user'],
where=['question.deleted=False AND question.author_id=%s AND question.last_activity_by_id = auth_user.id'],
params=[user_id],
- order_by=['-vote_count', '-last_activity_at']
- ).values('vote_count',
+ order_by=['-score', '-last_activity_at']
+ ).values('score',
'favorited_myself',
'id',
'title',
@@ -189,22 +189,22 @@ def user_stats(request, user_id, user_view):
'vote_up_count' : 'answer.vote_up_count',
'vote_down_count' : 'answer.vote_down_count',
'answer_id' : 'answer.id',
- 'accepted' : 'answer.accepted',
- 'vote_count' : 'answer.score',
+ 'answer_accepted' : 'answer.accepted',
+ 'answer_score' : 'answer.score',
'comment_count' : 'answer.comment_count'
},
tables=['question', 'answer'],
where=['answer.deleted=False AND question.deleted=False AND answer.author_id=%s AND answer.question_id=question.id'],
params=[user_id],
- order_by=['-vote_count', '-answer_id'],
+ order_by=['-answer_score', '-answer_id'],
select_params=[user_id]
).distinct().values('comment_count',
'id',
'answer_id',
'title',
'author_id',
- 'accepted',
- 'vote_count',
+ 'answer_accepted',
+ 'answer_score',
'answer_count',
'vote_up_count',
'vote_down_count')[:100]
@@ -796,7 +796,7 @@ def user_favorites(request, user_id, user_view):
user = get_object_or_404(User, id=user_id)
questions = Question.objects.extra(
select={
- 'vote_count' : 'question.vote_up_count + question.vote_down_count',
+ 'score' : 'question.vote_up_count + question.vote_down_count',
'favorited_myself' : 'SELECT count(*) FROM favorite_question f WHERE f.user_id = %s '+
'AND f.question_id = question.id',
'la_user_id' : 'auth_user.id',
@@ -811,8 +811,8 @@ def user_favorites(request, user_id, user_view):
where=['question.deleted=False AND question.last_activity_by_id = auth_user.id '+
'AND favorite_question.question_id = question.id AND favorite_question.user_id = %s'],
params=[user_id],
- order_by=['-vote_count', '-question.id']
- ).values('vote_count',
+ order_by=['-score', '-question.id']
+ ).values('score',
'favorited_myself',
'id',
'title',
diff --git a/forum_modules/grapefruit.py b/forum_modules/grapefruit.py
new file mode 100644
index 00000000..ca684745
--- /dev/null
+++ b/forum_modules/grapefruit.py
@@ -0,0 +1,1973 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-#
+
+# Copyright (c) 2008, Xavier Basty
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+'''GrapeFruit - Color manipulation in Python'''
+
+# $Id: grapefruit.py 31 2008-06-15 10:48:06Z xbasty $
+__author__ = 'Xavier Basty <xbasty@gmail.com>'
+__version__ = '0.1a3'
+
+
+# The default white reference, use 2° Standard Observer, D65 (daylight)
+_DEFAULT_WREF = (0.95043, 1.00000, 1.08890)
+
+_oneThird = 1.0 / 3
+_srgbGammaCorrInv = 0.03928 / 12.92
+_sixteenHundredsixteenth = 16.0 / 116
+
+_RybWheel = (
+ 0, 26, 52,
+ 83, 120, 130,
+ 141, 151, 162,
+ 177, 190, 204,
+ 218, 232, 246,
+ 261, 275, 288,
+ 303, 317, 330,
+ 338, 345, 352,
+ 360)
+
+_RgbWheel = (
+ 0, 8, 17,
+ 26, 34, 41,
+ 48, 54, 60,
+ 81, 103, 123,
+ 138, 155, 171,
+ 187, 204, 219,
+ 234, 251, 267,
+ 282, 298, 329,
+ 360)
+
+class Color:
+ '''Hold a color value.
+
+ Example usage:
+
+ To create an instance of the grapefruit.Color from RGB values:
+
+ >>> import grapefruit
+ >>> r, g, b = 1, 0.5, 0
+ >>> col = grapefruit.Color.NewFromRgb(r, g, b)
+
+ To get the values of the color in another colorspace:
+
+ >>> h, s, v = col.hsv
+ >>> l, a, b = col.lab
+
+ To get the complementary of a color:
+
+ >>> compl = col.ComplementaryColor()
+ >>> print compl.hsl
+ (210.0, 1.0, 0.5)
+
+ To directly convert RGB values to their HSL equivalent:
+
+ >>> h, s, l = Color.RgbToHsl(r, g, b)
+
+ '''
+
+ WHITE_REFERENCE = {
+ 'std_A' : (1.09847, 1.00000, 0.35582),
+ 'std_B' : (0.99093, 1.00000, 0.85313),
+ 'std_C' : (0.98071, 1.00000, 1.18225),
+ 'std_D50' : (0.96421, 1.00000, 0.82519),
+ 'std_D55' : (0.95680, 1.00000, 0.92148),
+ 'std_D65' : (0.95043, 1.00000, 1.08890),
+ 'std_D75' : (0.94972, 1.00000, 1.22639),
+ 'std_E' : (1.00000, 1.00000, 1.00000),
+ 'std_F1' : (0.92834, 1.00000, 1.03665),
+ 'std_F2' : (0.99145, 1.00000, 0.67316),
+ 'std_F3' : (1.03753, 1.00000, 0.49861),
+ 'std_F4' : (1.09147, 1.00000, 0.38813),
+ 'std_F5' : (0.90872, 1.00000, 0.98723),
+ 'std_F6' : (0.97309, 1.00000, 0.60191),
+ 'std_F7' : (0.95017, 1.00000, 1.08630),
+ 'std_F8' : (0.96413, 1.00000, 0.82333),
+ 'std_F9' : (1.00365, 1.00000, 0.67868),
+ 'std_F10' : (0.96174, 1.00000, 0.81712),
+ 'std_F11' : (1.00899, 1.00000, 0.64262),
+ 'std_F12' : (1.08046, 1.00000, 0.39228),
+ 'sup_A' : (1.11142, 1.00000, 0.35200),
+ 'sup_B' : (0.99178, 1.00000, 0.84349),
+ 'sup_C' : (0.97286, 1.00000, 1.16145),
+ 'sup_D50' : (0.96721, 1.00000, 0.81428),
+ 'sup_D55' : (0.95797, 1.00000, 0.90925),
+ 'sup_D65' : (0.94810, 1.00000, 1.07305),
+ 'sup_D75' : (0.94417, 1.00000, 1.20643),
+ 'sup_E' : (1.00000, 1.00000, 1.00000),
+ 'sup_F1' : (0.94791, 1.00000, 1.03191),
+ 'sup_F2' : (1.03245, 1.00000, 0.68990),
+ 'sup_F3' : (1.08968, 1.00000, 0.51965),
+ 'sup_F4' : (1.14961, 1.00000, 0.40963),
+ 'sup_F5' : (0.93369, 1.00000, 0.98636),
+ 'sup_F6' : (1.02148, 1.00000, 0.62074),
+ 'sup_F7' : (0.95780, 1.00000, 1.07618),
+ 'sup_F8' : (0.97115, 1.00000, 0.81135),
+ 'sup_F9' : (1.02116, 1.00000, 0.67826),
+ 'sup_F10' : (0.99001, 1.00000, 0.83134),
+ 'sup_F11' : (1.03820, 1.00000, 0.65555),
+ 'sup_F12' : (1.11428, 1.00000, 0.40353)}
+
+ NAMED_COLOR = {
+ 'aliceblue': '#f0f8ff',
+ 'antiquewhite': '#faebd7',
+ 'aqua': '#00ffff',
+ 'aquamarine': '#7fffd4',
+ 'azure': '#f0ffff',
+ 'beige': '#f5f5dc',
+ 'bisque': '#ffe4c4',
+ 'black': '#000000',
+ 'blanchedalmond': '#ffebcd',
+ 'blue': '#0000ff',
+ 'blueviolet': '#8a2be2',
+ 'brown': '#a52a2a',
+ 'burlywood': '#deb887',
+ 'cadetblue': '#5f9ea0',
+ 'chartreuse': '#7fff00',
+ 'chocolate': '#d2691e',
+ 'coral': '#ff7f50',
+ 'cornflowerblue': '#6495ed',
+ 'cornsilk': '#fff8dc',
+ 'crimson': '#dc143c',
+ 'cyan': '#00ffff',
+ 'darkblue': '#00008b',
+ 'darkcyan': '#008b8b',
+ 'darkgoldenrod': '#b8860b',
+ 'darkgray': '#a9a9a9',
+ 'darkgrey': '#a9a9a9',
+ 'darkgreen': '#006400',
+ 'darkkhaki': '#bdb76b',
+ 'darkmagenta': '#8b008b',
+ 'darkolivegreen': '#556b2f',
+ 'darkorange': '#ff8c00',
+ 'darkorchid': '#9932cc',
+ 'darkred': '#8b0000',
+ 'darksalmon': '#e9967a',
+ 'darkseagreen': '#8fbc8f',
+ 'darkslateblue': '#483d8b',
+ 'darkslategray': '#2f4f4f',
+ 'darkslategrey': '#2f4f4f',
+ 'darkturquoise': '#00ced1',
+ 'darkviolet': '#9400d3',
+ 'deeppink': '#ff1493',
+ 'deepskyblue': '#00bfff',
+ 'dimgray': '#696969',
+ 'dimgrey': '#696969',
+ 'dodgerblue': '#1e90ff',
+ 'firebrick': '#b22222',
+ 'floralwhite': '#fffaf0',
+ 'forestgreen': '#228b22',
+ 'fuchsia': '#ff00ff',
+ 'gainsboro': '#dcdcdc',
+ 'ghostwhite': '#f8f8ff',
+ 'gold': '#ffd700',
+ 'goldenrod': '#daa520',
+ 'gray': '#808080',
+ 'grey': '#808080',
+ 'green': '#008000',
+ 'greenyellow': '#adff2f',
+ 'honeydew': '#f0fff0',
+ 'hotpink': '#ff69b4',
+ 'indianred': '#cd5c5c',
+ 'indigo': '#4b0082',
+ 'ivory': '#fffff0',
+ 'khaki': '#f0e68c',
+ 'lavender': '#e6e6fa',
+ 'lavenderblush': '#fff0f5',
+ 'lawngreen': '#7cfc00',
+ 'lemonchiffon': '#fffacd',
+ 'lightblue': '#add8e6',
+ 'lightcoral': '#f08080',
+ 'lightcyan': '#e0ffff',
+ 'lightgoldenrodyellow': '#fafad2',
+ 'lightgreen': '#90ee90',
+ 'lightgray': '#d3d3d3',
+ 'lightgrey': '#d3d3d3',
+ 'lightpink': '#ffb6c1',
+ 'lightsalmon': '#ffa07a',
+ 'lightseagreen': '#20b2aa',
+ 'lightskyblue': '#87cefa',
+ 'lightslategray': '#778899',
+ 'lightslategrey': '#778899',
+ 'lightsteelblue': '#b0c4de',
+ 'lightyellow': '#ffffe0',
+ 'lime': '#00ff00',
+ 'limegreen': '#32cd32',
+ 'linen': '#faf0e6',
+ 'magenta': '#ff00ff',
+ 'maroon': '#800000',
+ 'mediumaquamarine': '#66cdaa',
+ 'mediumblue': '#0000cd',
+ 'mediumorchid': '#ba55d3',
+ 'mediumpurple': '#9370db',
+ 'mediumseagreen': '#3cb371',
+ 'mediumslateblue': '#7b68ee',
+ 'mediumspringgreen': '#00fa9a',
+ 'mediumturquoise': '#48d1cc',
+ 'mediumvioletred': '#c71585',
+ 'midnightblue': '#191970',
+ 'mintcream': '#f5fffa',
+ 'mistyrose': '#ffe4e1',
+ 'moccasin': '#ffe4b5',
+ 'navajowhite': '#ffdead',
+ 'navy': '#000080',
+ 'oldlace': '#fdf5e6',
+ 'olive': '#808000',
+ 'olivedrab': '#6b8e23',
+ 'orange': '#ffa500',
+ 'orangered': '#ff4500',
+ 'orchid': '#da70d6',
+ 'palegoldenrod': '#eee8aa',
+ 'palegreen': '#98fb98',
+ 'paleturquoise': '#afeeee',
+ 'palevioletred': '#db7093',
+ 'papayawhip': '#ffefd5',
+ 'peachpuff': '#ffdab9',
+ 'peru': '#cd853f',
+ 'pink': '#ffc0cb',
+ 'plum': '#dda0dd',
+ 'powderblue': '#b0e0e6',
+ 'purple': '#800080',
+ 'red': '#ff0000',
+ 'rosybrown': '#bc8f8f',
+ 'royalblue': '#4169e1',
+ 'saddlebrown': '#8b4513',
+ 'salmon': '#fa8072',
+ 'sandybrown': '#f4a460',
+ 'seagreen': '#2e8b57',
+ 'seashell': '#fff5ee',
+ 'sienna': '#a0522d',
+ 'silver': '#c0c0c0',
+ 'skyblue': '#87ceeb',
+ 'slateblue': '#6a5acd',
+ 'slategray': '#708090',
+ 'slategrey': '#708090',
+ 'snow': '#fffafa',
+ 'springgreen': '#00ff7f',
+ 'steelblue': '#4682b4',
+ 'tan': '#d2b48c',
+ 'teal': '#008080',
+ 'thistle': '#d8bfd8',
+ 'tomato': '#ff6347',
+ 'turquoise': '#40e0d0',
+ 'violet': '#ee82ee',
+ 'wheat': '#f5deb3',
+ 'white': '#ffffff',
+ 'whitesmoke': '#f5f5f5',
+ 'yellow': '#ffff00',
+ 'yellowgreen': '#9acd32'}
+
+ def __init__(self, values, mode='rgb', alpha=1.0, wref=_DEFAULT_WREF):
+ '''Instantiate a new grapefruit.Color object.
+
+ Parameters:
+ :values:
+ The values of this color, in the specified representation.
+ :mode:
+ The representation mode used for values.
+ :alpha:
+ the alpha value (transparency) of this color.
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ '''
+ if not(isinstance(values, tuple)):
+ raise TypeError, 'values must be a tuple'
+
+ if mode=='rgb':
+ self.__rgb = values
+ self.__hsl = Color.RgbToHsl(*values)
+ elif mode=='hsl':
+ self.__hsl = values
+ self.__rgb = Color.HslToRgb(*values)
+ else:
+ raise ValueError('Invalid color mode: ' + mode)
+
+ self.__a = alpha
+ self.__wref = wref
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __eq__(self, other):
+ try:
+ if isinstance(other, Color):
+ return (self.__rgb==other.__rgb) and (self.__a==other.__a)
+
+ if len(other) != 4:
+ return False
+ rgba = self.__rgb + (self.__a,)
+ return reduce(lambda x, y: x and (y[0]==y[1]), zip(rgba, other), True)
+ except TypeError:
+ return False
+ except AttributeError:
+ return False
+
+ def __repr__(self):
+ return str(self.__rgb + (self.__a,))
+
+ def __str__(self):
+ '''A string representation of this grapefruit.Color instance.
+
+ Returns:
+ The RGBA representation of this grapefruit.Color instance.
+
+ '''
+ return '(%g, %g, %g, %g)' % (self.__rgb + (self.__a,))
+
+ def __unicode__(self):
+ '''A unicode string representation of this grapefruit.Color instance.
+
+ Returns:
+ The RGBA representation of this grapefruit.Color instance.
+
+ '''
+ return u'(%g, %g, %g, %g)' % (self.__rgb + (self.__a,))
+
+ def __iter__(self):
+ return iter(self.__rgb + (self.__a,))
+
+ def __len__(self):
+ return 4
+
+ @staticmethod
+ def RgbToHsl(r, g, b):
+ '''Convert the color from RGB coordinates to HSL.
+
+ Parameters:
+ :r:
+ The Red component value [0...1]
+ :g:
+ The Green component value [0...1]
+ :b:
+ The Blue component value [0...1]
+
+ Returns:
+ The color as an (h, s, l) tuple in the range:
+ h[0...360],
+ s[0...1],
+ l[0...1]
+
+ >>> Color.RgbToHsl(1, 0.5, 0)
+ (30.0, 1.0, 0.5)
+
+ '''
+ minVal = min(r, g, b) # min RGB value
+ maxVal = max(r, g, b) # max RGB value
+
+ l = (maxVal + minVal) / 2.0
+ if minVal==maxVal:
+ return (0.0, 0.0, l) # achromatic (gray)
+
+ d = maxVal - minVal # delta RGB value
+
+ if l < 0.5: s = d / (maxVal + minVal)
+ else: s = d / (2.0 - maxVal - minVal)
+
+ dr, dg, db = [(maxVal-val) / d for val in (r, g, b)]
+
+ if r==maxVal:
+ h = db - dg
+ elif g==maxVal:
+ h = 2.0 + dr - db
+ else:
+ h = 4.0 + dg - dr
+
+ h = (h*60.0) % 360.0
+ return (h, s, l)
+
+ @staticmethod
+ def _HueToRgb(n1, n2, h):
+ h %= 6.0
+ if h < 1.0: return n1 + ((n2-n1) * h)
+ if h < 3.0: return n2
+ if h < 4.0: return n1 + ((n2-n1) * (4.0 - h))
+ return n1
+
+ @staticmethod
+ def HslToRgb(h, s, l):
+ '''Convert the color from HSL coordinates to RGB.
+
+ Parameters:
+ :h:
+ The Hue component value [0...1]
+ :s:
+ The Saturation component value [0...1]
+ :l:
+ The Lightness component value [0...1]
+
+ Returns:
+ The color as an (r, g, b) tuple in the range:
+ r[0...1],
+ g[0...1],
+ b[0...1]
+
+ >>> Color.HslToRgb(30.0, 1.0, 0.5)
+ (1.0, 0.5, 0.0)
+
+ '''
+ if s==0: return (l, l, l) # achromatic (gray)
+
+ if l<0.5: n2 = l * (1.0 + s)
+ else: n2 = l+s - (l*s)
+
+ n1 = (2.0 * l) - n2
+
+ h /= 60.0
+ hueToRgb = Color._HueToRgb
+ r = hueToRgb(n1, n2, h + 2)
+ g = hueToRgb(n1, n2, h)
+ b = hueToRgb(n1, n2, h - 2)
+
+ return (r, g, b)
+
+ @staticmethod
+ def RgbToHsv(r, g, b):
+ '''Convert the color from RGB coordinates to HSV.
+
+ Parameters:
+ :r:
+ The Red component value [0...1]
+ :g:
+ The Green component value [0...1]
+ :b:
+ The Blue component value [0...1]
+
+ Returns:
+ The color as an (h, s, v) tuple in the range:
+ h[0...360],
+ s[0...1],
+ v[0...1]
+
+ >>> Color.RgbToHsv(1, 0.5, 0)
+ (30.0, 1, 1)
+
+ '''
+ v = max(r, g, b)
+ d = v - min(r, g, b)
+ if d==0: return (0.0, 0.0, v)
+ s = d / v
+
+ dr, dg, db = [(v - val) / d for val in (r, g, b)]
+
+ if r==v:
+ h = db - dg # between yellow & magenta
+ elif g==v:
+ h = 2.0 + dr - db # between cyan & yellow
+ else: # b==v
+ h = 4.0 + dg - dr # between magenta & cyan
+
+ h = (h*60.0) % 360.0
+ return (h, s, v)
+
+ @staticmethod
+ def HsvToRgb(h, s, v):
+ '''Convert the color from RGB coordinates to HSV.
+
+ Parameters:
+ :h:
+ The Hus component value [0...1]
+ :s:
+ The Saturation component value [0...1]
+ :v:
+ The Value component [0...1]
+
+ Returns:
+ The color as an (r, g, b) tuple in the range:
+ r[0...1],
+ g[0...1],
+ b[0...1]
+
+ >>> Color.HslToRgb(30.0, 1.0, 0.5)
+ (1.0, 0.5, 0.0)
+
+ '''
+ if s==0: return (v, v, v) # achromatic (gray)
+
+ h /= 60.0
+ h = h % 6.0
+
+ i = int(h)
+ f = h - i
+ if not(i&1): f = 1-f # if i is even
+
+ m = v * (1.0 - s)
+ n = v * (1.0 - (s * f))
+
+ if i==0: return (v, n, m)
+ if i==1: return (n, v, m)
+ if i==2: return (m, v, n)
+ if i==3: return (m, n, v)
+ if i==4: return (n, m, v)
+ return (v, m, n)
+
+ @staticmethod
+ def RgbToYiq(r, g, b):
+ '''Convert the color from RGB to YIQ.
+
+ Parameters:
+ :r:
+ The Red component value [0...1]
+ :g:
+ The Green component value [0...1]
+ :b:
+ The Blue component value [0...1]
+
+ Returns:
+ The color as an (y, i, q) tuple in the range:
+ y[0...1],
+ i[0...1],
+ q[0...1]
+
+ >>> '(%g, %g, %g)' % Color.RgbToYiq(1, 0.5, 0)
+ '(0.592263, 0.458874, -0.0499818)'
+
+ '''
+ y = (r * 0.29895808) + (g * 0.58660979) + (b *0.11443213)
+ i = (r * 0.59590296) - (g * 0.27405705) - (b *0.32184591)
+ q = (r * 0.21133576) - (g * 0.52263517) + (b *0.31129940)
+ return (y, i, q)
+
+ @staticmethod
+ def YiqToRgb(y, i, q):
+ '''Convert the color from YIQ coordinates to RGB.
+
+ Parameters:
+ :y:
+ Tte Y component value [0...1]
+ :i:
+ The I component value [0...1]
+ :q:
+ The Q component value [0...1]
+
+ Returns:
+ The color as an (r, g, b) tuple in the range:
+ r[0...1],
+ g[0...1],
+ b[0...1]
+
+ >>> '(%g, %g, %g)' % Color.YiqToRgb(0.592263, 0.458874, -0.0499818)
+ '(1, 0.5, 5.442e-007)'
+
+ '''
+ r = y + (i * 0.9562) + (q * 0.6210)
+ g = y - (i * 0.2717) - (q * 0.6485)
+ b = y - (i * 1.1053) + (q * 1.7020)
+ return (r, g, b)
+
+ @staticmethod
+ def RgbToYuv(r, g, b):
+ '''Convert the color from RGB coordinates to YUV.
+
+ Parameters:
+ :r:
+ The Red component value [0...1]
+ :g:
+ The Green component value [0...1]
+ :b:
+ The Blue component value [0...1]
+
+ Returns:
+ The color as an (y, u, v) tuple in the range:
+ y[0...1],
+ u[-0.436...0.436],
+ v[-0.615...0.615]
+
+ >>> '(%g, %g, %g)' % Color.RgbToYuv(1, 0.5, 0)
+ '(0.5925, -0.29156, 0.357505)'
+
+ '''
+ y = (r * 0.29900) + (g * 0.58700) + (b * 0.11400)
+ u = -(r * 0.14713) - (g * 0.28886) + (b * 0.43600)
+ v = (r * 0.61500) - (g * 0.51499) - (b * 0.10001)
+ return (y, u, v)
+
+ @staticmethod
+ def YuvToRgb(y, u, v):
+ '''Convert the color from YUV coordinates to RGB.
+
+ Parameters:
+ :y:
+ The Y component value [0...1]
+ :u:
+ The U component value [-0.436...0.436]
+ :v:
+ The V component value [-0.615...0.615]
+
+ Returns:
+ The color as an (r, g, b) tuple in the range:
+ r[0...1],
+ g[0...1],
+ b[0...1]
+
+ >>> '(%g, %g, %g)' % Color.YuvToRgb(0.5925, -0.2916, 0.3575)
+ '(0.999989, 0.500015, -6.3276e-005)'
+
+ '''
+ r = y + (v * 1.13983)
+ g = y - (u * 0.39465) - (v * 0.58060)
+ b = y + (u * 2.03211)
+ return (r, g, b)
+
+ @staticmethod
+ def RgbToXyz(r, g, b):
+ '''Convert the color from sRGB to CIE XYZ.
+
+ The methods assumes that the RGB coordinates are given in the sRGB
+ colorspace (D65).
+
+ .. note::
+
+ Compensation for the sRGB gamma correction is applied before converting.
+
+ Parameters:
+ :r:
+ The Red component value [0...1]
+ :g:
+ The Green component value [0...1]
+ :b:
+ The Blue component value [0...1]
+
+ Returns:
+ The color as an (x, y, z) tuple in the range:
+ x[0...1],
+ y[0...1],
+ z[0...1]
+
+ >>> '(%g, %g, %g)' % Color.RgbToXyz(1, 0.5, 0)
+ '(0.488941, 0.365682, 0.0448137)'
+
+ '''
+ r, g, b = [((v <= 0.03928) and [v / 12.92] or [((v+0.055) / 1.055) **2.4])[0] for v in (r, g, b)]
+
+ x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805)
+ y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722)
+ z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505)
+ return (x, y, z)
+
+ @staticmethod
+ def XyzToRgb(x, y, z):
+ '''Convert the color from CIE XYZ coordinates to sRGB.
+
+ .. note::
+
+ Compensation for sRGB gamma correction is applied before converting.
+
+ Parameters:
+ :x:
+ The X component value [0...1]
+ :y:
+ The Y component value [0...1]
+ :z:
+ The Z component value [0...1]
+
+ Returns:
+ The color as an (r, g, b) tuple in the range:
+ r[0...1],
+ g[0...1],
+ b[0...1]
+
+ >>> '(%g, %g, %g)' % Color.XyzToRgb(0.488941, 0.365682, 0.0448137)
+ '(1, 0.5, 6.81883e-008)'
+
+ '''
+ r = (x * 3.2406255) - (y * 1.5372080) - (z * 0.4986286)
+ g = -(x * 0.9689307) + (y * 1.8757561) + (z * 0.0415175)
+ b = (x * 0.0557101) - (y * 0.2040211) + (z * 1.0569959)
+ return tuple((((v <= _srgbGammaCorrInv) and [v * 12.92] or [(1.055 * (v ** (1/2.4))) - 0.055])[0] for v in (r, g, b)))
+
+ @staticmethod
+ def XyzToLab(x, y, z, wref=_DEFAULT_WREF):
+ '''Convert the color from CIE XYZ to CIE L*a*b*.
+
+ Parameters:
+ :x:
+ The X component value [0...1]
+ :y:
+ The Y component value [0...1]
+ :z:
+ The Z component value [0...1]
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ Returns:
+ The color as an (L, a, b) tuple in the range:
+ L[0...100],
+ a[-1...1],
+ b[-1...1]
+
+ >>> '(%g, %g, %g)' % Color.XyzToLab(0.488941, 0.365682, 0.0448137)
+ '(66.9518, 0.43084, 0.739692)'
+
+ >>> '(%g, %g, %g)' % Color.XyzToLab(0.488941, 0.365682, 0.0448137, Color.WHITE_REFERENCE['std_D50'])
+ '(66.9518, 0.411663, 0.67282)'
+
+ '''
+ # White point correction
+ x /= wref[0]
+ y /= wref[1]
+ z /= wref[2]
+
+ # Nonlinear distortion and linear transformation
+ x, y, z = [((v > 0.008856) and [v**_oneThird] or [(7.787 * v) + _sixteenHundredsixteenth])[0] for v in (x, y, z)]
+
+ # Vector scaling
+ l = (116 * y) - 16
+ a = 5.0 * (x - y)
+ b = 2.0 * (y - z)
+
+ return (l, a, b)
+
+ @staticmethod
+ def LabToXyz(l, a, b, wref=_DEFAULT_WREF):
+ '''Convert the color from CIE L*a*b* to CIE 1931 XYZ.
+
+ Parameters:
+ :l:
+ The L component [0...100]
+ :a:
+ The a component [-1...1]
+ :b:
+ The a component [-1...1]
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ Returns:
+ The color as an (x, y, z) tuple in the range:
+ x[0...q],
+ y[0...1],
+ z[0...1]
+
+ >>> '(%g, %g, %g)' % Color.LabToXyz(66.9518, 0.43084, 0.739692)
+ '(0.488941, 0.365682, 0.0448137)'
+
+ >>> '(%g, %g, %g)' % Color.LabToXyz(66.9518, 0.411663, 0.67282, Color.WHITE_REFERENCE['std_D50'])
+ '(0.488941, 0.365682, 0.0448138)'
+
+ '''
+ y = (l + 16) / 116
+ x = (a / 5.0) + y
+ z = y - (b / 2.0)
+ return tuple((((v > 0.206893) and [v**3] or [(v - _sixteenHundredsixteenth) / 7.787])[0] * w for v, w in zip((x, y, z), wref)))
+
+ @staticmethod
+ def CmykToCmy(c, m, y, k):
+ '''Convert the color from CMYK coordinates to CMY.
+
+ Parameters:
+ :c:
+ The Cyan component value [0...1]
+ :m:
+ The Magenta component value [0...1]
+ :y:
+ The Yellow component value [0...1]
+ :k:
+ The Black component value [0...1]
+
+ Returns:
+ The color as an (c, m, y) tuple in the range:
+ c[0...1],
+ m[0...1],
+ y[0...1]
+
+ >>> '(%g, %g, %g)' % Color.CmykToCmy(1, 0.32, 0, 0.5)
+ '(1, 0.66, 0.5)'
+
+ '''
+ mk = 1-k
+ return ((c*mk + k), (m*mk + k), (y*mk + k))
+
+ @staticmethod
+ def CmyToCmyk(c, m, y):
+ '''Convert the color from CMY coordinates to CMYK.
+
+ Parameters:
+ :c:
+ The Cyan component value [0...1]
+ :m:
+ The Magenta component value [0...1]
+ :y:
+ The Yellow component value [0...1]
+
+ Returns:
+ The color as an (c, m, y, k) tuple in the range:
+ c[0...1],
+ m[0...1],
+ y[0...1],
+ k[0...1]
+
+ >>> '(%g, %g, %g, %g)' % Color.CmyToCmyk(1, 0.66, 0.5)
+ '(1, 0.32, 0, 0.5)'
+
+ '''
+ k = min(c, m, y)
+ if k==1.0: return (0.0, 0.0, 0.0, 1.0)
+ mk = 1-k
+ return ((c-k) / mk, (m-k) / mk, (y-k) / mk, k)
+
+ @staticmethod
+ def RgbToCmy(r, g, b):
+ '''Convert the color from RGB coordinates to CMY.
+
+ Parameters:
+ :r:
+ The Red component value [0...1]
+ :g:
+ The Green component value [0...1]
+ :b:
+ The Blue component value [0...1]
+
+ Returns:
+ The color as an (c, m, y) tuple in the range:
+ c[0...1],
+ m[0...1],
+ y[0...1]
+
+ >>> Color.RgbToCmy(1, 0.5, 0)
+ (0, 0.5, 1)
+
+ '''
+ return (1-r, 1-g, 1-b)
+
+ @staticmethod
+ def CmyToRgb(c, m, y):
+ '''Convert the color from CMY coordinates to RGB.
+
+ Parameters:
+ :c:
+ The Cyan component value [0...1]
+ :m:
+ The Magenta component value [0...1]
+ :y:
+ The Yellow component value [0...1]
+
+ Returns:
+ The color as an (r, g, b) tuple in the range:
+ r[0...1],
+ g[0...1],
+ b[0...1]
+
+ >>> Color.CmyToRgb(0, 0.5, 1)
+ (1, 0.5, 0)
+
+ '''
+ return (1-c, 1-m, 1-y)
+
+ @staticmethod
+ def RgbToHtml(r, g, b):
+ '''Convert the color from (r, g, b) to #RRGGBB.
+
+ Parameters:
+ :r:
+ The Red component value [0...1]
+ :g:
+ The Green component value [0...1]
+ :b:
+ The Blue component value [0...1]
+
+ Returns:
+ A CSS string representation of this color (#RRGGBB).
+
+ >>> Color.RgbToHtml(1, 0.5, 0)
+ '#ff8000'
+
+ '''
+ return '#%02x%02x%02x' % tuple((min(round(v*255), 255) for v in (r, g, b)))
+
+ @staticmethod
+ def HtmlToRgb(html):
+ '''Convert the HTML color to (r, g, b).
+
+ Parameters:
+ :html:
+ the HTML definition of the color (#RRGGBB or #RGB or a color name).
+
+ Returns:
+ The color as an (r, g, b) tuple in the range:
+ r[0...1],
+ g[0...1],
+ b[0...1]
+
+ Throws:
+ :ValueError:
+ If html is neither a known color name or a hexadecimal RGB
+ representation.
+
+ >>> '(%g, %g, %g)' % Color.HtmlToRgb('#ff8000')
+ '(1, 0.501961, 0)'
+ >>> '(%g, %g, %g)' % Color.HtmlToRgb('ff8000')
+ '(1, 0.501961, 0)'
+ >>> '(%g, %g, %g)' % Color.HtmlToRgb('#f60')
+ '(1, 0.4, 0)'
+ >>> '(%g, %g, %g)' % Color.HtmlToRgb('f60')
+ '(1, 0.4, 0)'
+ >>> '(%g, %g, %g)' % Color.HtmlToRgb('lemonchiffon')
+ '(1, 0.980392, 0.803922)'
+
+ '''
+ html = html.strip().lower()
+ if html[0]=='#':
+ html = html[1:]
+ elif Color.NAMED_COLOR.has_key(html):
+ html = Color.NAMED_COLOR[html][1:]
+
+ if len(html)==6:
+ rgb = html[:2], html[2:4], html[4:]
+ elif len(html)==3:
+ rgb = ['%c%c' % (v,v) for v in html]
+ else:
+ raise ValueError, 'input #%s is not in #RRGGBB format' % html
+
+ return tuple(((int(n, 16) / 255.0) for n in rgb))
+
+ @staticmethod
+ def RgbToPil(r, g, b):
+ '''Convert the color from RGB to a PIL-compatible integer.
+
+ Parameters:
+ :r:
+ The Red component value [0...1]
+ :g:
+ The Green component value [0...1]
+ :b:
+ The Blue component value [0...1]
+
+ Returns:
+ A PIL compatible integer (0xBBGGRR).
+
+ >>> '0x%06x' % Color.RgbToPil(1, 0.5, 0)
+ '0x0080ff'
+
+ '''
+ r, g, b = [min(int(round(v*255)), 255) for v in (r, g, b)]
+ return (b << 16) + (g << 8) + r
+
+ @staticmethod
+ def PilToRgb(pil):
+ '''Convert the color from a PIL-compatible integer to RGB.
+
+ Parameters:
+ pil: a PIL compatible color representation (0xBBGGRR)
+ Returns:
+ The color as an (r, g, b) tuple in the range:
+ the range:
+ r: [0...1]
+ g: [0...1]
+ b: [0...1]
+
+ >>> '(%g, %g, %g)' % Color.PilToRgb(0x0080ff)
+ '(1, 0.501961, 0)'
+
+ '''
+ r = 0xff & pil
+ g = 0xff & (pil >> 8)
+ b = 0xff & (pil >> 16)
+ return tuple((v / 255.0 for v in (r, g, b)))
+
+ @staticmethod
+ def _WebSafeComponent(c, alt=False):
+ '''Convert a color component to its web safe equivalent.
+
+ Parameters:
+ :c:
+ The component value [0...1]
+ :alt:
+ If True, return the alternative value instead of the nearest one.
+
+ Returns:
+ The web safe equivalent of the component value.
+
+ '''
+ # This sucks, but floating point between 0 and 1 is quite fuzzy...
+ # So we just change the scale a while to make the equality tests
+ # work, otherwise it gets wrong at some decimal far to the right.
+ sc = c * 100.0
+
+ # If the color is already safe, return it straight away
+ d = sc % 20
+ if d==0: return c
+
+ # Get the lower and upper safe values
+ l = sc - d
+ u = l + 20
+
+ # Return the 'closest' value according to the alt flag
+ if alt:
+ if (sc-l) >= (u-sc): return l/100.0
+ else: return u/100.0
+ else:
+ if (sc-l) >= (u-sc): return u/100.0
+ else: return l/100.0
+
+ @staticmethod
+ def RgbToWebSafe(r, g, b, alt=False):
+ '''Convert the color from RGB to 'web safe' RGB
+
+ Parameters:
+ :r:
+ The Red component value [0...1]
+ :g:
+ The Green component value [0...1]
+ :b:
+ The Blue component value [0...1]
+ :alt:
+ If True, use the alternative color instead of the nearest one.
+ Can be used for dithering.
+
+ Returns:
+ The color as an (r, g, b) tuple in the range:
+ the range:
+ r[0...1],
+ g[0...1],
+ b[0...1]
+
+ >>> '(%g, %g, %g)' % Color.RgbToWebSafe(1, 0.55, 0.0)
+ '(1, 0.6, 0)'
+
+ '''
+ webSafeComponent = Color._WebSafeComponent
+ return tuple((webSafeComponent(v, alt) for v in (r, g, b)))
+
+ @staticmethod
+ def RgbToGreyscale(r, g, b):
+ '''Convert the color from RGB to its greyscale equivalent
+
+ Parameters:
+ :r:
+ The Red component value [0...1]
+ :g:
+ The Green component value [0...1]
+ :b:
+ The Blue component value [0...1]
+
+ Returns:
+ The color as an (r, g, b) tuple in the range:
+ the range:
+ r[0...1],
+ g[0...1],
+ b[0...1]
+
+ >>> '(%g, %g, %g)' % Color.RgbToGreyscale(1, 0.8, 0)
+ '(0.6, 0.6, 0.6)'
+
+ '''
+ v = (r + g + b) / 3.0
+ return (v, v, v)
+
+ @staticmethod
+ def RgbToRyb(hue):
+ '''Maps a hue on the RGB color wheel to Itten's RYB wheel.
+
+ Parameters:
+ :hue:
+ The hue on the RGB color wheel [0...360]
+
+ Returns:
+ An approximation of the corresponding hue on Itten's RYB wheel.
+
+ >>> Color.RgbToRyb(15)
+ 26
+
+ '''
+ d = hue % 15
+ i = int(hue / 15)
+ x0 = _RybWheel[i]
+ x1 = _RybWheel[i+1]
+ return x0 + (x1-x0) * d / 15
+
+ @staticmethod
+ def RybToRgb(hue):
+ '''Maps a hue on Itten's RYB color wheel to the standard RGB wheel.
+
+ Parameters:
+ :hue:
+ The hue on Itten's RYB color wheel [0...360]
+
+ Returns:
+ An approximation of the corresponding hue on the standard RGB wheel.
+
+ >>> Color.RybToRgb(15)
+ 8
+
+ '''
+ d = hue % 15
+ i = int(hue / 15)
+ x0 = _RgbWheel[i]
+ x1 = _RgbWheel[i+1]
+ return x0 + (x1-x0) * d / 15
+
+ @staticmethod
+ def NewFromRgb(r, g, b, alpha=1.0, wref=_DEFAULT_WREF):
+ '''Create a new instance based on the specifed RGB values.
+
+ Parameters:
+ :r:
+ The Red component value [0...1]
+ :g:
+ The Green component value [0...1]
+ :b:
+ The Blue component value [0...1]
+ :alpha:
+ The color transparency [0...1], default is opaque
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> Color.NewFromRgb(1.0, 0.5, 0.0)
+ (1.0, 0.5, 0.0, 1.0)
+ >>> Color.NewFromRgb(1.0, 0.5, 0.0, 0.5)
+ (1.0, 0.5, 0.0, 0.5)
+
+ '''
+ return Color((r, g, b), 'rgb', alpha, wref)
+
+ @staticmethod
+ def NewFromHsl(h, s, l, alpha=1.0, wref=_DEFAULT_WREF):
+ '''Create a new instance based on the specifed HSL values.
+
+ Parameters:
+ :h:
+ The Hue component value [0...1]
+ :s:
+ The Saturation component value [0...1]
+ :l:
+ The Lightness component value [0...1]
+ :alpha:
+ The color transparency [0...1], default is opaque
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> Color.NewFromHsl(30, 1, 0.5)
+ (1.0, 0.5, 0.0, 1.0)
+ >>> Color.NewFromHsl(30, 1, 0.5, 0.5)
+ (1.0, 0.5, 0.0, 0.5)
+
+ '''
+ return Color((h, s, l), 'hsl', alpha, wref)
+
+ @staticmethod
+ def NewFromHsv(h, s, v, alpha=1.0, wref=_DEFAULT_WREF):
+ '''Create a new instance based on the specifed HSV values.
+
+ Parameters:
+ :h:
+ The Hus component value [0...1]
+ :s:
+ The Saturation component value [0...1]
+ :v:
+ The Value component [0...1]
+ :alpha:
+ The color transparency [0...1], default is opaque
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> Color.NewFromHsv(30, 1, 1)
+ (1.0, 0.5, 0.0, 1.0)
+ >>> Color.NewFromHsv(30, 1, 1, 0.5)
+ (1.0, 0.5, 0.0, 0.5)
+
+ '''
+ h2, s, l = Color.RgbToHsl(*Color.HsvToRgb(h, s, v))
+ return Color((h, s, l), 'hsl', alpha, wref)
+
+ @staticmethod
+ def NewFromYiq(y, i, q, alpha=1.0, wref=_DEFAULT_WREF):
+ '''Create a new instance based on the specifed YIQ values.
+
+ Parameters:
+ :y:
+ The Y component value [0...1]
+ :i:
+ The I component value [0...1]
+ :q:
+ The Q component value [0...1]
+ :alpha:
+ The color transparency [0...1], default is opaque
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> str(Color.NewFromYiq(0.5922, 0.45885,-0.05))
+ '(0.999902, 0.499955, -6.6905e-005, 1)'
+ >>> str(Color.NewFromYiq(0.5922, 0.45885,-0.05, 0.5))
+ '(0.999902, 0.499955, -6.6905e-005, 0.5)'
+
+ '''
+ return Color(Color.YiqToRgb(y, i, q), 'rgb', alpha, wref)
+
+ @staticmethod
+ def NewFromYuv(y, u, v, alpha=1.0, wref=_DEFAULT_WREF):
+ '''Create a new instance based on the specifed YUV values.
+
+ Parameters:
+ :y:
+ The Y component value [0...1]
+ :u:
+ The U component value [-0.436...0.436]
+ :v:
+ The V component value [-0.615...0.615]
+ :alpha:
+ The color transparency [0...1], default is opaque
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> str(Color.NewFromYuv(0.5925, -0.2916, 0.3575))
+ '(0.999989, 0.500015, -6.3276e-005, 1)'
+ >>> str(Color.NewFromYuv(0.5925, -0.2916, 0.3575, 0.5))
+ '(0.999989, 0.500015, -6.3276e-005, 0.5)'
+
+ '''
+ return Color(Color.YuvToRgb(y, u, v), 'rgb', alpha, wref)
+
+ @staticmethod
+ def NewFromXyz(x, y, z, alpha=1.0, wref=_DEFAULT_WREF):
+ '''Create a new instance based on the specifed CIE-XYZ values.
+
+ Parameters:
+ :x:
+ The Red component value [0...1]
+ :y:
+ The Green component value [0...1]
+ :z:
+ The Blue component value [0...1]
+ :alpha:
+ The color transparency [0...1], default is opaque
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> str(Color.NewFromXyz(0.488941, 0.365682, 0.0448137))
+ '(1, 0.5, 6.81883e-008, 1)'
+ >>> str(Color.NewFromXyz(0.488941, 0.365682, 0.0448137, 0.5))
+ '(1, 0.5, 6.81883e-008, 0.5)'
+
+ '''
+ return Color(Color.XyzToRgb(x, y, z), 'rgb', alpha, wref)
+
+ @staticmethod
+ def NewFromLab(l, a, b, alpha=1.0, wref=_DEFAULT_WREF):
+ '''Create a new instance based on the specifed CIE-LAB values.
+
+ Parameters:
+ :l:
+ The L component [0...100]
+ :a:
+ The a component [-1...1]
+ :b:
+ The a component [-1...1]
+ :alpha:
+ The color transparency [0...1], default is opaque
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> str(Color.NewFromLab(66.9518, 0.43084, 0.739692))
+ '(1, 0.5, 1.09491e-008, 1)'
+ >>> str(Color.NewFromLab(66.9518, 0.43084, 0.739692, wref=Color.WHITE_REFERENCE['std_D50']))
+ '(1.01238, 0.492011, -0.14311, 1)'
+ >>> str(Color.NewFromLab(66.9518, 0.43084, 0.739692, 0.5))
+ '(1, 0.5, 1.09491e-008, 0.5)'
+ >>> str(Color.NewFromLab(66.9518, 0.43084, 0.739692, 0.5, Color.WHITE_REFERENCE['std_D50']))
+ '(1.01238, 0.492011, -0.14311, 0.5)'
+
+ '''
+ return Color(Color.XyzToRgb(*Color.LabToXyz(l, a, b, wref)), 'rgb', alpha, wref)
+
+ @staticmethod
+ def NewFromCmy(c, m, y, alpha=1.0, wref=_DEFAULT_WREF):
+ '''Create a new instance based on the specifed CMY values.
+
+ Parameters:
+ :c:
+ The Cyan component value [0...1]
+ :m:
+ The Magenta component value [0...1]
+ :y:
+ The Yellow component value [0...1]
+ :alpha:
+ The color transparency [0...1], default is opaque
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> Color.NewFromCmy(0, 0.5, 1)
+ (1, 0.5, 0, 1.0)
+ >>> Color.NewFromCmy(0, 0.5, 1, 0.5)
+ (1, 0.5, 0, 0.5)
+
+ '''
+ return Color(Color.CmyToRgb(c, m, y), 'rgb', alpha, wref)
+
+ @staticmethod
+ def NewFromCmyk(c, m, y, k, alpha=1.0, wref=_DEFAULT_WREF):
+ '''Create a new instance based on the specifed CMYK values.
+
+ Parameters:
+ :c:
+ The Cyan component value [0...1]
+ :m:
+ The Magenta component value [0...1]
+ :y:
+ The Yellow component value [0...1]
+ :k:
+ The Black component value [0...1]
+ :alpha:
+ The color transparency [0...1], default is opaque
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> str(Color.NewFromCmyk(1, 0.32, 0, 0.5))
+ '(0, 0.34, 0.5, 1)'
+ >>> str(Color.NewFromCmyk(1, 0.32, 0, 0.5, 0.5))
+ '(0, 0.34, 0.5, 0.5)'
+
+ '''
+ return Color(Color.CmyToRgb(*Color.CmykToCmy(c, m, y, k)), 'rgb', alpha, wref)
+
+ @staticmethod
+ def NewFromHtml(html, alpha=1.0, wref=_DEFAULT_WREF):
+ '''Create a new instance based on the specifed HTML color definition.
+
+ Parameters:
+ :html:
+ The HTML definition of the color (#RRGGBB or #RGB or a color name).
+ :alpha:
+ The color transparency [0...1], default is opaque.
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> str(Color.NewFromHtml('#ff8000'))
+ '(1, 0.501961, 0, 1)'
+ >>> str(Color.NewFromHtml('ff8000'))
+ '(1, 0.501961, 0, 1)'
+ >>> str(Color.NewFromHtml('#f60'))
+ '(1, 0.4, 0, 1)'
+ >>> str(Color.NewFromHtml('f60'))
+ '(1, 0.4, 0, 1)'
+ >>> str(Color.NewFromHtml('lemonchiffon'))
+ '(1, 0.980392, 0.803922, 1)'
+ >>> str(Color.NewFromHtml('#ff8000', 0.5))
+ '(1, 0.501961, 0, 0.5)'
+
+ '''
+ return Color(Color.HtmlToRgb(html), 'rgb', alpha, wref)
+
+ @staticmethod
+ def NewFromPil(pil, alpha=1.0, wref=_DEFAULT_WREF):
+ '''Create a new instance based on the specifed PIL color.
+
+ Parameters:
+ :pil:
+ A PIL compatible color representation (0xBBGGRR)
+ :alpha:
+ The color transparency [0...1], default is opaque
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> str(Color.NewFromPil(0x0080ff))
+ '(1, 0.501961, 0, 1)'
+ >>> str(Color.NewFromPil(0x0080ff, 0.5))
+ '(1, 0.501961, 0, 0.5)'
+
+ '''
+ return Color(Color.PilToRgb(pil), 'rgb', alpha, wref)
+
+ def __GetAlpha(self):
+ return self.__a
+ alpha = property(fget=__GetAlpha, doc='The transparency of this color. 0.0 is transparent and 1.0 is fully opaque.')
+
+ def __GetWRef(self):
+ return self.__wref
+ whiteRef = property(fget=__GetWRef, doc='the white reference point of this color.')
+
+ def __GetRGB(self):
+ return self.__rgb
+ rgb = property(fget=__GetRGB, doc='The RGB values of this Color.')
+
+ def __GetHue(self):
+ return self.__hsl[0]
+ hue = property(fget=__GetHue, doc='The hue of this color.')
+
+ def __GetHSL(self):
+ return self.__hsl
+ hsl = property(fget=__GetHSL, doc='The HSL values of this Color.')
+
+ def __GetHSV(self):
+ h, s, v = Color.RgbToHsv(*self.__rgb)
+ return (self.__hsl[0], s, v)
+ hsv = property(fget=__GetHSV, doc='The HSV values of this Color.')
+
+ def __GetYIQ(self):
+ return Color.RgbToYiq(*self.__rgb)
+ yiq = property(fget=__GetYIQ, doc='The YIQ values of this Color.')
+
+ def __GetYUV(self):
+ return Color.RgbToYuv(*self.__rgb)
+ yuv = property(fget=__GetYUV, doc='The YUV values of this Color.')
+
+ def __GetXYZ(self):
+ return Color.RgbToXyz(*self.__rgb)
+ xyz = property(fget=__GetXYZ, doc='The CIE-XYZ values of this Color.')
+
+ def __GetLAB(self):
+ return Color.XyzToLab(wref=self.__wref, *Color.RgbToXyz(*self.__rgb))
+ lab = property(fget=__GetLAB, doc='The CIE-LAB values of this Color.')
+
+ def __GetCMY(self):
+ return Color.RgbToCmy(*self.__rgb)
+ cmy = property(fget=__GetCMY, doc='The CMY values of this Color.')
+
+ def __GetCMYK(self):
+ return Color.CmyToCmyk(*Color.RgbToCmy(*self.__rgb))
+ cmyk = property(fget=__GetCMYK, doc='The CMYK values of this Color.')
+
+ def __GetHTML(self):
+ return Color.RgbToHtml(*self.__rgb)
+ html = property(fget=__GetHTML, doc='This Color as an HTML color definition.')
+
+ def __GetPIL(self):
+ return Color.RgbToPil(*self.__rgb)
+ pil = property(fget=__GetPIL, doc='This Color as a PIL compatible value.')
+
+ def __GetwebSafe(self):
+ return Color.RgbToWebSafe(*self.__rgb)
+ webSafe = property(fget=__GetwebSafe, doc='The web safe color nearest to this one (RGB).')
+
+ def __GetGreyscale(self):
+ return Color.RgbToGreyscale(*self.rgb)
+ greyscale = property(fget=__GetGreyscale, doc='The greyscale equivalent to this color (RGB).')
+
+ def ColorWithAlpha(self, alpha):
+ '''Create a new instance based on this one with a new alpha value.
+
+ Parameters:
+ :alpha:
+ The transparency of the new color [0...1].
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> Color.NewFromRgb(1.0, 0.5, 0.0, 1.0).ColorWithAlpha(0.5)
+ (1.0, 0.5, 0.0, 0.5)
+
+ '''
+ return Color(self.__rgb, 'rgb', alpha, self.__wref)
+
+ def ColorWithWhiteRef(self, wref, labAsRef=False):
+ '''Create a new instance based on this one with a new white reference.
+
+ Parameters:
+ :wref:
+ The whitepoint reference.
+ :labAsRef:
+ If True, the L*a*b* values of the current instance are used as reference
+ for the new color; otherwise, the RGB values are used as reference.
+
+ Returns:
+ A grapefruit.Color instance.
+
+
+ >>> c = Color.NewFromRgb(1.0, 0.5, 0.0, 1.0, Color.WHITE_REFERENCE['std_D65'])
+
+ >>> c2 = c.ColorWithWhiteRef(Color.WHITE_REFERENCE['sup_D50'])
+ >>> c2.rgb
+ (1.0, 0.5, 0.0)
+ >>> '(%g, %g, %g)' % c2.whiteRef
+ '(0.96721, 1, 0.81428)'
+
+ >>> c2 = c.ColorWithWhiteRef(Color.WHITE_REFERENCE['sup_D50'], labAsRef=True)
+ >>> '(%g, %g, %g)' % c2.rgb
+ '(1.01463, 0.490339, -0.148131)'
+ >>> '(%g, %g, %g)' % c2.whiteRef
+ '(0.96721, 1, 0.81428)'
+ >>> '(%g, %g, %g)' % c.lab
+ '(66.9518, 0.43084, 0.739692)'
+ >>> '(%g, %g, %g)' % c2.lab
+ '(66.9518, 0.43084, 0.739693)'
+
+ '''
+ if labAsRef:
+ l, a, b = self.__GetLAB()
+ return Color.NewFromLab(l, a, b, self.__a, wref)
+ else:
+ return Color(self.__rgb, 'rgb', self.__a, wref)
+
+ def ColorWithHue(self, hue):
+ '''Create a new instance based on this one with a new hue.
+
+ Parameters:
+ :hue:
+ The hue of the new color [0...360].
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> Color.NewFromHsl(30, 1, 0.5).ColorWithHue(60)
+ (1.0, 1.0, 0.0, 1.0)
+ >>> Color.NewFromHsl(30, 1, 0.5).ColorWithHue(60).hsl
+ (60, 1, 0.5)
+
+ '''
+ h, s, l = self.__hsl
+ return Color((hue, s, l), 'hsl', self.__a, self.__wref)
+
+ def ColorWithSaturation(self, saturation):
+ '''Create a new instance based on this one with a new saturation value.
+
+ .. note::
+
+ The saturation is defined for the HSL mode.
+
+ Parameters:
+ :saturation:
+ The saturation of the new color [0...1].
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> Color.NewFromHsl(30, 1, 0.5).ColorWithSaturation(0.5)
+ (0.75, 0.5, 0.25, 1.0)
+ >>> Color.NewFromHsl(30, 1, 0.5).ColorWithSaturation(0.5).hsl
+ (30, 0.5, 0.5)
+
+ '''
+ h, s, l = self.__hsl
+ return Color((h, saturation, l), 'hsl', self.__a, self.__wref)
+
+ def ColorWithLightness(self, lightness):
+ '''Create a new instance based on this one with a new lightness value.
+
+ Parameters:
+ :lightness:
+ The lightness of the new color [0...1].
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> Color.NewFromHsl(30, 1, 0.5).ColorWithLightness(0.25)
+ (0.5, 0.25, 0.0, 1.0)
+ >>> Color.NewFromHsl(30, 1, 0.5).ColorWithLightness(0.25).hsl
+ (30, 1, 0.25)
+
+ '''
+ h, s, l = self.__hsl
+ return Color((h, s, lightness), 'hsl', self.__a, self.__wref)
+
+ def DarkerColor(self, level):
+ '''Create a new instance based on this one but darker.
+
+ Parameters:
+ :level:
+ The amount by which the color should be darkened to produce
+ the new one [0...1].
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> Color.NewFromHsl(30, 1, 0.5).DarkerColor(0.25)
+ (0.5, 0.25, 0.0, 1.0)
+ >>> Color.NewFromHsl(30, 1, 0.5).DarkerColor(0.25).hsl
+ (30, 1, 0.25)
+
+ '''
+ h, s, l = self.__hsl
+ return Color((h, s, max(l - level, 0)), 'hsl', self.__a, self.__wref)
+
+ def LighterColor(self, level):
+ '''Create a new instance based on this one but lighter.
+
+ Parameters:
+ :level:
+ The amount by which the color should be lightened to produce
+ the new one [0...1].
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> Color.NewFromHsl(30, 1, 0.5).LighterColor(0.25)
+ (1.0, 0.75, 0.5, 1.0)
+ >>> Color.NewFromHsl(30, 1, 0.5).LighterColor(0.25).hsl
+ (30, 1, 0.75)
+
+ '''
+ h, s, l = self.__hsl
+ return Color((h, s, min(l + level, 1)), 'hsl', self.__a, self.__wref)
+
+ def Saturate(self, level):
+ '''Create a new instance based on this one but more saturated.
+
+ Parameters:
+ :level:
+ The amount by which the color should be saturated to produce
+ the new one [0...1].
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> Color.NewFromHsl(30, 0.5, 0.5).Saturate(0.25)
+ (0.875, 0.5, 0.125, 1.0)
+ >>> Color.NewFromHsl(30, 0.5, 0.5).Saturate(0.25).hsl
+ (30, 0.75, 0.5)
+
+ '''
+ h, s, l = self.__hsl
+ return Color((h, min(s + level, 1), l), 'hsl', self.__a, self.__wref)
+
+ def Desaturate(self, level):
+ '''Create a new instance based on this one but less saturated.
+
+ Parameters:
+ :level:
+ The amount by which the color should be desaturated to produce
+ the new one [0...1].
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> Color.NewFromHsl(30, 0.5, 0.5).Desaturate(0.25)
+ (0.625, 0.5, 0.375, 1.0)
+ >>> Color.NewFromHsl(30, 0.5, 0.5).Desaturate(0.25).hsl
+ (30, 0.25, 0.5)
+
+ '''
+ h, s, l = self.__hsl
+ return Color((h, max(s - level, 0), l), 'hsl', self.__a, self.__wref)
+
+ def WebSafeDither(self):
+ '''Return the two websafe colors nearest to this one.
+
+ Returns:
+ A tuple of two grapefruit.Color instances which are the two
+ web safe colors closest this one.
+
+ >>> c = Color.NewFromRgb(1.0, 0.45, 0.0)
+ >>> c1, c2 = c.WebSafeDither()
+ >>> str(c1)
+ '(1, 0.4, 0, 1)'
+ >>> str(c2)
+ '(1, 0.6, 0, 1)'
+
+ '''
+ return (
+ Color(Color.RgbToWebSafe(*self.__rgb), 'rgb', self.__a, self.__wref),
+ Color(Color.RgbToWebSafe(alt=True, *self.__rgb), 'rgb', self.__a, self.__wref))
+
+ def Gradient(self, target, steps=100):
+ '''Create a list with the gradient colors between this and the other color.
+
+ Parameters:
+ :target:
+ The grapefruit.Color at the other end of the gradient.
+ :steps:
+ The number of gradients steps to create.
+
+
+ Returns:
+ A list of grapefruit.Color instances.
+
+ >>> c1 = Color.NewFromRgb(1.0, 0.0, 0.0, alpha=1)
+ >>> c2 = Color.NewFromRgb(0.0, 1.0, 0.0, alpha=0)
+ >>> c1.Gradient(c2, 3)
+ [(0.75, 0.25, 0.0, 0.75), (0.5, 0.5, 0.0, 0.5), (0.25, 0.75, 0.0, 0.25)]
+
+ '''
+ gradient = []
+ rgba1 = self.__rgb + (self.__a,)
+ rgba2 = target.__rgb + (target.__a,)
+
+ steps += 1
+ for n in xrange(1, steps):
+ d = 1.0*n/steps
+ r = (rgba1[0]*(1-d)) + (rgba2[0]*d)
+ g = (rgba1[1]*(1-d)) + (rgba2[1]*d)
+ b = (rgba1[2]*(1-d)) + (rgba2[2]*d)
+ a = (rgba1[3]*(1-d)) + (rgba2[3]*d)
+
+ gradient.append(Color((r, g, b), 'rgb', a, self.__wref))
+
+ return gradient
+
+ def ComplementaryColor(self, mode='ryb'):
+ '''Create a new instance which is the complementary color of this one.
+
+ Parameters:
+ :mode:
+ Select which color wheel to use for the generation (ryb/rgb).
+
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> Color.NewFromHsl(30, 1, 0.5).ComplementaryColor()
+ (0.0, 0.5, 1.0, 1.0)
+ >>> Color.NewFromHsl(30, 1, 0.5).ComplementaryColor().hsl
+ (210, 1, 0.5)
+
+ '''
+ h, s, l = self.__hsl
+
+ if mode == 'ryb': h = Color.RgbToRyb(h)
+ h = (h+180)%360
+ if mode == 'ryb': h = Color.RybToRgb(h)
+
+ return Color((h, s, l), 'hsl', self.__a, self.__wref)
+
+ def MonochromeScheme(self):
+ '''Return 4 colors in the same hue with varying saturation/lightness.
+
+ Returns:
+ A tuple of 4 grapefruit.Color in the same hue as this one,
+ with varying saturation/lightness.
+
+ >>> c = Color.NewFromHsl(30, 0.5, 0.5)
+ >>> ['(%g, %g, %g)' % clr.hsl for clr in c.MonochromeScheme()]
+ ['(30, 0.2, 0.8)', '(30, 0.5, 0.3)', '(30, 0.2, 0.6)', '(30, 0.5, 0.8)']
+
+ '''
+ def _wrap(x, min, thres, plus):
+ if (x-min) < thres: return x + plus
+ else: return x-min
+
+ h, s, l = self.__hsl
+
+ s1 = _wrap(s, 0.3, 0.1, 0.3)
+ l1 = _wrap(l, 0.5, 0.2, 0.3)
+
+ s2 = s
+ l2 = _wrap(l, 0.2, 0.2, 0.6)
+
+ s3 = s1
+ l3 = max(0.2, l + (1-l)*0.2)
+
+ s4 = s
+ l4 = _wrap(l, 0.5, 0.2, 0.3)
+
+ return (
+ Color((h, s1, l1), 'hsl', self.__a, self.__wref),
+ Color((h, s2, l2), 'hsl', self.__a, self.__wref),
+ Color((h, s3, l3), 'hsl', self.__a, self.__wref),
+ Color((h, s4, l4), 'hsl', self.__a, self.__wref))
+
+ def TriadicScheme(self, angle=120, mode='ryb'):
+ '''Return two colors forming a triad or a split complementary with this one.
+
+ Parameters:
+ :angle:
+ The angle between the hues of the created colors.
+ The default value makes a triad.
+ :mode:
+ Select which color wheel to use for the generation (ryb/rgb).
+
+ Returns:
+ A tuple of two grapefruit.Color forming a color triad with
+ this one or a split complementary.
+
+ >>> c1 = Color.NewFromHsl(30, 1, 0.5)
+
+ >>> c2, c3 = c1.TriadicScheme()
+ >>> c2.hsl
+ (150.0, 1, 0.5)
+ >>> c3.hsl
+ (270.0, 1, 0.5)
+
+ >>> c2, c3 = c1.TriadicScheme(40)
+ >>> c2.hsl
+ (190.0, 1, 0.5)
+ >>> c3.hsl
+ (230.0, 1, 0.5)
+
+ '''
+ h, s, l = self.__hsl
+ angle = min(angle, 120) / 2.0
+
+ if mode == 'ryb': h = Color.RgbToRyb(h)
+ h += 180
+ h1 = (h - angle) % 360
+ h2 = (h + angle) % 360
+ if mode == 'ryb':
+ h1 = Color.RybToRgb(h1)
+ h2 = Color.RybToRgb(h2)
+
+ return (
+ Color((h1, s, l), 'hsl', self.__a, self.__wref),
+ Color((h2, s, l), 'hsl', self.__a, self.__wref))
+
+ def TetradicScheme(self, angle=30, mode='ryb'):
+ '''Return three colors froming a tetrad with this one.
+
+ Parameters:
+ :angle:
+ The angle to substract from the adjacent colors hues [-90...90].
+ You can use an angle of zero to generate a square tetrad.
+ :mode:
+ Select which color wheel to use for the generation (ryb/rgb).
+
+ Returns:
+ A tuple of three grapefruit.Color forming a color tetrad with
+ this one.
+
+ >>> col = Color.NewFromHsl(30, 1, 0.5)
+ >>> [c.hsl for c in col.TetradicScheme(mode='rgb', angle=30)]
+ [(90, 1, 0.5), (210, 1, 0.5), (270, 1, 0.5)]
+
+ '''
+ h, s, l = self.__hsl
+
+ if mode == 'ryb': h = Color.RgbToRyb(h)
+ h1 = (h + 90 - angle) % 360
+ h2 = (h + 180) % 360
+ h3 = (h + 270 - angle) % 360
+ if mode == 'ryb':
+ h1 = Color.RybToRgb(h1)
+ h2 = Color.RybToRgb(h2)
+ h3 = Color.RybToRgb(h3)
+
+ return (
+ Color((h1, s, l), 'hsl', self.__a, self.__wref),
+ Color((h2, s, l), 'hsl', self.__a, self.__wref),
+ Color((h3, s, l), 'hsl', self.__a, self.__wref))
+
+ def AnalogousScheme(self, angle=30, mode='ryb'):
+ '''Return two colors analogous to this one.
+
+ Args:
+ :angle:
+ The angle between the hues of the created colors and this one.
+ :mode:
+ Select which color wheel to use for the generation (ryb/rgb).
+
+ Returns:
+ A tuple of grapefruit.Colors analogous to this one.
+
+ >>> c1 = Color.NewFromHsl(30, 1, 0.5)
+
+ >>> c2, c3 = c1.AnalogousScheme()
+ >>> c2.hsl
+ (330, 1, 0.5)
+ >>> c3.hsl
+ (90, 1, 0.5)
+
+ >>> c2, c3 = c1.AnalogousScheme(10)
+ >>> c2.hsl
+ (20, 1, 0.5)
+ >>> c3.hsl
+ (40, 1, 0.5)
+
+ '''
+ h, s, l = self.__hsl
+
+ if mode == 'ryb': h = Color.RgbToRyb(h)
+ h += 360
+ h1 = (h - angle) % 360
+ h2 = (h + angle) % 360
+ if mode == 'ryb':
+ h1 = Color.RybToRgb(h1)
+ h2 = Color.RybToRgb(h2)
+
+ return (Color((h1, s, l), 'hsl', self.__a, self.__wref),
+ Color((h2, s, l), 'hsl', self.__a, self.__wref))
+
+ def AlphaBlend(self, other):
+ '''Alpha-blend this color on the other one.
+
+ Args:
+ :other:
+ The grapefruit.Color to alpha-blend with this one.
+
+ Returns:
+ A grapefruit.Color instance which is the result of alpha-blending
+ this color on the other one.
+
+ >>> c1 = Color.NewFromRgb(1, 0.5, 0, 0.2)
+ >>> c2 = Color.NewFromRgb(1, 1, 1, 0.8)
+ >>> c3 = c1.AlphaBlend(c2)
+ >>> str(c3)
+ '(1, 0.875, 0.75, 0.84)'
+
+ '''
+ # get final alpha channel
+ fa = self.__a + other.__a - (self.__a * other.__a)
+
+ # get percentage of source alpha compared to final alpha
+ if fa==0: sa = 0
+ else: sa = min(1.0, self.__a/other.__a)
+
+ # destination percentage is just the additive inverse
+ da = 1.0 - sa
+
+ sr, sg, sb = [v * sa for v in self.__rgb]
+ dr, dg, db = [v * da for v in other.__rgb]
+
+ return Color((sr+dr, sg+dg, sb+db), 'rgb', fa, self.__wref)
+
+ def Blend(self, other, percent=0.5):
+ '''Blend this color with the other one.
+
+ Args:
+ :other:
+ the grapefruit.Color to blend with this one.
+
+ Returns:
+ A grapefruit.Color instance which is the result of blending
+ this color on the other one.
+
+ >>> c1 = Color.NewFromRgb(1, 0.5, 0, 0.2)
+ >>> c2 = Color.NewFromRgb(1, 1, 1, 0.6)
+ >>> c3 = c1.Blend(c2)
+ >>> str(c3)
+ '(1, 0.75, 0.5, 0.4)'
+
+ '''
+ dest = 1.0 - percent
+ rgb = tuple(((u * percent) + (v * dest) for u, v in zip(self.__rgb, other.__rgb)))
+ a = (self.__a * percent) + (other.__a * dest)
+ return Color(rgb, 'rgb', a, self.__wref)
+
+def _test():
+ import doctest
+ reload(doctest)
+ doctest.testmod()
+
+if __name__=='__main__':
+ _test()
diff --git a/settings_local.py.dist b/settings_local.py.dist
index 681d1446..e44c1e72 100755
--- a/settings_local.py.dist
+++ b/settings_local.py.dist
@@ -93,3 +93,34 @@ RECAPTCHA_PRIVATE_KEY='...'
RECAPTCHA_PUBLIC_KEY='...'
ASKBOT_DEFAULT_SKIN = 'default'
+
+#Facebook settings
+USE_FB_CONNECT=False
+FB_API_KEY='' #your api key from facebook
+FB_SECRET='' #your application secret
+
+#counter colors
+VOTE_COUNTER_EXPECTED_MAXIMUM = 3
+from forum_modules.grapefruit import Color
+COLORS_VOTE_COUNTER_EMPTY_BG = 'white'
+COLORS_VOTE_COUNTER_EMPTY_FG = 'gray'
+COLORS_VOTE_COUNTER_MIN_BG = 'white'
+COLORS_VOTE_COUNTER_MIN_FG = 'black'
+COLORS_VOTE_COUNTER_MAX_BG = '#a9d0f5'
+COLORS_VOTE_COUNTER_MAX_FG = Color.NewFromHtml(COLORS_VOTE_COUNTER_MAX_BG).DarkerColor(0.7).html
+VIEW_COUNTER_EXPECTED_MAXIMUM = 100
+COLORS_VIEW_COUNTER_EMPTY_BG = 'gray'
+COLORS_VIEW_COUNTER_EMPTY_FG = 'white'
+COLORS_VIEW_COUNTER_MIN_BG = '#D0F5A9'
+COLORS_VIEW_COUNTER_MIN_FG = Color.NewFromHtml(COLORS_VIEW_COUNTER_MIN_BG).DarkerColor(0.6).html
+COLORS_VIEW_COUNTER_MAX_BG = '#FF8000'#'#F7BE81'
+COLORS_VIEW_COUNTER_MAX_FG = Color.NewFromHtml(COLORS_VIEW_COUNTER_MAX_BG).DarkerColor(0.7).html
+ANSWER_COUNTER_EXPECTED_MAXIMUM = 4
+COLORS_ANSWER_COUNTER_EMPTY_BG = '#a40000'
+COLORS_ANSWER_COUNTER_EMPTY_FG = 'yellow'
+COLORS_ANSWER_COUNTER_MIN_BG = '#AEB404'#'#81F7F3'#'#A9D0F5'#'#045FB4'
+COLORS_ANSWER_COUNTER_MIN_FG = 'white'#'#81F7F3'
+COLORS_ANSWER_COUNTER_MAX_BG = '#61380B'#'#4B088A'#'#0B3861'#'#045FB4'
+COLORS_ANSWER_COUNTER_MAX_FG = '#ffff00'
+COLORS_ANSWER_COUNTER_ACCEPTED_BG = 'darkgreen'
+COLORS_ANSWER_COUNTER_ACCEPTED_FG = '#D0F5A9'