1/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3%                                                                             %
4%                                                                             %
5%                                                                             %
6%                     SSSSS  IIIII  X   X  EEEEE  L                           %
7%                     SS       I     X X   E      L                           %
8%                      SSS     I      X    EEE    L                           %
9%                        SS    I     X X   E      L                           %
10%                     SSSSS  IIIII  X   X  EEEEE  LLLLL                       %
11%                                                                             %
12%                                                                             %
13%                        Read/Write DEC SIXEL Format                          %
14%                                                                             %
15%                              Software Design                                %
16%                               Hayaki Saito                                  %
17%                              September 2014                                 %
18%                    Based on kmiya's sixel (2014-03-28)                      %
19%                                                                             %
20%                                                                             %
21%  Copyright 1999-2016 ImageMagick Studio LLC, a non-profit organization      %
22%  dedicated to making software imaging solutions freely available.           %
23%                                                                             %
24%  You may not use this file except in compliance with the License.  You may  %
25%  obtain a copy of the License at                                            %
26%                                                                             %
27%    http://www.imagemagick.org/script/license.php                            %
28%                                                                             %
29%  Unless required by applicable law or agreed to in writing, software        %
30%  distributed under the License is distributed on an "AS IS" BASIS,          %
31%  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   %
32%  See the License for the specific language governing permissions and        %
33%  limitations under the License.                                             %
34%                                                                             %
35%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
36%
37%
38*/
39
40/*
41  Include declarations.
42*/
43#include "MagickCore/studio.h"
44#include "MagickCore/attribute.h"
45#include "MagickCore/blob.h"
46#include "MagickCore/blob-private.h"
47#include "MagickCore/cache.h"
48#include "MagickCore/color.h"
49#include "MagickCore/color-private.h"
50#include "MagickCore/colormap.h"
51#include "MagickCore/colorspace.h"
52#include "MagickCore/colorspace-private.h"
53#include "MagickCore/exception.h"
54#include "MagickCore/exception-private.h"
55#include "MagickCore/geometry.h"
56#include "MagickCore/image.h"
57#include "MagickCore/image-private.h"
58#include "MagickCore/list.h"
59#include "MagickCore/magick.h"
60#include "MagickCore/memory_.h"
61#include "MagickCore/monitor.h"
62#include "MagickCore/monitor-private.h"
63#include "MagickCore/pixel-accessor.h"
64#include "MagickCore/pixel-private.h"
65#include "MagickCore/quantize.h"
66#include "MagickCore/quantum-private.h"
67#include "MagickCore/resize.h"
68#include "MagickCore/resource_.h"
69#include "MagickCore/splay-tree.h"
70#include "MagickCore/static.h"
71#include "MagickCore/string_.h"
72#include "MagickCore/thread-private.h"
73#include "MagickCore/module.h"
74#include "MagickCore/threshold.h"
75#include "MagickCore/utility.h"
76
77/*
78  Definitions
79*/
80#define SIXEL_PALETTE_MAX 256
81#define SIXEL_OUTPUT_PACKET_SIZE 1024
82
83/*
84  Macros
85*/
86#define SIXEL_RGB(r, g, b) (((r) << 16) + ((g) << 8) +  (b))
87#define SIXEL_PALVAL(n,a,m) (((n) * (a) + ((m) / 2)) / (m))
88#define SIXEL_XRGB(r,g,b) SIXEL_RGB(SIXEL_PALVAL(r, 255, 100), SIXEL_PALVAL(g, 255, 100), SIXEL_PALVAL(b, 255, 100))
89
90/*
91  Structure declarations.
92*/
93typedef struct sixel_node {
94    struct sixel_node *next;
95    int color;
96    int left;
97    int right;
98    unsigned char *map;
99} sixel_node_t;
100
101typedef struct sixel_output {
102
103    /* compatiblity flags */
104
105    /* 0: 7bit terminal,
106     * 1: 8bit terminal */
107    unsigned char has_8bit_control;
108
109    int save_pixel;
110    int save_count;
111    int active_palette;
112
113    sixel_node_t *node_top;
114    sixel_node_t *node_free;
115
116    Image *image;
117    int pos;
118    unsigned char buffer[1];
119
120} sixel_output_t;
121
122static int const sixel_default_color_table[] = {
123    SIXEL_XRGB(0,  0,  0),   /*  0 Black    */
124    SIXEL_XRGB(20, 20, 80),  /*  1 Blue     */
125    SIXEL_XRGB(80, 13, 13),  /*  2 Red      */
126    SIXEL_XRGB(20, 80, 20),  /*  3 Green    */
127    SIXEL_XRGB(80, 20, 80),  /*  4 Magenta  */
128    SIXEL_XRGB(20, 80, 80),  /*  5 Cyan     */
129    SIXEL_XRGB(80, 80, 20),  /*  6 Yellow   */
130    SIXEL_XRGB(53, 53, 53),  /*  7 Gray 50% */
131    SIXEL_XRGB(26, 26, 26),  /*  8 Gray 25% */
132    SIXEL_XRGB(33, 33, 60),  /*  9 Blue*    */
133    SIXEL_XRGB(60, 26, 26),  /* 10 Red*     */
134    SIXEL_XRGB(33, 60, 33),  /* 11 Green*   */
135    SIXEL_XRGB(60, 33, 60),  /* 12 Magenta* */
136    SIXEL_XRGB(33, 60, 60),  /* 13 Cyan*    */
137    SIXEL_XRGB(60, 60, 33),  /* 14 Yellow*  */
138    SIXEL_XRGB(80, 80, 80),  /* 15 Gray 75% */
139};
140
141/*
142  Forward declarations.
143*/
144static MagickBooleanType
145  WriteSIXELImage(const ImageInfo *,Image *,ExceptionInfo *);
146
147static int hue_to_rgb(int n1, int n2, int hue)
148{
149    const int HLSMAX = 100;
150
151    if (hue < 0) {
152        hue += HLSMAX;
153    }
154
155    if (hue > HLSMAX) {
156        hue -= HLSMAX;
157    }
158
159    if (hue < (HLSMAX / 6)) {
160        return (n1 + (((n2 - n1) * hue + (HLSMAX / 12)) / (HLSMAX / 6)));
161    }
162    if (hue < (HLSMAX / 2)) {
163        return (n2);
164    }
165    if (hue < ((HLSMAX * 2) / 3)) {
166        return (n1 + (((n2 - n1) * (((HLSMAX * 2) / 3) - hue) + (HLSMAX / 12))/(HLSMAX / 6)));
167    }
168    return (n1);
169}
170
171static int hls_to_rgb(int hue, int lum, int sat)
172{
173    int R, G, B;
174    int Magic1, Magic2;
175    const int RGBMAX = 255;
176    const int HLSMAX = 100;
177
178    if (sat == 0) {
179        R = G = B = (lum * RGBMAX) / HLSMAX;
180    } else {
181        if (lum <= (HLSMAX / 2)) {
182            Magic2 = (lum * (HLSMAX + sat) + (HLSMAX / 2)) / HLSMAX;
183        } else {
184            Magic2 = lum + sat - ((lum * sat) + (HLSMAX / 2)) / HLSMAX;
185        }
186        Magic1 = 2 * lum - Magic2;
187
188        R = (hue_to_rgb(Magic1, Magic2, hue + (HLSMAX / 3)) * RGBMAX + (HLSMAX / 2)) / HLSMAX;
189        G = (hue_to_rgb(Magic1, Magic2, hue) * RGBMAX + (HLSMAX / 2)) / HLSMAX;
190        B = (hue_to_rgb(Magic1, Magic2, hue - (HLSMAX / 3)) * RGBMAX + (HLSMAX/2)) / HLSMAX;
191    }
192    return SIXEL_RGB(R, G, B);
193}
194
195static unsigned char *get_params(unsigned char *p, int *param, int *len)
196{
197    int n;
198
199    *len = 0;
200    while (*p != '\0') {
201        while (*p == ' ' || *p == '\t') {
202            p++;
203        }
204        if (isdigit(*p)) {
205            for (n = 0; isdigit(*p); p++) {
206                n = n * 10 + (*p - '0');
207            }
208            if (*len < 10) {
209                param[(*len)++] = n;
210            }
211            while (*p == ' ' || *p == '\t') {
212                p++;
213            }
214            if (*p == ';') {
215                p++;
216            }
217        } else if (*p == ';') {
218            if (*len < 10) {
219                param[(*len)++] = 0;
220            }
221            p++;
222        } else
223            break;
224    }
225    return p;
226}
227
228/* convert sixel data into indexed pixel bytes and palette data */
229MagickBooleanType sixel_decode(unsigned char              /* in */  *p,         /* sixel bytes */
230                               unsigned char              /* out */ **pixels,   /* decoded pixels */
231                               size_t                     /* out */ *pwidth,    /* image width */
232                               size_t                     /* out */ *pheight,   /* image height */
233                               unsigned char              /* out */ **palette,  /* ARGB palette */
234                               size_t                     /* out */ *ncolors    /* palette size (<= 256) */)
235{
236    int n, i, r, g, b, sixel_vertical_mask, c;
237    int posision_x, posision_y;
238    int max_x, max_y;
239    int attributed_pan, attributed_pad;
240    int attributed_ph, attributed_pv;
241    int repeat_count, color_index, max_color_index = 2, background_color_index;
242    int param[10];
243    int sixel_palet[SIXEL_PALETTE_MAX];
244    unsigned char *imbuf, *dmbuf;
245    int imsx, imsy;
246    int dmsx, dmsy;
247    int y;
248
249    posision_x = posision_y = 0;
250    max_x = max_y = 0;
251    attributed_pan = 2;
252    attributed_pad = 1;
253    attributed_ph = attributed_pv = 0;
254    repeat_count = 1;
255    color_index = 0;
256    background_color_index = 0;
257
258    imsx = 2048;
259    imsy = 2048;
260    imbuf = (unsigned char *) AcquireQuantumMemory(imsx * imsy,1);
261
262    if (imbuf == NULL) {
263        return(MagickFalse);
264    }
265
266    for (n = 0; n < 16; n++) {
267        sixel_palet[n] = sixel_default_color_table[n];
268    }
269
270    /* colors 16-231 are a 6x6x6 color cube */
271    for (r = 0; r < 6; r++) {
272        for (g = 0; g < 6; g++) {
273            for (b = 0; b < 6; b++) {
274                sixel_palet[n++] = SIXEL_RGB(r * 51, g * 51, b * 51);
275            }
276        }
277    }
278    /* colors 232-255 are a grayscale ramp, intentionally leaving out */
279    for (i = 0; i < 24; i++) {
280        sixel_palet[n++] = SIXEL_RGB(i * 11, i * 11, i * 11);
281    }
282
283    for (; n < SIXEL_PALETTE_MAX; n++) {
284        sixel_palet[n] = SIXEL_RGB(255, 255, 255);
285    }
286
287    (void) ResetMagickMemory(imbuf, background_color_index, imsx * imsy);
288
289    while (*p != '\0') {
290        if ((p[0] == '\033' && p[1] == 'P') || *p == 0x90) {
291            if (*p == '\033') {
292                p++;
293            }
294
295            p = get_params(++p, param, &n);
296
297            if (*p == 'q') {
298                p++;
299
300                if (n > 0) {        /* Pn1 */
301                    switch(param[0]) {
302                    case 0:
303                    case 1:
304                        attributed_pad = 2;
305                        break;
306                    case 2:
307                        attributed_pad = 5;
308                        break;
309                    case 3:
310                        attributed_pad = 4;
311                        break;
312                    case 4:
313                        attributed_pad = 4;
314                        break;
315                    case 5:
316                        attributed_pad = 3;
317                        break;
318                    case 6:
319                        attributed_pad = 3;
320                        break;
321                    case 7:
322                        attributed_pad = 2;
323                        break;
324                    case 8:
325                        attributed_pad = 2;
326                        break;
327                    case 9:
328                        attributed_pad = 1;
329                        break;
330                    }
331                }
332
333                if (n > 2) {        /* Pn3 */
334                    if (param[2] == 0) {
335                        param[2] = 10;
336                    }
337                    attributed_pan = attributed_pan * param[2] / 10;
338                    attributed_pad = attributed_pad * param[2] / 10;
339                    if (attributed_pan <= 0) attributed_pan = 1;
340                    if (attributed_pad <= 0) attributed_pad = 1;
341                }
342            }
343
344        } else if ((p[0] == '\033' && p[1] == '\\') || *p == 0x9C) {
345            break;
346        } else if (*p == '"') {
347            /* DECGRA Set Raster Attributes " Pan; Pad; Ph; Pv */
348            p = get_params(++p, param, &n);
349
350            if (n > 0) attributed_pad = param[0];
351            if (n > 1) attributed_pan = param[1];
352            if (n > 2 && param[2] > 0) attributed_ph = param[2];
353            if (n > 3 && param[3] > 0) attributed_pv = param[3];
354
355            if (attributed_pan <= 0) attributed_pan = 1;
356            if (attributed_pad <= 0) attributed_pad = 1;
357
358            if (imsx < attributed_ph || imsy < attributed_pv) {
359                dmsx = imsx > attributed_ph ? imsx : attributed_ph;
360                dmsy = imsy > attributed_pv ? imsy : attributed_pv;
361                dmbuf = (unsigned char *) AcquireQuantumMemory(dmsx * dmsy,1);
362                if (dmbuf == (unsigned char *) NULL) {
363                    imbuf = (unsigned char *) RelinquishMagickMemory(imbuf);
364                    return (MagickFalse);
365                }
366                (void) ResetMagickMemory(dmbuf, background_color_index, dmsx * dmsy);
367                for (y = 0; y < imsy; ++y) {
368                    (void) CopyMagickMemory(dmbuf + dmsx * y, imbuf + imsx * y, imsx);
369                }
370                imbuf = (unsigned char *) RelinquishMagickMemory(imbuf);
371                imsx = dmsx;
372                imsy = dmsy;
373                imbuf = dmbuf;
374            }
375
376        } else if (*p == '!') {
377            /* DECGRI Graphics Repeat Introducer ! Pn Ch */
378            p = get_params(++p, param, &n);
379
380            if (n > 0) {
381                repeat_count = param[0];
382            }
383
384        } else if (*p == '#') {
385            /* DECGCI Graphics Color Introducer # Pc; Pu; Px; Py; Pz */
386            p = get_params(++p, param, &n);
387
388            if (n > 0) {
389                if ((color_index = param[0]) < 0) {
390                    color_index = 0;
391                } else if (color_index >= SIXEL_PALETTE_MAX) {
392                    color_index = SIXEL_PALETTE_MAX - 1;
393                }
394            }
395
396            if (n > 4) {
397                if (param[1] == 1) {            /* HLS */
398                    if (param[2] > 360) param[2] = 360;
399                    if (param[3] > 100) param[3] = 100;
400                    if (param[4] > 100) param[4] = 100;
401                    sixel_palet[color_index] = hls_to_rgb(param[2] * 100 / 360, param[3], param[4]);
402                } else if (param[1] == 2) {    /* RGB */
403                    if (param[2] > 100) param[2] = 100;
404                    if (param[3] > 100) param[3] = 100;
405                    if (param[4] > 100) param[4] = 100;
406                    sixel_palet[color_index] = SIXEL_XRGB(param[2], param[3], param[4]);
407                }
408            }
409
410        } else if (*p == '$') {
411            /* DECGCR Graphics Carriage Return */
412            p++;
413            posision_x = 0;
414            repeat_count = 1;
415
416        } else if (*p == '-') {
417            /* DECGNL Graphics Next Line */
418            p++;
419            posision_x  = 0;
420            posision_y += 6;
421            repeat_count = 1;
422
423        } else if (*p >= '?' && *p <= '\177') {
424            if (imsx < (posision_x + repeat_count) || imsy < (posision_y + 6)) {
425                int nx = imsx * 2;
426                int ny = imsy * 2;
427
428                while (nx < (posision_x + repeat_count) || ny < (posision_y + 6)) {
429                    nx *= 2;
430                    ny *= 2;
431                }
432
433                dmsx = nx;
434                dmsy = ny;
435                dmbuf = (unsigned char *) AcquireQuantumMemory(dmsx * dmsy,1);
436                if (dmbuf == (unsigned char *) NULL) {
437                    imbuf = (unsigned char *) RelinquishMagickMemory(imbuf);
438                    return (MagickFalse);
439                }
440                (void) ResetMagickMemory(dmbuf, background_color_index, dmsx * dmsy);
441                for (y = 0; y < imsy; ++y) {
442                    (void) CopyMagickMemory(dmbuf + dmsx * y, imbuf + imsx * y, imsx);
443                }
444                imbuf = (unsigned char *) RelinquishMagickMemory(imbuf);
445                imsx = dmsx;
446                imsy = dmsy;
447                imbuf = dmbuf;
448            }
449
450            if (color_index > max_color_index) {
451                max_color_index = color_index;
452            }
453            if ((b = *(p++) - '?') == 0) {
454                posision_x += repeat_count;
455
456            } else {
457                sixel_vertical_mask = 0x01;
458
459                if (repeat_count <= 1) {
460                    for (i = 0; i < 6; i++) {
461                        if ((b & sixel_vertical_mask) != 0) {
462                            imbuf[imsx * (posision_y + i) + posision_x] = color_index;
463                            if (max_x < posision_x) {
464                                max_x = posision_x;
465                            }
466                            if (max_y < (posision_y + i)) {
467                                max_y = posision_y + i;
468                            }
469                        }
470                        sixel_vertical_mask <<= 1;
471                    }
472                    posision_x += 1;
473
474                } else { /* repeat_count > 1 */
475                    for (i = 0; i < 6; i++) {
476                        if ((b & sixel_vertical_mask) != 0) {
477                            c = sixel_vertical_mask << 1;
478                            for (n = 1; (i + n) < 6; n++) {
479                                if ((b & c) == 0) {
480                                    break;
481                                }
482                                c <<= 1;
483                            }
484                            for (y = posision_y + i; y < posision_y + i + n; ++y) {
485                                (void) ResetMagickMemory(imbuf + imsx * y + posision_x, color_index, repeat_count);
486                            }
487                            if (max_x < (posision_x + repeat_count - 1)) {
488                                max_x = posision_x + repeat_count - 1;
489                            }
490                            if (max_y < (posision_y + i + n - 1)) {
491                                max_y = posision_y + i + n - 1;
492                            }
493
494                            i += (n - 1);
495                            sixel_vertical_mask <<= (n - 1);
496                        }
497                        sixel_vertical_mask <<= 1;
498                    }
499                    posision_x += repeat_count;
500                }
501            }
502            repeat_count = 1;
503        } else {
504            p++;
505        }
506    }
507
508    if (++max_x < attributed_ph) {
509        max_x = attributed_ph;
510    }
511    if (++max_y < attributed_pv) {
512        max_y = attributed_pv;
513    }
514
515    if (imsx > max_x || imsy > max_y) {
516        dmsx = max_x;
517        dmsy = max_y;
518        if ((dmbuf = (unsigned char *) AcquireQuantumMemory(dmsx * dmsy,1)) == NULL) {
519            imbuf = (unsigned char *) RelinquishMagickMemory(imbuf);
520            return (MagickFalse);
521        }
522        for (y = 0; y < dmsy; ++y) {
523            (void) CopyMagickMemory(dmbuf + dmsx * y, imbuf + imsx * y, dmsx);
524        }
525        imbuf = (unsigned char *) RelinquishMagickMemory(imbuf);
526        imsx = dmsx;
527        imsy = dmsy;
528        imbuf = dmbuf;
529    }
530
531    *pixels = imbuf;
532    *pwidth = imsx;
533    *pheight = imsy;
534    *ncolors = max_color_index + 1;
535    *palette = (unsigned char *) AcquireQuantumMemory(*ncolors,4);
536    for (n = 0; n < (ssize_t) *ncolors; ++n) {
537        (*palette)[n * 4 + 0] = sixel_palet[n] >> 16 & 0xff;
538        (*palette)[n * 4 + 1] = sixel_palet[n] >> 8 & 0xff;
539        (*palette)[n * 4 + 2] = sixel_palet[n] & 0xff;
540        (*palette)[n * 4 + 3] = 0xff;
541    }
542    return(MagickTrue);
543}
544
545sixel_output_t *sixel_output_create(Image *image)
546{
547    sixel_output_t *output;
548
549    output = (sixel_output_t *) AcquireQuantumMemory(sizeof(sixel_output_t) + SIXEL_OUTPUT_PACKET_SIZE * 2, 1);
550    output->has_8bit_control = 0;
551    output->save_pixel = 0;
552    output->save_count = 0;
553    output->active_palette = (-1);
554    output->node_top = NULL;
555    output->node_free = NULL;
556    output->image = image;
557    output->pos = 0;
558
559    return output;
560}
561
562static void sixel_advance(sixel_output_t *context, int nwrite)
563{
564    if ((context->pos += nwrite) >= SIXEL_OUTPUT_PACKET_SIZE) {
565        WriteBlob(context->image,SIXEL_OUTPUT_PACKET_SIZE,context->buffer);
566        CopyMagickMemory(context->buffer,
567               context->buffer + SIXEL_OUTPUT_PACKET_SIZE,
568               (context->pos -= SIXEL_OUTPUT_PACKET_SIZE));
569    }
570}
571
572static int sixel_put_flash(sixel_output_t *const context)
573{
574    int n;
575    int nwrite;
576
577#if defined(USE_VT240)        /* VT240 Max 255 ? */
578    while (context->save_count > 255) {
579        nwrite = spritf((char *)context->buffer + context->pos, "!255%c", context->save_pixel);
580        if (nwrite <= 0) {
581            return (-1);
582        }
583        sixel_advance(context, nwrite);
584        context->save_count -= 255;
585    }
586#endif  /* defined(USE_VT240) */
587
588    if (context->save_count > 3) {
589        /* DECGRI Graphics Repeat Introducer ! Pn Ch */
590        nwrite = sprintf((char *)context->buffer + context->pos, "!%d%c", context->save_count, context->save_pixel);
591        if (nwrite <= 0) {
592            return (-1);
593        }
594        sixel_advance(context, nwrite);
595    } else {
596        for (n = 0; n < context->save_count; n++) {
597            context->buffer[context->pos] = (char)context->save_pixel;
598            sixel_advance(context, 1);
599        }
600    }
601
602    context->save_pixel = 0;
603    context->save_count = 0;
604
605    return 0;
606}
607
608static void sixel_put_pixel(sixel_output_t *const context, int pix)
609{
610    if (pix < 0 || pix > '?') {
611        pix = 0;
612    }
613
614    pix += '?';
615
616    if (pix == context->save_pixel) {
617        context->save_count++;
618    } else {
619        sixel_put_flash(context);
620        context->save_pixel = pix;
621        context->save_count = 1;
622    }
623}
624
625static void sixel_node_del(sixel_output_t *const context, sixel_node_t *np)
626{
627    sixel_node_t *tp;
628
629    if ((tp = context->node_top) == np) {
630        context->node_top = np->next;
631    }
632
633    else {
634        while (tp->next != NULL) {
635            if (tp->next == np) {
636                tp->next = np->next;
637                break;
638            }
639            tp = tp->next;
640        }
641    }
642
643    np->next = context->node_free;
644    context->node_free = np;
645}
646
647static int sixel_put_node(sixel_output_t *const context, int x,
648               sixel_node_t *np, int ncolors, int keycolor)
649{
650    int nwrite;
651
652    if (ncolors != 2 || keycolor == -1) {
653        /* designate palette index */
654        if (context->active_palette != np->color) {
655            nwrite = sprintf((char *)context->buffer + context->pos,
656                             "#%d", np->color);
657            sixel_advance(context, nwrite);
658            context->active_palette = np->color;
659        }
660    }
661
662    for (; x < np->left; x++) {
663        sixel_put_pixel(context, 0);
664    }
665
666    for (; x < np->right; x++) {
667        sixel_put_pixel(context, np->map[x]);
668    }
669
670    sixel_put_flash(context);
671
672    return x;
673}
674
675static MagickBooleanType sixel_encode_impl(unsigned char *pixels, size_t width,size_t height,
676                  unsigned char *palette, size_t ncolors, int keycolor,
677                  sixel_output_t *context)
678{
679#define RelinquishNodesAndMap \
680    while ((np = context->node_free) != NULL) { \
681        context->node_free = np->next; \
682        np=(sixel_node_t *) RelinquishMagickMemory(np); \
683    } \
684    map = (unsigned char *) RelinquishMagickMemory(map)
685
686    int x, y, i, n, c;
687    int left, right;
688    int pix;
689    unsigned char *map;
690    sixel_node_t *np, *tp, top;
691    int nwrite;
692    size_t len;
693
694    context->pos = 0;
695
696    if (ncolors < 1) {
697        return (MagickFalse);
698    }
699    len = ncolors * width;
700    context->active_palette = (-1);
701
702    if ((map = (unsigned char *)AcquireQuantumMemory(len, sizeof(unsigned char))) == NULL) {
703        return (MagickFalse);
704    }
705    (void) ResetMagickMemory(map, 0, len);
706
707    if (context->has_8bit_control) {
708        nwrite = sprintf((char *)context->buffer, "\x90" "0;0;0" "q");
709    } else {
710        nwrite = sprintf((char *)context->buffer, "\x1bP" "0;0;0" "q");
711    }
712    if (nwrite <= 0) {
713        return (MagickFalse);
714    }
715    sixel_advance(context, nwrite);
716    nwrite = sprintf((char *)context->buffer + context->pos, "\"1;1;%d;%d", (int) width, (int) height);
717    if (nwrite <= 0) {
718        RelinquishNodesAndMap;
719        return (MagickFalse);
720    }
721    sixel_advance(context, nwrite);
722
723    if (ncolors != 2 || keycolor == -1) {
724        for (n = 0; n < (ssize_t) ncolors; n++) {
725            /* DECGCI Graphics Color Introducer  # Pc ; Pu; Px; Py; Pz */
726            nwrite = sprintf((char *)context->buffer + context->pos, "#%d;2;%d;%d;%d",
727                             n,
728                             (palette[n * 3 + 0] * 100 + 127) / 255,
729                             (palette[n * 3 + 1] * 100 + 127) / 255,
730                             (palette[n * 3 + 2] * 100 + 127) / 255);
731            if (nwrite <= 0) {
732                RelinquishNodesAndMap;
733                return (MagickFalse);
734            }
735            sixel_advance(context, nwrite);
736            if (nwrite <= 0) {
737                RelinquishNodesAndMap;
738                return (MagickFalse);
739            }
740        }
741    }
742
743    for (y = i = 0; y < (ssize_t) height; y++) {
744        for (x = 0; x < (ssize_t) width; x++) {
745            pix = pixels[y * width + x];
746            if (pix >= 0 && pix < (ssize_t) ncolors && pix != keycolor) {
747                map[pix * width + x] |= (1 << i);
748            }
749        }
750
751        if (++i < 6 && (y + 1) < (ssize_t) height) {
752            continue;
753        }
754
755        for (c = 0; c < (ssize_t) ncolors; c++) {
756            for (left = 0; left < (ssize_t) width; left++) {
757                if (*(map + c * width + left) == 0) {
758                    continue;
759                }
760
761                for (right = left + 1; right < (ssize_t) width; right++) {
762                    if (*(map + c * width + right) != 0) {
763                        continue;
764                    }
765
766                    for (n = 1; (right + n) < (ssize_t) width; n++) {
767                        if (*(map + c * width + right + n) != 0) {
768                            break;
769                        }
770                    }
771
772                    if (n >= 10 || right + n >= (ssize_t) width) {
773                        break;
774                    }
775                    right = right + n - 1;
776                }
777
778                if ((np = context->node_free) != NULL) {
779                    context->node_free = np->next;
780                } else if ((np = (sixel_node_t *)AcquireMagickMemory(sizeof(sixel_node_t))) == NULL) {
781                    RelinquishNodesAndMap;
782                    return (MagickFalse);
783                }
784
785                np->color = c;
786                np->left = left;
787                np->right = right;
788                np->map = map + c * width;
789
790                top.next = context->node_top;
791                tp = &top;
792
793                while (tp->next != NULL) {
794                    if (np->left < tp->next->left) {
795                        break;
796                    }
797                    if (np->left == tp->next->left && np->right > tp->next->right) {
798                        break;
799                    }
800                    tp = tp->next;
801                }
802
803                np->next = tp->next;
804                tp->next = np;
805                context->node_top = top.next;
806
807                left = right - 1;
808            }
809
810        }
811
812        for (x = 0; (np = context->node_top) != NULL;) {
813            if (x > np->left) {
814                /* DECGCR Graphics Carriage Return */
815                context->buffer[context->pos] = '$';
816                sixel_advance(context, 1);
817                x = 0;
818            }
819
820            x = sixel_put_node(context, x, np, (int) ncolors, keycolor);
821            sixel_node_del(context, np);
822            np = context->node_top;
823
824            while (np != NULL) {
825                if (np->left < x) {
826                    np = np->next;
827                    continue;
828                }
829
830                x = sixel_put_node(context, x, np, (int) ncolors, keycolor);
831                sixel_node_del(context, np);
832                np = context->node_top;
833            }
834        }
835
836        /* DECGNL Graphics Next Line */
837        context->buffer[context->pos] = '-';
838        sixel_advance(context, 1);
839        if (nwrite <= 0) {
840            RelinquishNodesAndMap;
841            return (MagickFalse);
842        }
843
844        i = 0;
845        (void) ResetMagickMemory(map, 0, len);
846    }
847
848    if (context->has_8bit_control) {
849        context->buffer[context->pos] = 0x9c;
850        sixel_advance(context, 1);
851    } else {
852        context->buffer[context->pos] = 0x1b;
853        context->buffer[context->pos + 1] = '\\';
854        sixel_advance(context, 2);
855    }
856    if (nwrite <= 0) {
857        RelinquishNodesAndMap;
858        return (MagickFalse);
859    }
860
861    /* flush buffer */
862    if (context->pos > 0) {
863        WriteBlob(context->image,context->pos,context->buffer);
864    }
865
866    RelinquishNodesAndMap;
867
868    return(MagickTrue);
869}
870
871/*
872%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
873%                                                                             %
874%                                                                             %
875%                                                                             %
876%   I s S I X E L                                                             %
877%                                                                             %
878%                                                                             %
879%                                                                             %
880%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
881%
882%  IsSIXEL() returns MagickTrue if the image format type, identified by the
883%  magick string, is SIXEL.
884%
885%  The format of the IsSIXEL method is:
886%
887%      MagickBooleanType IsSIXEL(const unsigned char *magick,
888%        const size_t length)
889%
890%  A description of each parameter follows:
891%
892%    o magick: compare image format pattern against these bytes. or
893%      blob.
894%
895%    o length: Specifies the length of the magick string.
896%
897*/
898static MagickBooleanType IsSIXEL(const unsigned char *magick,
899  const size_t length)
900{
901  const unsigned char
902    *end = magick + length;
903
904  if (length < 3)
905    return(MagickFalse);
906
907  if (*magick == 0x90 || (*magick == 0x1b && *++magick == 'P')) {
908    while (++magick != end) {
909      if (*magick == 'q')
910        return(MagickTrue);
911      if (!(*magick >= '0' && *magick <= '9') && *magick != ';')
912        return(MagickFalse);
913    }
914  }
915  return(MagickFalse);
916}
917
918/*
919%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
920%                                                                             %
921%                                                                             %
922%                                                                             %
923%   R e a d S I X E L I m a g e                                               %
924%                                                                             %
925%                                                                             %
926%                                                                             %
927%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
928%
929%  ReadSIXELImage() reads an X11 pixmap image file and returns it.  It
930%  allocates the memory necessary for the new Image structure and returns a
931%  pointer to the new image.
932%
933%  The format of the ReadSIXELImage method is:
934%
935%      Image *ReadSIXELImage(const ImageInfo *image_info,
936%        ExceptionInfo *exception)
937%
938%  A description of each parameter follows:
939%
940%    o image_info: the image info.
941%
942%    o exception: return any errors or warnings in this structure.
943%
944*/
945static Image *ReadSIXELImage(const ImageInfo *image_info,ExceptionInfo *exception)
946{
947  char
948    *sixel_buffer;
949
950  Image
951    *image;
952
953  MagickBooleanType
954    status;
955
956  register char
957    *p;
958
959  register ssize_t
960    x;
961
962  register Quantum
963    *q;
964
965  size_t
966    length;
967
968  ssize_t
969    i,
970    j,
971    y;
972
973  unsigned char
974    *sixel_pixels,
975    *sixel_palette;
976
977  /*
978    Open image file.
979  */
980  assert(image_info != (const ImageInfo *) NULL);
981  assert(image_info->signature == MagickCoreSignature);
982  if (image_info->debug != MagickFalse)
983    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
984      image_info->filename);
985  assert(exception != (ExceptionInfo *) NULL);
986  assert(exception->signature == MagickCoreSignature);
987  image=AcquireImage(image_info,exception);
988  status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
989  if (status == MagickFalse)
990    {
991      image=DestroyImageList(image);
992      return((Image *) NULL);
993    }
994  /*
995    Read SIXEL file.
996  */
997  length=MagickPathExtent;
998  sixel_buffer=(char *) AcquireQuantumMemory((size_t) length,
999    sizeof(*sixel_buffer));
1000  p=sixel_buffer;
1001  if (sixel_buffer != (char *) NULL)
1002    while (ReadBlobString(image,p) != (char *) NULL)
1003    {
1004      if ((*p == '#') && ((p == sixel_buffer) || (*(p-1) == '\n')))
1005        continue;
1006      if ((*p == '}') && (*(p+1) == ';'))
1007        break;
1008      p+=strlen(p);
1009      if ((size_t) (p-sixel_buffer+MagickPathExtent) < length)
1010        continue;
1011      length<<=1;
1012      sixel_buffer=(char *) ResizeQuantumMemory(sixel_buffer,length+
1013        MagickPathExtent,sizeof(*sixel_buffer));
1014      if (sixel_buffer == (char *) NULL)
1015        break;
1016      p=sixel_buffer+strlen(sixel_buffer);
1017    }
1018  if (sixel_buffer == (char *) NULL)
1019    ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
1020  /*
1021    Decode SIXEL
1022  */
1023  if (sixel_decode((unsigned char *) sixel_buffer,&sixel_pixels,&image->columns,&image->rows,&sixel_palette,&image->colors) == MagickFalse)
1024    {
1025      sixel_buffer=(char *) RelinquishMagickMemory(sixel_buffer);
1026      ThrowReaderException(CorruptImageError,"CorruptImage");
1027    }
1028  sixel_buffer=(char *) RelinquishMagickMemory(sixel_buffer);
1029  image->depth=24;
1030  image->storage_class=PseudoClass;
1031  status=SetImageExtent(image,image->columns,image->rows,exception);
1032  if (status == MagickFalse)
1033    return(DestroyImageList(image));
1034
1035  if (AcquireImageColormap(image,image->colors, exception) == MagickFalse)
1036    {
1037      sixel_pixels=(unsigned char *) RelinquishMagickMemory(sixel_pixels);
1038      sixel_palette=(unsigned char *) RelinquishMagickMemory(sixel_palette);
1039      ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
1040    }
1041  for (i = 0; i < (ssize_t) image->colors; ++i) {
1042    image->colormap[i].red   = ScaleCharToQuantum(sixel_palette[i * 4 + 0]);
1043    image->colormap[i].green = ScaleCharToQuantum(sixel_palette[i * 4 + 1]);
1044    image->colormap[i].blue  = ScaleCharToQuantum(sixel_palette[i * 4 + 2]);
1045  }
1046
1047  j=0;
1048  if (image_info->ping == MagickFalse)
1049    {
1050      /*
1051        Read image pixels.
1052      */
1053      for (y=0; y < (ssize_t) image->rows; y++)
1054      {
1055        q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
1056        if (q == (Quantum *) NULL)
1057          break;
1058        for (x=0; x < (ssize_t) image->columns; x++)
1059        {
1060          j=(ssize_t) sixel_pixels[y * image->columns + x];
1061          SetPixelIndex(image,j,q);
1062          q+=GetPixelChannels(image);
1063        }
1064        if (SyncAuthenticPixels(image,exception) == MagickFalse)
1065          break;
1066      }
1067      if (y < (ssize_t) image->rows)
1068        {
1069          sixel_pixels=(unsigned char *) RelinquishMagickMemory(sixel_pixels);
1070          sixel_palette=(unsigned char *) RelinquishMagickMemory(sixel_palette);
1071          ThrowReaderException(CorruptImageError,"NotEnoughPixelData");
1072        }
1073    }
1074  /*
1075    Relinquish resources.
1076  */
1077  sixel_pixels=(unsigned char *) RelinquishMagickMemory(sixel_pixels);
1078  sixel_palette=(unsigned char *) RelinquishMagickMemory(sixel_palette);
1079  (void) CloseBlob(image);
1080  return(GetFirstImageInList(image));
1081}
1082
1083/*
1084%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1085%                                                                             %
1086%                                                                             %
1087%                                                                             %
1088%   R e g i s t e r S I X E L I m a g e                                       %
1089%                                                                             %
1090%                                                                             %
1091%                                                                             %
1092%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1093%
1094%  RegisterSIXELImage() adds attributes for the SIXEL image format to
1095%  the list of supported formats.  The attributes include the image format
1096%  tag, a method to read and/or write the format, whether the format
1097%  supports the saving of more than one frame to the same file or blob,
1098%  whether the format supports native in-memory I/O, and a brief
1099%  description of the format.
1100%
1101%  The format of the RegisterSIXELImage method is:
1102%
1103%      size_t RegisterSIXELImage(void)
1104%
1105*/
1106ModuleExport size_t RegisterSIXELImage(void)
1107{
1108  MagickInfo
1109    *entry;
1110
1111  entry=AcquireMagickInfo("SIXEL","SIXEL","DEC SIXEL Graphics Format");
1112  entry->decoder=(DecodeImageHandler *) ReadSIXELImage;
1113  entry->encoder=(EncodeImageHandler *) WriteSIXELImage;
1114  entry->magick=(IsImageFormatHandler *) IsSIXEL;
1115  entry->flags^=CoderAdjoinFlag;
1116  (void) RegisterMagickInfo(entry);
1117  entry=AcquireMagickInfo("SIX","SIX","DEC SIXEL Graphics Format");
1118  entry->decoder=(DecodeImageHandler *) ReadSIXELImage;
1119  entry->encoder=(EncodeImageHandler *) WriteSIXELImage;
1120  entry->magick=(IsImageFormatHandler *) IsSIXEL;
1121  entry->flags^=CoderAdjoinFlag;
1122  (void) RegisterMagickInfo(entry);
1123  return(MagickImageCoderSignature);
1124}
1125
1126/*
1127%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1128%                                                                             %
1129%                                                                             %
1130%                                                                             %
1131%   U n r e g i s t e r S I X E L I m a g e                                   %
1132%                                                                             %
1133%                                                                             %
1134%                                                                             %
1135%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1136%
1137%  UnregisterSIXELImage() removes format registrations made by the
1138%  SIXEL module from the list of supported formats.
1139%
1140%  The format of the UnregisterSIXELImage method is:
1141%
1142%      UnregisterSIXELImage(void)
1143%
1144*/
1145ModuleExport void UnregisterSIXELImage(void)
1146{
1147  (void) UnregisterMagickInfo("SIXEL");
1148  (void) UnregisterMagickInfo("SIX");
1149}
1150
1151/*
1152%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1153%                                                                             %
1154%                                                                             %
1155%                                                                             %
1156%   W r i t e S I X E L I m a g e                                             %
1157%                                                                             %
1158%                                                                             %
1159%                                                                             %
1160%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1161%
1162%  WriteSIXELImage() writes an image to a file in the X pixmap format.
1163%
1164%  The format of the WriteSIXELImage method is:
1165%
1166%      MagickBooleanType WriteSIXELImage(const ImageInfo *image_info,
1167%        Image *image,ExceptionInfo *exception)
1168%
1169%  A description of each parameter follows.
1170%
1171%    o image_info: the image info.
1172%
1173%    o image:  The image.
1174%
1175%    o exception: return any errors or warnings in this structure.
1176%
1177*/
1178static MagickBooleanType WriteSIXELImage(const ImageInfo *image_info,
1179  Image *image,ExceptionInfo *exception)
1180{
1181  MagickBooleanType
1182    status;
1183
1184  register const Quantum
1185    *q;
1186
1187  register ssize_t
1188    i,
1189    x;
1190
1191  ssize_t
1192    opacity,
1193    y;
1194
1195  sixel_output_t
1196    *output;
1197
1198  unsigned char
1199    sixel_palette[256*3],
1200    *sixel_pixels;
1201
1202  /*
1203    Open output image file.
1204  */
1205  assert(image_info != (const ImageInfo *) NULL);
1206  assert(image_info->signature == MagickCoreSignature);
1207  assert(image != (Image *) NULL);
1208  assert(image->signature == MagickCoreSignature);
1209  if (image->debug != MagickFalse)
1210    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1211  status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
1212  if (status == MagickFalse)
1213    return(status);
1214  if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
1215    (void) TransformImageColorspace(image,sRGBColorspace,exception);
1216  opacity=(-1);
1217  if (image->alpha_trait == UndefinedPixelTrait)
1218    {
1219      if ((image->storage_class == DirectClass) || (image->colors > 256))
1220        (void) SetImageType(image,PaletteType,exception);
1221    }
1222  else
1223    {
1224      MagickRealType
1225        alpha,
1226        beta;
1227
1228      /*
1229        Identify transparent colormap index.
1230      */
1231      if ((image->storage_class == DirectClass) || (image->colors > 256))
1232        (void) SetImageType(image,PaletteBilevelAlphaType,exception);
1233      for (i=0; i < (ssize_t) image->colors; i++)
1234        if (image->colormap[i].alpha != OpaqueAlpha)
1235          {
1236            if (opacity < 0)
1237              {
1238                opacity=i;
1239                continue;
1240              }
1241            alpha=image->colormap[i].alpha;
1242            beta=image->colormap[opacity].alpha;
1243            if (alpha < beta)
1244              opacity=i;
1245          }
1246      if (opacity == -1)
1247        {
1248          (void) SetImageType(image,PaletteBilevelAlphaType,exception);
1249          for (i=0; i < (ssize_t) image->colors; i++)
1250            if (image->colormap[i].alpha != OpaqueAlpha)
1251              {
1252                if (opacity < 0)
1253                  {
1254                    opacity=i;
1255                    continue;
1256                  }
1257                alpha=image->colormap[i].alpha;
1258                beta=image->colormap[opacity].alpha;
1259                if (alpha < beta)
1260                  opacity=i;
1261              }
1262        }
1263      if (opacity >= 0)
1264        {
1265          image->colormap[opacity].red=image->transparent_color.red;
1266          image->colormap[opacity].green=image->transparent_color.green;
1267          image->colormap[opacity].blue=image->transparent_color.blue;
1268        }
1269    }
1270  /*
1271    SIXEL header.
1272  */
1273  for (i=0; i < (ssize_t) image->colors; i++)
1274  {
1275    sixel_palette[3*i+0]=ScaleQuantumToChar(image->colormap[i].red);
1276    sixel_palette[3*i+1]=ScaleQuantumToChar(image->colormap[i].green);
1277    sixel_palette[3*i+2]=ScaleQuantumToChar(image->colormap[i].blue);
1278  }
1279
1280  /*
1281    Define SIXEL pixels.
1282  */
1283  output = sixel_output_create(image);
1284  sixel_pixels=(unsigned char *) AcquireQuantumMemory(image->columns,
1285    image->rows*sizeof(*sixel_pixels));
1286  for (y=0; y < (ssize_t) image->rows; y++)
1287  {
1288    q=GetVirtualPixels(image,0,y,image->columns,1,exception);
1289    if (q == (Quantum *) NULL)
1290      break;
1291    for (x=0; x < (ssize_t) image->columns; x++)
1292      {
1293        sixel_pixels[y*image->columns+x]= ((ssize_t) GetPixelIndex(image,q));
1294        q+=GetPixelChannels(image);
1295      }
1296  }
1297  status = sixel_encode_impl(sixel_pixels,image->columns,image->rows,
1298    sixel_palette,image->colors,-1,output);
1299  sixel_pixels=(unsigned char *) RelinquishMagickMemory(sixel_pixels);
1300  output=(sixel_output_t *) RelinquishMagickMemory(output);
1301  (void) CloseBlob(image);
1302  return(status);
1303}
1304