[gimp] Bug 687552 - Improve palette sorting functionality
- From: Michael Natterer <mitch src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gimp] Bug 687552 - Improve palette sorting functionality
- Date: Wed, 7 Nov 2012 21:52:31 +0000 (UTC)
commit 579a1a259ad87de7a1cb5a86ef926411c05011c7
Author: Michael Natterer <mitch gimp org>
Date: Wed Nov 7 22:51:22 2012 +0100
Bug 687552 - Improve palette sorting functionality
Apply patch from David Gowers that adds more options to the sort
palette plug-in.
plug-ins/pygimp/plug-ins/palette-sort.py | 311 +++++++++++++++++++++++++++---
1 files changed, 283 insertions(+), 28 deletions(-)
---
diff --git a/plug-ins/pygimp/plug-ins/palette-sort.py b/plug-ins/pygimp/plug-ins/palette-sort.py
index 1d590d1..62f522d 100644
--- a/plug-ins/pygimp/plug-ins/palette-sort.py
+++ b/plug-ins/pygimp/plug-ins/palette-sort.py
@@ -14,31 +14,283 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from gimpfu import *
+from colorsys import rgb_to_yiq
+from random import randint
gettext.install("gimp20-python", gimp.locale_directory, unicode=True)
-def palette_sort (palette, model, channel, ascending):
+AVAILABLE_CHANNELS = (_("Red"), _("Green"), _("Blue"),
+ _("Luma (Y)"),
+ _("Hue"), _("Saturation"), _("Value"),
+ _("Saturation (HSL)"), _("Lightness (HSL)"),
+ _("Index"),
+ _("Random"))
+
+GRAIN_SCALE = (1.0, 1.0 , 1.0,
+ 1.0,
+ 360., 100., 100.,
+ 100., 100.,
+ 16384.,
+ float(0x7ffffff),
+ 100., 256., 256.,
+ 256., 360.,)
+
+SELECT_ALL = 0
+SELECT_SLICE = 1
+SELECT_AUTOSLICE = 2
+SELECT_PARTITIONED = 3
+SELECTIONS = (SELECT_ALL, SELECT_SLICE, SELECT_AUTOSLICE, SELECT_PARTITIONED)
+
+
+def noop(v, i):
+ return v
+
+
+def to_hsv(v, i):
+ return v.to_hsv()
+
+
+def to_hsl(v, i):
+ return v.to_hsl()
+
+
+def to_yiq(v, i):
+ return rgb_to_yiq(*v[:-1])
+
+
+def to_index(v, i):
+ return (i,)
+
+def to_random(v, i):
+ return (randint(0, 0x7fffffff),)
+
+
+channel_getters = [ (noop, 0), (noop, 1), (noop, 2),
+ (to_yiq, 0),
+ (to_hsv, 0), (to_hsv, 1), (to_hsv, 2),
+ (to_hsl, 1), (to_hsl, 2),
+ (to_index, 0),
+ (to_random, 0)]
+
+
+try:
+ from colormath.color_objects import RGBColor, LabColor, LCHabColor
+ AVAILABLE_CHANNELS = AVAILABLE_CHANNELS + (_("Lightness (LAB)"), _("A-color"), _("B-color"),
+ _("Chroma (LCHab)"), _("Hue (LCHab)"))
+ to_lab = lambda v,i: RGBColor(*v[:-1]).convert_to('LAB').get_value_tuple()
+ to_lchab = lambda v,i: RGBColor(*v[:-1]).convert_to('LCHab').get_value_tuple()
+ channel_getters.extend([(to_lab, 0), (to_lab, 1), (to_lab, 2),
+ (to_lchab, 1), (to_lchab, 2)])
+except ImportError:
+ pass
+
+
+def parse_slice(s, numcolors):
+ """Parse a slice spec and return (start, nrows, length)
+ All items are optional. Omitting them makes the largest possible selection that
+ exactly fits the other items.
+
+ start:nrows,length
+
+
+ '' selects all items, as does ':'
+ ':4,' makes a 4-row selection out of all colors (length auto-determined)
+ ':4' also.
+ ':1,4' selects the first 4 colors
+ ':,4' selects rows of 4 colors (nrows auto-determined)
+ ':4,4' selects 4 rows of 4 colors
+ '4:' selects a single row of all colors after 4, inclusive.
+ '4:,4' selects rows of 4 colors, starting at 4 (nrows auto-determined)
+ '4:4,4' selects 4 rows of 4 colors (16 colors total), beginning at index 4.
+ '4' is illegal (ambiguous)
+
+
+ In general, slices are comparable to a numpy sub-array.
+ 'start at element START, with shape (NROWS, LENGTH)'
+
+ """
+ s = s.strip()
+
+ def notunderstood():
+ raise ValueError('Slice %r not understood. Should be in format'
+ ' START?:NROWS?,ROWLENGTH? eg. "0:4,16".' % s)
+ def _int(v):
+ try:
+ return int(v)
+ except ValueError:
+ notunderstood()
+ if s in ('', ':', ':,'):
+ return 0, 1, numcolors # entire palette, one row
+ if s.count(':') != 1:
+ notunderstood()
+ rowpos = s.find(':')
+ start = 0
+ if rowpos > 0:
+ start = _int(s[:rowpos])
+ numcolors -= start
+ nrows = 1
+ if ',' in s:
+ commapos = s.find(',')
+ nrows = s[rowpos+1:commapos]
+ length = s[commapos+1:]
+ if not nrows:
+ if not length:
+ notunderstood()
+ else:
+ length = _int(length)
+ if length == 0:
+ notunderstood()
+ nrows = numcolors // length
+ if numcolors % length:
+ nrows = -nrows
+ elif not length:
+ nrows = _int(nrows)
+ if nrows == 0:
+ notunderstood()
+ length = numcolors // nrows
+ if numcolors % nrows:
+ length = -length
+ else:
+ nrows = _int(nrows)
+ if nrows == 0:
+ notunderstood()
+ length = _int(length)
+ if length == 0:
+ notunderstood()
+ else:
+ nrows = _int(s[rowpos+1:])
+ if nrows == 0:
+ notunderstood()
+ length = numcolors // nrows
+ if numcolors % nrows:
+ length = -length
+ return start, nrows, length
+
+
+def quantization_grain(channel, g):
+ "Given a channel and a quantization, return the size of a quantization grain"
+ g = max(1.0, g)
+ if g <= 1.0:
+ g = 0.00001
+ else:
+ g = max(0.00001, GRAIN_SCALE[channel] / g)
+ return g
+
+
+def palette_sort (palette, selection, slice_expr, channel, quantize,
+ ascending, pchannel, pquantize):
+
+ grain = quantization_grain(channel, quantize)
+ pgrain = quantization_grain(pchannel, pquantize)
+
#If palette is read only, work on a copy:
editable = pdb.gimp_palette_is_editable(palette)
- if not editable:palette = pdb.gimp_palette_duplicate (palette)
+ if not editable:
+ palette = pdb.gimp_palette_duplicate (palette)
num_colors = pdb.gimp_palette_get_info (palette)
- entry_list = []
- for i in xrange (num_colors):
- entry = (pdb.gimp_palette_entry_get_name (palette, i),
- pdb.gimp_palette_entry_get_color (palette, i))
- index = entry[1][channel]
- if model == "HSV":
- index = entry[1].to_hsv()[channel]
- else:
- index = entry[1][channel]
- entry_list.append ((index, entry))
- entry_list.sort(lambda x,y: cmp(x[0], y[0]))
- if not ascending:
- entry_list.reverse()
- for i in xrange(num_colors):
- pdb.gimp_palette_entry_set_name (palette, i, entry_list[i][1][0])
- pdb.gimp_palette_entry_set_color (palette, i, entry_list[i][1][1])
+
+ start, nrows, length = None, None, None
+ if selection == SELECT_AUTOSLICE:
+ def find_index(color, startindex=0):
+ for i in range(startindex, num_colors):
+ c = pdb.gimp_palette_entry_get_color (palette, i)
+ if c == color:
+ return i
+ return None
+ def hexcolor(c):
+ return "#%02x%02x%02x" % tuple(c[:-1])
+ fg = pdb.gimp_context_get_foreground()
+ bg = pdb.gimp_context_get_background()
+ start = find_index(fg)
+ end = find_index(bg)
+ if start is None:
+ raise ValueError("Couldn't find foreground color %r in palette" % list(fg))
+ if end is None:
+ raise ValueError("Couldn't find background color %r in palette" % list(bg))
+ if find_index(fg, start + 1):
+ raise ValueError('Autoslice cannot be used when more than one'
+ ' instance of an endpoint'
+ ' (%s) is present' % hexcolor(fg))
+ if find_index(bg, end + 1):
+ raise ValueError('Autoslice cannot be used when more than one'
+ ' instance of an endpoint'
+ ' (%s) is present' % hexcolor(bg))
+ if start > end:
+ end, start = start, end
+ length = (end - start) + 1
+ try:
+ _, nrows, _ = parse_slice(slice_expr, length)
+ nrows = abs(nrows)
+ if length % nrows:
+ raise ValueError('Total length %d not evenly divisible'
+ ' by number of rows %d' % (length, nrows))
+ length /= nrows
+ except ValueError:
+ # bad expression is okay here, just assume one row
+ nrows = 1
+ # remaining behaviour is implemented by SELECT_SLICE 'inheritance'.
+ selection= SELECT_SLICE
+ elif selection in (SELECT_SLICE, SELECT_PARTITIONED):
+ start, nrows, length = parse_slice(slice_expr, num_colors)
+
+ channels_getter, channel_index = channel_getters[channel]
+
+ def get_colors (start, end):
+ result = []
+ for i in range (start, end):
+ entry = (pdb.gimp_palette_entry_get_name (palette, i),
+ pdb.gimp_palette_entry_get_color (palette, i))
+ index = channels_getter(entry[1], i)[channel_index]
+ index = (index - (index % grain))
+ result.append((index, entry))
+ return result
+
+ if selection == SELECT_ALL:
+ entry_list = get_colors(0, num_colors)
+ entry_list.sort(key=lambda v:v[0], reverse=not ascending)
+ for i in range(num_colors):
+ pdb.gimp_palette_entry_set_name (palette, i, entry_list[i][1][0])
+ pdb.gimp_palette_entry_set_color (palette, i, entry_list[i][1][1])
+
+ elif selection == SELECT_PARTITIONED:
+ if num_colors < (start + length * nrows) - 1:
+ raise ValueError('Not enough entries in palette to sort complete rows!'
+ ' Got %d, expected >=%d' % (num_colors, start + length * nrows))
+ pchannels_getter, pchannel_index = channel_getters[pchannel]
+ for row in range(nrows):
+ partition_spans = [1]
+ rowstart = start + (row * length)
+ old_color = pdb.gimp_palette_entry_get_color (palette,
+ rowstart)
+ old_partition = pchannels_getter(old_color, rowstart)[pchannel_index]
+ old_partition = old_partition - (old_partition % pgrain)
+ for i in range(rowstart + 1, rowstart + length):
+ this_color = pdb.gimp_palette_entry_get_color (palette, i)
+ this_partition = pchannels_getter(this_color, i)[pchannel_index]
+ this_partition = this_partition - (this_partition % pgrain)
+ if this_partition == old_partition:
+ partition_spans[-1] += 1
+ else:
+ partition_spans.append(1)
+ old_partition = this_partition
+ base = rowstart
+ for size in partition_spans:
+ palette_sort(palette, SELECT_SLICE, '%d:1,%d' % (base, size),
+ channel, quantize, ascending, 0, 1.0)
+ base += size
+ else:
+ stride = length
+ if num_colors < (start + stride * nrows) - 1:
+ raise ValueError('Not enough entries in palette to sort complete rows!'
+ ' Got %d, expected >=%d' % (num_colors, start + stride * nrows))
+
+ for row_start in range(start, start + stride * nrows, stride):
+ sublist = get_colors(row_start, row_start + stride)
+ sublist.sort(key=lambda v:v[0], reverse=not ascending)
+ for i, entry in zip(range(row_start, row_start + stride), sublist):
+ pdb.gimp_palette_entry_set_name (palette, i, entry[1][0])
+ pdb.gimp_palette_entry_set_color (palette, i, entry[1][1])
return palette
@@ -46,22 +298,25 @@ def palette_sort (palette, model, channel, ascending):
register(
"python-fu-palette-sort",
N_("Sort the colors in a palette"),
- "palette_merge (palette, model, channel, ascending) -> new_palette",
- "Joao S. O. Bueno Calligaris, Carol Spears",
+ "palette_sort (palette, selection, slice_expr, channel, quantize,"
+ " ascending, pchannel, pquantize) -> new_palette",
+ "Joao S. O. Bueno Calligaris, Carol Spears, David Gowers",
"Joao S. O. Bueno Calligaris",
"2006",
N_("_Sort Palette..."),
"",
[
(PF_PALETTE, "palette", _("Palette"), ""),
- (PF_RADIO, "model", _("Color _model"), "HSV",
- ((_("RGB"), "RGB"),
- (_("HSV"), "HSV"))),
- (PF_RADIO, "channel", _("Channel to _sort"), 2,
- ((_("Red or Hue"), 0),
- (_("Green or Saturation"), 1),
- (_("Blue or Value"), 2))),
- (PF_BOOL, "ascending", _("_Ascending"), True)
+ (PF_OPTION, "selections", _("Se_lections"), SELECT_ALL,
+ (_("All"), _("Slice / Array"), _("Autoslice (fg->bg)"), _("Partitioned"))),
+ (PF_STRING, "slice-expr", _("Slice _expression"), ''),
+ (PF_OPTION, "channel", _("Channel to _sort"), 3,
+ AVAILABLE_CHANNELS),
+ (PF_FLOAT, "quantize", _("_Quantization"), 0.0),
+ (PF_BOOL, "ascending", _("_Ascending"), True),
+ (PF_OPTION, "pchannel", _("_Partitioning channel"), 3,
+ AVAILABLE_CHANNELS),
+ (PF_FLOAT, "pquantize", _("Partition q_uantization"), 0.0),
],
[],
palette_sort,
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]