source: libcaca/trunk/cucul/dither.c @ 1876

Last change on this file since 1876 was 1876, checked in by Sam Hocevar, 15 years ago
  • cucul_get_dither_brightness() is no longer a stub.
  • Implemented missing cucul_get_dither_contrast().
  • Property svn:keywords set to Id
File size: 39.1 KB
Line 
1/*
2 *  libcucul      Canvas for ultrafast compositing of Unicode letters
3 *  Copyright (c) 2002-2006 Sam Hocevar <sam@zoy.org>
4 *                All Rights Reserved
5 *
6 *  $Id: dither.c 1876 2007-11-04 09:56:40Z sam $
7 *
8 *  This library is free software. It comes without any warranty, to
9 *  the extent permitted by applicable law. You can redistribute it
10 *  and/or modify it under the terms of the Do What The Fuck You Want
11 *  To Public License, Version 2, as published by Sam Hocevar. See
12 *  http://sam.zoy.org/wtfpl/COPYING for more details.
13 */
14
15/*
16 *  This file contains bitmap dithering functions.
17 */
18
19#include "config.h"
20#include "common.h"
21
22#if !defined(__KERNEL__)
23#   if defined(HAVE_ENDIAN_H)
24#       include <endian.h>
25#   endif
26#   include <stdio.h>
27#   include <stdlib.h>
28#   include <limits.h>
29#   include <string.h>
30#endif
31
32#include "cucul.h"
33#include "cucul_internals.h"
34
35#define CP437 0
36
37/*
38 * Local variables
39 */
40#if !defined(_DOXYGEN_SKIP_ME)
41#   define LOOKUP_VAL 32
42#   define LOOKUP_SAT 32
43#   define LOOKUP_HUE 16
44#endif
45static unsigned char hsv_distances[LOOKUP_VAL][LOOKUP_SAT][LOOKUP_HUE];
46static uint16_t lookup_colors[8];
47static int lookup_initialised = 0;
48
49static int const hsv_palette[] =
50{
51    /* weight, hue, saturation, value */
52    4,    0x0,    0x0,    0x0,   /* black */
53    5,    0x0,    0x0,    0x5ff, /* 30% */
54    5,    0x0,    0x0,    0x9ff, /* 70% */
55    4,    0x0,    0x0,    0xfff, /* white */
56    3,    0x1000, 0xfff,  0x5ff, /* dark yellow */
57    2,    0x1000, 0xfff,  0xfff, /* light yellow */
58    3,    0x0,    0xfff,  0x5ff, /* dark red */
59    2,    0x0,    0xfff,  0xfff  /* light red */
60};
61
62/* RGB palette for the new colour picker */
63static int const rgb_palette[] =
64{
65    0x0,   0x0,   0x0,
66    0x0,   0x0,   0x7ff,
67    0x0,   0x7ff, 0x0,
68    0x0,   0x7ff, 0x7ff,
69    0x7ff, 0x0,   0x0,
70    0x7ff, 0x0,   0x7ff,
71    0x7ff, 0x7ff, 0x0,
72    0xaaa, 0xaaa, 0xaaa,
73    0x555, 0x555, 0x555,
74    0x000, 0x000, 0xfff,
75    0x000, 0xfff, 0x000,
76    0x000, 0xfff, 0xfff,
77    0xfff, 0x000, 0x000,
78    0xfff, 0x000, 0xfff,
79    0xfff, 0xfff, 0x000,
80    0xfff, 0xfff, 0xfff,
81};
82
83static int const rgb_weight[] =
84{
85    /* 2, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 2 */
86    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
87};
88
89/* List of glyphs */
90static uint32_t ascii_glyphs[] =
91{
92    ' ', '.', ':', ';', 't', '%', 'S', 'X', '@', '8', '?'
93};
94
95static uint32_t shades_glyphs[] =
96{
97    /* ' '. '·', '░', '▒', '?' */
98    ' ', 0xb7, 0x2591, 0x2592, '?'
99};
100
101static uint32_t blocks_glyphs[] =
102{
103    /* ' ', '▘', '▚', '?' */
104    ' ', 0x2598, 0x259a, '?'
105};
106
107#if !defined(_DOXYGEN_SKIP_ME)
108enum color_mode
109{
110    COLOR_MODE_MONO,
111    COLOR_MODE_GRAY,
112    COLOR_MODE_8,
113    COLOR_MODE_16,
114    COLOR_MODE_FULLGRAY,
115    COLOR_MODE_FULL8,
116    COLOR_MODE_FULL16
117};
118
119struct cucul_dither
120{
121    int bpp, has_palette, has_alpha;
122    int w, h, pitch;
123    int rmask, gmask, bmask, amask;
124    int rright, gright, bright, aright;
125    int rleft, gleft, bleft, aleft;
126    void (*get_hsv)(cucul_dither_t *, char *, int, int);
127    int red[256], green[256], blue[256], alpha[256];
128
129    /* Colour features */
130    float gamma, brightness, contrast;
131    int gammatab[4097];
132
133    /* Bitmap features */
134    int invert, antialias;
135
136    /* Colour mode used for rendering */
137    enum color_mode color_mode;
138
139    /* Glyphs used for rendering */
140    uint32_t const * glyphs;
141    unsigned glyph_count;
142
143    /* Current dithering method */
144    void (*init_dither) (int);
145    unsigned int (*get_dither) (void);
146    void (*increment_dither) (void);
147};
148
149#define HSV_XRATIO 6
150#define HSV_YRATIO 3
151#define HSV_HRATIO 3
152
153#define HSV_DISTANCE(h, s, v, index) \
154    (hsv_palette[index * 4] \
155     * ((HSV_XRATIO * ((v) - hsv_palette[index * 4 + 3]) \
156                    * ((v) - hsv_palette[index * 4 + 3])) \
157       + (hsv_palette[index * 4 + 3] \
158           ? (HSV_YRATIO * ((s) - hsv_palette[index * 4 + 2]) \
159                         * ((s) - hsv_palette[index * 4 + 2])) \
160           : 0) \
161       + (hsv_palette[index * 4 + 2] \
162           ? (HSV_HRATIO * ((h) - hsv_palette[index * 4 + 1]) \
163                         * ((h) - hsv_palette[index * 4 + 1])) \
164           : 0)))
165#endif
166
167/*
168 * Local prototypes
169 */
170static void mask2shift(unsigned long int, int *, int *);
171static float gammapow(float x, float y);
172
173static void get_rgba_default(cucul_dither_t const *, uint8_t *, int, int,
174                             unsigned int *);
175static int init_lookup(void);
176
177/* Dithering methods */
178static void init_no_dither(int);
179static unsigned int get_no_dither(void);
180static void increment_no_dither(void);
181
182static void init_fstein_dither(int);
183static unsigned int get_fstein_dither(void);
184static void increment_fstein_dither(void);
185
186static void init_ordered2_dither(int);
187static unsigned int get_ordered2_dither(void);
188static void increment_ordered2_dither(void);
189
190static void init_ordered4_dither(int);
191static unsigned int get_ordered4_dither(void);
192static void increment_ordered4_dither(void);
193
194static void init_ordered8_dither(int);
195static unsigned int get_ordered8_dither(void);
196static void increment_ordered8_dither(void);
197
198static void init_random_dither(int);
199static unsigned int get_random_dither(void);
200static void increment_random_dither(void);
201
202static inline int sq(int x)
203{
204    return x * x;
205}
206
207static inline void rgb2hsv_default(int r, int g, int b,
208                                   int *hue, int *sat, int *val)
209{
210    int min, max, delta;
211
212    min = r; max = r;
213    if(min > g) min = g; if(max < g) max = g;
214    if(min > b) min = b; if(max < b) max = b;
215
216    delta = max - min; /* 0 - 0xfff */
217    *val = max; /* 0 - 0xfff */
218
219    if(delta)
220    {
221        *sat = 0xfff * delta / max; /* 0 - 0xfff */
222
223        /* Generate *hue between 0 and 0x5fff */
224        if( r == max )
225            *hue = 0x1000 + 0x1000 * (g - b) / delta;
226        else if( g == max )
227            *hue = 0x3000 + 0x1000 * (b - r) / delta;
228        else
229            *hue = 0x5000 + 0x1000 * (r - g) / delta;
230    }
231    else
232    {
233        *sat = 0;
234        *hue = 0;
235    }
236}
237
238/** \brief Create an internal dither object.
239 *
240 *  Create a dither structure from its coordinates (depth, width, height and
241 *  pitch) and pixel mask values. If the depth is 8 bits per pixel, the mask
242 *  values are ignored and the colour palette should be set using the
243 *  cucul_set_dither_palette() function. For depths greater than 8 bits per
244 *  pixel, a zero alpha mask causes the alpha values to be ignored.
245 *
246 *  If an error occurs, NULL is returned and \b errno is set accordingly:
247 *  - \c EINVAL Requested width, height, pitch or bits per pixel value was
248 *    invalid.
249 *  - \c ENOMEM Not enough memory to allocate dither structure.
250 *
251 *  \param bpp Bitmap depth in bits per pixel.
252 *  \param w Bitmap width in pixels.
253 *  \param h Bitmap height in pixels.
254 *  \param pitch Bitmap pitch in bytes.
255 *  \param rmask Bitmask for red values.
256 *  \param gmask Bitmask for green values.
257 *  \param bmask Bitmask for blue values.
258 *  \param amask Bitmask for alpha values.
259 *  \return Dither object upon success, NULL if an error occurred.
260 */
261cucul_dither_t *cucul_create_dither(unsigned int bpp, unsigned int w,
262                                    unsigned int h, unsigned int pitch,
263                                    unsigned long int rmask,
264                                    unsigned long int gmask,
265                                    unsigned long int bmask,
266                                    unsigned long int amask)
267{
268    cucul_dither_t *d;
269    int i;
270
271    /* Minor sanity test */
272    if(!w || !h || !pitch || bpp > 32 || bpp < 8)
273    {
274        seterrno(EINVAL);
275        return NULL;
276    }
277
278    d = malloc(sizeof(cucul_dither_t));
279    if(!d)
280    {
281        seterrno(ENOMEM);
282        return NULL;
283    }
284
285    if(!lookup_initialised)
286    {
287        /* XXX: because we do not wish to be thread-safe, there is a slight
288         * chance that the following code will be executed twice. It is
289         * totally harmless. */
290        init_lookup();
291        lookup_initialised = 1;
292    }
293
294    d->bpp = bpp;
295    d->has_palette = 0;
296    d->has_alpha = amask ? 1 : 0;
297
298    d->w = w;
299    d->h = h;
300    d->pitch = pitch;
301
302    d->rmask = rmask;
303    d->gmask = gmask;
304    d->bmask = bmask;
305    d->amask = amask;
306
307    /* Load bitmasks */
308    if(rmask || gmask || bmask || amask)
309    {
310        mask2shift(rmask, &d->rright, &d->rleft);
311        mask2shift(gmask, &d->gright, &d->gleft);
312        mask2shift(bmask, &d->bright, &d->bleft);
313        mask2shift(amask, &d->aright, &d->aleft);
314    }
315
316    /* In 8 bpp mode, default to a grayscale palette */
317    if(bpp == 8)
318    {
319        d->has_palette = 1;
320        d->has_alpha = 0;
321        for(i = 0; i < 256; i++)
322        {
323            d->red[i] = i * 0xfff / 256;
324            d->green[i] = i * 0xfff / 256;
325            d->blue[i] = i * 0xfff / 256;
326        }
327    }
328
329    /* Default gamma value */
330    d->gamma = 1.0;
331    for(i = 0; i < 4096; i++)
332        d->gammatab[i] = i;
333
334    /* Default colour properties */
335    d->brightness = 1.0;
336    d->contrast = 1.0;
337
338    /* Default features */
339    d->invert = 0;
340    d->antialias = 1;
341
342    /* Default colour mode */
343    d->color_mode = COLOR_MODE_FULL16;
344
345    /* Default character set */
346    d->glyphs = ascii_glyphs;
347    d->glyph_count = sizeof(ascii_glyphs) / sizeof(*ascii_glyphs);
348
349    /* Default dithering mode */
350    d->init_dither = init_fstein_dither;
351    d->get_dither = get_fstein_dither;
352    d->increment_dither = increment_fstein_dither;
353
354    return d;
355}
356
357/** \brief Set the palette of an 8bpp dither object.
358 *
359 *  Set the palette of an 8 bits per pixel bitmap. Values should be between
360 *  0 and 4095 (0xfff).
361 *
362 *  If an error occurs, -1 is returned and \b errno is set accordingly:
363 *  - \c EINVAL Dither bits per pixel value is not 8, or one of the pixel
364 *   values was outside the range 0 - 4095.
365 *
366 *  \param d Dither object.
367 *  \param red Array of 256 red values.
368 *  \param green Array of 256 green values.
369 *  \param blue Array of 256 blue values.
370 *  \param alpha Array of 256 alpha values.
371 *  \return 0 in case of success, -1 if an error occurred.
372 */
373int cucul_set_dither_palette(cucul_dither_t *d,
374                             unsigned int red[], unsigned int green[],
375                             unsigned int blue[], unsigned int alpha[])
376{
377    int i, has_alpha = 0;
378
379    if(d->bpp != 8)
380    {
381        seterrno(EINVAL);
382        return -1;
383    }
384
385    for(i = 0; i < 256; i++)
386    {
387        if((red[i] | green[i] | blue[i] | alpha[i]) >= 0x1000)
388        {
389            seterrno(EINVAL);
390            return -1;
391        }
392    }
393
394    for(i = 0; i < 256; i++)
395    {
396        d->red[i] = red[i];
397        d->green[i] = green[i];
398        d->blue[i] = blue[i];
399        if(alpha[i])
400        {
401            d->alpha[i] = alpha[i];
402            has_alpha = 1;
403        }
404    }
405
406    d->has_alpha = has_alpha;
407
408    return 0;
409}
410
411/** \brief Set the brightness of a dither object.
412 *
413 *  Set the brightness of dither.
414 *
415 *  If an error occurs, -1 is returned and \b errno is set accordingly:
416 *  - \c EINVAL Brightness value was out of range.
417 *
418 *  \param d Dither object.
419 *  \param brightness brightness value.
420 *  \return 0 in case of success, -1 if an error occurred.
421 */
422int cucul_set_dither_brightness(cucul_dither_t *d, float brightness)
423{
424    /* FIXME */
425    d->brightness = brightness;
426
427    return 0;
428}
429
430/** \brief Get the brightness of a dither object.
431 *
432 *  Get the brightness of the given dither object.
433 *
434 *  This function never fails.
435 *
436 *  \param d Dither object.
437 *  \return Brightness value.
438 */
439float cucul_get_dither_brightness(cucul_dither_t *d)
440{
441    return d->brightness;
442}
443
444/** \brief Set the gamma of a dither object.
445 *
446 *  Set the gamma of the given dither object. A negative value causes
447 *  colour inversion.
448 *
449 *  If an error occurs, -1 is returned and \b errno is set accordingly:
450 *  - \c EINVAL Gamma value was out of range.
451 *
452 *  \param d Dither object.
453 *  \param gamma Gamma value.
454 *  \return 0 in case of success, -1 if an error occurred.
455 */
456int cucul_set_dither_gamma(cucul_dither_t *d, float gamma)
457{
458    /* FIXME: we don't need 4096 calls to gammapow(), we could just compute
459     * a few of them and do linear interpolation for the rest. This will
460     * probably speed up things a lot. */
461    int i;
462
463    if(gamma < 0.0)
464    {
465        d->invert = 1;
466        gamma = -gamma;
467    }
468    else if(gamma == 0.0)
469    {
470        seterrno(EINVAL);
471        return -1;
472    }
473
474    d->gamma = gamma;
475
476    for(i = 0; i < 4096; i++)
477        d->gammatab[i] = 4096.0 * gammapow((float)i / 4096.0, 1.0 / gamma);
478
479    return 0;
480}
481
482/** \brief Get the gamma of a dither object.
483 *
484 *  Get the gamma of the given dither object.
485 *
486 *  This function never fails.
487 *
488 *  \param d Dither object.
489 *  \return Gamma value.
490 */
491float cucul_get_dither_gamma(cucul_dither_t *d)
492{
493    return d->gamma;
494}
495
496/** \brief Set the contrast of a dither object.
497 *
498 *  Set the contrast of dither.
499 *
500 *  If an error occurs, -1 is returned and \b errno is set accordingly:
501 *  - \c EINVAL Contrast value was out of range.
502 *
503 *  \param d Dither object.
504 *  \param contrast contrast value.
505 *  \return 0 in case of success, -1 if an error occurred.
506 */
507int cucul_set_dither_contrast(cucul_dither_t *d, float contrast)
508{
509    /* FIXME */
510    d->contrast = contrast;
511
512    return 0;
513}
514
515/** \brief Get the contrast of a dither object.
516 *
517 *  Get the contrast of the given dither object.
518 *
519 *  This function never fails.
520 *
521 *  \param d Dither object.
522 *  \return Contrast value.
523 */
524float cucul_get_dither_contrast(cucul_dither_t *d)
525{
526    return d->contrast;
527}
528
529/** \brief Set dither antialiasing
530 *
531 *  Tell the renderer whether to antialias the dither. Antialiasing smoothens
532 *  the rendered image and avoids the commonly seen staircase effect.
533 *  - \c "none": no antialiasing.
534 *  - \c "prefilter" or \c "default": simple prefilter antialiasing. This
535 *    is the default value.
536 *
537 *  If an error occurs, -1 is returned and \b errno is set accordingly:
538 *  - \c EINVAL Invalid antialiasing mode.
539 *
540 *  \param d Dither object.
541 *  \param str A string describing the antialiasing method that will be used
542 *         for the dithering.
543 *  \return 0 in case of success, -1 if an error occurred.
544 */
545int cucul_set_dither_antialias(cucul_dither_t *d, char const *str)
546{
547    if(!strcasecmp(str, "none"))
548        d->antialias = 0;
549    else if(!strcasecmp(str, "prefilter") || !strcasecmp(str, "default"))
550        d->antialias = 1;
551    else
552    {
553        seterrno(EINVAL);
554        return -1;
555    }
556
557    return 0;
558}
559
560/** \brief Get available antialiasing methods
561 *
562 *  Return a list of available antialiasing methods for a given dither. The
563 *  list is a NULL-terminated array of strings, interleaving a string
564 *  containing the internal value for the antialiasing method to be used with
565 *  cucul_set_dither_antialias(), and a string containing the natural
566 *  language description for that antialiasing method.
567 *
568 *  This function never fails.
569 *
570 *  \param d Dither object.
571 *  \return An array of strings.
572 */
573char const * const *
574    cucul_get_dither_antialias_list(cucul_dither_t const *d)
575{
576    static char const * const list[] =
577    {
578        "none", "No antialiasing",
579        "prefilter", "Prefilter antialiasing",
580        NULL, NULL
581    };
582
583    return list;
584}
585
586/** \brief Choose colours used for dithering
587 *
588 *  Tell the renderer which colours should be used to render the
589 *  bitmap. Valid values for \c str are:
590 *  - \c "mono": use light gray on a black background.
591 *  - \c "gray": use white and two shades of gray on a black background.
592 *  - \c "8": use the 8 ANSI colours on a black background.
593 *  - \c "16": use the 16 ANSI colours on a black background.
594 *  - \c "fullgray": use black, white and two shades of gray for both the
595 *    characters and the background.
596 *  - \c "full8": use the 8 ANSI colours for both the characters and the
597 *    background.
598 *  - \c "full16" or \c "default": use the 16 ANSI colours for both the
599 *    characters and the background. This is the default value.
600 *
601 *  If an error occurs, -1 is returned and \b errno is set accordingly:
602 *  - \c EINVAL Invalid colour set.
603 *
604 *  \param d Dither object.
605 *  \param str A string describing the colour set that will be used
606 *         for the dithering.
607 *  \return 0 in case of success, -1 if an error occurred.
608 */
609int cucul_set_dither_color(cucul_dither_t *d, char const *str)
610{
611    if(!strcasecmp(str, "mono"))
612        d->color_mode = COLOR_MODE_MONO;
613    else if(!strcasecmp(str, "gray"))
614        d->color_mode = COLOR_MODE_GRAY;
615    else if(!strcasecmp(str, "8"))
616        d->color_mode = COLOR_MODE_8;
617    else if(!strcasecmp(str, "16"))
618        d->color_mode = COLOR_MODE_16;
619    else if(!strcasecmp(str, "fullgray"))
620        d->color_mode = COLOR_MODE_FULLGRAY;
621    else if(!strcasecmp(str, "full8"))
622        d->color_mode = COLOR_MODE_FULL8;
623    else if(!strcasecmp(str, "full16") || !strcasecmp(str, "default"))
624        d->color_mode = COLOR_MODE_FULL16;
625    else
626    {
627        seterrno(EINVAL);
628        return -1;
629    }
630
631    return 0;
632}
633
634/** \brief Get available colour modes
635 *
636 *  Return a list of available colour modes for a given dither. The list
637 *  is a NULL-terminated array of strings, interleaving a string containing
638 *  the internal value for the colour mode, to be used with
639 *  cucul_set_dither_color(), and a string containing the natural
640 *  language description for that colour mode.
641 *
642 *  This function never fails.
643 *
644 *  \param d Dither object.
645 *  \return An array of strings.
646 */
647char const * const *
648    cucul_get_dither_color_list(cucul_dither_t const *d)
649{
650    static char const * const list[] =
651    {
652        "mono", "white on black",
653        "gray", "grayscale on black",
654        "8", "8 colours on black",
655        "16", "16 colours on black",
656        "fullgray", "full grayscale",
657        "full8", "full 8 colours",
658        "full16", "full 16 colours",
659        NULL, NULL
660    };
661
662    return list;
663}
664
665/** \brief Choose characters used for dithering
666 *
667 *  Tell the renderer which characters should be used to render the
668 *  dither. Valid values for \c str are:
669 *  - \c "ascii" or \c "default": use only ASCII characters. This is the
670 *    default value.
671 *  - \c "shades": use Unicode characters "U+2591 LIGHT SHADE", "U+2592
672 *    MEDIUM SHADE" and "U+2593 DARK SHADE". These characters are also
673 *    present in the CP437 codepage available on DOS and VGA.
674 *  - \c "blocks": use Unicode quarter-cell block combinations. These
675 *    characters are only found in the Unicode set.
676 *
677 *  If an error occurs, -1 is returned and \b errno is set accordingly:
678 *  - \c EINVAL Invalid character set.
679 *
680 *  \param d Dither object.
681 *  \param str A string describing the characters that need to be used
682 *         for the dithering.
683 *  \return 0 in case of success, -1 if an error occurred.
684 */
685int cucul_set_dither_charset(cucul_dither_t *d, char const *str)
686{
687    if(!strcasecmp(str, "shades"))
688    {
689        d->glyphs = shades_glyphs;
690        d->glyph_count = sizeof(shades_glyphs) / sizeof(*shades_glyphs);
691    }
692    else if(!strcasecmp(str, "blocks"))
693    {
694        d->glyphs = blocks_glyphs;
695        d->glyph_count = sizeof(blocks_glyphs) / sizeof(*blocks_glyphs);
696    }
697    else if(!strcasecmp(str, "ascii") || !strcasecmp(str, "default"))
698    {
699        d->glyphs = ascii_glyphs;
700        d->glyph_count = sizeof(ascii_glyphs) / sizeof(*ascii_glyphs);
701    }
702    else
703    {
704        seterrno(EINVAL);
705        return -1;
706    }
707
708    return 0;
709}
710
711/** \brief Get available dither character sets
712 *
713 *  Return a list of available character sets for a given dither. The list
714 *  is a NULL-terminated array of strings, interleaving a string containing
715 *  the internal value for the character set, to be used with
716 *  cucul_set_dither_charset(), and a string containing the natural
717 *  language description for that character set.
718 *
719 *  This function never fails.
720 *
721 *  \param d Dither object.
722 *  \return An array of strings.
723 */
724char const * const * cucul_get_dither_charset_list(cucul_dither_t const *d)
725{
726    static char const * const list[] =
727    {
728        "ascii", "plain ASCII",
729        "shades", "CP437 shades",
730        "blocks", "Unicode blocks",
731        NULL, NULL
732    };
733
734    return list;
735}
736
737/** \brief Set dithering method
738 *
739 *  Tell the renderer which dithering method should be used. Dithering is
740 *  necessary because the picture being rendered has usually far more colours
741 *  than the available palette. Valid values for \c str are:
742 *  - \c "none": no dithering is used, the nearest matching colour is used.
743 *  - \c "ordered2": use a 2x2 Bayer matrix for dithering.
744 *  - \c "ordered4": use a 4x4 Bayer matrix for dithering.
745 *  - \c "ordered8": use a 8x8 Bayer matrix for dithering.
746 *  - \c "random": use random dithering.
747 *  - \c "fstein": use Floyd-Steinberg dithering. This is the default value.
748 *
749 *  If an error occurs, -1 is returned and \b errno is set accordingly:
750 *  - \c EINVAL Unknown dithering mode.
751 *
752 *  \param d Dither object.
753 *  \param str A string describing the method that needs to be used
754 *         for the dithering.
755 *  \return 0 in case of success, -1 if an error occurred.
756 */
757int cucul_set_dither_mode(cucul_dither_t *d, char const *str)
758{
759    if(!strcasecmp(str, "none"))
760    {
761        d->init_dither = init_no_dither;
762        d->get_dither = get_no_dither;
763        d->increment_dither = increment_no_dither;
764    }
765    else if(!strcasecmp(str, "ordered2"))
766    {
767        d->init_dither = init_ordered2_dither;
768        d->get_dither = get_ordered2_dither;
769        d->increment_dither = increment_ordered2_dither;
770    }
771    else if(!strcasecmp(str, "ordered4"))
772    {
773        d->init_dither = init_ordered4_dither;
774        d->get_dither = get_ordered4_dither;
775        d->increment_dither = increment_ordered4_dither;
776    }
777    else if(!strcasecmp(str, "ordered8"))
778    {
779        d->init_dither = init_ordered8_dither;
780        d->get_dither = get_ordered8_dither;
781        d->increment_dither = increment_ordered8_dither;
782    }
783    else if(!strcasecmp(str, "random"))
784    {
785        d->init_dither = init_random_dither;
786        d->get_dither = get_random_dither;
787        d->increment_dither = increment_random_dither;
788    }
789    else if(!strcasecmp(str, "fstein") || !strcasecmp(str, "default"))
790    {
791        d->init_dither = init_fstein_dither;
792        d->get_dither = get_fstein_dither;
793        d->increment_dither = increment_fstein_dither;
794    }
795    else
796    {
797        seterrno(EINVAL);
798        return -1;
799    }
800
801    return 0;
802}
803
804/** \brief Get dithering methods
805 *
806 *  Return a list of available dithering methods for a given dither. The list
807 *  is a NULL-terminated array of strings, interleaving a string containing
808 *  the internal value for the dithering method, to be used with
809 *  cucul_set_dither_dithering(), and a string containing the natural
810 *  language description for that dithering method.
811 *
812 *  This function never fails.
813 *
814 *  \param d Dither object.
815 *  \return An array of strings.
816 */
817char const * const * cucul_get_dither_mode_list(cucul_dither_t const *d)
818{
819    static char const * const list[] =
820    {
821        "none", "no dithering",
822        "ordered2", "2x2 ordered dithering",
823        "ordered4", "4x4 ordered dithering",
824        "ordered8", "8x8 ordered dithering",
825        "random", "random dithering",
826        "fstein", "Floyd-Steinberg dithering",
827        NULL, NULL
828    };
829
830    return list;
831}
832
833/** \brief Dither a bitmap on the canvas.
834 *
835 *  Dither a bitmap at the given coordinates. The dither can be of any size
836 *  and will be stretched to the text area.
837 *
838 *  This function never fails.
839 *
840 *  \param cv A handle to the libcucul canvas.
841 *  \param x X coordinate of the upper-left corner of the drawing area.
842 *  \param y Y coordinate of the upper-left corner of the drawing area.
843 *  \param w Width of the drawing area.
844 *  \param h Height of the drawing area.
845 *  \param d Dither object to be drawn.
846 *  \param pixels Bitmap's pixels.
847 *  \return This function always returns 0.
848 */
849int cucul_dither_bitmap(cucul_canvas_t *cv, int x, int y, int w, int h,
850                        cucul_dither_t const *d, void *pixels)
851{
852    int *floyd_steinberg, *fs_r, *fs_g, *fs_b;
853    uint32_t savedattr;
854    int fs_length;
855    int x1, y1, x2, y2, pitch, deltax, deltay;
856    unsigned int dchmax;
857
858    if(!d || !pixels)
859        return 0;
860
861    savedattr = cucul_get_attr(cv, -1, -1);
862
863    x1 = x; x2 = x + w - 1;
864    y1 = y; y2 = y + h - 1;
865
866    /* FIXME: do not overwrite arguments */
867    w = d->w;
868    h = d->h;
869    pitch = d->pitch;
870
871    deltax = x2 - x1 + 1;
872    deltay = y2 - y1 + 1;
873    dchmax = d->glyph_count;
874
875    fs_length = ((int)cv->width <= x2 ? (int)cv->width : x2) + 1;
876    floyd_steinberg = malloc(3 * (fs_length + 2) * sizeof(int));
877    memset(floyd_steinberg, 0, 3 * (fs_length + 2) * sizeof(int));
878    fs_r = floyd_steinberg + 1;
879    fs_g = fs_r + fs_length + 2;
880    fs_b = fs_g + fs_length + 2;
881
882    for(y = y1 > 0 ? y1 : 0; y <= y2 && y <= (int)cv->height; y++)
883    {
884        int remain_r = 0, remain_g = 0, remain_b = 0;
885
886        for(x = x1 > 0 ? x1 : 0, d->init_dither(y);
887            x <= x2 && x <= (int)cv->width;
888            x++)
889    {
890        unsigned int i;
891        int ch = 0, distmin;
892        unsigned int rgba[4];
893        int fg_r = 0, fg_g = 0, fg_b = 0, bg_r, bg_g, bg_b;
894        int fromx, fromy, tox, toy, myx, myy, dots, dist;
895        int error[3];
896
897        unsigned int outfg = 0, outbg = 0;
898        uint32_t outch;
899
900        rgba[0] = rgba[1] = rgba[2] = rgba[3] = 0;
901
902        /* First get RGB */
903        if(d->antialias)
904        {
905            fromx = (x - x1) * w / deltax;
906            fromy = (y - y1) * h / deltay;
907            tox = (x - x1 + 1) * w / deltax;
908            toy = (y - y1 + 1) * h / deltay;
909
910            /* We want at least one pixel */
911            if(tox == fromx) tox++;
912            if(toy == fromy) toy++;
913
914            dots = 0;
915
916            for(myx = fromx; myx < tox; myx++)
917                for(myy = fromy; myy < toy; myy++)
918            {
919                dots++;
920                get_rgba_default(d, pixels, myx, myy, rgba);
921            }
922
923            /* Normalize */
924            rgba[0] /= dots;
925            rgba[1] /= dots;
926            rgba[2] /= dots;
927            rgba[3] /= dots;
928        }
929        else
930        {
931            fromx = (x - x1) * w / deltax;
932            fromy = (y - y1) * h / deltay;
933            tox = (x - x1 + 1) * w / deltax;
934            toy = (y - y1 + 1) * h / deltay;
935
936            /* tox and toy can overflow the canvas, but they cannot overflow
937             * when averaged with fromx and fromy because these are guaranteed
938             * to be within the pixel boundaries. */
939            myx = (fromx + tox) / 2;
940            myy = (fromy + toy) / 2;
941
942            get_rgba_default(d, pixels, myx, myy, rgba);
943        }
944
945        if(d->has_alpha && rgba[3] < 0x800)
946        {
947            remain_r = remain_g = remain_b = 0;
948            fs_r[x] = 0;
949            fs_g[x] = 0;
950            fs_b[x] = 0;
951            continue;
952        }
953
954        /* XXX: OMG HAX */
955        if(d->init_dither == init_fstein_dither)
956        {
957            rgba[0] += remain_r;
958            rgba[1] += remain_g;
959            rgba[2] += remain_b;
960        }
961        else
962        {
963            rgba[0] += (d->get_dither() - 0x80) * 4;
964            rgba[1] += (d->get_dither() - 0x80) * 4;
965            rgba[2] += (d->get_dither() - 0x80) * 4;
966        }
967
968        distmin = INT_MAX;
969        for(i = 0; i < 16; i++)
970        {
971            dist = sq(rgba[0] - rgb_palette[i * 3])
972                 + sq(rgba[1] - rgb_palette[i * 3 + 1])
973                 + sq(rgba[2] - rgb_palette[i * 3 + 2]);
974            dist *= rgb_weight[i];
975            if(dist < distmin)
976            {
977                outbg = i;
978                distmin = dist;
979            }
980        }
981        bg_r = rgb_palette[outbg * 3];
982        bg_g = rgb_palette[outbg * 3 + 1];
983        bg_b = rgb_palette[outbg * 3 + 2];
984
985        /* FIXME: we currently only honour "full16" */
986        if(d->color_mode == COLOR_MODE_FULL16)
987        {
988            distmin = INT_MAX;
989            for(i = 0; i < 16; i++)
990            {
991                if(i == outbg)
992                    continue;
993                dist = sq(rgba[0] - rgb_palette[i * 3])
994                     + sq(rgba[1] - rgb_palette[i * 3 + 1])
995                     + sq(rgba[2] - rgb_palette[i * 3 + 2]);
996                dist *= rgb_weight[i];
997                if(dist < distmin)
998                {
999                    outfg = i;
1000                    distmin = dist;
1001                }
1002            }
1003            fg_r = rgb_palette[outfg * 3];
1004            fg_g = rgb_palette[outfg * 3 + 1];
1005            fg_b = rgb_palette[outfg * 3 + 2];
1006
1007            distmin = INT_MAX;
1008            for(i = 0; i < dchmax - 1; i++)
1009            {
1010                int newr = i * fg_r + ((2*dchmax-1) - i) * bg_r;
1011                int newg = i * fg_g + ((2*dchmax-1) - i) * bg_g;
1012                int newb = i * fg_b + ((2*dchmax-1) - i) * bg_b;
1013                dist = abs(rgba[0] * (2*dchmax-1) - newr)
1014                     + abs(rgba[1] * (2*dchmax-1) - newg)
1015                     + abs(rgba[2] * (2*dchmax-1) - newb);
1016
1017                if(dist < distmin)
1018                {
1019                    ch = i;
1020                    distmin = dist;
1021                }
1022            }
1023            outch = d->glyphs[ch];
1024
1025            /* XXX: OMG HAX */
1026            if(d->init_dither == init_fstein_dither)
1027            {
1028                error[0] = rgba[0] - (fg_r * ch + bg_r * ((2*dchmax-1) - ch)) / (2*dchmax-1);
1029                error[1] = rgba[1] - (fg_g * ch + bg_g * ((2*dchmax-1) - ch)) / (2*dchmax-1);
1030                error[2] = rgba[2] - (fg_b * ch + bg_b * ((2*dchmax-1) - ch)) / (2*dchmax-1);
1031            }
1032        }
1033        else
1034        {
1035            unsigned int lum = rgba[0];
1036            if(rgba[1] > lum) lum = rgba[1];
1037            if(rgba[2] > lum) lum = rgba[2];
1038            outfg = outbg;
1039            outbg = CUCUL_BLACK;
1040
1041            ch = lum * dchmax / 0x1000;
1042            if(ch < 0)
1043                ch = 0;
1044            else if(ch > (int)(dchmax - 1))
1045                ch = dchmax - 1;
1046            outch = d->glyphs[ch];
1047
1048            /* XXX: OMG HAX */
1049            if(d->init_dither == init_fstein_dither)
1050            {
1051                error[0] = rgba[0] - bg_r * ch / (dchmax-1);
1052                error[1] = rgba[1] - bg_g * ch / (dchmax-1);
1053                error[2] = rgba[2] - bg_b * ch / (dchmax-1);
1054            }
1055        }
1056
1057        /* XXX: OMG HAX */
1058        if(d->init_dither == init_fstein_dither)
1059        {
1060            remain_r = fs_r[x+1] + 7 * error[0] / 16;
1061            remain_g = fs_g[x+1] + 7 * error[1] / 16;
1062            remain_b = fs_b[x+1] + 7 * error[2] / 16;
1063            fs_r[x-1] += 3 * error[0] / 16;
1064            fs_g[x-1] += 3 * error[1] / 16;
1065            fs_b[x-1] += 3 * error[2] / 16;
1066            fs_r[x] = 5 * error[0] / 16;
1067            fs_g[x] = 5 * error[1] / 16;
1068            fs_b[x] = 5 * error[2] / 16;
1069            fs_r[x+1] = 1 * error[0] / 16;
1070            fs_g[x+1] = 1 * error[1] / 16;
1071            fs_b[x+1] = 1 * error[2] / 16;
1072        }
1073
1074        if(d->invert)
1075        {
1076            outfg = 15 - outfg;
1077            outbg = 15 - outbg;
1078        }
1079
1080        /* Now output the character */
1081        cucul_set_color_ansi(cv, outfg, outbg);
1082        cucul_put_char(cv, x, y, outch);
1083
1084        d->increment_dither();
1085    }
1086        /* end loop */
1087    }
1088
1089    free(floyd_steinberg);
1090
1091    cucul_set_attr(cv, savedattr);
1092
1093    return 0;
1094}
1095
1096/** \brief Free the memory associated with a dither.
1097 *
1098 *  Free the memory allocated by cucul_create_dither().
1099 *
1100 *  This function never fails.
1101 *
1102 *  \param d Dither object.
1103 *  \return This function always returns 0.
1104 */
1105int cucul_free_dither(cucul_dither_t *d)
1106{
1107    if(!d)
1108        return 0;
1109
1110    free(d);
1111
1112    return 0;
1113}
1114
1115/*
1116 * XXX: The following functions are local.
1117 */
1118
1119/* Convert a mask, eg. 0x0000ff00, to shift values, eg. 8 and -4. */
1120static void mask2shift(unsigned long int mask, int *right, int *left)
1121{
1122    int rshift = 0, lshift = 0;
1123
1124    if(!mask)
1125    {
1126        *right = *left = 0;
1127        return;
1128    }
1129
1130    while(!(mask & 1))
1131    {
1132        mask >>= 1;
1133        rshift++;
1134    }
1135    *right = rshift;
1136
1137    while(mask & 1)
1138    {
1139        mask >>= 1;
1140        lshift++;
1141    }
1142    *left = 12 - lshift;
1143}
1144
1145/* Compute x^y without relying on the math library */
1146static float gammapow(float x, float y)
1147{
1148#ifdef HAVE_FLDLN2
1149    register double logx;
1150    register long double v, e;
1151#else
1152    register float tmp, t, t2, r;
1153    int i;
1154#endif
1155
1156    if(x == 0.0)
1157        return y == 0.0 ? 1.0 : 0.0;
1158
1159#ifdef HAVE_FLDLN2
1160    /* FIXME: this can be optimised by directly calling fyl2x for x and y */
1161    asm volatile("fldln2; fxch; fyl2x"
1162                 : "=t" (logx) : "0" (x) : "st(1)");
1163
1164    asm volatile("fldl2e\n\t"
1165                 "fmul %%st(1)\n\t"
1166                 "fst %%st(1)\n\t"
1167                 "frndint\n\t"
1168                 "fxch\n\t"
1169                 "fsub %%st(1)\n\t"
1170                 "f2xm1\n\t"
1171                 : "=t" (v), "=u" (e) : "0" (y * logx));
1172    v += 1.0;
1173    asm volatile("fscale"
1174                 : "=t" (v) : "0" (v), "u" (e));
1175    return v;
1176#else
1177    /* Compute ln(x) for x ∈ ]0,1]
1178     *   ln(x) = 2 * (t + t^3/3 + t^5/5 + ...) with t = (x-1)/(x+1)
1179     * The convergence is a bit slow, especially when x is near 0. */
1180    t = (x - 1.0) / (x + 1.0);
1181    t2 = t * t;
1182    tmp = r = t;
1183    for(i = 3; i < 20; i += 2)
1184    {
1185        r *= t2;
1186        tmp += r / i;
1187    }
1188
1189    /* Compute -y*ln(x) */
1190    tmp = - y * 2.0 * tmp;
1191
1192    /* Compute x^-y as e^t where t = -y*ln(x):
1193     *   e^t = 1 + t/1! + t^2/2! + t^3/3! + t^4/4! + t^5/5! ...
1194     * The convergence is quite faster here, thanks to the factorial. */
1195    r = t = tmp;
1196    tmp = 1.0 + t;
1197    for(i = 2; i < 16; i++)
1198    {
1199        r = r * t / i;
1200        tmp += r;
1201    }
1202
1203    /* Return x^y as 1/(x^-y) */
1204    return 1.0 / tmp;
1205#endif
1206}
1207
1208static void get_rgba_default(cucul_dither_t const *d, uint8_t *pixels,
1209                             int x, int y, unsigned int *rgba)
1210{
1211    uint32_t bits;
1212
1213    pixels += (d->bpp / 8) * x + d->pitch * y;
1214
1215    switch(d->bpp / 8)
1216    {
1217        case 4:
1218            bits = *(uint32_t *)pixels;
1219            break;
1220        case 3:
1221        {
1222#if defined(HAVE_ENDIAN_H)
1223            if(__BYTE_ORDER == __BIG_ENDIAN)
1224#else
1225            /* This is compile-time optimised with at least -O1 or -Os */
1226            uint32_t const tmp = 0x12345678;
1227            if(*(uint8_t const *)&tmp == 0x12)
1228#endif
1229                bits = ((uint32_t)pixels[0] << 16) |
1230                       ((uint32_t)pixels[1] << 8) |
1231                       ((uint32_t)pixels[2]);
1232            else
1233                bits = ((uint32_t)pixels[2] << 16) |
1234                       ((uint32_t)pixels[1] << 8) |
1235                       ((uint32_t)pixels[0]);
1236            break;
1237        }
1238        case 2:
1239            bits = *(uint16_t *)pixels;
1240            break;
1241        case 1:
1242        default:
1243            bits = pixels[0];
1244            break;
1245    }
1246
1247    if(d->has_palette)
1248    {
1249        rgba[0] += d->gammatab[d->red[bits]];
1250        rgba[1] += d->gammatab[d->green[bits]];
1251        rgba[2] += d->gammatab[d->blue[bits]];
1252        rgba[3] += d->alpha[bits];
1253    }
1254    else
1255    {
1256        rgba[0] += d->gammatab[((bits & d->rmask) >> d->rright) << d->rleft];
1257        rgba[1] += d->gammatab[((bits & d->gmask) >> d->gright) << d->gleft];
1258        rgba[2] += d->gammatab[((bits & d->bmask) >> d->bright) << d->bleft];
1259        rgba[3] += ((bits & d->amask) >> d->aright) << d->aleft;
1260    }
1261}
1262
1263/*
1264 * No dithering
1265 */
1266static void init_no_dither(int line)
1267{
1268    ;
1269}
1270
1271static unsigned int get_no_dither(void)
1272{
1273    return 0x80;
1274}
1275
1276static void increment_no_dither(void)
1277{
1278    return;
1279}
1280
1281/*
1282 * Floyd-Steinberg dithering
1283 */
1284static void init_fstein_dither(int line)
1285{
1286    ;
1287}
1288
1289static unsigned int get_fstein_dither(void)
1290{
1291    return 0x80;
1292}
1293
1294static void increment_fstein_dither(void)
1295{
1296    return;
1297}
1298
1299/*
1300 * Ordered 2 dithering
1301 */
1302static unsigned int const *ordered2_table;
1303static unsigned int ordered2_index;
1304
1305static void init_ordered2_dither(int line)
1306{
1307    static unsigned int const dither2x2[] =
1308    {
1309        0x00, 0x80,
1310        0xc0, 0x40,
1311    };
1312
1313    ordered2_table = dither2x2 + (line % 2) * 2;
1314    ordered2_index = 0;
1315}
1316
1317static unsigned int get_ordered2_dither(void)
1318{
1319    return ordered2_table[ordered2_index];
1320}
1321
1322static void increment_ordered2_dither(void)
1323{
1324    ordered2_index = (ordered2_index + 1) % 2;
1325}
1326
1327/*
1328 * Ordered 4 dithering
1329 */
1330/*static int dither4x4[] = { 5,  0,  1,  6,
1331                          -1, -6, -5,  2,
1332                          -2, -7, -8,  3,
1333                           4, -3, -4, -7};*/
1334static unsigned int const *ordered4_table;
1335static unsigned int ordered4_index;
1336
1337static void init_ordered4_dither(int line)
1338{
1339    static unsigned int const dither4x4[] =
1340    {
1341        0x00, 0x80, 0x20, 0xa0,
1342        0xc0, 0x40, 0xe0, 0x60,
1343        0x30, 0xb0, 0x10, 0x90,
1344        0xf0, 0x70, 0xd0, 0x50
1345    };
1346
1347    ordered4_table = dither4x4 + (line % 4) * 4;
1348    ordered4_index = 0;
1349}
1350
1351static unsigned int get_ordered4_dither(void)
1352{
1353    return ordered4_table[ordered4_index];
1354}
1355
1356static void increment_ordered4_dither(void)
1357{
1358    ordered4_index = (ordered4_index + 1) % 4;
1359}
1360
1361/*
1362 * Ordered 8 dithering
1363 */
1364static unsigned int const *ordered8_table;
1365static unsigned int ordered8_index;
1366
1367static void init_ordered8_dither(int line)
1368{
1369    static unsigned int const dither8x8[] =
1370    {
1371        0x00, 0x80, 0x20, 0xa0, 0x08, 0x88, 0x28, 0xa8,
1372        0xc0, 0x40, 0xe0, 0x60, 0xc8, 0x48, 0xe8, 0x68,
1373        0x30, 0xb0, 0x10, 0x90, 0x38, 0xb8, 0x18, 0x98,
1374        0xf0, 0x70, 0xd0, 0x50, 0xf8, 0x78, 0xd8, 0x58,
1375        0x0c, 0x8c, 0x2c, 0xac, 0x04, 0x84, 0x24, 0xa4,
1376        0xcc, 0x4c, 0xec, 0x6c, 0xc4, 0x44, 0xe4, 0x64,
1377        0x3c, 0xbc, 0x1c, 0x9c, 0x34, 0xb4, 0x14, 0x94,
1378        0xfc, 0x7c, 0xdc, 0x5c, 0xf4, 0x74, 0xd4, 0x54,
1379    };
1380
1381    ordered8_table = dither8x8 + (line % 8) * 8;
1382    ordered8_index = 0;
1383}
1384
1385static unsigned int get_ordered8_dither(void)
1386{
1387    return ordered8_table[ordered8_index];
1388}
1389
1390static void increment_ordered8_dither(void)
1391{
1392    ordered8_index = (ordered8_index + 1) % 8;
1393}
1394
1395/*
1396 * Random dithering
1397 */
1398static void init_random_dither(int line)
1399{
1400    ;
1401}
1402
1403static unsigned int get_random_dither(void)
1404{
1405    return cucul_rand(0x00, 0x100);
1406}
1407
1408static void increment_random_dither(void)
1409{
1410    return;
1411}
1412
1413/*
1414 * Lookup tables
1415 */
1416static int init_lookup(void)
1417{
1418    unsigned int v, s, h;
1419
1420    /* These ones are constant */
1421    lookup_colors[0] = CUCUL_BLACK;
1422    lookup_colors[1] = CUCUL_DARKGRAY;
1423    lookup_colors[2] = CUCUL_LIGHTGRAY;
1424    lookup_colors[3] = CUCUL_WHITE;
1425
1426    /* These ones will be overwritten */
1427    lookup_colors[4] = CUCUL_MAGENTA;
1428    lookup_colors[5] = CUCUL_LIGHTMAGENTA;
1429    lookup_colors[6] = CUCUL_RED;
1430    lookup_colors[7] = CUCUL_LIGHTRED;
1431
1432    for(v = 0; v < LOOKUP_VAL; v++)
1433        for(s = 0; s < LOOKUP_SAT; s++)
1434            for(h = 0; h < LOOKUP_HUE; h++)
1435    {
1436        int i, distbg, distfg, dist;
1437        int val, sat, hue;
1438        unsigned char outbg, outfg;
1439
1440        val = 0xfff * v / (LOOKUP_VAL - 1);
1441        sat = 0xfff * s / (LOOKUP_SAT - 1);
1442        hue = 0xfff * h / (LOOKUP_HUE - 1);
1443
1444        /* Initialise distances to the distance between pure black HSV
1445         * coordinates and our white colour (3) */
1446        outbg = outfg = 3;
1447        distbg = distfg = HSV_DISTANCE(0, 0, 0, 3);
1448
1449        /* Calculate distances to eight major colour values and store the
1450         * two nearest points in our lookup table. */
1451        for(i = 0; i < 8; i++)
1452        {
1453            dist = HSV_DISTANCE(hue, sat, val, i);
1454            if(dist <= distbg)
1455            {
1456                outfg = outbg;
1457                distfg = distbg;
1458                outbg = i;
1459                distbg = dist;
1460            }
1461            else if(dist <= distfg)
1462            {
1463                outfg = i;
1464                distfg = dist;
1465            }
1466        }
1467
1468        hsv_distances[v][s][h] = (outfg << 4) | outbg;
1469    }
1470
1471    return 0;
1472}
1473
Note: See TracBrowser for help on using the repository browser.