• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
93 typedef 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 
101 typedef 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 
122 static 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 */
144 static MagickBooleanType
145   WriteSIXELImage(const ImageInfo *,Image *,ExceptionInfo *);
146 
hue_to_rgb(int n1,int n2,int hue)147 static 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 
hls_to_rgb(int hue,int lum,int sat)171 static 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 
get_params(unsigned char * p,int * param,int * len)195 static 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 */
sixel_decode(unsigned char * p,unsigned char ** pixels,size_t * pwidth,size_t * pheight,unsigned char ** palette,size_t * ncolors)229 MagickBooleanType 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 
sixel_output_create(Image * image)545 sixel_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 
sixel_advance(sixel_output_t * context,int nwrite)562 static 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 
sixel_put_flash(sixel_output_t * const context)572 static 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 
sixel_put_pixel(sixel_output_t * const context,int pix)608 static 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 
sixel_node_del(sixel_output_t * const context,sixel_node_t * np)625 static 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 
sixel_put_node(sixel_output_t * const context,int x,sixel_node_t * np,int ncolors,int keycolor)647 static 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 
sixel_encode_impl(unsigned char * pixels,size_t width,size_t height,unsigned char * palette,size_t ncolors,int keycolor,sixel_output_t * context)675 static 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 */
IsSIXEL(const unsigned char * magick,const size_t length)898 static 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 */
ReadSIXELImage(const ImageInfo * image_info,ExceptionInfo * exception)945 static 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 */
RegisterSIXELImage(void)1106 ModuleExport 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 */
UnregisterSIXELImage(void)1145 ModuleExport 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 */
WriteSIXELImage(const ImageInfo * image_info,Image * image,ExceptionInfo * exception)1178 static 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