cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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 % |
dirk | fc313e5 | 2014-10-07 19:28:14 +0000 | [diff] [blame] | 18 | % Based on kmiya's sixel (2014-03-28) % |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 19 | % % |
| 20 | % % |
Cristy | 7ce65e7 | 2015-12-12 18:03:16 -0500 | [diff] [blame] | 21 | % Copyright 1999-2016 ImageMagick Studio LLC, a non-profit organization % |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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 | |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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 |
dirk | a1e4ab8 | 2014-10-07 05:06:49 +0000 | [diff] [blame] | 145 | WriteSIXELImage(const ImageInfo *,Image *,ExceptionInfo *); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 146 | |
| 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 | |
| 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 | |
| 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 */ |
| 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]; |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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 | |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 289 | while (*p != '\0') { |
| 290 | if ((p[0] == '\033' && p[1] == 'P') || *p == 0x90) { |
| 291 | if (*p == '\033') { |
| 292 | p++; |
| 293 | } |
| 294 | |
dirk | a1e4ab8 | 2014-10-07 05:06:49 +0000 | [diff] [blame] | 295 | p = get_params(++p, param, &n); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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 */ |
dirk | a1e4ab8 | 2014-10-07 05:06:49 +0000 | [diff] [blame] | 348 | p = get_params(++p, param, &n); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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); |
cristy | 02bcb98 | 2014-10-05 19:22:25 +0000 | [diff] [blame] | 536 | for (n = 0; n < (ssize_t) *ncolors; ++n) { |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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, |
dirk | fc313e5 | 2014-10-07 19:28:14 +0000 | [diff] [blame] | 656 | "#%d", np->color); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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 | |
cristy | 6544304 | 2014-10-25 17:41:18 +0000 | [diff] [blame] | 675 | static MagickBooleanType sixel_encode_impl(unsigned char *pixels, size_t width,size_t height, |
| 676 | unsigned char *palette, size_t ncolors, int keycolor, |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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; |
dirk | 39c51eb | 2014-12-27 01:01:27 +0000 | [diff] [blame] | 688 | int pix; |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 689 | unsigned char *map; |
| 690 | sixel_node_t *np, *tp, top; |
| 691 | int nwrite; |
dirk | 39c51eb | 2014-12-27 01:01:27 +0000 | [diff] [blame] | 692 | size_t len; |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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); |
cristy | 53c56cb | 2014-10-26 13:11:53 +0000 | [diff] [blame] | 716 | nwrite = sprintf((char *)context->buffer + context->pos, "\"1;1;%d;%d", (int) width, (int) height); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 717 | if (nwrite <= 0) { |
| 718 | RelinquishNodesAndMap; |
| 719 | return (MagickFalse); |
| 720 | } |
| 721 | sixel_advance(context, nwrite); |
| 722 | |
| 723 | if (ncolors != 2 || keycolor == -1) { |
cristy | 53c56cb | 2014-10-26 13:11:53 +0000 | [diff] [blame] | 724 | for (n = 0; n < (ssize_t) ncolors; n++) { |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 725 | /* DECGCI Graphics Color Introducer # Pc ; Pu; Px; Py; Pz */ |
| 726 | nwrite = sprintf((char *)context->buffer + context->pos, "#%d;2;%d;%d;%d", |
dirk | fc313e5 | 2014-10-07 19:28:14 +0000 | [diff] [blame] | 727 | n, |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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 | |
cristy | 53c56cb | 2014-10-26 13:11:53 +0000 | [diff] [blame] | 743 | for (y = i = 0; y < (ssize_t) height; y++) { |
| 744 | for (x = 0; x < (ssize_t) width; x++) { |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 745 | pix = pixels[y * width + x]; |
cristy | 53c56cb | 2014-10-26 13:11:53 +0000 | [diff] [blame] | 746 | if (pix >= 0 && pix < (ssize_t) ncolors && pix != keycolor) { |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 747 | map[pix * width + x] |= (1 << i); |
| 748 | } |
| 749 | } |
| 750 | |
cristy | 53c56cb | 2014-10-26 13:11:53 +0000 | [diff] [blame] | 751 | if (++i < 6 && (y + 1) < (ssize_t) height) { |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 752 | continue; |
| 753 | } |
| 754 | |
cristy | 53c56cb | 2014-10-26 13:11:53 +0000 | [diff] [blame] | 755 | for (c = 0; c < (ssize_t) ncolors; c++) { |
| 756 | for (left = 0; left < (ssize_t) width; left++) { |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 757 | if (*(map + c * width + left) == 0) { |
| 758 | continue; |
| 759 | } |
| 760 | |
cristy | 53c56cb | 2014-10-26 13:11:53 +0000 | [diff] [blame] | 761 | for (right = left + 1; right < (ssize_t) width; right++) { |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 762 | if (*(map + c * width + right) != 0) { |
| 763 | continue; |
| 764 | } |
| 765 | |
cristy | 53c56cb | 2014-10-26 13:11:53 +0000 | [diff] [blame] | 766 | for (n = 1; (right + n) < (ssize_t) width; n++) { |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 767 | if (*(map + c * width + right + n) != 0) { |
| 768 | break; |
| 769 | } |
| 770 | } |
| 771 | |
cristy | 53c56cb | 2014-10-26 13:11:53 +0000 | [diff] [blame] | 772 | if (n >= 10 || right + n >= (ssize_t) width) { |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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 = ⊤ |
| 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 | |
dirk | 39c51eb | 2014-12-27 01:01:27 +0000 | [diff] [blame] | 820 | x = sixel_put_node(context, x, np, (int) ncolors, keycolor); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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 | |
dirk | 39c51eb | 2014-12-27 01:01:27 +0000 | [diff] [blame] | 830 | x = sixel_put_node(context, x, np, (int) ncolors, keycolor); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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 | % |
cristy | 565117b | 2015-06-21 19:59:07 +0000 | [diff] [blame] | 887 | % MagickBooleanType IsSIXEL(const unsigned char *magick, |
| 888 | % const size_t length) |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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 | */ |
cristy | 565117b | 2015-06-21 19:59:07 +0000 | [diff] [blame] | 898 | static MagickBooleanType IsSIXEL(const unsigned char *magick, |
| 899 | const size_t length) |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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 | % |
cristy | 565117b | 2015-06-21 19:59:07 +0000 | [diff] [blame] | 935 | % Image *ReadSIXELImage(const ImageInfo *image_info, |
| 936 | % ExceptionInfo *exception) |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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 | */ |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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 | |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 959 | register ssize_t |
| 960 | x; |
| 961 | |
dirk | a1e4ab8 | 2014-10-07 05:06:49 +0000 | [diff] [blame] | 962 | register Quantum |
| 963 | *q; |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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); |
cristy | e1c94d9 | 2015-06-28 12:16:33 +0000 | [diff] [blame] | 981 | assert(image_info->signature == MagickCoreSignature); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 982 | if (image_info->debug != MagickFalse) |
| 983 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s", |
| 984 | image_info->filename); |
| 985 | assert(exception != (ExceptionInfo *) NULL); |
cristy | e1c94d9 | 2015-06-28 12:16:33 +0000 | [diff] [blame] | 986 | assert(exception->signature == MagickCoreSignature); |
dirk | a1e4ab8 | 2014-10-07 05:06:49 +0000 | [diff] [blame] | 987 | image=AcquireImage(image_info,exception); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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 | */ |
cristy | 151b66d | 2015-04-15 10:50:31 +0000 | [diff] [blame] | 997 | length=MagickPathExtent; |
cristy | 565117b | 2015-06-21 19:59:07 +0000 | [diff] [blame] | 998 | sixel_buffer=(char *) AcquireQuantumMemory((size_t) length, |
| 999 | sizeof(*sixel_buffer)); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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); |
cristy | 151b66d | 2015-04-15 10:50:31 +0000 | [diff] [blame] | 1009 | if ((size_t) (p-sixel_buffer+MagickPathExtent) < length) |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1010 | continue; |
| 1011 | length<<=1; |
cristy | 565117b | 2015-06-21 19:59:07 +0000 | [diff] [blame] | 1012 | sixel_buffer=(char *) ResizeQuantumMemory(sixel_buffer,length+ |
| 1013 | MagickPathExtent,sizeof(*sixel_buffer)); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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"); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1020 | /* |
| 1021 | Decode SIXEL |
| 1022 | */ |
cristy | 565117b | 2015-06-21 19:59:07 +0000 | [diff] [blame] | 1023 | if (sixel_decode((unsigned char *) sixel_buffer,&sixel_pixels,&image->columns,&image->rows,&sixel_palette,&image->colors) == MagickFalse) |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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; |
cristy | acabb84 | 2014-12-14 23:36:33 +0000 | [diff] [blame] | 1031 | status=SetImageExtent(image,image->columns,image->rows,exception); |
| 1032 | if (status == MagickFalse) |
| 1033 | return(DestroyImageList(image)); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1034 | |
dirk | a1e4ab8 | 2014-10-07 05:06:49 +0000 | [diff] [blame] | 1035 | if (AcquireImageColormap(image,image->colors, exception) == MagickFalse) |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1036 | { |
| 1037 | sixel_pixels=(unsigned char *) RelinquishMagickMemory(sixel_pixels); |
| 1038 | sixel_palette=(unsigned char *) RelinquishMagickMemory(sixel_palette); |
| 1039 | ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed"); |
| 1040 | } |
cristy | 53c56cb | 2014-10-26 13:11:53 +0000 | [diff] [blame] | 1041 | for (i = 0; i < (ssize_t) image->colors; ++i) { |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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 | { |
dirk | a1e4ab8 | 2014-10-07 05:06:49 +0000 | [diff] [blame] | 1055 | q=QueueAuthenticPixels(image,0,y,image->columns,1,exception); |
| 1056 | if (q == (Quantum *) NULL) |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1057 | break; |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1058 | for (x=0; x < (ssize_t) image->columns; x++) |
| 1059 | { |
| 1060 | j=(ssize_t) sixel_pixels[y * image->columns + x]; |
dirk | a1e4ab8 | 2014-10-07 05:06:49 +0000 | [diff] [blame] | 1061 | SetPixelIndex(image,j,q); |
| 1062 | q+=GetPixelChannels(image); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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 | } |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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 | */ |
| 1106 | ModuleExport size_t RegisterSIXELImage(void) |
| 1107 | { |
| 1108 | MagickInfo |
| 1109 | *entry; |
| 1110 | |
dirk | 06b627a | 2015-04-06 18:59:17 +0000 | [diff] [blame] | 1111 | entry=AcquireMagickInfo("SIXEL","SIXEL","DEC SIXEL Graphics Format"); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1112 | entry->decoder=(DecodeImageHandler *) ReadSIXELImage; |
| 1113 | entry->encoder=(EncodeImageHandler *) WriteSIXELImage; |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1114 | entry->magick=(IsImageFormatHandler *) IsSIXEL; |
dirk | 08e9a11 | 2015-02-22 01:51:41 +0000 | [diff] [blame] | 1115 | entry->flags^=CoderAdjoinFlag; |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1116 | (void) RegisterMagickInfo(entry); |
dirk | 06b627a | 2015-04-06 18:59:17 +0000 | [diff] [blame] | 1117 | entry=AcquireMagickInfo("SIX","SIX","DEC SIXEL Graphics Format"); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1118 | entry->decoder=(DecodeImageHandler *) ReadSIXELImage; |
| 1119 | entry->encoder=(EncodeImageHandler *) WriteSIXELImage; |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1120 | entry->magick=(IsImageFormatHandler *) IsSIXEL; |
dirk | 08e9a11 | 2015-02-22 01:51:41 +0000 | [diff] [blame] | 1121 | entry->flags^=CoderAdjoinFlag; |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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 | */ |
| 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 | */ |
cristy | 565117b | 2015-06-21 19:59:07 +0000 | [diff] [blame] | 1178 | static MagickBooleanType WriteSIXELImage(const ImageInfo *image_info, |
| 1179 | Image *image,ExceptionInfo *exception) |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1180 | { |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1181 | MagickBooleanType |
| 1182 | status; |
| 1183 | |
dirk | a1e4ab8 | 2014-10-07 05:06:49 +0000 | [diff] [blame] | 1184 | register const Quantum |
| 1185 | *q; |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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 |
cristy | 565117b | 2015-06-21 19:59:07 +0000 | [diff] [blame] | 1199 | sixel_palette[256*3], |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1200 | *sixel_pixels; |
| 1201 | |
| 1202 | /* |
| 1203 | Open output image file. |
| 1204 | */ |
| 1205 | assert(image_info != (const ImageInfo *) NULL); |
cristy | e1c94d9 | 2015-06-28 12:16:33 +0000 | [diff] [blame] | 1206 | assert(image_info->signature == MagickCoreSignature); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1207 | assert(image != (Image *) NULL); |
cristy | e1c94d9 | 2015-06-28 12:16:33 +0000 | [diff] [blame] | 1208 | assert(image->signature == MagickCoreSignature); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1209 | if (image->debug != MagickFalse) |
| 1210 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1211 | status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception); |
| 1212 | if (status == MagickFalse) |
| 1213 | return(status); |
| 1214 | if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse) |
dirk | a1e4ab8 | 2014-10-07 05:06:49 +0000 | [diff] [blame] | 1215 | (void) TransformImageColorspace(image,sRGBColorspace,exception); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1216 | opacity=(-1); |
cristy | 17f11b0 | 2014-12-20 19:37:04 +0000 | [diff] [blame] | 1217 | if (image->alpha_trait == UndefinedPixelTrait) |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1218 | { |
| 1219 | if ((image->storage_class == DirectClass) || (image->colors > 256)) |
dirk | a1e4ab8 | 2014-10-07 05:06:49 +0000 | [diff] [blame] | 1220 | (void) SetImageType(image,PaletteType,exception); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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)) |
cristy | def23e5 | 2015-01-22 11:52:01 +0000 | [diff] [blame] | 1232 | (void) SetImageType(image,PaletteBilevelAlphaType,exception); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1233 | for (i=0; i < (ssize_t) image->colors; i++) |
dirk | a1e4ab8 | 2014-10-07 05:06:49 +0000 | [diff] [blame] | 1234 | if (image->colormap[i].alpha != OpaqueAlpha) |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1235 | { |
| 1236 | if (opacity < 0) |
| 1237 | { |
| 1238 | opacity=i; |
| 1239 | continue; |
| 1240 | } |
dirk | a1e4ab8 | 2014-10-07 05:06:49 +0000 | [diff] [blame] | 1241 | alpha=image->colormap[i].alpha; |
| 1242 | beta=image->colormap[opacity].alpha; |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1243 | if (alpha < beta) |
| 1244 | opacity=i; |
| 1245 | } |
| 1246 | if (opacity == -1) |
| 1247 | { |
cristy | def23e5 | 2015-01-22 11:52:01 +0000 | [diff] [blame] | 1248 | (void) SetImageType(image,PaletteBilevelAlphaType,exception); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1249 | for (i=0; i < (ssize_t) image->colors; i++) |
dirk | a1e4ab8 | 2014-10-07 05:06:49 +0000 | [diff] [blame] | 1250 | if (image->colormap[i].alpha != OpaqueAlpha) |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1251 | { |
| 1252 | if (opacity < 0) |
| 1253 | { |
| 1254 | opacity=i; |
| 1255 | continue; |
| 1256 | } |
dirk | a1e4ab8 | 2014-10-07 05:06:49 +0000 | [diff] [blame] | 1257 | alpha=image->colormap[i].alpha; |
| 1258 | beta=image->colormap[opacity].alpha; |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 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 | { |
cristy | 565117b | 2015-06-21 19:59:07 +0000 | [diff] [blame] | 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); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1278 | } |
| 1279 | |
| 1280 | /* |
| 1281 | Define SIXEL pixels. |
| 1282 | */ |
| 1283 | output = sixel_output_create(image); |
cristy | 565117b | 2015-06-21 19:59:07 +0000 | [diff] [blame] | 1284 | sixel_pixels=(unsigned char *) AcquireQuantumMemory(image->columns, |
| 1285 | image->rows*sizeof(*sixel_pixels)); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1286 | for (y=0; y < (ssize_t) image->rows; y++) |
| 1287 | { |
dirk | a1e4ab8 | 2014-10-07 05:06:49 +0000 | [diff] [blame] | 1288 | q=GetVirtualPixels(image,0,y,image->columns,1,exception); |
dirk | ccad00b | 2015-06-21 16:58:22 +0000 | [diff] [blame] | 1289 | if (q == (Quantum *) NULL) |
dirk | 95f893f | 2015-06-22 19:37:37 +0000 | [diff] [blame] | 1290 | break; |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1291 | for (x=0; x < (ssize_t) image->columns; x++) |
dirk | a1e4ab8 | 2014-10-07 05:06:49 +0000 | [diff] [blame] | 1292 | { |
cristy | 565117b | 2015-06-21 19:59:07 +0000 | [diff] [blame] | 1293 | sixel_pixels[y*image->columns+x]= ((ssize_t) GetPixelIndex(image,q)); |
dirk | a1e4ab8 | 2014-10-07 05:06:49 +0000 | [diff] [blame] | 1294 | q+=GetPixelChannels(image); |
| 1295 | } |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1296 | } |
cristy | 565117b | 2015-06-21 19:59:07 +0000 | [diff] [blame] | 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); |
cristy | 77f78f7 | 2014-10-04 13:29:40 +0000 | [diff] [blame] | 1301 | (void) CloseBlob(image); |
| 1302 | return(status); |
| 1303 | } |