├── .gitignore ├── LICENSE ├── README.markdown ├── image.h ├── libnsbmp.c ├── libnsbmp.h ├── libnsgif.c ├── libnsgif.h ├── loader.h ├── lodepng.c ├── lodepng.h ├── makefile ├── scripts ├── spdf ├── surl └── sviewer ├── sdump.c ├── sdump.h ├── sixel.h ├── static ├── libsixel │ ├── config.h │ ├── dither.c │ ├── dither.h │ ├── fromsixel.c │ ├── image.c │ ├── image.h │ ├── output.c │ ├── output.h │ ├── quant.c │ ├── quant.h │ ├── sixel.h │ └── tosixel.c ├── makefile └── sdump.c ├── stb_image.h ├── util.h └── yaimg-sixel ├── makefile ├── parsearg.h ├── yaimg-sixel.c └── yaimg-sixel.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 haru 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # sdump 2 | 3 | sixel image dumper 4 | 5 | this program is a variant of [idump](https://github.com/uobikiemukot/idump) 6 | 7 | ## install 8 | 9 | you need to install following library before build 10 | 11 | - libsixel (https://github.com/saitoha/libsixel) 12 | - libpng 13 | - libjpeg or libjpeg-turbo 14 | 15 | then just type "make" 16 | 17 | ## install (static) 18 | 19 | static link version (located in ./static) depends no extra (shared) library 20 | 21 | just type "cd static; make" 22 | 23 | - libsixel (included in ./static/libsixel) 24 | - stb_image.h (for jpeg) 25 | - lodepng.h, lodepng.c (for png) 26 | 27 | ## usage 28 | 29 | $ sdump [-h] [-f] [-r angle] image 30 | 31 | $ cat image | sdump 32 | 33 | $ wget -q -O - url | sdump 34 | 35 | ## options 36 | 37 | - -h: show help 38 | - -f: fit image to display size (reduce only) 39 | - -r: rotate image (90 or 180 or 270) 40 | 41 | ## supported image format 42 | 43 | - jpeg by libjpeg 44 | - png by libpng 45 | - gif by libnsgif 46 | - bmp by libnsbmp 47 | - pnm by sdump 48 | 49 | ## wrapper scripts 50 | 51 | - surl: equal "wget -q -O - url | sdump" (depends wget) 52 | - sviewer: take multiple files as arguments 53 | - spdf: pdf viewer (depends mupdf >= 1.5) 54 | 55 | ## license 56 | 57 | The MIT License (MIT) 58 | 59 | Copyright (c) 2014 haru 60 | -------------------------------------------------------------------------------- /image.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE for licence details. */ 2 | /* this header file depends loader.h */ 3 | 4 | enum { 5 | MULTIPLER = 1024, /* value for avoid to use float */ 6 | }; 7 | 8 | /* inline functions: 9 | never access member of struct image directly */ 10 | static inline int get_frame_count(struct image *img) 11 | { 12 | return img->frame_count; 13 | } 14 | 15 | static inline uint8_t *get_current_frame(struct image *img) 16 | { 17 | return img->data[img->current_frame]; 18 | } 19 | 20 | static inline int get_current_delay(struct image *img) 21 | { 22 | return img->delay[img->current_frame]; 23 | } 24 | 25 | static inline void increment_frame(struct image *img) 26 | { 27 | img->current_frame = (img->current_frame + 1) % img->frame_count; 28 | } 29 | 30 | static inline int get_image_width(struct image *img) 31 | { 32 | return img->width; 33 | } 34 | 35 | static inline int get_image_height(struct image *img) 36 | { 37 | return img->height; 38 | } 39 | 40 | static inline int get_image_channel(struct image *img) 41 | { 42 | return img->channel; 43 | } 44 | 45 | static inline void get_rgb(struct image *img, uint8_t *data, int x, int y, uint8_t *r, uint8_t *g, uint8_t *b) 46 | { 47 | uint8_t *ptr; 48 | 49 | ptr = data + img->channel * (y * img->width + x); 50 | 51 | if (img->channel <= 2) { /* grayscale (+ alpha) */ 52 | *r = *g = *b = *ptr; 53 | } else { /* rgb (+ alpha) */ 54 | *r = *ptr; *g = *(ptr + 1); *b = *(ptr + 2); 55 | } 56 | } 57 | 58 | static inline void get_average(struct image *img, uint8_t *data, int x_from, int y_from, int x_to, int y_to, uint8_t pixel[]) 59 | { 60 | int cell_num; 61 | uint8_t r, g, b; 62 | uint16_t rsum, gsum, bsum; 63 | 64 | rsum = gsum = bsum = 0; 65 | for (int y = y_from; y < y_to; y++) { 66 | for (int x = x_from; x < x_to; x++) { 67 | get_rgb(img, data, x, y, &r, &g, &b); 68 | rsum += r; gsum += g; bsum += b; 69 | } 70 | } 71 | 72 | cell_num = (y_to - y_from) * (x_to - x_from); 73 | if (cell_num > 1) { 74 | rsum /= cell_num; gsum /= cell_num; bsum /= cell_num; 75 | } 76 | 77 | if (img->channel <= 2) 78 | *pixel++ = rsum; 79 | else { 80 | *pixel++ = rsum; *pixel++ = gsum; *pixel++ = bsum; 81 | } 82 | 83 | if (img->alpha) 84 | *pixel = 0; 85 | } 86 | 87 | /* some image proccessing functions: 88 | never use *_single functions directly */ 89 | uint8_t *rotate_image_single(struct image *img, uint8_t *data, int angle) 90 | { 91 | int x1, x2, y1, y2, r, dst_width, dst_height; 92 | uint8_t *rotated_data; 93 | long offset_dst, offset_src; 94 | 95 | static const int cos[3] = {0, -1, 0}; 96 | static const int sin[3] = {1, 0, -1}; 97 | 98 | int shift[3][3] = { 99 | /* x_shift, y_shift, sign */ 100 | {img->height - 1, 0 , -1}, 101 | {img->width - 1, img->height - 1, 1}, 102 | { 0, img->width - 1, -1} 103 | }; 104 | 105 | if (angle != 90 && angle != 180 && angle != 270) 106 | return NULL; 107 | /* r == 0: clockwise : (angle 90) */ 108 | /* r == 1: upside down : (angle 180) */ 109 | /* r == 2: counter clockwise: (angle 270) */ 110 | r = angle / 90 - 1; 111 | 112 | if (angle == 90 || angle == 270) { 113 | dst_width = img->height; 114 | dst_height = img->width; 115 | } else { 116 | dst_width = img->width; 117 | dst_height = img->height; 118 | } 119 | 120 | if ((rotated_data = (uint8_t *) ecalloc(dst_width * dst_height, img->channel)) == NULL) 121 | return NULL; 122 | 123 | logging(DEBUG, "rotated image: %dx%d size:%d\n", 124 | dst_width, dst_height, dst_width * dst_height * img->channel); 125 | 126 | for (y2 = 0; y2 < dst_height; y2++) { 127 | for (x2 = 0; x2 < dst_width; x2++) { 128 | x1 = ((x2 - shift[r][0]) * cos[r] - (y2 - shift[r][1]) * sin[r]) * shift[r][2]; 129 | y1 = ((x2 - shift[r][0]) * sin[r] + (y2 - shift[r][1]) * cos[r]) * shift[r][2]; 130 | offset_src = img->channel * (y1 * img->width + x1); 131 | offset_dst = img->channel * (y2 * dst_width + x2); 132 | memcpy(rotated_data + offset_dst, data + offset_src, img->channel); 133 | } 134 | } 135 | free(data); 136 | 137 | img->width = dst_width; 138 | img->height = dst_height; 139 | 140 | return rotated_data; 141 | } 142 | 143 | void rotate_image(struct image *img, int angle, bool rotate_all) 144 | { 145 | uint8_t *rotated_data; 146 | 147 | if (rotate_all) { 148 | for (int i = 0; i < img->frame_count; i++) 149 | if ((rotated_data = rotate_image_single(img, img->data[i], angle)) != NULL) 150 | img->data[i] = rotated_data; 151 | } else { 152 | if ((rotated_data = rotate_image_single(img, img->data[img->current_frame], angle)) != NULL) 153 | img->data[img->current_frame] = rotated_data; 154 | } 155 | } 156 | 157 | uint8_t *resize_image_single(struct image *img, uint8_t *data, int disp_width, int disp_height) 158 | { 159 | /* TODO: support enlarge */ 160 | int width_rate, height_rate, resize_rate; 161 | int dst_width, dst_height, y_from, x_from, y_to, x_to; 162 | uint8_t *resized_data, pixel[img->channel]; 163 | long offset_dst; 164 | 165 | width_rate = MULTIPLER * disp_width / img->width; 166 | height_rate = MULTIPLER * disp_height / img->height; 167 | resize_rate = (width_rate < height_rate) ? width_rate: height_rate; 168 | 169 | logging(DEBUG, "width_rate:%.2d height_rate:%.2d resize_rate:%.2d\n", 170 | width_rate, height_rate, resize_rate); 171 | 172 | /* only support shrink */ 173 | if ((resize_rate / MULTIPLER) >= 1) 174 | return NULL; 175 | 176 | /* FIXME: let the same num (img->width == fb->width), if it causes SEGV, remove "+ 1" */ 177 | dst_width = resize_rate * img->width / MULTIPLER + 1; 178 | dst_height = resize_rate * img->height / MULTIPLER; 179 | 180 | if ((resized_data = (uint8_t *) ecalloc(dst_width * dst_height, img->channel)) == NULL) 181 | return NULL; 182 | 183 | logging(DEBUG, "resized image: %dx%d size:%d\n", 184 | dst_width, dst_height, dst_width * dst_height * img->channel); 185 | 186 | for (int y = 0; y < dst_height; y++) { 187 | y_from = MULTIPLER * y / resize_rate; 188 | y_to = MULTIPLER * (y + 1) / resize_rate; 189 | for (int x = 0; x < dst_width; x++) { 190 | x_from = MULTIPLER * x / resize_rate; 191 | x_to = MULTIPLER * (x + 1) / resize_rate; 192 | get_average(img, data, x_from, y_from, x_to, y_to, pixel); 193 | offset_dst = img->channel * (y * dst_width + x); 194 | memcpy(resized_data + offset_dst, pixel, img->channel); 195 | } 196 | } 197 | free(data); 198 | 199 | img->width = dst_width; 200 | img->height = dst_height; 201 | 202 | return resized_data; 203 | } 204 | 205 | void resize_image(struct image *img, int disp_width, int disp_height, bool resize_all) 206 | { 207 | uint8_t *resized_data; 208 | 209 | if (resize_all) { 210 | for (int i = 0; i < img->frame_count; i++) 211 | if ((resized_data = resize_image_single(img, img->data[i], disp_width, disp_height)) != NULL) 212 | img->data[i] = resized_data; 213 | } else { 214 | if ((resized_data = resize_image_single(img, img->data[img->current_frame], disp_width, disp_height)) != NULL) 215 | img->data[img->current_frame] = resized_data; 216 | } 217 | } 218 | 219 | uint8_t *normalize_bpp_single(struct image *img, uint8_t *data, int bytes_per_pixel) 220 | { 221 | uint8_t *normalized_data, *src, *dst, r, g, b; 222 | 223 | if ((normalized_data = (uint8_t *) 224 | ecalloc(img->width * img->height, bytes_per_pixel)) == NULL) 225 | return NULL; 226 | 227 | if (img->channel <= 2) { /* grayscale (+ alpha) */ 228 | for (int y = 0; y < img->height; y++) { 229 | for (int x = 0; x < img->width; x++) { 230 | src = data + img->channel * (y * img->width + x); 231 | dst = normalized_data + bytes_per_pixel * (y * img->width + x); 232 | *dst = *src; *(dst + 1) = *src; *(dst + 2) = *src; 233 | } 234 | } 235 | } else { /* rgb (+ alpha) */ 236 | for (int y = 0; y < img->height; y++) { 237 | for (int x = 0; x < img->width; x++) { 238 | get_rgb(img, data, x, y, &r, &g, &b); 239 | dst = normalized_data + bytes_per_pixel * (y * img->width + x); 240 | *dst = r; *(dst + 1) = g; *(dst + 2) = b; 241 | } 242 | } 243 | } 244 | free(data); 245 | 246 | return normalized_data; 247 | } 248 | 249 | void normalize_bpp(struct image *img, int bytes_per_pixel, bool normalize_all) 250 | { 251 | uint8_t *normalized_data; 252 | 253 | /* XXX: now only support bytes_per_pixel == 3 */ 254 | if (bytes_per_pixel != 3) 255 | return; 256 | 257 | if (normalize_all) { 258 | for (int i = 0; i < img->frame_count; i++) 259 | if ((normalized_data = normalize_bpp_single(img, img->data[i], bytes_per_pixel)) != NULL) 260 | img->data[i] = normalized_data; 261 | } else { 262 | if ((normalized_data = normalize_bpp_single(img, img->data[img->current_frame], bytes_per_pixel)) != NULL) 263 | img->data[img->current_frame] = normalized_data; 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /libnsbmp.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2006 Richard Wilson 3 | * Copyright 2008 Sean Fox 4 | * 5 | * This file is part of NetSurf's libnsbmp, http://www.netsurf-browser.org/ 6 | * Licenced under the MIT License, 7 | * http://www.opensource.org/licenses/mit-license.php 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "libnsbmp.h" 18 | 19 | /* The functions provided by this file allow for the decoding of 20 | Microsoft's BMP and ICO image file formats. 21 | 22 | READING BMP FILES 23 | ================= 24 | 25 | To begin decoding a BMP, the caller should initialise a 26 | 'bmp_bitmap_callback_vt' structure with the appropriate values necessary 27 | to handle bitmap images. Next, a 'bmp_image' structure should be 28 | initialised by calling bmp_create(). This structure should then be 29 | passed to bmp_analyse() along with the BMP data to process and the size 30 | of this data. 31 | 32 | Once the analysis has begun, the decoder completes the width and height 33 | variables. 34 | 35 | To decode the image, the caller must use bmp_decode() which selects the 36 | proper decoding method based on the BMP info header and assigns the 37 | decoded bitmap image to the 'bitmap' member of the 'bmp_image' 38 | structure. The bitmap image is stored with 4 bytes-per-pixel in RGBA 39 | format. 40 | 41 | It should be noted that bmp_finalise() should always be called, even if 42 | the image was never decoded. It is also the responsibility of the 43 | caller to free 'bmp_data'. 44 | 45 | READING ICO FILES 46 | ================= 47 | 48 | To begin decoding an ICO, the caller should initialise a 49 | 'bmp_bitmap_callback_vt' structure with the appropriate values necessary 50 | to handle bitmap images. Next, an 'ico_collection' structure should be 51 | initialised by calling ico_create(). This structure should then be 52 | passed to ico_analyse() along with the ICO data to process and the size 53 | of this data. 54 | 55 | Once the analysis has begun, the decoder completes the width and height 56 | variables. Because ICO collections contain multiple bitmap images, the 57 | width and height will contain the values of the largest available image. 58 | 59 | The caller then obtains a BMP from the ICO collection by calling 60 | ico_find() with the requested width and height. 61 | 62 | To decode the image, the caller must use bmp_decode() which selects the 63 | proper decoding method based on the BMP info header and assigns the 64 | decoded bitmap image to the 'bitmap' member of the 'bmp_image' 65 | structure. The bitmap image is stored with 4 bytes-per-pixel in RGBA 66 | format. 67 | 68 | It should be noted that ico_finalise() should always be called, even if 69 | no images were decoded. Because ico_finalise() calls bmp_finalise() for 70 | each bitmap within the collection, the caller is not required to perform 71 | this function. However, it is the responsibility of the caller to free 72 | 'ico_data'. 73 | 74 | [dynis] - Tue 1st July 2008 75 | */ 76 | 77 | /* squashes unused variable compiler warnings */ 78 | #define UNUSED(x) ((x)=(x)) 79 | 80 | /* BMP flags */ 81 | #define BMP_FILE_HEADER_SIZE 14 82 | #define ICO_FILE_HEADER_SIZE 6 83 | #define ICO_DIR_ENTRY_SIZE 16 84 | 85 | static inline int8_t read_int8(uint8_t *data, unsigned int o) { 86 | return (int8_t) data[o]; 87 | } 88 | 89 | static inline uint8_t read_uint8(uint8_t *data, unsigned int o) { 90 | return (uint8_t) data[o]; 91 | } 92 | 93 | static inline int16_t read_int16(uint8_t *data, unsigned int o) { 94 | return (int16_t) (data[o] | (data[o+1] << 8)); 95 | } 96 | 97 | static inline uint16_t read_uint16(uint8_t *data, unsigned int o) { 98 | return (uint16_t) (data[o] | (data[o+1] << 8)); 99 | } 100 | 101 | static inline int32_t read_int32(uint8_t *data, unsigned int o) { 102 | return (int32_t) (data[o] | (data[o+1] << 8) | (data[o+2] << 16) | (data[o+3] << 24)); 103 | } 104 | 105 | static inline uint32_t read_uint32(uint8_t *data, unsigned int o) { 106 | return (uint32_t) (data[o] | (data[o+1] << 8) | (data[o+2] << 16) | (data[o+3] << 24)); 107 | } 108 | 109 | static bmp_result next_ico_image(ico_collection *ico, ico_image *image); 110 | static bmp_result bmp_analyse_header(bmp_image *bmp, unsigned char *data); 111 | static bmp_result bmp_decode_rgb24(bmp_image *bmp, uint8_t **start, int bytes); 112 | static bmp_result bmp_decode_rgb16(bmp_image *bmp, uint8_t **start, int bytes); 113 | static bmp_result bmp_decode_rgb(bmp_image *bmp, uint8_t **start, int bytes); 114 | static bmp_result bmp_decode_mask(bmp_image *bmp, uint8_t *data, int bytes); 115 | static bmp_result bmp_decode_rle(bmp_image *bmp, uint8_t *data, int bytes, int size); 116 | 117 | 118 | 119 | /** Initialises necessary bmp_image members. 120 | */ 121 | void bmp_create(bmp_image *bmp, bmp_bitmap_callback_vt *bitmap_callbacks) { 122 | memset(bmp, 0, sizeof(bmp_image)); 123 | bmp->bitmap_callbacks = *bitmap_callbacks; 124 | } 125 | 126 | 127 | /** Initialises necessary ico_collection members. 128 | */ 129 | void ico_collection_create(ico_collection *ico, bmp_bitmap_callback_vt *bitmap_callbacks) { 130 | memset(ico, 0, sizeof(ico_collection)); 131 | ico->bitmap_callbacks = *bitmap_callbacks; 132 | } 133 | 134 | 135 | /** 136 | * Analyse a BMP prior to decoding. 137 | * 138 | * This function will scan the data provided and perform simple checks to 139 | * ensure the data is a valid BMP. 140 | * 141 | * This function must be called before bmp_decode() and sets up all the 142 | * relevant values in the bmp structure. 143 | * 144 | * \param bmp the BMP image to analyse 145 | * \return BMP_OK on success 146 | */ 147 | bmp_result bmp_analyse(bmp_image *bmp, size_t size, unsigned char *cdata) { 148 | uint8_t *data = (uint8_t *)cdata; 149 | 150 | /* ensure we aren't already initialised */ 151 | if (bmp->bitmap) 152 | return BMP_OK; 153 | 154 | /* initialize values */ 155 | bmp->buffer_size = size; 156 | bmp->bmp_data = data; 157 | 158 | /* standard 14-byte BMP file header is: 159 | * 160 | * +0 UINT16 File Type ('BM') 161 | * +2 UINT32 Size of File (in bytes) 162 | * +6 INT16 Reserved Field (1) 163 | * +8 INT16 Reserved Field (2) 164 | * +10 UINT32 Starting Position of Image Data (offset in bytes) 165 | */ 166 | if (bmp->buffer_size < BMP_FILE_HEADER_SIZE) 167 | return BMP_INSUFFICIENT_DATA; 168 | if ((data[0] != (uint8_t)'B') || (data[1] != (uint8_t)'M')) 169 | return BMP_DATA_ERROR; 170 | bmp->bitmap_offset = read_uint32(data, 10); 171 | data += BMP_FILE_HEADER_SIZE; 172 | 173 | /* boundary checking */ 174 | if (bmp->bitmap_offset >= size) 175 | return BMP_INSUFFICIENT_DATA; 176 | 177 | /* decode the BMP header */ 178 | return bmp_analyse_header(bmp, data); 179 | } 180 | 181 | 182 | /** 183 | * Analyse an ICO prior to decoding. 184 | * 185 | * This function will scan the data provided and perform simple checks to 186 | * ensure the data is a valid ICO. 187 | * 188 | * This function must be called before ico_find(). 189 | * 190 | * \param ico the ICO image to analyse 191 | * \return BMP_OK on success 192 | */ 193 | bmp_result ico_analyse(ico_collection *ico, size_t size, uint8_t *data) { 194 | uint16_t count, i; 195 | bmp_result result; 196 | int area, max_area = 0; 197 | 198 | /* ensure we aren't already initialised */ 199 | if (ico->first) 200 | return BMP_OK; 201 | 202 | /* initialize values */ 203 | ico->buffer_size = size; 204 | ico->ico_data = data; 205 | 206 | /* 6-byte ICO file header is: 207 | * 208 | * +0 INT16 Reserved (should be 0) 209 | * +2 UINT16 Type (1 for ICO, 2 for CUR) 210 | * +4 UINT16 Number of BMPs to follow 211 | */ 212 | if (ico->buffer_size < ICO_FILE_HEADER_SIZE) 213 | return BMP_INSUFFICIENT_DATA; 214 | // if (read_int16(data, 2) != 0x0000) 215 | // return BMP_DATA_ERROR; 216 | if (read_uint16(data, 2) != 0x0001) 217 | return BMP_DATA_ERROR; 218 | count = read_uint16(data, 4); 219 | if (count == 0) 220 | return BMP_DATA_ERROR; 221 | data += ICO_FILE_HEADER_SIZE; 222 | 223 | /* check if we have enough data for the directory */ 224 | if (ico->buffer_size < (uint32_t)(ICO_FILE_HEADER_SIZE + (ICO_DIR_ENTRY_SIZE * count))) 225 | return BMP_INSUFFICIENT_DATA; 226 | 227 | /* Decode the BMP files. 228 | * 229 | * 16-byte ICO directory entry is: 230 | * 231 | * +0 UINT8 Width (0 for 256 pixels) 232 | * +1 UINT8 Height (0 for 256 pixels) 233 | * +2 UINT8 Colour count (0 if more than 256 colours) 234 | * +3 INT8 Reserved (should be 0, but may not be) 235 | * +4 UINT16 Colour Planes (should be 0 or 1) 236 | * +6 UINT16 Bits Per Pixel 237 | * +8 UINT32 Size of BMP info header + bitmap data in bytes 238 | * +12 UINT32 Offset (points to the BMP info header, not the bitmap data) 239 | */ 240 | for (i = 0; i < count; i++) { 241 | ico_image *image; 242 | image = calloc(1, sizeof(ico_image)); 243 | if (!image) 244 | return BMP_INSUFFICIENT_MEMORY; 245 | result = next_ico_image(ico, image); 246 | if (result != BMP_OK) 247 | return result; 248 | image->bmp.width = read_uint8(data, 0); 249 | if (image->bmp.width == 0) 250 | image->bmp.width = 256; 251 | image->bmp.height = read_uint8(data, 1); 252 | if (image->bmp.height == 0) 253 | image->bmp.height = 256; 254 | image->bmp.buffer_size = read_uint32(data, 8); 255 | image->bmp.bmp_data = ico->ico_data + read_uint32(data, 12); 256 | image->bmp.ico = true; 257 | data += ICO_DIR_ENTRY_SIZE; 258 | 259 | /* Ensure that the bitmap data resides in the buffer */ 260 | if (image->bmp.bmp_data - ico->ico_data >= 0 && 261 | (uint32_t)(image->bmp.bmp_data - 262 | ico->ico_data) >= ico->buffer_size) 263 | return BMP_DATA_ERROR; 264 | 265 | /* Ensure that we have sufficient data to read the bitmap */ 266 | if (image->bmp.buffer_size - ICO_DIR_ENTRY_SIZE >= 267 | ico->buffer_size - (ico->ico_data - data)) 268 | return BMP_INSUFFICIENT_DATA; 269 | 270 | result = bmp_analyse_header(&image->bmp, image->bmp.bmp_data); 271 | if (result != BMP_OK) 272 | return result; 273 | 274 | /* adjust the size based on the images available */ 275 | area = image->bmp.width * image->bmp.height; 276 | if (area > max_area) { 277 | ico->width = image->bmp.width; 278 | ico->height = image->bmp.height; 279 | max_area = area; 280 | } 281 | } 282 | return BMP_OK; 283 | } 284 | 285 | 286 | /** 287 | * Allocates memory for the next BMP in an ICO collection 288 | * 289 | * Sets proper structure values 290 | * 291 | * \param ico the ICO collection to add the image to 292 | * \param image a pointer to the ICO image to be initialised 293 | */ 294 | static bmp_result next_ico_image(ico_collection *ico, ico_image *image) { 295 | bmp_create(&image->bmp, &ico->bitmap_callbacks); 296 | image->next = ico->first; 297 | ico->first = image; 298 | return BMP_OK; 299 | } 300 | 301 | 302 | static bmp_result bmp_analyse_header(bmp_image *bmp, uint8_t *data) { 303 | uint32_t header_size; 304 | uint32_t i; 305 | uint8_t j; 306 | int32_t width, height; 307 | uint8_t palette_size; 308 | unsigned int flags = 0; 309 | 310 | /* a variety of different bitmap headers can follow, depending 311 | * on the BMP variant. A full description of the various headers 312 | * can be found at 313 | * http://msdn.microsoft.com/en-us/library/ms532301(VS.85).aspx 314 | */ 315 | header_size = read_uint32(data, 0); 316 | if (bmp->buffer_size < (14 + header_size)) 317 | return BMP_INSUFFICIENT_DATA; 318 | if (header_size == 12) { 319 | /* the following header is for os/2 and windows 2.x and consists of: 320 | * 321 | * +0 UINT32 size of this header (in bytes) 322 | * +4 INT16 image width (in pixels) 323 | * +6 INT16 image height (in pixels) 324 | * +8 UINT16 number of colour planes (always 1) 325 | * +10 UINT16 number of bits per pixel 326 | */ 327 | width = read_int16(data, 4); 328 | height = read_int16(data, 6); 329 | if ((width <= 0) || (height == 0)) 330 | return BMP_DATA_ERROR; 331 | if (height < 0) { 332 | bmp->reversed = true; 333 | height = -height; 334 | } 335 | /* ICOs only support 256*256 resolutions 336 | * In the case of the ICO header, the height is actually the added 337 | * height of XOR-Bitmap and AND-Bitmap (double the visible height) 338 | * Technically we could remove this check and ICOs with bitmaps 339 | * of any size could be processed; this is to conform to the spec. 340 | */ 341 | if (bmp->ico) { 342 | if ((width > 256) || (height > 512)) { 343 | return BMP_DATA_ERROR; 344 | } else { 345 | bmp->width = width; 346 | bmp->height = height / 2; 347 | } 348 | } else { 349 | bmp->width = width; 350 | bmp->height = height; 351 | } 352 | if (read_uint16(data, 8) != 1) 353 | return BMP_DATA_ERROR; 354 | bmp->bpp = read_uint16(data, 10); 355 | /** 356 | * The bpp value should be in the range 1-32, but the only 357 | * values considered legal are: 358 | * RGB ENCODING: 1, 4, 8, 16, 24 and 32 359 | */ 360 | if ((bmp->bpp != 1) && (bmp->bpp != 4) && 361 | (bmp->bpp != 8) && 362 | (bmp->bpp != 16) && 363 | (bmp->bpp != 24) && 364 | (bmp->bpp != 32)) 365 | return BMP_DATA_ERROR; 366 | bmp->colours = (1 << bmp->bpp); 367 | palette_size = 3; 368 | } else if (header_size < 40) { 369 | return BMP_DATA_ERROR; 370 | } else { 371 | /* the following header is for windows 3.x and onwards. it is a 372 | * minimum of 40 bytes and (as of Windows 95) a maximum of 108 bytes. 373 | * 374 | * +0 UINT32 size of this header (in bytes) 375 | * +4 INT32 image width (in pixels) 376 | * +8 INT32 image height (in pixels) 377 | * +12 UINT16 number of colour planes (always 1) 378 | * +14 UINT16 number of bits per pixel 379 | * +16 UINT32 compression methods used 380 | * +20 UINT32 size of bitmap (in bytes) 381 | * +24 UINT32 horizontal resolution (in pixels per meter) 382 | * +28 UINT32 vertical resolution (in pixels per meter) 383 | * +32 UINT32 number of colours in the image 384 | * +36 UINT32 number of important colours 385 | * +40 UINT32 mask identifying bits of red component 386 | * +44 UINT32 mask identifying bits of green component 387 | * +48 UINT32 mask identifying bits of blue component 388 | * +52 UINT32 mask identifying bits of alpha component 389 | * +56 UINT32 color space type 390 | * +60 UINT32 x coordinate of red endpoint 391 | * +64 UINT32 y coordinate of red endpoint 392 | * +68 UINT32 z coordinate of red endpoint 393 | * +72 UINT32 x coordinate of green endpoint 394 | * +76 UINT32 y coordinate of green endpoint 395 | * +80 UINT32 z coordinate of green endpoint 396 | * +84 UINT32 x coordinate of blue endpoint 397 | * +88 UINT32 y coordinate of blue endpoint 398 | * +92 UINT32 z coordinate of blue endpoint 399 | * +96 UINT32 gamma red coordinate scale value 400 | * +100 UINT32 gamma green coordinate scale value 401 | * +104 UINT32 gamma blue coordinate scale value 402 | */ 403 | width = read_int32(data, 4); 404 | height = read_int32(data, 8); 405 | if ((width <= 0) || (height == 0)) 406 | return BMP_DATA_ERROR; 407 | if (height < 0) { 408 | bmp->reversed = true; 409 | height = -height; 410 | } 411 | /* ICOs only support 256*256 resolutions 412 | * In the case of the ICO header, the height is actually the added 413 | * height of XOR-Bitmap and AND-Bitmap (double the visible height) 414 | * Technically we could remove this check and ICOs with bitmaps 415 | * of any size could be processed; this is to conform to the spec. 416 | */ 417 | if (bmp->ico) { 418 | if ((width > 256) || (height > 512)) { 419 | return BMP_DATA_ERROR; 420 | } else { 421 | bmp->width = width; 422 | bmp->height = height / 2; 423 | } 424 | } else { 425 | bmp->width = width; 426 | bmp->height = height; 427 | } 428 | if (read_uint16(data, 12) != 1) 429 | return BMP_DATA_ERROR; 430 | bmp->bpp = read_uint16(data, 14); 431 | if (bmp->bpp == 0) 432 | bmp->bpp = 8; 433 | bmp->encoding = read_uint32(data, 16); 434 | /** 435 | * The bpp value should be in the range 1-32, but the only 436 | * values considered legal are: 437 | * RGB ENCODING: 1, 4, 8, 16, 24 and 32 438 | * RLE4 ENCODING: 4 439 | * RLE8 ENCODING: 8 440 | * BITFIELD ENCODING: 16 and 32 441 | */ 442 | switch (bmp->encoding) { 443 | case BMP_ENCODING_RGB: 444 | if ((bmp->bpp != 1) && (bmp->bpp != 4) && 445 | (bmp->bpp != 8) && 446 | (bmp->bpp != 16) && 447 | (bmp->bpp != 24) && 448 | (bmp->bpp != 32)) 449 | return BMP_DATA_ERROR; 450 | break; 451 | case BMP_ENCODING_RLE8: 452 | if (bmp->bpp != 8) 453 | return BMP_DATA_ERROR; 454 | break; 455 | case BMP_ENCODING_RLE4: 456 | if (bmp->bpp != 4) 457 | return BMP_DATA_ERROR; 458 | break; 459 | case BMP_ENCODING_BITFIELDS: 460 | if ((bmp->bpp != 16) && (bmp->bpp != 32)) 461 | return BMP_DATA_ERROR; 462 | break; 463 | /* invalid encoding */ 464 | default: 465 | return BMP_DATA_ERROR; 466 | break; 467 | } 468 | /* Bitfield encoding means we have red, green, blue, and alpha masks. 469 | * Here we aquire the masks and determine the required bit shift to 470 | * align them in our 24-bit color 8-bit alpha format. 471 | */ 472 | if (bmp->encoding == BMP_ENCODING_BITFIELDS) { 473 | if (header_size == 40) { 474 | header_size += 12; 475 | if (bmp->buffer_size < (14 + header_size)) 476 | return BMP_INSUFFICIENT_DATA; 477 | for (i = 0; i < 3; i++) 478 | bmp->mask[i] = read_uint32(data, 40 + (i << 2)); 479 | } else { 480 | for (i = 0; i < 4; i++) 481 | bmp->mask[i] = read_uint32(data, 40 + (i << 2)); 482 | } 483 | for (i = 0; i < 4; i++) { 484 | if (bmp->mask[i] == 0) 485 | break; 486 | for (j = 31; j > 0; j--) 487 | if (bmp->mask[i] & (1 << j)) { 488 | if ((j - 7) > 0) 489 | bmp->mask[i] &= 0xff << (j - 7); 490 | else 491 | bmp->mask[i] &= 0xff >> (-(j - 7)); 492 | bmp->shift[i] = (i << 3) - (j - 7); 493 | break; 494 | } 495 | } 496 | } 497 | bmp->colours = read_uint32(data, 32); 498 | if (bmp->colours == 0) 499 | bmp->colours = (1 << bmp->bpp); 500 | palette_size = 4; 501 | } 502 | data += header_size; 503 | 504 | /* if there's no alpha mask, flag the bmp opaque */ 505 | if ((!bmp->ico) && (bmp->mask[3] == 0)) { 506 | flags |= BMP_OPAQUE; 507 | bmp->opaque = true; 508 | } 509 | 510 | /* we only have a palette for <16bpp */ 511 | if (bmp->bpp < 16) { 512 | /* we now have a series of palette entries of the format: 513 | * 514 | * +0 BYTE blue 515 | * +1 BYTE green 516 | * +2 BYTE red 517 | * 518 | * if the palette is from an OS/2 or Win2.x file then the entries 519 | * are padded with an extra byte. 520 | */ 521 | 522 | /* boundary checking */ 523 | if (bmp->buffer_size < (14 + header_size + ((uint64_t)4 * bmp->colours))) 524 | return BMP_INSUFFICIENT_DATA; 525 | 526 | /* create the colour table */ 527 | bmp->colour_table = (uint32_t *)malloc(bmp->colours * 4); 528 | if (!bmp->colour_table) 529 | return BMP_INSUFFICIENT_MEMORY; 530 | for (i = 0; i < bmp->colours; i++) { 531 | bmp->colour_table[i] = data[2] | (data[1] << 8) | (data[0] << 16); 532 | if (bmp->opaque) 533 | bmp->colour_table[i] |= (0xff << 24); 534 | data += palette_size; 535 | bmp->colour_table[i] = read_uint32((uint8_t *)&bmp->colour_table[i],0); 536 | } 537 | } 538 | 539 | /* create our bitmap */ 540 | flags |= BMP_NEW | BMP_CLEAR_MEMORY; 541 | bmp->bitmap = bmp->bitmap_callbacks.bitmap_create(bmp->width, bmp->height, flags); 542 | if (!bmp->bitmap) { 543 | if (bmp->colour_table) 544 | free(bmp->colour_table); 545 | bmp->colour_table = NULL; 546 | return BMP_INSUFFICIENT_MEMORY; 547 | } 548 | /* BMPs within ICOs don't have BMP file headers, so the image data should 549 | * always be right after the colour table. 550 | */ 551 | if (bmp->ico) 552 | bmp->bitmap_offset = (intptr_t)data - (intptr_t)bmp->bmp_data; 553 | return BMP_OK; 554 | } 555 | 556 | 557 | /** 558 | * Finds the closest BMP within an ICO collection 559 | * 560 | * This function finds the BMP with dimensions as close to a specified set 561 | * as possible from the images in the collection. 562 | * 563 | * \param ico the ICO collection to examine 564 | * \param width the preferred width (0 to use ICO header width) 565 | * \param height the preferred height (0 to use ICO header height) 566 | */ 567 | bmp_image *ico_find(ico_collection *ico, uint16_t width, uint16_t height) { 568 | bmp_image *bmp = NULL; 569 | ico_image *image; 570 | int x, y, cur, distance = (1 << 24); 571 | 572 | if (width == 0) 573 | width = ico->width; 574 | if (height == 0) 575 | height = ico->height; 576 | for (image = ico->first; image; image = image->next) { 577 | if ((image->bmp.width == width) && (image->bmp.height == height)) 578 | return &image->bmp; 579 | x = image->bmp.width - width; 580 | y = image->bmp.height - height; 581 | cur = (x * x) + (y * y); 582 | if (cur < distance) { 583 | distance = cur; 584 | bmp = &image->bmp; 585 | } 586 | } 587 | return bmp; 588 | } 589 | 590 | 591 | /** 592 | * Decode a BMP 593 | * 594 | * This function decodes the BMP data such that bmp->bitmap is a valid 595 | * image. The state of bmp->decoded is set to TRUE on exit such that it 596 | * can easily be identified which BMPs are in a fully decoded state. 597 | * 598 | * \param bmp the BMP image to decode 599 | * \return BMP_OK on success 600 | */ 601 | bmp_result bmp_decode(bmp_image *bmp) { 602 | uint8_t *data; 603 | uint32_t bytes; 604 | bmp_result result = BMP_OK; 605 | 606 | assert(bmp->bitmap); 607 | 608 | data = bmp->bmp_data + bmp->bitmap_offset; 609 | bytes = bmp->buffer_size - bmp->bitmap_offset; 610 | 611 | switch (bmp->encoding) { 612 | case BMP_ENCODING_RGB: 613 | if ((bmp->bpp == 24) || (bmp->bpp == 32)) 614 | result = bmp_decode_rgb24(bmp, &data, bytes); 615 | else if (bmp->bpp == 16) 616 | result = bmp_decode_rgb16(bmp, &data, bytes); 617 | else 618 | result = bmp_decode_rgb(bmp, &data, bytes); 619 | break; 620 | case BMP_ENCODING_RLE8: 621 | result = bmp_decode_rle(bmp, data, bytes, 8); 622 | break; 623 | case BMP_ENCODING_RLE4: 624 | result = bmp_decode_rle(bmp, data, bytes, 4); 625 | break; 626 | case BMP_ENCODING_BITFIELDS: 627 | if (bmp->bpp == 32) 628 | result = bmp_decode_rgb24(bmp, &data, bytes); 629 | else if (bmp->bpp == 16) 630 | result = bmp_decode_rgb16(bmp, &data, bytes); 631 | else 632 | return BMP_DATA_ERROR; 633 | } 634 | 635 | if ((!bmp->ico) || (result != BMP_OK)) 636 | return result; 637 | 638 | bytes = (uintptr_t)bmp->bmp_data + bmp->buffer_size - (uintptr_t)data; 639 | return bmp_decode_mask(bmp, data, bytes); 640 | } 641 | 642 | /** 643 | * Decode a BMP using "limited transparency" 644 | * 645 | * Bitmaps do not have native transparency support. However, there is a 646 | * "trick" that is used in some instances in which the first pixel of the 647 | * bitmap becomes the "transparency index". The decoding application can 648 | * replace this index with whatever background colour it chooses to 649 | * create the illusion of transparency. 650 | * 651 | * When to use transparency is at the discretion of the decoding 652 | * application. 653 | * 654 | * \param bmp the BMP image to decode 655 | * \param colour the colour to use as "transparent" 656 | * \return BMP_OK on success 657 | */ 658 | bmp_result bmp_decode_trans(bmp_image *bmp, uint32_t colour) { 659 | bmp->limited_trans = true; 660 | bmp->trans_colour = colour; 661 | return bmp_decode(bmp); 662 | } 663 | 664 | 665 | /** 666 | * Decode BMP data stored in 24bpp colour. 667 | * 668 | * \param bmp the BMP image to decode 669 | * \param start the data to decode, updated to last byte read on success 670 | * \param bytes the number of bytes of data available 671 | * \return BMP_OK on success 672 | * BMP_INSUFFICIENT_DATA if the bitmap data ends unexpectedly; 673 | * in this case, the image may be partially viewable 674 | */ 675 | static bmp_result bmp_decode_rgb24(bmp_image *bmp, uint8_t **start, int bytes) { 676 | uint8_t *top, *bottom, *end, *data; 677 | uint32_t *scanline; 678 | uint32_t x, y; 679 | uint32_t swidth, skip; 680 | intptr_t addr; 681 | uint8_t i; 682 | uint32_t word; 683 | 684 | data = *start; 685 | swidth = bmp->bitmap_callbacks.bitmap_get_bpp(bmp->bitmap) * bmp->width; 686 | top = bmp->bitmap_callbacks.bitmap_get_buffer(bmp->bitmap); 687 | if (!top) 688 | return BMP_INSUFFICIENT_MEMORY; 689 | bottom = top + (uint64_t)swidth * (bmp->height - 1); 690 | end = data + bytes; 691 | addr = ((intptr_t)data) & 3; 692 | skip = bmp->bpp >> 3; 693 | bmp->decoded = true; 694 | 695 | /* Determine transparent index */ 696 | if (bmp->limited_trans) { 697 | if ((data + skip) > end) 698 | return BMP_INSUFFICIENT_DATA; 699 | if (bmp->encoding == BMP_ENCODING_BITFIELDS) 700 | bmp->transparent_index = read_uint32(data, 0); 701 | else 702 | bmp->transparent_index = data[2] | (data[1] << 8) | (data[0] << 16); 703 | } 704 | 705 | for (y = 0; y < bmp->height; y++) { 706 | while (addr != (((intptr_t)data) & 3)) 707 | data++; 708 | if ((data + (skip * bmp->width)) > end) 709 | return BMP_INSUFFICIENT_DATA; 710 | if (bmp->reversed) 711 | scanline = (void *)(top + (y * swidth)); 712 | else 713 | scanline = (void *)(bottom - (y * swidth)); 714 | if (bmp->encoding == BMP_ENCODING_BITFIELDS) { 715 | for (x = 0; x < bmp->width; x++) { 716 | word = read_uint32(data, 0); 717 | for (i = 0; i < 4; i++) 718 | if (bmp->shift[i] > 0) 719 | scanline[x] |= ((word & bmp->mask[i]) << bmp->shift[i]); 720 | else 721 | scanline[x] |= ((word & bmp->mask[i]) >> (-bmp->shift[i])); 722 | /* 32-bit BMPs have alpha masks, but sometimes they're not utilized */ 723 | if (bmp->opaque) 724 | scanline[x] |= (0xff << 24); 725 | data += skip; 726 | scanline[x] = read_uint32((uint8_t *)&scanline[x],0); 727 | } 728 | } else { 729 | for (x = 0; x < bmp->width; x++) { 730 | scanline[x] = data[2] | (data[1] << 8) | (data[0] << 16); 731 | if ((bmp->limited_trans) && (scanline[x] == bmp->transparent_index)) 732 | scanline[x] = bmp->trans_colour; 733 | if (bmp->opaque) 734 | scanline[x] |= (0xff << 24); 735 | data += skip; 736 | scanline[x] = read_uint32((uint8_t *)&scanline[x],0); 737 | } 738 | } 739 | } 740 | *start = data; 741 | return BMP_OK; 742 | } 743 | 744 | 745 | /** 746 | * Decode BMP data stored in 16bpp colour. 747 | * 748 | * \param bmp the BMP image to decode 749 | * \param start the data to decode, updated to last byte read on success 750 | * \param bytes the number of bytes of data available 751 | * \return BMP_OK on success 752 | * BMP_INSUFFICIENT_DATA if the bitmap data ends unexpectedly; 753 | * in this case, the image may be partially viewable 754 | */ 755 | static bmp_result bmp_decode_rgb16(bmp_image *bmp, uint8_t **start, int bytes) { 756 | uint8_t *top, *bottom, *end, *data; 757 | uint32_t *scanline; 758 | uint32_t x, y, swidth; 759 | intptr_t addr; 760 | uint8_t i; 761 | uint16_t word; 762 | 763 | data = *start; 764 | swidth = bmp->bitmap_callbacks.bitmap_get_bpp(bmp->bitmap) * bmp->width; 765 | top = bmp->bitmap_callbacks.bitmap_get_buffer(bmp->bitmap); 766 | if (!top) 767 | return BMP_INSUFFICIENT_MEMORY; 768 | bottom = top + (uint64_t)swidth * (bmp->height - 1); 769 | end = data + bytes; 770 | addr = ((intptr_t)data) & 3; 771 | bmp->decoded = true; 772 | 773 | /* Determine transparent index */ 774 | if (bmp->limited_trans) { 775 | if ((data + 2) > end) 776 | return BMP_INSUFFICIENT_DATA; 777 | bmp->transparent_index = read_uint16(data, 0); 778 | } 779 | 780 | for (y = 0; y < bmp->height; y++) { 781 | while (addr != (((intptr_t)data) & 3)) 782 | data += 2; 783 | if ((data + (2 * bmp->width)) > end) 784 | return BMP_INSUFFICIENT_DATA; 785 | if (bmp->reversed) 786 | scanline = (void *)(top + (y * swidth)); 787 | else 788 | scanline = (void *)(bottom - (y * swidth)); 789 | if (bmp->encoding == BMP_ENCODING_BITFIELDS) { 790 | for (x = 0; x < bmp->width; x++) { 791 | word = read_uint16(data, 0); 792 | if ((bmp->limited_trans) && (word == bmp->transparent_index)) 793 | scanline[x] = bmp->trans_colour; 794 | else { 795 | scanline[x] = 0; 796 | for (i = 0; i < 4; i++) 797 | if (bmp->shift[i] > 0) 798 | scanline[x] |= ((word & bmp->mask[i]) << bmp->shift[i]); 799 | else 800 | scanline[x] |= ((word & bmp->mask[i]) >> (-bmp->shift[i])); 801 | if (bmp->opaque) 802 | scanline[x] |= (0xff << 24); 803 | } 804 | data += 2; 805 | scanline[x] = read_uint32((uint8_t *)&scanline[x],0); 806 | } 807 | } else { 808 | for (x = 0; x < bmp->width; x++) { 809 | word = read_uint16(data, 0); 810 | if ((bmp->limited_trans) && (word == bmp->transparent_index)) 811 | scanline[x] = bmp->trans_colour; 812 | else { 813 | /* 16-bit RGB defaults to RGB555 */ 814 | scanline[x] = ((word & (31 << 0)) << 19) | 815 | ((word & (31 << 5)) << 6) | 816 | ((word & (31 << 10)) >> 7); 817 | } 818 | if (bmp->opaque) 819 | scanline[x] |= (0xff << 24); 820 | data += 2; 821 | scanline[x] = read_uint32((uint8_t *)&scanline[x],0); 822 | } 823 | } 824 | } 825 | *start = data; 826 | return BMP_OK; 827 | } 828 | 829 | 830 | /** 831 | * Decode BMP data stored with a palette and in 8bpp colour or less. 832 | * 833 | * \param bmp the BMP image to decode 834 | * \param start the data to decode, updated to last byte read on success 835 | * \param bytes the number of bytes of data available 836 | * \return BMP_OK on success 837 | * BMP_INSUFFICIENT_DATA if the bitmap data ends unexpectedly; 838 | * in this case, the image may be partially viewable 839 | */ 840 | static bmp_result bmp_decode_rgb(bmp_image *bmp, uint8_t **start, int bytes) { 841 | uint8_t *top, *bottom, *end, *data; 842 | uint32_t *scanline; 843 | intptr_t addr; 844 | uint32_t x, y, swidth; 845 | uint8_t bit_shifts[8]; 846 | uint8_t ppb = 8 / bmp->bpp; 847 | uint8_t bit_mask = (1 << bmp->bpp) - 1; 848 | uint8_t cur_byte = 0, bit, i; 849 | 850 | for (i = 0; i < ppb; i++) 851 | bit_shifts[i] = 8 - ((i + 1) * bmp->bpp); 852 | 853 | data = *start; 854 | swidth = bmp->bitmap_callbacks.bitmap_get_bpp(bmp->bitmap) * bmp->width; 855 | top = bmp->bitmap_callbacks.bitmap_get_buffer(bmp->bitmap); 856 | if (!top) 857 | return BMP_INSUFFICIENT_MEMORY; 858 | bottom = top + (uint64_t)swidth * (bmp->height - 1); 859 | end = data + bytes; 860 | addr = ((intptr_t)data) & 3; 861 | bmp->decoded = true; 862 | 863 | /* Determine transparent index */ 864 | if (bmp->limited_trans) 865 | bmp->transparent_index = bmp->colour_table[(*data >> bit_shifts[0]) & bit_mask]; 866 | 867 | for (y = 0; y < bmp->height; y++) { 868 | while (addr != (((intptr_t)data) & 3)) 869 | data++; 870 | bit = 8; 871 | if ((data + (bmp->width / ppb)) > end) 872 | return BMP_INSUFFICIENT_DATA; 873 | if (bmp->reversed) 874 | scanline = (void *)(top + (y * swidth)); 875 | else 876 | scanline = (void *)(bottom - (y * swidth)); 877 | for (x = 0; x < bmp->width; x++) { 878 | if (bit >= ppb) { 879 | bit = 0; 880 | cur_byte = *data++; 881 | } 882 | scanline[x] = bmp->colour_table[(cur_byte >> bit_shifts[bit++]) & bit_mask]; 883 | if ((bmp->limited_trans) && (scanline[x] == bmp->transparent_index)) 884 | scanline[x] = bmp->trans_colour; 885 | } 886 | } 887 | *start = data; 888 | return BMP_OK; 889 | } 890 | 891 | 892 | /** 893 | * Decode a 1bpp mask for an ICO 894 | * 895 | * \param bmp the BMP image to decode 896 | * \param data the data to decode 897 | * \param bytes the number of bytes of data available 898 | * \return BMP_OK on success 899 | */ 900 | static bmp_result bmp_decode_mask(bmp_image *bmp, uint8_t *data, int bytes) { 901 | uint8_t *top, *bottom, *end; 902 | uint32_t *scanline; 903 | intptr_t addr; 904 | uint32_t x, y, swidth; 905 | uint32_t cur_byte = 0; 906 | 907 | swidth = bmp->bitmap_callbacks.bitmap_get_bpp(bmp->bitmap) * bmp->width; 908 | top = bmp->bitmap_callbacks.bitmap_get_buffer(bmp->bitmap); 909 | if (!top) 910 | return BMP_INSUFFICIENT_MEMORY; 911 | bottom = top + (uint64_t)swidth * (bmp->height - 1); 912 | end = data + bytes; 913 | addr = ((intptr_t)data) & 3; 914 | 915 | for (y = 0; y < bmp->height; y++) { 916 | while (addr != (((intptr_t)data) & 3)) 917 | data++; 918 | if ((data + (bmp->width >> 3)) > end) 919 | return BMP_INSUFFICIENT_DATA; 920 | scanline = (void *)(bottom - (y * swidth)); 921 | for (x = 0; x < bmp->width; x++) { 922 | if ((x & 7) == 0) 923 | cur_byte = *data++; 924 | if ((cur_byte & 128) == 0) { 925 | scanline[x] = read_uint32((uint8_t *)&scanline[x], 0); 926 | scanline[x] |= (0xff << 24); 927 | scanline[x] = read_uint32((uint8_t *)&scanline[x], 0); 928 | } 929 | cur_byte = cur_byte << 1; 930 | } 931 | } 932 | return BMP_OK; 933 | } 934 | 935 | 936 | /** 937 | * Decode BMP data stored encoded in either RLE4 or RLE8. 938 | * 939 | * \param bmp the BMP image to decode 940 | * \param data the data to decode 941 | * \param bytes the number of bytes of data available 942 | * \param size the size of the RLE tokens (4 or 8) 943 | * \return BMP_OK on success 944 | * BMP_INSUFFICIENT_DATA if the bitmap data ends unexpectedly; 945 | * in this case, the image may be partially viewable 946 | */ 947 | static bmp_result bmp_decode_rle(bmp_image *bmp, uint8_t *data, int bytes, int size) { 948 | uint8_t *top, *bottom, *end; 949 | uint32_t *scanline; 950 | uint32_t swidth; 951 | uint32_t i, length, pixels_left; 952 | uint32_t x = 0, y = 0, last_y = 0; 953 | uint32_t pixel = 0, pixel2; 954 | 955 | if (bmp->ico) 956 | return BMP_DATA_ERROR; 957 | 958 | swidth = bmp->bitmap_callbacks.bitmap_get_bpp(bmp->bitmap) * bmp->width; 959 | top = bmp->bitmap_callbacks.bitmap_get_buffer(bmp->bitmap); 960 | if (!top) 961 | return BMP_INSUFFICIENT_MEMORY; 962 | bottom = top + (uint64_t)swidth * (bmp->height - 1); 963 | end = data + bytes; 964 | bmp->decoded = true; 965 | 966 | do { 967 | if (data + 2 > end) 968 | return BMP_INSUFFICIENT_DATA; 969 | length = *data++; 970 | if (length == 0) { 971 | length = *data++; 972 | if (length == 0) { 973 | /* 00 - 00 means end of scanline */ 974 | x = 0; 975 | if (last_y == y) { 976 | if (++y > bmp->height) 977 | return BMP_DATA_ERROR; 978 | } 979 | last_y = y; 980 | } else if (length == 1) { 981 | /* 00 - 01 means end of RLE data */ 982 | return BMP_OK; 983 | } else if (length == 2) { 984 | /* 00 - 02 - XX - YY means move cursor */ 985 | if (data + 2 > end) 986 | return BMP_INSUFFICIENT_DATA; 987 | x += *data++; 988 | if (x >= bmp->width) 989 | return BMP_DATA_ERROR; 990 | y += *data++; 991 | if (y >= bmp->height) 992 | return BMP_DATA_ERROR; 993 | } else { 994 | /* 00 - NN means escape NN pixels */ 995 | if (bmp->reversed) { 996 | pixels_left = (y + 1) * bmp->width - x; 997 | scanline = (void *)(top + (y * swidth)); 998 | } else { 999 | pixels_left = (bmp->height - y + 1) * bmp->width - x; 1000 | scanline = (void *)(bottom - (y * swidth)); 1001 | } 1002 | if (length > pixels_left) 1003 | length = pixels_left; 1004 | if (data + length > end) 1005 | return BMP_INSUFFICIENT_DATA; 1006 | 1007 | /* the following code could be easily optimised by simply 1008 | * checking the bounds on entry and using some simply copying 1009 | * routines if so */ 1010 | if (size == 8) { 1011 | for (i = 0; i < length; i++) { 1012 | if (x >= bmp->width) { 1013 | x = 0; 1014 | if (++y > bmp->height) 1015 | return BMP_DATA_ERROR; 1016 | scanline -= bmp->width; 1017 | } 1018 | scanline[x++] = bmp->colour_table[(int)*data++]; 1019 | } 1020 | } else { 1021 | for (i = 0; i < length; i++) { 1022 | if (x >= bmp->width) { 1023 | x = 0; 1024 | if (++y > bmp->height) 1025 | return BMP_DATA_ERROR; 1026 | scanline -= bmp->width; 1027 | } 1028 | if ((i & 1) == 0) { 1029 | pixel = *data++; 1030 | scanline[x++] = bmp->colour_table 1031 | [pixel >> 4]; 1032 | } else { 1033 | scanline[x++] = bmp->colour_table 1034 | [pixel & 0xf]; 1035 | } 1036 | } 1037 | length = (length + 1) >> 1; 1038 | } 1039 | if ((length & 1) && (*data++ != 0x00)) 1040 | return BMP_DATA_ERROR; 1041 | 1042 | } 1043 | } else { 1044 | /* NN means perform RLE for NN pixels */ 1045 | if (bmp->reversed) { 1046 | pixels_left = (y + 1) * bmp->width - x; 1047 | scanline = (void *)(top + (y * swidth)); 1048 | } else { 1049 | pixels_left = (bmp->height - y + 1) * bmp->width - x; 1050 | scanline = (void *)(bottom - (y * swidth)); 1051 | } 1052 | if (length > pixels_left) 1053 | length = pixels_left; 1054 | 1055 | /* boundary checking */ 1056 | if (data + 1 > end) 1057 | return BMP_INSUFFICIENT_DATA; 1058 | 1059 | /* the following code could be easily optimised by simply 1060 | * checking the bounds on entry and using some simply copying 1061 | * routines if so */ 1062 | if (size == 8) { 1063 | pixel = bmp->colour_table[(int)*data++]; 1064 | for (i = 0; i < length; i++) { 1065 | if (x >= bmp->width) { 1066 | x = 0; 1067 | if (++y > bmp->height) 1068 | return BMP_DATA_ERROR; 1069 | scanline -= bmp->width; 1070 | } 1071 | scanline[x++] = pixel; 1072 | } 1073 | } else { 1074 | pixel2 = *data++; 1075 | pixel = bmp->colour_table[pixel2 >> 4]; 1076 | pixel2 = bmp->colour_table[pixel2 & 0xf]; 1077 | for (i = 0; i < length; i++) { 1078 | if (x >= bmp->width) { 1079 | x = 0; 1080 | if (++y > bmp->height) 1081 | return BMP_DATA_ERROR; 1082 | scanline -= bmp->width; 1083 | } 1084 | if ((i & 1) == 0) 1085 | scanline[x++] = pixel; 1086 | else 1087 | scanline[x++] = pixel2; 1088 | } 1089 | } 1090 | } 1091 | } while (data < end); 1092 | return BMP_OK; 1093 | } 1094 | 1095 | 1096 | /** 1097 | * Finalise a BMP prior to destruction. 1098 | * 1099 | * \param bmp the BMP image to finalise 1100 | */ 1101 | void bmp_finalise(bmp_image *bmp) { 1102 | if (bmp->bitmap) 1103 | bmp->bitmap_callbacks.bitmap_destroy(bmp->bitmap); 1104 | bmp->bitmap = NULL; 1105 | if (bmp->colour_table) 1106 | free(bmp->colour_table); 1107 | bmp->colour_table = NULL; 1108 | } 1109 | 1110 | 1111 | /** 1112 | * Finalise an ICO prior to destruction. 1113 | * 1114 | * \param ico the ICO image to finalise 1115 | */ 1116 | void ico_finalise(ico_collection *ico) { 1117 | ico_image *image; 1118 | 1119 | for (image = ico->first; image; image = image->next) 1120 | bmp_finalise(&image->bmp); 1121 | while (ico->first) { 1122 | image = ico->first; 1123 | ico->first = image->next; 1124 | free(image); 1125 | } 1126 | } 1127 | -------------------------------------------------------------------------------- /libnsbmp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2006 Richard Wilson 3 | * Copyright 2008 Sean Fox 4 | * 5 | * This file is part of NetSurf's libnsbmp, http://www.netsurf-browser.org/ 6 | * Licenced under the MIT License, 7 | * http://www.opensource.org/licenses/mit-license.php 8 | */ 9 | 10 | /** \file 11 | * BMP file decoding (interface). 12 | */ 13 | 14 | #ifndef libnsbmp_h_ 15 | #define libnsbmp_h_ 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | /* bmp flags */ 22 | #define BMP_NEW 0 23 | #define BMP_OPAQUE (1 << 0) /** image is opaque (as opposed to having an alpha mask) */ 24 | #define BMP_CLEAR_MEMORY (1 << 1) /** memory should be wiped */ 25 | 26 | /* error return values */ 27 | typedef enum { 28 | BMP_OK = 0, 29 | BMP_INSUFFICIENT_MEMORY = 1, 30 | BMP_INSUFFICIENT_DATA = 2, 31 | BMP_DATA_ERROR = 3 32 | } bmp_result; 33 | 34 | /* encoding types */ 35 | typedef enum { 36 | BMP_ENCODING_RGB = 0, 37 | BMP_ENCODING_RLE8 = 1, 38 | BMP_ENCODING_RLE4 = 2, 39 | BMP_ENCODING_BITFIELDS = 3 40 | } bmp_encoding; 41 | 42 | /* API for Bitmap callbacks 43 | */ 44 | typedef void* (*bmp_bitmap_cb_create)(int width, int height, unsigned int state); 45 | typedef void (*bmp_bitmap_cb_destroy)(void *bitmap); 46 | typedef unsigned char* (*bmp_bitmap_cb_get_buffer)(void *bitmap); 47 | typedef size_t (*bmp_bitmap_cb_get_bpp)(void *bitmap); 48 | 49 | /* The Bitmap callbacks function table 50 | */ 51 | typedef struct bmp_bitmap_callback_vt_s { 52 | bmp_bitmap_cb_create bitmap_create; /**< Create a bitmap. */ 53 | bmp_bitmap_cb_destroy bitmap_destroy; /**< Free a bitmap. */ 54 | bmp_bitmap_cb_get_buffer bitmap_get_buffer; /**< Return a pointer to the pixel data in a bitmap. */ 55 | bmp_bitmap_cb_get_bpp bitmap_get_bpp; /**< Find the width of a pixel row in bytes. */ 56 | } bmp_bitmap_callback_vt; 57 | 58 | typedef struct bmp_image { 59 | bmp_bitmap_callback_vt bitmap_callbacks; /**< callbacks for bitmap functions */ 60 | uint8_t *bmp_data; /** pointer to BMP data */ 61 | uint32_t width; /** width of BMP (valid after _analyse) */ 62 | uint32_t height; /** heigth of BMP (valid after _analyse) */ 63 | bool decoded; /** whether the image has been decoded */ 64 | void *bitmap; /** decoded image */ 65 | /** Internal members are listed below 66 | */ 67 | uint32_t buffer_size; /** total number of bytes of BMP data available */ 68 | bmp_encoding encoding; /** pixel encoding type */ 69 | uint32_t bitmap_offset; /** offset of bitmap data */ 70 | uint16_t bpp; /** bits per pixel */ 71 | uint32_t colours; /** number of colours */ 72 | uint32_t *colour_table; /** colour table */ 73 | bool limited_trans; /** whether to use bmp's limited transparency */ 74 | uint32_t trans_colour; /** colour to display for "transparent" pixels when 75 | * using limited transparency */ 76 | bool reversed; /** scanlines are top to bottom */ 77 | bool ico; /** image is part of an ICO, mask follows */ 78 | bool opaque; /** true if the bitmap does not contain an alpha channel */ 79 | uint32_t mask[4]; /** four bitwise mask */ 80 | int32_t shift[4]; /** four bitwise shifts */ 81 | uint32_t transparent_index; /** colour representing "transparency" in the bitmap */ 82 | } bmp_image; 83 | 84 | typedef struct ico_image { 85 | bmp_image bmp; 86 | struct ico_image *next; 87 | } ico_image; 88 | 89 | typedef struct ico_collection { 90 | bmp_bitmap_callback_vt bitmap_callbacks; /**< callbacks for bitmap functions */ 91 | uint16_t width; /** width of largest BMP */ 92 | uint16_t height; /** heigth of largest BMP */ 93 | /** Internal members are listed below 94 | */ 95 | uint8_t *ico_data; /** pointer to ICO data */ 96 | uint32_t buffer_size; /** total number of bytes of ICO data available */ 97 | ico_image *first; 98 | } ico_collection; 99 | 100 | void bmp_create(bmp_image *bmp, bmp_bitmap_callback_vt *bitmap_callbacks); 101 | void ico_collection_create(ico_collection *ico, 102 | bmp_bitmap_callback_vt *bitmap_callbacks); 103 | bmp_result bmp_analyse(bmp_image *bmp, size_t size, uint8_t *data); 104 | bmp_result bmp_decode(bmp_image *bmp); 105 | bmp_result bmp_decode_trans(bmp_image *bmp, uint32_t transparent_colour); 106 | void bmp_finalise(bmp_image *bmp); 107 | 108 | bmp_result ico_analyse(ico_collection *ico, size_t size, uint8_t *data); 109 | bmp_image *ico_find(ico_collection *ico, uint16_t width, uint16_t height); 110 | void ico_finalise(ico_collection *ico); 111 | 112 | #endif 113 | -------------------------------------------------------------------------------- /libnsgif.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2004 Richard Wilson 3 | * Copyright 2008 Sean Fox 4 | * 5 | * This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/ 6 | * Licenced under the MIT License, 7 | * http://www.opensource.org/licenses/mit-license.php 8 | */ 9 | 10 | /** \file 11 | * Progressive animated GIF file decoding (interface). 12 | */ 13 | 14 | #ifndef _LIBNSGIF_H_ 15 | #define _LIBNSGIF_H_ 16 | 17 | #include 18 | #include 19 | 20 | /* Error return values 21 | */ 22 | typedef enum { 23 | GIF_WORKING = 1, 24 | GIF_OK = 0, 25 | GIF_INSUFFICIENT_FRAME_DATA = -1, 26 | GIF_FRAME_DATA_ERROR = -2, 27 | GIF_INSUFFICIENT_DATA = -3, 28 | GIF_DATA_ERROR = -4, 29 | GIF_INSUFFICIENT_MEMORY = -5, 30 | GIF_FRAME_NO_DISPLAY = -6, 31 | GIF_END_OF_FRAME = -7 32 | } gif_result; 33 | 34 | /* The GIF frame data 35 | */ 36 | typedef struct gif_frame { 37 | bool display; /**< whether the frame should be displayed/animated */ 38 | unsigned int frame_delay; /**< delay (in cs) before animating the frame */ 39 | /** Internal members are listed below 40 | */ 41 | unsigned int frame_pointer; /**< offset (in bytes) to the GIF frame data */ 42 | bool virgin; /**< whether the frame has previously been used */ 43 | bool opaque; /**< whether the frame is totally opaque */ 44 | bool redraw_required; /**< whether a forcable screen redraw is required */ 45 | unsigned char disposal_method; /**< how the previous frame should be disposed; affects plotting */ 46 | bool transparency; /**< whether we acknoledge transparency */ 47 | unsigned char transparency_index; /**< the index designating a transparent pixel */ 48 | unsigned int redraw_x; /**< x co-ordinate of redraw rectangle */ 49 | unsigned int redraw_y; /**< y co-ordinate of redraw rectangle */ 50 | unsigned int redraw_width; /**< width of redraw rectangle */ 51 | unsigned int redraw_height; /**< height of redraw rectangle */ 52 | } gif_frame; 53 | 54 | /* API for Bitmap callbacks 55 | */ 56 | typedef void* (*gif_bitmap_cb_create)(int width, int height); 57 | typedef void (*gif_bitmap_cb_destroy)(void *bitmap); 58 | typedef unsigned char* (*gif_bitmap_cb_get_buffer)(void *bitmap); 59 | typedef void (*gif_bitmap_cb_set_opaque)(void *bitmap, bool opaque); 60 | typedef bool (*gif_bitmap_cb_test_opaque)(void *bitmap); 61 | typedef void (*gif_bitmap_cb_modified)(void *bitmap); 62 | 63 | /* The Bitmap callbacks function table 64 | */ 65 | typedef struct gif_bitmap_callback_vt { 66 | gif_bitmap_cb_create bitmap_create; /**< Create a bitmap. */ 67 | gif_bitmap_cb_destroy bitmap_destroy; /**< Free a bitmap. */ 68 | gif_bitmap_cb_get_buffer bitmap_get_buffer; /**< Return a pointer to the pixel data in a bitmap. */ 69 | /** Members below are optional 70 | */ 71 | gif_bitmap_cb_set_opaque bitmap_set_opaque; /**< Sets whether a bitmap should be plotted opaque. */ 72 | gif_bitmap_cb_test_opaque bitmap_test_opaque; /**< Tests whether a bitmap has an opaque alpha channel. */ 73 | gif_bitmap_cb_modified bitmap_modified; /**< The bitmap image has changed, so flush any persistant cache. */ 74 | } gif_bitmap_callback_vt; 75 | 76 | /* The GIF animation data 77 | */ 78 | typedef struct gif_animation { 79 | gif_bitmap_callback_vt bitmap_callbacks; /**< callbacks for bitmap functions */ 80 | unsigned char *gif_data; /**< pointer to GIF data */ 81 | unsigned int width; /**< width of GIF (may increase during decoding) */ 82 | unsigned int height; /**< heigth of GIF (may increase during decoding) */ 83 | unsigned int frame_count; /**< number of frames decoded */ 84 | unsigned int frame_count_partial; /**< number of frames partially decoded */ 85 | gif_frame *frames; /**< decoded frames */ 86 | int decoded_frame; /**< current frame decoded to bitmap */ 87 | void *frame_image; /**< currently decoded image; stored as bitmap from bitmap_create callback */ 88 | int loop_count; /**< number of times to loop animation */ 89 | gif_result current_error; /**< current error type, or 0 for none*/ 90 | /** Internal members are listed below 91 | */ 92 | unsigned int buffer_position; /**< current index into GIF data */ 93 | unsigned int buffer_size; /**< total number of bytes of GIF data available */ 94 | unsigned int frame_holders; /**< current number of frame holders */ 95 | unsigned int background_index; /**< index in the colour table for the background colour */ 96 | unsigned int aspect_ratio; /**< image aspect ratio (ignored) */ 97 | unsigned int colour_table_size; /**< size of colour table (in entries) */ 98 | bool global_colours; /**< whether the GIF has a global colour table */ 99 | unsigned int *global_colour_table; /**< global colour table */ 100 | unsigned int *local_colour_table; /**< local colour table */ 101 | } gif_animation; 102 | 103 | void gif_create(gif_animation *gif, gif_bitmap_callback_vt *bitmap_callbacks); 104 | gif_result gif_initialise(gif_animation *gif, size_t size, unsigned char *data); 105 | gif_result gif_decode_frame(gif_animation *gif, unsigned int frame); 106 | void gif_finalise(gif_animation *gif); 107 | 108 | #endif 109 | -------------------------------------------------------------------------------- /loader.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE for licence details. */ 2 | 3 | /* for jpeg */ 4 | #include 5 | 6 | /* for png */ 7 | #include 8 | 9 | /* for gif/bmp/(ico not supported) */ 10 | #include "libnsgif.h" 11 | #include "libnsbmp.h" 12 | 13 | //#define STB_IMAGE_IMPLEMENTATION 14 | /* to remove math.h dependency */ 15 | //#define STBI_NO_HDR 16 | //#include "stb_image.h" 17 | 18 | enum { 19 | CHECK_HEADER_SIZE = 8, 20 | BYTES_PER_PIXEL = 4, 21 | PNG_HEADER_SIZE = 8, 22 | MAX_FRAME_NUM = 128, /* limit of gif frames */ 23 | }; 24 | 25 | enum filetype_t { 26 | TYPE_JPEG, 27 | TYPE_PNG, 28 | TYPE_BMP, 29 | TYPE_GIF, 30 | TYPE_PNM, 31 | TYPE_UNKNOWN, 32 | }; 33 | 34 | struct image { 35 | /* normally use data[0], data[n] (n > 1) for animanion gif */ 36 | uint8_t *data[MAX_FRAME_NUM]; 37 | int width; 38 | int height; 39 | int channel; 40 | bool alpha; 41 | /* for animation gif */ 42 | int delay[MAX_FRAME_NUM]; 43 | int frame_count; /* normally 1 */ 44 | int loop_count; 45 | /* for w3mimg */ 46 | int current_frame; 47 | bool already_drew; 48 | }; 49 | 50 | /* libjpeg functions */ 51 | struct my_jpeg_error_mgr { 52 | struct jpeg_error_mgr pub; 53 | jmp_buf setjmp_buffer; 54 | }; 55 | 56 | void my_jpeg_exit(j_common_ptr cinfo) 57 | { 58 | char last_msg[JMSG_LENGTH_MAX]; 59 | struct my_jpeg_error_mgr *myerr = (struct my_jpeg_error_mgr *) cinfo->err; 60 | 61 | (*cinfo->err->format_message)(cinfo, last_msg); 62 | logging(ERROR, "libjpeg: %s\n", last_msg); 63 | 64 | longjmp(myerr->setjmp_buffer, 1); 65 | } 66 | 67 | void my_jpeg_error(j_common_ptr cinfo) 68 | { 69 | char last_msg[JMSG_LENGTH_MAX]; 70 | 71 | (*cinfo->err->format_message)(cinfo, last_msg); 72 | logging(ERROR, "libjpeg: %s\n", last_msg); 73 | } 74 | 75 | void my_jpeg_warning(j_common_ptr cinfo, int msg_level) 76 | { 77 | char last_msg[JMSG_LENGTH_MAX]; 78 | 79 | (*cinfo->err->format_message)(cinfo, last_msg); 80 | if (msg_level < 0) { /* warning */ 81 | if (cinfo->err->num_warnings == 0 || cinfo->err->trace_level >= 3) 82 | logging(WARN, "libjpeg: %s\n", last_msg); 83 | cinfo->err->num_warnings++; 84 | } else { /* trace */ 85 | if (cinfo->err->trace_level >= msg_level) 86 | logging(WARN, "libjpeg: %s\n", last_msg); 87 | } 88 | } 89 | 90 | bool load_jpeg(FILE *fp, struct image *img) 91 | { 92 | int row_stride, size; 93 | JSAMPARRAY buffer; 94 | struct jpeg_decompress_struct cinfo; 95 | struct my_jpeg_error_mgr jerr; 96 | 97 | cinfo.err = jpeg_std_error(&jerr.pub); 98 | jerr.pub.error_exit = my_jpeg_exit; 99 | jerr.pub.emit_message = my_jpeg_warning; 100 | jerr.pub.output_message = my_jpeg_error; 101 | 102 | if (setjmp(jerr.setjmp_buffer)) { 103 | jpeg_destroy_decompress(&cinfo); 104 | return false; 105 | } 106 | 107 | jpeg_create_decompress(&cinfo); 108 | jpeg_stdio_src(&cinfo, fp); 109 | jpeg_read_header(&cinfo, TRUE); 110 | 111 | /* disable colormap (indexed color), grayscale -> rgb */ 112 | cinfo.quantize_colors = FALSE; 113 | cinfo.out_color_space = JCS_RGB; 114 | jpeg_start_decompress(&cinfo); 115 | 116 | img->width = cinfo.output_width; 117 | img->height = cinfo.output_height; 118 | img->channel = cinfo.output_components; 119 | 120 | size = img->width * img->height * img->channel; 121 | if ((img->data[0] = (uint8_t *) ecalloc(1, size)) == NULL) { 122 | jpeg_finish_decompress(&cinfo); 123 | jpeg_destroy_decompress(&cinfo); 124 | return false; 125 | } 126 | 127 | row_stride = cinfo.output_width * cinfo.output_components; 128 | buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1); 129 | 130 | while (cinfo.output_scanline < cinfo.output_height) { 131 | jpeg_read_scanlines(&cinfo, buffer, 1); 132 | memcpy(img->data[0] + (cinfo.output_scanline - 1) * row_stride, buffer[0], row_stride); 133 | } 134 | 135 | jpeg_finish_decompress(&cinfo); 136 | jpeg_destroy_decompress(&cinfo); 137 | 138 | return true; 139 | } 140 | 141 | /* libpng function */ 142 | 143 | void my_png_error(png_structp png_ptr, png_const_charp error_msg) 144 | { 145 | logging(ERROR, "libpng: %s\n", error_msg); 146 | if (png_ptr) 147 | longjmp(png_jmpbuf(png_ptr), 1); 148 | } 149 | 150 | void my_png_warning(png_structp png_ptr, png_const_charp warning_msg) 151 | { 152 | (void) png_ptr; 153 | logging(WARN, "libpng: %s\n", warning_msg); 154 | } 155 | 156 | bool load_png(FILE *fp, struct image *img) 157 | { 158 | int row_stride, size; 159 | png_bytep *row_pointers = NULL; 160 | unsigned char header[PNG_HEADER_SIZE]; 161 | png_structp png_ptr; 162 | png_infop info_ptr; 163 | 164 | if (fread(header, 1, PNG_HEADER_SIZE, fp) != PNG_HEADER_SIZE 165 | || png_sig_cmp(header, 0, PNG_HEADER_SIZE) 166 | || (png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, my_png_error, my_png_warning)) == NULL) 167 | return false; 168 | 169 | if ((info_ptr = png_create_info_struct(png_ptr)) == NULL) { 170 | png_destroy_read_struct(&png_ptr, (png_infopp) NULL, (png_infopp) NULL); 171 | return false; 172 | } 173 | 174 | if (setjmp(png_jmpbuf(png_ptr))) { 175 | png_destroy_read_struct(&png_ptr, &info_ptr, NULL); 176 | return false; 177 | } 178 | 179 | png_init_io(png_ptr, fp); 180 | png_set_sig_bytes(png_ptr, PNG_HEADER_SIZE); 181 | /* force 3 bytes per pixel image 182 | - strip alpha 183 | - 6 bytes per pixel -> 3 bytes per pixel 184 | - 1,2,4 bits per color -> 8 bits per color 185 | - grayscale -> rgb 186 | - perform set_expand() */ 187 | png_read_png(png_ptr, info_ptr, 188 | PNG_TRANSFORM_STRIP_ALPHA | PNG_TRANSFORM_STRIP_16 | 189 | PNG_TRANSFORM_PACKING | PNG_TRANSFORM_GRAY_TO_RGB | 190 | PNG_TRANSFORM_EXPAND, NULL); 191 | 192 | img->width = png_get_image_width(png_ptr, info_ptr); 193 | img->height = png_get_image_height(png_ptr, info_ptr); 194 | img->channel = png_get_channels(png_ptr, info_ptr); 195 | 196 | size = img->width * img->height * img->channel; 197 | if ((img->data[0] = (uint8_t *) ecalloc(1, size)) == NULL) { 198 | png_destroy_read_struct(&png_ptr, &info_ptr, NULL); 199 | return false; 200 | } 201 | 202 | row_stride = png_get_rowbytes(png_ptr, info_ptr); 203 | row_pointers = png_get_rows(png_ptr, info_ptr); 204 | 205 | for (int i = 0; i < img->height; i++) 206 | memcpy(img->data[0] + row_stride * i, row_pointers[i], row_stride); 207 | 208 | png_destroy_read_struct(&png_ptr, &info_ptr, NULL); 209 | 210 | return true; 211 | } 212 | 213 | /* libns{gif,bmp} functions */ 214 | unsigned char *file_into_memory(FILE *fp, size_t *data_size) 215 | { 216 | unsigned char *buffer; 217 | size_t n, size; 218 | 219 | fseek(fp, 0L, SEEK_END); 220 | size = ftell(fp); 221 | 222 | if ((buffer = ecalloc(1, size)) == NULL) 223 | return NULL; 224 | 225 | fseek(fp, 0L, SEEK_SET); 226 | if ((n = fread(buffer, 1, size, fp)) != size) { 227 | free(buffer); 228 | return NULL; 229 | } 230 | *data_size = size; 231 | 232 | return buffer; 233 | } 234 | 235 | void *gif_bitmap_create(int width, int height) 236 | { 237 | return calloc(width * height, BYTES_PER_PIXEL); 238 | } 239 | 240 | void gif_bitmap_set_opaque(void *bitmap, bool opaque) 241 | { 242 | (void) opaque; /* unused */ 243 | (void) bitmap; 244 | } 245 | 246 | bool gif_bitmap_test_opaque(void *bitmap) 247 | { 248 | (void) bitmap; /* unused */ 249 | return false; 250 | } 251 | 252 | unsigned char *gif_bitmap_get_buffer(void *bitmap) 253 | { 254 | return bitmap; 255 | } 256 | 257 | void gif_bitmap_destroy(void *bitmap) 258 | { 259 | free(bitmap); 260 | } 261 | 262 | void gif_bitmap_modified(void *bitmap) 263 | { 264 | (void) bitmap; /* unused */ 265 | return; 266 | } 267 | 268 | bool load_gif(FILE *fp, struct image *img) 269 | { 270 | gif_bitmap_callback_vt gif_callbacks = { 271 | gif_bitmap_create, 272 | gif_bitmap_destroy, 273 | gif_bitmap_get_buffer, 274 | gif_bitmap_set_opaque, 275 | gif_bitmap_test_opaque, 276 | gif_bitmap_modified 277 | }; 278 | size_t size; 279 | gif_result code; 280 | unsigned char *mem; 281 | gif_animation gif; 282 | int i; 283 | 284 | gif_create(&gif, &gif_callbacks); 285 | if ((mem = file_into_memory(fp, &size)) == NULL) 286 | return false; 287 | 288 | code = gif_initialise(&gif, size, mem); 289 | if (code != GIF_OK && code != GIF_WORKING) 290 | goto error_initialize_failed; 291 | 292 | img->width = gif.width; 293 | img->height = gif.height; 294 | img->channel = BYTES_PER_PIXEL; /* libnsgif always return 4bpp image */ 295 | size = img->width * img->height * img->channel; 296 | 297 | /* read animation gif */ 298 | img->frame_count = (gif.frame_count < MAX_FRAME_NUM) ? gif.frame_count: MAX_FRAME_NUM - 1; 299 | img->loop_count = gif.loop_count; 300 | 301 | for (i = 0; i < img->frame_count; i++) { 302 | code = gif_decode_frame(&gif, i); 303 | if (code != GIF_OK) 304 | goto error_decode_failed; 305 | 306 | if ((img->data[i] = (uint8_t *) ecalloc(1, size)) == NULL) 307 | goto error_decode_failed; 308 | memcpy(img->data[i], gif.frame_image, size); 309 | 310 | img->delay[i] = gif.frames[i].frame_delay; 311 | } 312 | 313 | gif_finalise(&gif); 314 | free(mem); 315 | return true; 316 | 317 | error_decode_failed: 318 | img->frame_count = i; 319 | for (i = 0; i < img->frame_count; i++) { 320 | free(img->data[i]); 321 | img->data[i] = NULL; 322 | } 323 | gif_finalise(&gif); 324 | error_initialize_failed: 325 | free(mem); 326 | return false; 327 | } 328 | 329 | void *bmp_bitmap_create(int width, int height, unsigned int state) 330 | { 331 | (void) state; /* unused */ 332 | return calloc(width * height, BYTES_PER_PIXEL); 333 | } 334 | 335 | unsigned char *bmp_bitmap_get_buffer(void *bitmap) 336 | { 337 | return bitmap; 338 | } 339 | 340 | void bmp_bitmap_destroy(void *bitmap) 341 | { 342 | free(bitmap); 343 | } 344 | 345 | size_t bmp_bitmap_get_bpp(void *bitmap) 346 | { 347 | (void) bitmap; /* unused */ 348 | return BYTES_PER_PIXEL; 349 | } 350 | 351 | bool load_bmp(FILE *fp, struct image *img) 352 | { 353 | bmp_bitmap_callback_vt bmp_callbacks = { 354 | bmp_bitmap_create, 355 | bmp_bitmap_destroy, 356 | bmp_bitmap_get_buffer, 357 | bmp_bitmap_get_bpp 358 | }; 359 | bmp_result code; 360 | size_t size; 361 | unsigned char *mem; 362 | bmp_image bmp; 363 | 364 | bmp_create(&bmp, &bmp_callbacks); 365 | if ((mem = file_into_memory(fp, &size)) == NULL) 366 | return false; 367 | 368 | code = bmp_analyse(&bmp, size, mem); 369 | if (code != BMP_OK) 370 | goto error_analyse_failed; 371 | 372 | code = bmp_decode(&bmp); 373 | if (code != BMP_OK) 374 | goto error_decode_failed; 375 | 376 | img->width = bmp.width; 377 | img->height = bmp.height; 378 | img->channel = BYTES_PER_PIXEL; /* libnsbmp always return 4bpp image */ 379 | 380 | size = img->width * img->height * img->channel; 381 | if ((img->data[0] = (uint8_t *) ecalloc(1, size)) == NULL) 382 | goto error_decode_failed; 383 | memcpy(img->data[0], bmp.bitmap, size); 384 | 385 | bmp_finalise(&bmp); 386 | free(mem); 387 | return true; 388 | 389 | error_decode_failed: 390 | bmp_finalise(&bmp); 391 | error_analyse_failed: 392 | free(mem); 393 | return false; 394 | } 395 | 396 | /* pnm functions */ 397 | inline int getint(FILE *fp) 398 | { 399 | int c, n = 0; 400 | 401 | do { 402 | c = fgetc(fp); 403 | } while (isspace(c)); 404 | 405 | while (isdigit(c)) { 406 | n = n * 10 + c - '0'; 407 | c = fgetc(fp); 408 | } 409 | return n; 410 | } 411 | 412 | inline uint8_t pnm_normalize(int c, int type, int max_value) 413 | { 414 | if (type == 1 || type == 4) 415 | return (c == 0) ? 0: 0xFF; 416 | else 417 | return 0xFF * c / max_value; 418 | } 419 | 420 | bool load_pnm(FILE *fp, struct image *img) 421 | { 422 | int size, type, c, count, max_value = 0; 423 | 424 | if (fgetc(fp) != 'P') 425 | return false; 426 | 427 | type = fgetc(fp) - '0'; 428 | img->channel = (type == 1 || type == 2 || type == 4 || type == 5) ? 1: 429 | (type == 3 || type == 6) ? 3: -1; 430 | 431 | if (img->channel == -1) 432 | return false; 433 | 434 | /* read header */ 435 | while ((c = fgetc(fp)) != EOF) { 436 | if (c == '#') 437 | while ((c = fgetc(fp)) != '\n'); 438 | 439 | if (isspace(c)) 440 | continue; 441 | 442 | if (isdigit(c)) { 443 | ungetc(c, fp); 444 | img->width = getint(fp); 445 | img->height = getint(fp); 446 | if (type != 1 && type != 4) 447 | max_value = getint(fp); 448 | break; 449 | } 450 | } 451 | 452 | size = img->width * img->height * img->channel; 453 | if ((img->data[0] = ecalloc(1, size)) == NULL) 454 | return false; 455 | 456 | /* read data */ 457 | count = 0; 458 | if (1 <= type && type <= 3) { 459 | while ((c = fgetc(fp)) != EOF) { 460 | if (c == '#') 461 | while ((c = fgetc(fp)) != '\n'); 462 | 463 | if (isspace(c)) 464 | continue; 465 | 466 | if (isdigit(c)) { 467 | ungetc(c, fp); 468 | *(img->data[0] + count++) = pnm_normalize(getint(fp), type, max_value); 469 | } 470 | } 471 | } 472 | else { 473 | while ((c = fgetc(fp)) != EOF) 474 | *(img->data[0] + count++) = pnm_normalize(c, type, max_value); 475 | } 476 | 477 | return true; 478 | } 479 | 480 | void init_image(struct image *img) 481 | { 482 | for (int i = 0; i < MAX_FRAME_NUM; i++) { 483 | img->data[i] = NULL; 484 | img->delay[i] = 0; 485 | } 486 | img->width = 0; 487 | img->height = 0; 488 | img->channel = 0; 489 | img->alpha = false; 490 | /* for animation gif */ 491 | img->frame_count = 1; 492 | img->loop_count = 0; 493 | img->current_frame = 0; 494 | img->already_drew = false; 495 | } 496 | 497 | void free_image(struct image *img) 498 | { 499 | for (int i = 0; i < img->frame_count; i++) { 500 | free(img->data[i]); 501 | img->data[i] = NULL; 502 | } 503 | } 504 | 505 | enum filetype_t check_filetype(FILE *fp) 506 | { 507 | /* 508 | JPEG(JFIF): FF D8 509 | PNG : 89 50 4E 47 0D 0A 1A 0A (0x89 'P' 'N' 'G' '\r' '\n' 0x1A '\n') 510 | GIF : 47 49 46 (ASCII 'G' 'I' 'F') 511 | BMP : 42 4D (ASCII 'B' 'M') 512 | PNM : 50 [31|32|33|34|35|36] ('P' ['1' - '6']) 513 | */ 514 | uint8_t header[CHECK_HEADER_SIZE]; 515 | static uint8_t jpeg_header[] = {0xFF, 0xD8}; 516 | static uint8_t png_header[] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; 517 | static uint8_t gif_header[] = {0x47, 0x49, 0x46}; 518 | static uint8_t bmp_header[] = {0x42, 0x4D}; 519 | size_t size; 520 | 521 | if ((size = fread(header, 1, CHECK_HEADER_SIZE, fp)) != CHECK_HEADER_SIZE) { 522 | logging(ERROR, "couldn't read header\n"); 523 | return TYPE_UNKNOWN; 524 | } 525 | fseek(fp, 0L, SEEK_SET); 526 | 527 | if (memcmp(header, jpeg_header, 2) == 0) 528 | return TYPE_JPEG; 529 | else if (memcmp(header, png_header, 8) == 0) 530 | return TYPE_PNG; 531 | else if (memcmp(header, gif_header, 3) == 0) 532 | return TYPE_GIF; 533 | else if (memcmp(header, bmp_header, 2) == 0) 534 | return TYPE_BMP; 535 | else if (header[0] == 'P' && ('0' <= header[1] && header[1] <= '6')) 536 | return TYPE_PNM; 537 | else 538 | return TYPE_UNKNOWN; 539 | } 540 | 541 | bool load_image(const char *file, struct image *img) 542 | { 543 | int i; 544 | enum filetype_t type; 545 | FILE *fp; 546 | 547 | static bool (*loader[])(FILE *fp, struct image *img) = { 548 | [TYPE_JPEG] = load_jpeg, 549 | [TYPE_PNG] = load_png, 550 | [TYPE_GIF] = load_gif, 551 | [TYPE_BMP] = load_bmp, 552 | [TYPE_PNM] = load_pnm, 553 | }; 554 | 555 | if ((fp = efopen(file, "r")) == NULL) 556 | return false; 557 | 558 | if ((type = check_filetype(fp)) == TYPE_UNKNOWN) { 559 | logging(ERROR, "unknown file type: %s\n", file); 560 | goto image_load_error; 561 | } 562 | 563 | if (loader[type](fp, img)) { 564 | img->alpha = (img->channel == 2 || img->channel == 4) ? true: false; 565 | logging(DEBUG, "image width:%d height:%d channel:%d alpha:%s\n", 566 | img->width, img->height, img->channel, (img->alpha) ? "true": "false"); 567 | if (img->frame_count > 1) { 568 | logging(DEBUG, "frame:%d loop:%d\n", img->frame_count, img->loop_count); 569 | for (i = 0; i < img->frame_count; i++) 570 | logging(DEBUG, "delay[%u]:%u\n", i, img->delay[i]); 571 | } 572 | efclose(fp); 573 | return true; 574 | } 575 | 576 | image_load_error: 577 | logging(ERROR, "image load error: %s\n", file); 578 | //init_image(img); 579 | efclose(fp); 580 | return false; 581 | } 582 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | CC ?= gcc 2 | #CC ?= clang 3 | 4 | CFLAGS ?= -Wall -Wextra -std=c99 -pedantic \ 5 | -O3 -pipe -s 6 | #-Og -g -rdynamic #-pg 7 | LDFLAGS ?= -ljpeg -lpng -lsixel 8 | 9 | HDR = stb_image.h libnsgif.h libnsbmp.h \ 10 | sdump.h util.h loader.h image.h 11 | SRC = sdump.c libnsgif.c libnsbmp.c 12 | 13 | DST = sdump 14 | 15 | all: $(DST) 16 | 17 | sdump: sdump.c libnsgif.c libnsbmp.c $(HDR) 18 | $(CC) $(CFLAGS) $(LDFLAGS) libnsgif.c libnsbmp.c $< -o $@ 19 | 20 | clean: 21 | rm -f $(DST) 22 | -------------------------------------------------------------------------------- /scripts/spdf: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | FILE=$1 4 | SDUMP_OPT="-f" 5 | MUDRAW_OPT="-o /dev/stdout -F png" 6 | 7 | INDEX=1 # page origin:1 8 | PAGES=`mutool info $FILE | grep "^Pages:" | cut -d" " -f2 ` 9 | 10 | tput civis 11 | tput clear 12 | 13 | echo "filename:$FILE page:$INDEX" 14 | mudraw $MUDRAW_OPT $FILE $INDEX 2> /dev/null | sdump $SDUMP_OPT 2> /dev/null 15 | 16 | while :; do 17 | read -s -n 1 KEY 18 | 19 | tput clear 20 | 21 | case "$KEY" in 22 | "j") 23 | INDEX=`expr $INDEX + 1`;; 24 | "J") 25 | INDEX=`expr $INDEX + 10`;; 26 | "k") 27 | INDEX=`expr $INDEX - 1`;; 28 | "K") 29 | INDEX=`expr $INDEX - 10`;; 30 | "g") 31 | INDEX=1;; 32 | "G") 33 | INDEX=$PAGES;; 34 | "q") 35 | tput cnorm 36 | exit 0;; 37 | esac 38 | 39 | if test $INDEX -lt "1"; then 40 | INDEX=$PAGES 41 | elif test $INDEX -gt $PAGES; then 42 | INDEX=1 43 | fi 44 | 45 | echo "filename:$FILE page:$INDEX" 46 | mudraw $MUDRAW_OPT $FILE $INDEX 2> /dev/null | sdump $SDUMP_OPT 2> /dev/null 47 | done 48 | -------------------------------------------------------------------------------- /scripts/surl: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SDUMP_OPT="-f" 4 | WGET_OPT="-q -O -" 5 | 6 | wget $WGET_OPT $1 | sdump $SDUMP_OPT 2> /dev/null 7 | -------------------------------------------------------------------------------- /scripts/sviewer: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SDUMP_OPT="-f" 4 | FILES=($@) 5 | INDEX=0 6 | 7 | #echo "file num: ${#FILES[@]}" 8 | if test ${#FILES[@]} -le "0"; then 9 | exit 0 10 | fi 11 | 12 | tput civis # set cursor hidden 13 | tput clear # clear screen and move to (0, 0) 14 | 15 | echo "filename:${FILES[$INDEX]}" 16 | #echo -ne "\ekfilename:${FILES[$INDEX]}\e\\" # xterm like title change 17 | sdump "$SDUMP_OPT" "${FILES[$INDEX]}" 2> /dev/null 18 | 19 | while :; do 20 | read -s -n 1 KEY 21 | 22 | tput clear # clear screen and move to (0, 0) 23 | 24 | case "$KEY" in 25 | "j") 26 | INDEX=`expr $INDEX + 1`;; 27 | "J") 28 | INDEX=`expr $INDEX + 10`;; 29 | "k") 30 | INDEX=`expr $INDEX - 1`;; 31 | "K") 32 | INDEX=`expr $INDEX - 10`;; 33 | "q") 34 | tput cnorm # set cursor visible 35 | exit 0;; 36 | esac 37 | 38 | if test $INDEX -lt "0"; then 39 | INDEX=`expr ${#FILES[@]} - 1` 40 | elif test $INDEX -ge "${#FILES[@]}"; then 41 | INDEX=0 42 | fi 43 | 44 | echo "filename:${FILES[$INDEX]}" 45 | #echo -ne "\ekfilename:${FILES[$INDEX]}\e\\" # xterm like title change 46 | sdump "$SDUMP_OPT" "${FILES[$INDEX]}" 2> /dev/null 47 | done 48 | -------------------------------------------------------------------------------- /sdump.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE for licence details. */ 2 | #include "sdump.h" 3 | #include "util.h" 4 | #include "loader.h" 5 | #include "image.h" 6 | #include "sixel.h" 7 | 8 | void usage() 9 | { 10 | printf("usage:\n" 11 | "\tsdump [-h] [-f] [-r angle] image\n" 12 | "\tcat image | sdump\n" 13 | "\twget -O - image_url | sdump\n" 14 | "options:\n" 15 | "\t-h: show this help\n" 16 | "\t-f: fit image to display\n" 17 | "\t-r: rotate image (90/180/270)\n" 18 | ); 19 | } 20 | 21 | void remove_temp_file() 22 | { 23 | extern char temp_file[PATH_MAX]; /* global */ 24 | remove(temp_file); 25 | } 26 | 27 | char *make_temp_file(const char *template) 28 | { 29 | extern char temp_file[PATH_MAX]; /* global */ 30 | int fd; 31 | ssize_t size, file_size = 0; 32 | char buf[BUFSIZE], *env; 33 | 34 | /* stdin is tty or not */ 35 | if (isatty(STDIN_FILENO)) { 36 | logging(ERROR, "stdin is neither pipe nor redirect\n"); 37 | return NULL; 38 | } 39 | 40 | /* prepare temp file */ 41 | memset(temp_file, 0, BUFSIZE); 42 | if ((env = getenv("TMPDIR")) != NULL) { 43 | snprintf(temp_file, BUFSIZE, "%s/%s", env, template); 44 | } else { 45 | snprintf(temp_file, BUFSIZE, "/tmp/%s", template); 46 | } 47 | 48 | if ((fd = emkstemp(temp_file)) < 0) 49 | return NULL; 50 | logging(DEBUG, "tmp file:%s\n", temp_file); 51 | 52 | /* register cleanup function */ 53 | if (atexit(remove_temp_file)) 54 | logging(ERROR, "atexit() failed\nmaybe temporary file remains...\n"); 55 | 56 | /* read data */ 57 | while ((size = read(STDIN_FILENO, buf, BUFSIZE)) > 0) { 58 | write(fd, buf, size); 59 | file_size += size; 60 | } 61 | eclose(fd); 62 | 63 | if (file_size == 0) { 64 | logging(ERROR, "stdin is empty\n"); 65 | return NULL; 66 | } 67 | 68 | return temp_file; 69 | } 70 | 71 | void cleanup(struct sixel_t *sixel, struct image *img) 72 | { 73 | sixel_die(sixel); 74 | free_image(img); 75 | } 76 | 77 | int main(int argc, char **argv) 78 | { 79 | const char *template = "sdump.XXXXXX"; 80 | char *file; 81 | bool resize = false; 82 | int angle = 0, opt; 83 | struct winsize ws; 84 | struct image img; 85 | struct tty_t tty = { 86 | .cell_width = CELL_WIDTH, .cell_height = CELL_HEIGHT, 87 | };; 88 | struct sixel_t sixel = { 89 | .context = NULL, .dither = NULL, 90 | }; 91 | 92 | /* check arg */ 93 | while ((opt = getopt(argc, argv, "hfr:")) != -1) { 94 | switch (opt) { 95 | case 'h': 96 | usage(); 97 | return EXIT_SUCCESS; 98 | case 'f': 99 | resize = true; 100 | break; 101 | case 'r': 102 | angle = str2num(optarg); 103 | break; 104 | default: 105 | break; 106 | } 107 | } 108 | 109 | /* open file */ 110 | if (optind < argc) 111 | file = argv[optind]; 112 | else 113 | file = make_temp_file(template); 114 | 115 | if (file == NULL) { 116 | logging(FATAL, "input file not found\n"); 117 | usage(); 118 | return EXIT_FAILURE; 119 | } 120 | 121 | /* init */ 122 | init_image(&img); 123 | 124 | if (load_image(file, &img) == false) { 125 | logging(FATAL, "couldn't load image\n"); 126 | return EXIT_FAILURE; 127 | } 128 | 129 | if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws)) { 130 | logging(ERROR, "ioctl: TIOCGWINSZ failed\n"); 131 | tty.width = tty.cell_width * 80; 132 | tty.height = tty.cell_height * 24; 133 | } else { 134 | tty.width = tty.cell_width * ws.ws_col; 135 | tty.height = tty.cell_height * ws.ws_row; 136 | } 137 | 138 | /* rotate/resize and draw */ 139 | /* TODO: support color reduction for 8bpp mode */ 140 | if (angle != 0) 141 | rotate_image(&img, angle, true); 142 | 143 | if (resize) 144 | resize_image(&img, tty.width, tty.height, true); 145 | 146 | /* sixel */ 147 | if (!sixel_init(&tty, &sixel, &img)) 148 | goto error_occured; 149 | sixel_write(&tty, &sixel, &img); 150 | 151 | /* cleanup resource */ 152 | cleanup(&sixel, &img); 153 | return EXIT_SUCCESS; 154 | 155 | error_occured: 156 | cleanup(&sixel, &img); 157 | return EXIT_FAILURE;; 158 | } 159 | -------------------------------------------------------------------------------- /sdump.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE for licence details. */ 2 | #define _XOPEN_SOURCE 600 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | enum { 23 | VERBOSE = false, 24 | BUFSIZE = 1024, 25 | CELL_WIDTH = 16, 26 | CELL_HEIGHT = 8, 27 | }; 28 | 29 | struct tty_t { 30 | int fd; /* fd of current controlling terminal */ 31 | int width, height; /* terminal size (by pixel) */ 32 | int cell_width, cell_height; /* cell_size (by pixel) */ 33 | }; 34 | 35 | char temp_file[PATH_MAX]; 36 | -------------------------------------------------------------------------------- /sixel.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE for licence details. */ 2 | #include 3 | 4 | enum { 5 | SIXEL_COLORS = 256, 6 | SIXEL_BPP = 3, 7 | }; 8 | 9 | struct sixel_t { 10 | sixel_output_t *context; 11 | sixel_dither_t *dither; 12 | }; 13 | 14 | int sixel_write_callback(char *data, int size, void *priv) 15 | { 16 | struct tty_t *tty = (struct tty_t *) priv; 17 | char *ptr; 18 | ssize_t wsize, left; 19 | 20 | logging(DEBUG, "callback() data size:%d\n", size); 21 | 22 | ptr = data; 23 | left = size; 24 | 25 | while (ptr < (data + size)) { 26 | wsize = ewrite(tty->fd, ptr, left); 27 | ptr += wsize; 28 | left -= wsize; 29 | } 30 | 31 | return true; 32 | } 33 | 34 | bool sixel_init(struct tty_t *tty, struct sixel_t *sixel, struct image *img) 35 | { 36 | /* XXX: libsixel only allows 3 bytes per pixel image, 37 | we should convert bpp when bpp is 1 or 2 or 4 */ 38 | if (get_image_channel(img) != SIXEL_BPP) 39 | normalize_bpp(img, SIXEL_BPP, true); 40 | 41 | if ((sixel->dither = sixel_dither_create(SIXEL_COLORS)) == NULL) { 42 | logging(ERROR, "couldn't create dither\n"); 43 | return false; 44 | } 45 | 46 | /* XXX: use first frame for dither initialize */ 47 | if (sixel_dither_initialize(sixel->dither, get_current_frame(img), 48 | get_image_width(img), get_image_height(img), 49 | SIXEL_BPP, LARGE_AUTO, REP_AUTO, QUALITY_AUTO) != 0) { 50 | logging(ERROR, "couldn't initialize dither\n"); 51 | sixel_dither_unref(sixel->dither); 52 | return false; 53 | } 54 | sixel_dither_set_diffusion_type(sixel->dither, DIFFUSE_AUTO); 55 | //sixel_dither_set_diffusion_type(sixel->dither, DIFFUSE_NONE); 56 | 57 | if ((sixel->context = sixel_output_create(sixel_write_callback, (void *) tty)) == NULL) { 58 | logging(ERROR, "couldn't create sixel context\n"); 59 | return false; 60 | } 61 | sixel_output_set_8bit_availability(sixel->context, CSIZE_7BIT); 62 | 63 | return true; 64 | } 65 | 66 | void sixel_die(struct sixel_t *sixel) 67 | { 68 | if (sixel->dither) 69 | sixel_dither_unref(sixel->dither); 70 | 71 | if (sixel->context) 72 | sixel_output_unref(sixel->context); 73 | } 74 | 75 | void sixel_write(struct tty_t *tty, struct sixel_t *sixel, struct image *img) 76 | { 77 | (void) tty; 78 | 79 | sixel_encode(get_current_frame(img), get_image_width(img), get_image_height(img), 80 | get_image_channel(img), sixel->dither, sixel->context); 81 | } 82 | -------------------------------------------------------------------------------- /static/libsixel/config.h: -------------------------------------------------------------------------------- 1 | /* config.h. Generated from config.h.in by configure. */ 2 | /* config.h.in. Generated from configure.ac by autoheader. */ 3 | 4 | /* Define to 1 if you have the `calloc' function. */ 5 | #define HAVE_CALLOC 1 6 | 7 | /* Define to 1 if you have the `clock' function. */ 8 | #define HAVE_CLOCK 1 9 | 10 | /* Define to 1 if you have the declaration of `gdImageCreateFromBmpPtr', and 11 | to 0 if you don't. */ 12 | /* #undef HAVE_DECL_GDIMAGECREATEFROMBMPPTR */ 13 | 14 | /* Define to 1 if you have the declaration of `gdImageCreateFromGd2Ptr', and 15 | to 0 if you don't. */ 16 | /* #undef HAVE_DECL_GDIMAGECREATEFROMGD2PTR */ 17 | 18 | /* Define to 1 if you have the declaration of `gdImageCreateFromGifPtr', and 19 | to 0 if you don't. */ 20 | /* #undef HAVE_DECL_GDIMAGECREATEFROMGIFPTR */ 21 | 22 | /* Define to 1 if you have the declaration of `gdImageCreateFromJpegPtr', and 23 | to 0 if you don't. */ 24 | /* #undef HAVE_DECL_GDIMAGECREATEFROMJPEGPTR */ 25 | 26 | /* Define to 1 if you have the declaration of `gdImageCreateFromJpegPtrEx', 27 | and to 0 if you don't. */ 28 | /* #undef HAVE_DECL_GDIMAGECREATEFROMJPEGPTREX */ 29 | 30 | /* Define to 1 if you have the declaration of `gdImageCreateFromPngPtr', and 31 | to 0 if you don't. */ 32 | /* #undef HAVE_DECL_GDIMAGECREATEFROMPNGPTR */ 33 | 34 | /* Define to 1 if you have the declaration of `gdImageCreateFromTgaPtr', and 35 | to 0 if you don't. */ 36 | /* #undef HAVE_DECL_GDIMAGECREATEFROMTGAPTR */ 37 | 38 | /* Define to 1 if you have the declaration of `gdImageCreateFromTiffPtr', and 39 | to 0 if you don't. */ 40 | /* #undef HAVE_DECL_GDIMAGECREATEFROMTIFFPTR */ 41 | 42 | /* Define to 1 if you have the declaration of `gdImageCreateFromWBMPPtr', and 43 | to 0 if you don't. */ 44 | /* #undef HAVE_DECL_GDIMAGECREATEFROMWBMPPTR */ 45 | 46 | /* Define to 1 if you have the declaration of `gdImagePaletteToTrueColor', and 47 | to 0 if you don't. */ 48 | /* #undef HAVE_DECL_GDIMAGEPALETTETOTRUECOLOR */ 49 | 50 | /* Define to 1 if you have the declaration of `SIGHUP', and to 0 if you don't. 51 | */ 52 | #define HAVE_DECL_SIGHUP 1 53 | 54 | /* Define to 1 if you have the declaration of `SIGINT', and to 0 if you don't. 55 | */ 56 | #define HAVE_DECL_SIGINT 1 57 | 58 | /* Define to 1 if you have the declaration of `SIGTERM', and to 0 if you 59 | don't. */ 60 | #define HAVE_DECL_SIGTERM 1 61 | 62 | /* Define to 1 if you have the header file. */ 63 | #define HAVE_DLFCN_H 1 64 | 65 | /* Define to 1 if you have the header file. */ 66 | #define HAVE_ERRNO_H 1 67 | 68 | /* Define to 1 if you have the header file. */ 69 | #define HAVE_FCNTL_H 1 70 | 71 | /* Define to 1 if the system has the `deprecated' function attribute */ 72 | #define HAVE_FUNC_ATTRIBUTE_DEPRECATED 1 73 | 74 | /* whether gd is available */ 75 | /* #undef HAVE_GD */ 76 | 77 | /* whether gdk-pixbuf2 is available */ 78 | /* #undef HAVE_GDK_PIXBUF2 */ 79 | 80 | /* whether getopt is avilable */ 81 | /* #undef HAVE_GETOPT */ 82 | 83 | /* Define to 1 if you have the header file. */ 84 | #define HAVE_GETOPT_H 1 85 | 86 | /* whether getopt_long is avilable */ 87 | #define HAVE_GETOPT_LONG 1 88 | 89 | /* Define to 1 if you have the header file. */ 90 | #define HAVE_INTTYPES_H 1 91 | 92 | /* whether libcurl is available */ 93 | /* #undef HAVE_LIBCURL */ 94 | 95 | /* Define to 1 if you have the `iconv' library (-liconv). */ 96 | /* #undef HAVE_LIBICONV */ 97 | 98 | /* Define to 1 if your system has a GNU libc compatible `malloc' function, and 99 | to 0 otherwise. */ 100 | #define HAVE_MALLOC 1 101 | 102 | /* Define to 1 if you have the `memcpy' function. */ 103 | #define HAVE_MEMCPY 1 104 | 105 | /* Define to 1 if you have the `memmove' function. */ 106 | #define HAVE_MEMMOVE 1 107 | 108 | /* Define to 1 if you have the header file. */ 109 | #define HAVE_MEMORY_H 1 110 | 111 | /* Define to 1 if you have the `pow' function. */ 112 | /* #undef HAVE_POW */ 113 | 114 | /* Define to 1 if your system has a GNU libc compatible `realloc' function, 115 | and to 0 otherwise. */ 116 | #define HAVE_REALLOC 1 117 | 118 | /* Define to 1 if you have the `setmode' function. */ 119 | /* #undef HAVE_SETMODE */ 120 | 121 | /* Define to 1 if you have the `signal' function. */ 122 | #define HAVE_SIGNAL 1 123 | 124 | /* Define to 1 if you have the header file. */ 125 | #define HAVE_SIGNAL_H 1 126 | 127 | /* Define to 1 if you have the header file. */ 128 | #define HAVE_STDINT_H 1 129 | 130 | /* Define to 1 if you have the header file. */ 131 | #define HAVE_STDLIB_H 1 132 | 133 | /* Define to 1 if you have the header file. */ 134 | #define HAVE_STRINGS_H 1 135 | 136 | /* Define to 1 if you have the header file. */ 137 | #define HAVE_STRING_H 1 138 | 139 | /* Define to 1 if you have the `strtol' function. */ 140 | #define HAVE_STRTOL 1 141 | 142 | /* Define to 1 if you have the header file. */ 143 | #define HAVE_SYS_SIGNAL_H 1 144 | 145 | /* Define to 1 if you have the header file. */ 146 | #define HAVE_SYS_STAT_H 1 147 | 148 | /* Define to 1 if you have the header file. */ 149 | #define HAVE_SYS_TIME_H 1 150 | 151 | /* Define to 1 if you have the header file. */ 152 | #define HAVE_SYS_TYPES_H 1 153 | 154 | /* Define to 1 if you have the header file. */ 155 | #define HAVE_SYS_UNISTD_H 1 156 | 157 | /* Define to 1 if you have the header file. */ 158 | #define HAVE_TIME_H 1 159 | 160 | /* Define to 1 if you have the header file. */ 161 | #define HAVE_UNISTD_H 1 162 | 163 | /* Define to 1 if you have the `usleep' function. */ 164 | #define HAVE_USLEEP 1 165 | 166 | /* Define to 1 if the system has the `deprecated' variable attribute */ 167 | #define HAVE_VAR_ATTRIBUTE_DEPRECATED 1 168 | 169 | /* Define to 1 if you have the `_setmode' function. */ 170 | /* #undef HAVE__SETMODE */ 171 | 172 | /* Define to the sub-directory in which libtool stores uninstalled libraries. 173 | */ 174 | #define LT_OBJDIR ".libs/" 175 | 176 | /* Name of package */ 177 | #define PACKAGE "sixel" 178 | 179 | /* Define to the address where bug reports for this package should be sent. */ 180 | #define PACKAGE_BUGREPORT "user@zuse.jp" 181 | 182 | /* Define to the full name of this package. */ 183 | #define PACKAGE_NAME "sixel" 184 | 185 | /* Define to the full name and version of this package. */ 186 | #define PACKAGE_STRING "sixel 1.0.3" 187 | 188 | /* Define to the one symbol short name of this package. */ 189 | #define PACKAGE_TARNAME "sixel" 190 | 191 | /* Define to the home page for this package. */ 192 | #define PACKAGE_URL "" 193 | 194 | /* Define to the version of this package. */ 195 | #define PACKAGE_VERSION "1.0.3" 196 | 197 | /* Define to 1 if you have the ANSI C header files. */ 198 | #define STDC_HEADERS 1 199 | 200 | /* Version number of package */ 201 | #define VERSION "1.0.3" 202 | 203 | /* Define to rpl_malloc if the replacement function should be used. */ 204 | /* #undef malloc */ 205 | 206 | /* Define to rpl_realloc if the replacement function should be used. */ 207 | /* #undef realloc */ 208 | -------------------------------------------------------------------------------- /static/libsixel/dither.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Hayaki Saito 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | * this software and associated documentation files (the "Software"), to deal in 6 | * the Software without restriction, including without limitation the rights to 7 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | * the Software, and to permit persons to whom the Software is furnished to do so, 9 | * subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | #include "config.h" 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #if defined(HAVE_INTTYPES_H) 31 | # include 32 | #endif 33 | 34 | #include "dither.h" 35 | #include "quant.h" 36 | #include "sixel.h" 37 | 38 | 39 | static const unsigned char pal_mono_dark[] = { 40 | 0x00, 0x00, 0x00, 0xff, 0xff, 0xff 41 | }; 42 | 43 | static const unsigned char pal_mono_light[] = { 44 | 0xff, 0xff, 0xff, 0x00, 0x00, 0x00 45 | }; 46 | 47 | static const unsigned char pal_xterm256[] = { 48 | 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0x00, 49 | 0x00, 0x00, 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x80, 0xc0, 0xc0, 0xc0, 50 | 0x80, 0x80, 0x80, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00, 51 | 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 52 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x87, 0x00, 0x00, 0xaf, 53 | 0x00, 0x00, 0xd7, 0x00, 0x00, 0xff, 0x00, 0x5f, 0x00, 0x00, 0x5f, 0x5f, 54 | 0x00, 0x5f, 0x87, 0x00, 0x5f, 0xaf, 0x00, 0x5f, 0xd7, 0x00, 0x5f, 0xff, 55 | 0x00, 0x87, 0x00, 0x00, 0x87, 0x5f, 0x00, 0x87, 0x87, 0x00, 0x87, 0xaf, 56 | 0x00, 0x87, 0xd7, 0x00, 0x87, 0xff, 0x00, 0xaf, 0x00, 0x00, 0xaf, 0x5f, 57 | 0x00, 0xaf, 0x87, 0x00, 0xaf, 0xaf, 0x00, 0xaf, 0xd7, 0x00, 0xaf, 0xff, 58 | 0x00, 0xd7, 0x00, 0x00, 0xd7, 0x5f, 0x00, 0xd7, 0x87, 0x00, 0xd7, 0xaf, 59 | 0x00, 0xd7, 0xd7, 0x00, 0xd7, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0x5f, 60 | 0x00, 0xff, 0x87, 0x00, 0xff, 0xaf, 0x00, 0xff, 0xd7, 0x00, 0xff, 0xff, 61 | 0x5f, 0x00, 0x00, 0x5f, 0x00, 0x5f, 0x5f, 0x00, 0x87, 0x5f, 0x00, 0xaf, 62 | 0x5f, 0x00, 0xd7, 0x5f, 0x00, 0xff, 0x5f, 0x5f, 0x00, 0x5f, 0x5f, 0x5f, 63 | 0x5f, 0x5f, 0x87, 0x5f, 0x5f, 0xaf, 0x5f, 0x5f, 0xd7, 0x5f, 0x5f, 0xff, 64 | 0x5f, 0x87, 0x00, 0x5f, 0x87, 0x5f, 0x5f, 0x87, 0x87, 0x5f, 0x87, 0xaf, 65 | 0x5f, 0x87, 0xd7, 0x5f, 0x87, 0xff, 0x5f, 0xaf, 0x00, 0x5f, 0xaf, 0x5f, 66 | 0x5f, 0xaf, 0x87, 0x5f, 0xaf, 0xaf, 0x5f, 0xaf, 0xd7, 0x5f, 0xaf, 0xff, 67 | 0x5f, 0xd7, 0x00, 0x5f, 0xd7, 0x5f, 0x5f, 0xd7, 0x87, 0x5f, 0xd7, 0xaf, 68 | 0x5f, 0xd7, 0xd7, 0x5f, 0xd7, 0xff, 0x5f, 0xff, 0x00, 0x5f, 0xff, 0x5f, 69 | 0x5f, 0xff, 0x87, 0x5f, 0xff, 0xaf, 0x5f, 0xff, 0xd7, 0x5f, 0xff, 0xff, 70 | 0x87, 0x00, 0x00, 0x87, 0x00, 0x5f, 0x87, 0x00, 0x87, 0x87, 0x00, 0xaf, 71 | 0x87, 0x00, 0xd7, 0x87, 0x00, 0xff, 0x87, 0x5f, 0x00, 0x87, 0x5f, 0x5f, 72 | 0x87, 0x5f, 0x87, 0x87, 0x5f, 0xaf, 0x87, 0x5f, 0xd7, 0x87, 0x5f, 0xff, 73 | 0x87, 0x87, 0x00, 0x87, 0x87, 0x5f, 0x87, 0x87, 0x87, 0x87, 0x87, 0xaf, 74 | 0x87, 0x87, 0xd7, 0x87, 0x87, 0xff, 0x87, 0xaf, 0x00, 0x87, 0xaf, 0x5f, 75 | 0x87, 0xaf, 0x87, 0x87, 0xaf, 0xaf, 0x87, 0xaf, 0xd7, 0x87, 0xaf, 0xff, 76 | 0x87, 0xd7, 0x00, 0x87, 0xd7, 0x5f, 0x87, 0xd7, 0x87, 0x87, 0xd7, 0xaf, 77 | 0x87, 0xd7, 0xd7, 0x87, 0xd7, 0xff, 0x87, 0xff, 0x00, 0x87, 0xff, 0x5f, 78 | 0x87, 0xff, 0x87, 0x87, 0xff, 0xaf, 0x87, 0xff, 0xd7, 0x87, 0xff, 0xff, 79 | 0xaf, 0x00, 0x00, 0xaf, 0x00, 0x5f, 0xaf, 0x00, 0x87, 0xaf, 0x00, 0xaf, 80 | 0xaf, 0x00, 0xd7, 0xaf, 0x00, 0xff, 0xaf, 0x5f, 0x00, 0xaf, 0x5f, 0x5f, 81 | 0xaf, 0x5f, 0x87, 0xaf, 0x5f, 0xaf, 0xaf, 0x5f, 0xd7, 0xaf, 0x5f, 0xff, 82 | 0xaf, 0x87, 0x00, 0xaf, 0x87, 0x5f, 0xaf, 0x87, 0x87, 0xaf, 0x87, 0xaf, 83 | 0xaf, 0x87, 0xd7, 0xaf, 0x87, 0xff, 0xaf, 0xaf, 0x00, 0xaf, 0xaf, 0x5f, 84 | 0xaf, 0xaf, 0x87, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xd7, 0xaf, 0xaf, 0xff, 85 | 0xaf, 0xd7, 0x00, 0xaf, 0xd7, 0x5f, 0xaf, 0xd7, 0x87, 0xaf, 0xd7, 0xaf, 86 | 0xaf, 0xd7, 0xd7, 0xaf, 0xd7, 0xff, 0xaf, 0xff, 0x00, 0xaf, 0xff, 0x5f, 87 | 0xaf, 0xff, 0x87, 0xaf, 0xff, 0xaf, 0xaf, 0xff, 0xd7, 0xaf, 0xff, 0xff, 88 | 0xd7, 0x00, 0x00, 0xd7, 0x00, 0x5f, 0xd7, 0x00, 0x87, 0xd7, 0x00, 0xaf, 89 | 0xd7, 0x00, 0xd7, 0xd7, 0x00, 0xff, 0xd7, 0x5f, 0x00, 0xd7, 0x5f, 0x5f, 90 | 0xd7, 0x5f, 0x87, 0xd7, 0x5f, 0xaf, 0xd7, 0x5f, 0xd7, 0xd7, 0x5f, 0xff, 91 | 0xd7, 0x87, 0x00, 0xd7, 0x87, 0x5f, 0xd7, 0x87, 0x87, 0xd7, 0x87, 0xaf, 92 | 0xd7, 0x87, 0xd7, 0xd7, 0x87, 0xff, 0xd7, 0xaf, 0x00, 0xd7, 0xaf, 0x5f, 93 | 0xd7, 0xaf, 0x87, 0xd7, 0xaf, 0xaf, 0xd7, 0xaf, 0xd7, 0xd7, 0xaf, 0xff, 94 | 0xd7, 0xd7, 0x00, 0xd7, 0xd7, 0x5f, 0xd7, 0xd7, 0x87, 0xd7, 0xd7, 0xaf, 95 | 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xff, 0xd7, 0xff, 0x00, 0xd7, 0xff, 0x5f, 96 | 0xd7, 0xff, 0x87, 0xd7, 0xff, 0xaf, 0xd7, 0xff, 0xd7, 0xd7, 0xff, 0xff, 97 | 0xff, 0x00, 0x00, 0xff, 0x00, 0x5f, 0xff, 0x00, 0x87, 0xff, 0x00, 0xaf, 98 | 0xff, 0x00, 0xd7, 0xff, 0x00, 0xff, 0xff, 0x5f, 0x00, 0xff, 0x5f, 0x5f, 99 | 0xff, 0x5f, 0x87, 0xff, 0x5f, 0xaf, 0xff, 0x5f, 0xd7, 0xff, 0x5f, 0xff, 100 | 0xff, 0x87, 0x00, 0xff, 0x87, 0x5f, 0xff, 0x87, 0x87, 0xff, 0x87, 0xaf, 101 | 0xff, 0x87, 0xd7, 0xff, 0x87, 0xff, 0xff, 0xaf, 0x00, 0xff, 0xaf, 0x5f, 102 | 0xff, 0xaf, 0x87, 0xff, 0xaf, 0xaf, 0xff, 0xaf, 0xd7, 0xff, 0xaf, 0xff, 103 | 0xff, 0xd7, 0x00, 0xff, 0xd7, 0x5f, 0xff, 0xd7, 0x87, 0xff, 0xd7, 0xaf, 104 | 0xff, 0xd7, 0xd7, 0xff, 0xd7, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0x5f, 105 | 0xff, 0xff, 0x87, 0xff, 0xff, 0xaf, 0xff, 0xff, 0xd7, 0xff, 0xff, 0xff, 106 | 0x08, 0x08, 0x08, 0x12, 0x12, 0x12, 0x1c, 0x1c, 0x1c, 0x26, 0x26, 0x26, 107 | 0x30, 0x30, 0x30, 0x3a, 0x3a, 0x3a, 0x44, 0x44, 0x44, 0x4e, 0x4e, 0x4e, 108 | 0x58, 0x58, 0x58, 0x62, 0x62, 0x62, 0x6c, 0x6c, 0x6c, 0x76, 0x76, 0x76, 109 | 0x80, 0x80, 0x80, 0x8a, 0x8a, 0x8a, 0x94, 0x94, 0x94, 0x9e, 0x9e, 0x9e, 110 | 0xa8, 0xa8, 0xa8, 0xb2, 0xb2, 0xb2, 0xbc, 0xbc, 0xbc, 0xc6, 0xc6, 0xc6, 111 | 0xd0, 0xd0, 0xd0, 0xda, 0xda, 0xda, 0xe4, 0xe4, 0xe4, 0xee, 0xee, 0xee, 112 | }; 113 | 114 | 115 | sixel_dither_t * 116 | sixel_dither_create(int ncolors) 117 | { 118 | sixel_dither_t *dither; 119 | int headsize; 120 | int datasize; 121 | int wholesize; 122 | 123 | if (ncolors > SIXEL_PALETTE_MAX) { 124 | ncolors = 256; 125 | } else if (ncolors < 2) { 126 | ncolors = 2; 127 | } 128 | headsize = sizeof(sixel_dither_t); 129 | datasize = ncolors * 3; 130 | wholesize = headsize + datasize;// + cachesize; 131 | 132 | dither = malloc(wholesize); 133 | if (dither == NULL) { 134 | return NULL; 135 | } 136 | dither->ref = 1; 137 | dither->palette = (unsigned char*)(dither + 1); 138 | dither->cachetable = NULL; 139 | dither->reqcolors = ncolors; 140 | dither->ncolors = ncolors; 141 | dither->origcolors = (-1); 142 | dither->keycolor = (-1); 143 | dither->optimized = 0; 144 | dither->method_for_largest = LARGE_NORM; 145 | dither->method_for_rep = REP_CENTER_BOX; 146 | dither->method_for_diffuse = DIFFUSE_FS; 147 | dither->quality_mode = QUALITY_LOW; 148 | 149 | return dither; 150 | } 151 | 152 | 153 | void 154 | sixel_dither_destroy(sixel_dither_t *dither) 155 | { 156 | if (dither->cachetable) { 157 | free(dither->cachetable); 158 | } 159 | free(dither); 160 | } 161 | 162 | 163 | void 164 | sixel_dither_ref(sixel_dither_t *dither) 165 | { 166 | /* TODO: be thread safe */ 167 | ++dither->ref; 168 | } 169 | 170 | 171 | void 172 | sixel_dither_unref(sixel_dither_t *dither) 173 | { 174 | /* TODO: be thread safe */ 175 | if (dither != NULL && --dither->ref == 0) { 176 | sixel_dither_destroy(dither); 177 | } 178 | } 179 | 180 | 181 | sixel_dither_t * 182 | sixel_dither_get(int builtin_dither) 183 | { 184 | unsigned char *palette; 185 | int ncolors; 186 | int keycolor; 187 | sixel_dither_t *dither; 188 | 189 | switch (builtin_dither) { 190 | case BUILTIN_MONO_DARK: 191 | ncolors = 2; 192 | palette = (unsigned char *)pal_mono_dark; 193 | keycolor = 0; 194 | break; 195 | case BUILTIN_MONO_LIGHT: 196 | ncolors = 2; 197 | palette = (unsigned char *)pal_mono_light; 198 | keycolor = 0; 199 | break; 200 | case BUILTIN_XTERM16: 201 | ncolors = 16; 202 | palette = (unsigned char *)pal_xterm256; 203 | keycolor = (-1); 204 | break; 205 | case BUILTIN_XTERM256: 206 | ncolors = 256; 207 | palette = (unsigned char *)pal_xterm256; 208 | keycolor = (-1); 209 | break; 210 | default: 211 | return NULL; 212 | } 213 | 214 | dither = sixel_dither_create(ncolors); 215 | dither->palette = palette; 216 | dither->keycolor = keycolor; 217 | dither->optimized = 1; 218 | 219 | return dither; 220 | } 221 | 222 | 223 | int 224 | sixel_dither_initialize(sixel_dither_t *dither, unsigned char *data, 225 | int width, int height, int depth, 226 | int method_for_largest, int method_for_rep, 227 | int quality_mode) 228 | { 229 | unsigned char *buf; 230 | 231 | buf = LSQ_MakePalette(data, width, height, depth, 232 | dither->reqcolors, &dither->ncolors, 233 | &dither->origcolors, 234 | dither->method_for_largest, 235 | dither->method_for_rep, 236 | dither->quality_mode); 237 | if (buf == NULL) { 238 | return (-1); 239 | } 240 | memcpy(dither->palette, buf, dither->ncolors * depth); 241 | free(buf); 242 | 243 | dither->optimized = 1; 244 | if (dither->origcolors <= dither->ncolors) { 245 | dither->method_for_diffuse = DIFFUSE_NONE; 246 | } 247 | 248 | return 0; 249 | } 250 | 251 | 252 | void 253 | sixel_dither_set_diffusion_type(sixel_dither_t *dither, int method_for_diffuse) 254 | { 255 | dither->method_for_diffuse = method_for_diffuse; 256 | } 257 | 258 | 259 | int 260 | sixel_dither_get_num_of_palette_colors(sixel_dither_t *dither) 261 | { 262 | return dither->ncolors; 263 | } 264 | 265 | 266 | int 267 | sixel_dither_get_num_of_histgram_colors(sixel_dither_t *dither) 268 | { 269 | return dither->origcolors; 270 | } 271 | 272 | 273 | unsigned char * 274 | sixel_dither_get_palette(sixel_dither_t /* in */ *dither) /* dither context object */ 275 | { 276 | return dither->palette; 277 | } 278 | 279 | 280 | int 281 | sixel_apply_palette(sixel_image_t *im) 282 | { 283 | int ret; 284 | unsigned char *src; 285 | int bufsize; 286 | int cachesize; 287 | sixel_dither_t *dither; 288 | 289 | dither = im->dither; 290 | src = im->pixels; 291 | 292 | if (im->borrowed) { 293 | bufsize = im->sx * im->sy * sizeof(unsigned char); 294 | im->pixels = malloc(bufsize); 295 | if (im->pixels == NULL) { 296 | return (-1); 297 | } 298 | im->borrowed = 0; 299 | } 300 | 301 | if (dither->cachetable == NULL && dither->optimized) { 302 | if (dither->palette != pal_mono_dark && dither->palette != pal_mono_light) { 303 | cachesize = (1 << 3 * 5) * sizeof(unsigned short); 304 | #if HAVE_CALLOC 305 | dither->cachetable = calloc(cachesize, 1); 306 | #else 307 | dither->cachetable = malloc(cachesize); 308 | memset(dither->cachetable, 0, cachesize); 309 | #endif 310 | } 311 | } 312 | 313 | ret = LSQ_ApplyPalette(src, im->sx, im->sy, 3, 314 | dither->palette, 315 | dither->ncolors, 316 | dither->method_for_diffuse, 317 | dither->optimized, 318 | dither->cachetable, 319 | im->pixels); 320 | 321 | if (ret != 0) { 322 | return ret; 323 | } 324 | 325 | return 0; 326 | } 327 | 328 | /* emacs, -*- Mode: C; tab-width: 4; indent-tabs-mode: nil -*- */ 329 | /* vim: set expandtab ts=4 : */ 330 | /* EOF */ 331 | -------------------------------------------------------------------------------- /static/libsixel/dither.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Hayaki Saito 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | * this software and associated documentation files (the "Software"), to deal in 6 | * the Software without restriction, including without limitation the rights to 7 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | * the Software, and to permit persons to whom the Software is furnished to do so, 9 | * subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | #ifndef LIBSIXEL_DITHER_H 23 | #define LIBSIXEL_DITHER_H 24 | 25 | /* dither context object */ 26 | typedef struct sixel_dither { 27 | unsigned int ref; /* reference counter */ 28 | unsigned char *palette; /* palette definition */ 29 | unsigned short *cachetable; /* cache table */ 30 | int reqcolors; /* requested colors */ 31 | int ncolors; /* active colors */ 32 | int origcolors; /* original colors */ 33 | int optimized; /* pixel is 15bpp compressable */ 34 | int method_for_largest; /* method for finding the largest dimention 35 | for splitting */ 36 | int method_for_rep; /* method for choosing a color from the box */ 37 | int method_for_diffuse; /* method for diffusing */ 38 | int quality_mode; /* quality of histgram */ 39 | int keycolor; /* background color */ 40 | } sixel_dither_t; 41 | 42 | /* sixel_image_t definition */ 43 | typedef struct sixel_image { 44 | /* Palette-based image pixels */ 45 | unsigned char *pixels; /* pixel buffer */ 46 | int sx; /* width */ 47 | int sy; /* height */ 48 | int depth; /* bytes per pixel */ 49 | int borrowed; /* whether pixels is borrowed reference */ 50 | sixel_dither_t *dither; /* dithering context object */ 51 | } sixel_image_t; 52 | 53 | #ifdef __cplusplus 54 | extern "C" { 55 | #endif 56 | 57 | sixel_image_t * sixel_create_image(unsigned char *pixels, int sx, int sy, int depth, 58 | int borrowed, sixel_dither_t *dither); 59 | 60 | void sixel_image_destroy(sixel_image_t *im); 61 | 62 | /* apply palette */ 63 | int sixel_apply_palette(sixel_image_t *im); 64 | 65 | #ifdef __cplusplus 66 | } 67 | #endif 68 | 69 | #endif /* LIBSIXEL_DITHER_H */ 70 | 71 | /* emacs, -*- Mode: C; tab-width: 4; indent-tabs-mode: nil -*- */ 72 | /* vim: set expandtab ts=4 : */ 73 | /* EOF */ 74 | -------------------------------------------------------------------------------- /static/libsixel/fromsixel.c: -------------------------------------------------------------------------------- 1 | /* 2 | * this file is derived from "sixel" original version (2014-3-2) 3 | * http://nanno.dip.jp/softlib/man/rlogin/sixel.tar.gz 4 | * 5 | * Initial developer of this file is kmiya@culti. 6 | * 7 | * He distributes it under very permissive license which permits 8 | * useing, copying, modification, redistribution, and all other 9 | * public activities without any restrictions. 10 | * 11 | * He declares this is compatible with MIT/BSD/GPL. 12 | * 13 | * Hayaki Saito modified this and re-licensed 14 | * it under the MIT license. 15 | * 16 | */ 17 | #include "config.h" 18 | #include /* NULL */ 19 | #include /* isdigit */ 20 | #include /* memcpy */ 21 | 22 | #if defined(HAVE_INTTYPES_H) 23 | # include 24 | #endif 25 | 26 | #include "output.h" 27 | #include "sixel.h" 28 | 29 | #define RGB(r, g, b) (((r) << 16) + ((g) << 8) + (b)) 30 | 31 | #define RGBA(r, g, b, a) (((a) << 24) + ((r) << 16) + ((g) << 8) + (b)) 32 | 33 | #define PALVAL(n,a,m) (((n) * (a) + ((m) / 2)) / (m)) 34 | 35 | #define XRGB(r,g,b) RGB(PALVAL(r, 255, 100), PALVAL(g, 255, 100), PALVAL(b, 255, 100)) 36 | 37 | static int const ColTab[] = { 38 | XRGB(0, 0, 0), /* 0 Black */ 39 | XRGB(20, 20, 80), /* 1 Blue */ 40 | XRGB(80, 13, 13), /* 2 Red */ 41 | XRGB(20, 80, 20), /* 3 Green */ 42 | XRGB(80, 20, 80), /* 4 Magenta */ 43 | XRGB(20, 80, 80), /* 5 Cyan */ 44 | XRGB(80, 80, 20), /* 6 Yellow */ 45 | XRGB(53, 53, 53), /* 7 Gray 50% */ 46 | XRGB(26, 26, 26), /* 8 Gray 25% */ 47 | XRGB(33, 33, 60), /* 9 Blue* */ 48 | XRGB(60, 26, 26), /* 10 Red* */ 49 | XRGB(33, 60, 33), /* 11 Green* */ 50 | XRGB(60, 33, 60), /* 12 Magenta* */ 51 | XRGB(33, 60, 60), /* 13 Cyan* */ 52 | XRGB(60, 60, 33), /* 14 Yellow* */ 53 | XRGB(80, 80, 80), /* 15 Gray 75% */ 54 | }; 55 | 56 | 57 | static int 58 | HueToRGB(int n1, int n2, int hue) 59 | { 60 | const int HLSMAX = 100; 61 | 62 | if (hue < 0) { 63 | hue += HLSMAX; 64 | } 65 | 66 | if (hue > HLSMAX) { 67 | hue -= HLSMAX; 68 | } 69 | 70 | if (hue < (HLSMAX / 6)) { 71 | return (n1 + (((n2 - n1) * hue + (HLSMAX / 12)) / (HLSMAX / 6))); 72 | } 73 | if (hue < (HLSMAX / 2)) { 74 | return (n2); 75 | } 76 | if (hue < ((HLSMAX * 2) / 3)) { 77 | return (n1 + (((n2 - n1) * (((HLSMAX * 2) / 3) - hue) + (HLSMAX / 12))/(HLSMAX / 6))); 78 | } 79 | return (n1); 80 | } 81 | 82 | 83 | static int 84 | HLStoRGB(int hue, int lum, int sat) 85 | { 86 | int R, G, B; 87 | int Magic1, Magic2; 88 | const int RGBMAX = 255; 89 | const int HLSMAX = 100; 90 | 91 | if (sat == 0) { 92 | R = G = B = (lum * RGBMAX) / HLSMAX; 93 | } else { 94 | if (lum <= (HLSMAX / 2)) { 95 | Magic2 = (lum * (HLSMAX + sat) + (HLSMAX / 2)) / HLSMAX; 96 | } else { 97 | Magic2 = lum + sat - ((lum * sat) + (HLSMAX / 2)) / HLSMAX; 98 | } 99 | Magic1 = 2 * lum - Magic2; 100 | 101 | R = (HueToRGB(Magic1, Magic2, hue + (HLSMAX / 3)) * RGBMAX + (HLSMAX / 2)) / HLSMAX; 102 | G = (HueToRGB(Magic1, Magic2, hue) * RGBMAX + (HLSMAX / 2)) / HLSMAX; 103 | B = (HueToRGB(Magic1, Magic2, hue - (HLSMAX / 3)) * RGBMAX + (HLSMAX/2)) / HLSMAX; 104 | } 105 | return RGB(R, G, B); 106 | } 107 | 108 | 109 | static unsigned char * 110 | GetParam(unsigned char *p, int *param, int *len) 111 | { 112 | int n; 113 | 114 | *len = 0; 115 | while (*p != '\0') { 116 | while (*p == ' ' || *p == '\t') { 117 | p++; 118 | } 119 | if (isdigit(*p)) { 120 | for (n = 0; isdigit(*p); p++) { 121 | n = n * 10 + (*p - '0'); 122 | } 123 | if (*len < 10) { 124 | param[(*len)++] = n; 125 | } 126 | while (*p == ' ' || *p == '\t') { 127 | p++; 128 | } 129 | if (*p == ';') { 130 | p++; 131 | } 132 | } else if (*p == ';') { 133 | if (*len < 10) { 134 | param[(*len)++] = 0; 135 | } 136 | p++; 137 | } else 138 | break; 139 | } 140 | return p; 141 | } 142 | 143 | 144 | /* convert sixel data into indexed pixel bytes and palette data */ 145 | int 146 | sixel_decode(unsigned char /* in */ *p, /* sixel bytes */ 147 | int /* in */ len, /* size of sixel bytes */ 148 | unsigned char /* out */ **pixels, /* decoded pixels */ 149 | int /* out */ *pwidth, /* image width */ 150 | int /* out */ *pheight, /* image height */ 151 | unsigned char /* out */ **palette, /* ARGB palette */ 152 | int /* out */ *ncolors, /* palette size (<= 256) */ 153 | sixel_allocator_function /* out */ allocator) /* malloc function */ 154 | { 155 | int n, i, r, g, b, sixel_vertical_mask, c; 156 | int posision_x, posision_y; 157 | int max_x, max_y; 158 | int attributed_pan, attributed_pad; 159 | int attributed_ph, attributed_pv; 160 | int repeat_count, color_index, max_color_index = 2, background_color_index; 161 | int param[10]; 162 | unsigned char *s; 163 | static char pam[256]; 164 | static char gra[256]; 165 | int sixel_palet[SIXEL_PALETTE_MAX]; 166 | unsigned char *imbuf, *dmbuf; 167 | int imsx, imsy; 168 | int dmsx, dmsy; 169 | int y; 170 | 171 | posision_x = posision_y = 0; 172 | max_x = max_y = 0; 173 | attributed_pan = 2; 174 | attributed_pad = 1; 175 | attributed_ph = attributed_pv = 0; 176 | repeat_count = 1; 177 | color_index = 0; 178 | background_color_index = 0; 179 | 180 | imsx = 2048; 181 | imsy = 2048; 182 | imbuf = allocator(imsx * imsy); 183 | 184 | if (imbuf == NULL) { 185 | return (-1); 186 | } 187 | 188 | for (n = 0; n < 16; n++) { 189 | sixel_palet[n] = ColTab[n]; 190 | } 191 | 192 | /* colors 16-231 are a 6x6x6 color cube */ 193 | for (r = 0; r < 6; r++) { 194 | for (g = 0; g < 6; g++) { 195 | for (b = 0; b < 6; b++) { 196 | sixel_palet[n++] = RGB(r * 51, g * 51, b * 51); 197 | } 198 | } 199 | } 200 | /* colors 232-255 are a grayscale ramp, intentionally leaving out */ 201 | for (i = 0; i < 24; i++) { 202 | sixel_palet[n++] = RGB(i * 11, i * 11, i * 11); 203 | } 204 | 205 | for (; n < SIXEL_PALETTE_MAX; n++) { 206 | sixel_palet[n] = RGB(255, 255, 255); 207 | } 208 | 209 | memset(imbuf, background_color_index, imsx * imsy); 210 | 211 | pam[0] = gra[0] = '\0'; 212 | 213 | while (*p != '\0') { 214 | if ((p[0] == '\033' && p[1] == 'P') || *p == 0x90) { 215 | if (*p == '\033') { 216 | p++; 217 | } 218 | 219 | s = ++p; 220 | p = GetParam(p, param, &n); 221 | if (s < p) { 222 | for (i = 0; i < 255 && s < p;) { 223 | pam[i++] = *(s++); 224 | } 225 | pam[i] = '\0'; 226 | } 227 | 228 | if (*p == 'q') { 229 | p++; 230 | 231 | if (n > 0) { /* Pn1 */ 232 | switch(param[0]) { 233 | case 0: 234 | case 1: 235 | attributed_pad = 2; 236 | break; 237 | case 2: 238 | attributed_pad = 5; 239 | break; 240 | case 3: 241 | attributed_pad = 4; 242 | break; 243 | case 4: 244 | attributed_pad = 4; 245 | break; 246 | case 5: 247 | attributed_pad = 3; 248 | break; 249 | case 6: 250 | attributed_pad = 3; 251 | break; 252 | case 7: 253 | attributed_pad = 2; 254 | break; 255 | case 8: 256 | attributed_pad = 2; 257 | break; 258 | case 9: 259 | attributed_pad = 1; 260 | break; 261 | } 262 | } 263 | 264 | if (n > 2) { /* Pn3 */ 265 | if (param[2] == 0) { 266 | param[2] = 10; 267 | } 268 | attributed_pan = attributed_pan * param[2] / 10; 269 | attributed_pad = attributed_pad * param[2] / 10; 270 | if (attributed_pan <= 0) attributed_pan = 1; 271 | if (attributed_pad <= 0) attributed_pad = 1; 272 | } 273 | } 274 | 275 | } else if ((p[0] == '\033' && p[1] == '\\') || *p == 0x9C) { 276 | break; 277 | } else if (*p == '"') { 278 | /* DECGRA Set Raster Attributes " Pan; Pad; Ph; Pv */ 279 | s = p++; 280 | p = GetParam(p, param, &n); 281 | if (s < p) { 282 | for (i = 0; i < 255 && s < p;) { 283 | gra[i++] = *(s++); 284 | } 285 | gra[i] = '\0'; 286 | } 287 | 288 | if (n > 0) attributed_pad = param[0]; 289 | if (n > 1) attributed_pan = param[1]; 290 | if (n > 2 && param[2] > 0) attributed_ph = param[2]; 291 | if (n > 3 && param[3] > 0) attributed_pv = param[3]; 292 | 293 | if (attributed_pan <= 0) attributed_pan = 1; 294 | if (attributed_pad <= 0) attributed_pad = 1; 295 | 296 | if (imsx < attributed_ph || imsy < attributed_pv) { 297 | dmsx = imsx > attributed_ph ? imsx : attributed_ph; 298 | dmsy = imsy > attributed_pv ? imsy : attributed_pv; 299 | dmbuf = allocator(dmsx * dmsy); 300 | if (dmbuf == NULL) { 301 | return (-1); 302 | } 303 | memset(dmbuf, background_color_index, dmsx * dmsy); 304 | for (y = 0; y < imsy; ++y) { 305 | memcpy(dmbuf + dmsx * y, imbuf + imsx * y, imsx); 306 | } 307 | free(imbuf); 308 | imsx = dmsx; 309 | imsy = dmsy; 310 | imbuf = dmbuf; 311 | } 312 | 313 | } else if (*p == '!') { 314 | /* DECGRI Graphics Repeat Introducer ! Pn Ch */ 315 | p = GetParam(++p, param, &n); 316 | 317 | if (n > 0) { 318 | repeat_count = param[0]; 319 | } 320 | 321 | } else if (*p == '#') { 322 | /* DECGCI Graphics Color Introducer # Pc; Pu; Px; Py; Pz */ 323 | p = GetParam(++p, param, &n); 324 | 325 | if (n > 0) { 326 | if ((color_index = param[0]) < 0) { 327 | color_index = 0; 328 | } else if (color_index >= SIXEL_PALETTE_MAX) { 329 | color_index = SIXEL_PALETTE_MAX - 1; 330 | } 331 | } 332 | 333 | if (n > 4) { 334 | if (param[1] == 1) { /* HLS */ 335 | if (param[2] > 360) param[2] = 360; 336 | if (param[3] > 100) param[3] = 100; 337 | if (param[4] > 100) param[4] = 100; 338 | sixel_palet[color_index] = HLStoRGB(param[2] * 100 / 360, param[3], param[4]); 339 | } else if (param[1] == 2) { /* RGB */ 340 | if (param[2] > 100) param[2] = 100; 341 | if (param[3] > 100) param[3] = 100; 342 | if (param[4] > 100) param[4] = 100; 343 | sixel_palet[color_index] = XRGB(param[2], param[3], param[4]); 344 | } 345 | } 346 | 347 | } else if (*p == '$') { 348 | /* DECGCR Graphics Carriage Return */ 349 | p++; 350 | posision_x = 0; 351 | repeat_count = 1; 352 | 353 | } else if (*p == '-') { 354 | /* DECGNL Graphics Next Line */ 355 | p++; 356 | posision_x = 0; 357 | posision_y += 6; 358 | repeat_count = 1; 359 | 360 | } else if (*p >= '?' && *p <= '\177') { 361 | if (imsx < (posision_x + repeat_count) || imsy < (posision_y + 6)) { 362 | int nx = imsx * 2; 363 | int ny = imsy * 2; 364 | 365 | while (nx < (posision_x + repeat_count) || ny < (posision_y + 6)) { 366 | nx *= 2; 367 | ny *= 2; 368 | } 369 | 370 | dmsx = nx; 371 | dmsy = ny; 372 | if ((dmbuf = allocator(dmsx * dmsy)) == NULL) { 373 | return (-1); 374 | } 375 | memset(dmbuf, background_color_index, dmsx * dmsy); 376 | for (y = 0; y < imsy; ++y) { 377 | memcpy(dmbuf + dmsx * y, imbuf + imsx * y, imsx); 378 | } 379 | free(imbuf); 380 | imsx = dmsx; 381 | imsy = dmsy; 382 | imbuf = dmbuf; 383 | } 384 | 385 | if (color_index > max_color_index) { 386 | max_color_index = color_index; 387 | } 388 | if ((b = *(p++) - '?') == 0) { 389 | posision_x += repeat_count; 390 | 391 | } else { 392 | sixel_vertical_mask = 0x01; 393 | 394 | if (repeat_count <= 1) { 395 | for (i = 0; i < 6; i++) { 396 | if ((b & sixel_vertical_mask) != 0) { 397 | imbuf[imsx * (posision_y + i) + posision_x] = color_index; 398 | if (max_x < posision_x) { 399 | max_x = posision_x; 400 | } 401 | if (max_y < (posision_y + i)) { 402 | max_y = posision_y + i; 403 | } 404 | } 405 | sixel_vertical_mask <<= 1; 406 | } 407 | posision_x += 1; 408 | 409 | } else { /* repeat_count > 1 */ 410 | for (i = 0; i < 6; i++) { 411 | if ((b & sixel_vertical_mask) != 0) { 412 | c = sixel_vertical_mask << 1; 413 | for (n = 1; (i + n) < 6; n++) { 414 | if ((b & c) == 0) { 415 | break; 416 | } 417 | c <<= 1; 418 | } 419 | for (y = posision_y + i; y < posision_y + i + n; ++y) { 420 | memset(imbuf + imsx * y + posision_x, color_index, repeat_count); 421 | } 422 | if (max_x < (posision_x + repeat_count - 1)) { 423 | max_x = posision_x + repeat_count - 1; 424 | } 425 | if (max_y < (posision_y + i + n - 1)) { 426 | max_y = posision_y + i + n - 1; 427 | } 428 | 429 | i += (n - 1); 430 | sixel_vertical_mask <<= (n - 1); 431 | } 432 | sixel_vertical_mask <<= 1; 433 | } 434 | posision_x += repeat_count; 435 | } 436 | } 437 | repeat_count = 1; 438 | } else { 439 | p++; 440 | } 441 | } 442 | 443 | if (++max_x < attributed_ph) { 444 | max_x = attributed_ph; 445 | } 446 | if (++max_y < attributed_pv) { 447 | max_y = attributed_pv; 448 | } 449 | 450 | if (imsx > max_x || imsy > max_y) { 451 | dmsx = max_x; 452 | dmsy = max_y; 453 | if ((dmbuf = allocator(dmsx * dmsy)) == NULL) { 454 | return (-1); 455 | } 456 | for (y = 0; y < dmsy; ++y) { 457 | memcpy(dmbuf + dmsx * y, imbuf + imsx * y, dmsx); 458 | } 459 | free(imbuf); 460 | imsx = dmsx; 461 | imsy = dmsy; 462 | imbuf = dmbuf; 463 | } 464 | 465 | *pixels = imbuf; 466 | *pwidth = imsx; 467 | *pheight = imsy; 468 | *ncolors = max_color_index + 1; 469 | *palette = allocator(*ncolors * 4); 470 | for (n = 0; n < *ncolors; ++n) { 471 | (*palette)[n * 4 + 0] = sixel_palet[n] >> 16 & 0xff; 472 | (*palette)[n * 4 + 1] = sixel_palet[n] >> 8 & 0xff; 473 | (*palette)[n * 4 + 2] = sixel_palet[n] & 0xff; 474 | (*palette)[n * 4 + 3] = 0xff; 475 | } 476 | return 0; 477 | } 478 | 479 | /* emacs, -*- Mode: C; tab-width: 4; indent-tabs-mode: nil -*- */ 480 | /* vim: set expandtab ts=4 : */ 481 | /* EOF */ 482 | -------------------------------------------------------------------------------- /static/libsixel/image.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Hayaki Saito 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | * this software and associated documentation files (the "Software"), to deal in 6 | * the Software without restriction, including without limitation the rights to 7 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | * the Software, and to permit persons to whom the Software is furnished to do so, 9 | * subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | #include "config.h" 22 | #include 23 | 24 | #if defined(HAVE_INTTYPES_H) 25 | # include 26 | #endif 27 | 28 | #include "dither.h" 29 | #include "sixel.h" 30 | 31 | sixel_image_t * 32 | sixel_create_image(unsigned char *pixels, int sx, int sy, int depth, 33 | int borrowed, sixel_dither_t *dither) 34 | { 35 | sixel_image_t *im; 36 | 37 | im = (sixel_image_t *)malloc(sizeof(sixel_image_t)); 38 | im->pixels = pixels; 39 | im->sx = sx; 40 | im->sy = sy; 41 | im->depth = depth; 42 | im->borrowed = borrowed; 43 | im->dither = dither; 44 | sixel_dither_ref(dither); 45 | return im; 46 | } 47 | 48 | 49 | void 50 | sixel_image_destroy(sixel_image_t *im) 51 | { 52 | if (im->dither) { 53 | sixel_dither_unref(im->dither); 54 | } 55 | if (im->pixels && !im->borrowed) { 56 | free(im->pixels); 57 | } 58 | free(im); 59 | } 60 | 61 | /* emacs, -*- Mode: C; tab-width: 4; indent-tabs-mode: nil -*- */ 62 | /* vim: set expandtab ts=4 : */ 63 | /* EOF */ 64 | -------------------------------------------------------------------------------- /static/libsixel/image.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Hayaki Saito 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | * this software and associated documentation files (the "Software"), to deal in 6 | * the Software without restriction, including without limitation the rights to 7 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | * the Software, and to permit persons to whom the Software is furnished to do so, 9 | * subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | #ifndef LIBSIXEL_IMAGE_H 23 | #define LIBSIXEL_IMAGE_H 24 | 25 | #include "dither.h" 26 | 27 | #endif /* LIBSIXEL_IMAGE_H */ 28 | 29 | /* emacs, -*- Mode: C; tab-width: 4; indent-tabs-mode: nil -*- */ 30 | /* vim: set expandtab ts=4 : */ 31 | /* EOF */ 32 | -------------------------------------------------------------------------------- /static/libsixel/output.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Hayaki Saito 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | * this software and associated documentation files (the "Software"), to deal in 6 | * the Software without restriction, including without limitation the rights to 7 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | * the Software, and to permit persons to whom the Software is furnished to do so, 9 | * subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | #include "config.h" 23 | #include 24 | #include 25 | #include "output.h" 26 | #include "sixel.h" 27 | 28 | 29 | sixel_output_t * const 30 | sixel_output_create(sixel_write_function fn_write, void *priv) 31 | { 32 | sixel_output_t *output; 33 | 34 | output = malloc(sizeof(sixel_output_t) + SIXEL_OUTPUT_PACKET_SIZE * 2); 35 | output->ref = 1; 36 | output->has_8bit_control = 0; 37 | output->has_sdm_glitch = 0; 38 | output->fn_write = fn_write; 39 | output->save_pixel = 0; 40 | output->save_count = 0; 41 | output->active_palette = (-1); 42 | output->node_top = NULL; 43 | output->node_free = NULL; 44 | output->priv = priv; 45 | output->pos = 0; 46 | 47 | return output; 48 | } 49 | 50 | 51 | void 52 | sixel_output_destroy(sixel_output_t *output) 53 | { 54 | free(output); 55 | } 56 | 57 | 58 | void 59 | sixel_output_ref(sixel_output_t *output) 60 | { 61 | /* TODO: be thread-safe */ 62 | ++output->ref; 63 | } 64 | 65 | void 66 | sixel_output_unref(sixel_output_t *output) 67 | { 68 | /* TODO: be thread-safe */ 69 | if (output && --output->ref == 0) { 70 | sixel_output_destroy(output); 71 | } 72 | } 73 | 74 | int 75 | sixel_output_get_8bit_availability(sixel_output_t *output) 76 | { 77 | return output->has_8bit_control; 78 | } 79 | 80 | 81 | void 82 | sixel_output_set_8bit_availability(sixel_output_t *output, int availability) 83 | { 84 | output->has_8bit_control = availability; 85 | } 86 | 87 | 88 | /* emacs, -*- Mode: C; tab-width: 4; indent-tabs-mode: nil -*- */ 89 | /* vim: set expandtab ts=4 : */ 90 | /* EOF */ 91 | -------------------------------------------------------------------------------- /static/libsixel/output.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Hayaki Saito 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | * this software and associated documentation files (the "Software"), to deal in 6 | * the Software without restriction, including without limitation the rights to 7 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | * the Software, and to permit persons to whom the Software is furnished to do so, 9 | * subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | #ifndef LIBSIXEL_OUTPUT_H 23 | #define LIBSIXEL_OUTPUT_H 24 | 25 | #ifdef __cplusplus 26 | extern "C" { 27 | #endif 28 | 29 | typedef struct sixel_node { 30 | struct sixel_node *next; 31 | int pal; 32 | int sx; 33 | int mx; 34 | unsigned char *map; 35 | } sixel_node_t; 36 | 37 | typedef int (* sixel_write_function)(char *data, int size, void *priv); 38 | 39 | typedef struct sixel_output { 40 | 41 | int ref; 42 | 43 | /* compatiblity flags */ 44 | 45 | /* 0: 7bit terminal, 46 | * 1: 8bit terminal */ 47 | unsigned char has_8bit_control; 48 | 49 | /* 0: the terminal has sixel scrolling 50 | * 1: the terminal does not have sixel scrolling */ 51 | unsigned char has_sixel_scrolling; 52 | 53 | /* 0: DECSDM set (CSI ? 80 h) enables sixel scrolling 54 | 1: DECSDM set (CSI ? 80 h) disables sixel scrolling */ 55 | unsigned char has_sdm_glitch; 56 | 57 | sixel_write_function fn_write; 58 | 59 | unsigned char conv_palette[256]; 60 | int save_pixel; 61 | int save_count; 62 | int active_palette; 63 | 64 | sixel_node_t *node_top; 65 | sixel_node_t *node_free; 66 | 67 | void *priv; 68 | int pos; 69 | unsigned char buffer[1]; 70 | 71 | } sixel_output_t; 72 | 73 | #ifdef __cplusplus 74 | } 75 | #endif 76 | 77 | #endif /* LIBSIXEL_OUTPUT_H */ 78 | 79 | /* emacs, -*- Mode: C; tab-width: 4; indent-tabs-mode: nil -*- */ 80 | /* vim: set expandtab ts=4 : */ 81 | /* EOF */ 82 | -------------------------------------------------------------------------------- /static/libsixel/quant.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Hayaki Saito 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | * this software and associated documentation files (the "Software"), to deal in 6 | * the Software without restriction, including without limitation the rights to 7 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | * the Software, and to permit persons to whom the Software is furnished to do so, 9 | * subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | #ifndef LIBSIXEL_QUANT_H 23 | #define LIBSIXEL_QUANT_H 24 | 25 | #ifdef __cplusplus 26 | extern "C" { 27 | #endif 28 | 29 | unsigned char * 30 | LSQ_MakePalette(unsigned char *data, int x, int y, int depth, 31 | int reqcolors, int *ncolors, int *origcolors, 32 | int const methodForLargest, 33 | int const methodForRep, 34 | int const qualityMode); 35 | 36 | int 37 | LSQ_ApplyPalette(unsigned char *data, int width, int height, int depth, 38 | unsigned char *palette, int ncolor, 39 | int const methodForDiffuse, 40 | int foptimize, 41 | unsigned short *cachetable, 42 | unsigned char *result); 43 | 44 | 45 | extern void 46 | LSQ_FreePalette(unsigned char * data); 47 | 48 | #ifdef __cplusplus 49 | } 50 | #endif 51 | 52 | #endif /* LIBSIXEL_QUANT_H */ 53 | 54 | /* emacs, -*- Mode: C; tab-width: 4; indent-tabs-mode: nil -*- */ 55 | /* vim: set expandtab ts=4 : */ 56 | /* EOF */ 57 | -------------------------------------------------------------------------------- /static/libsixel/sixel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Hayaki Saito 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | * this software and associated documentation files (the "Software"), to deal in 6 | * the Software without restriction, including without limitation the rights to 7 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | * the Software, and to permit persons to whom the Software is furnished to do so, 9 | * subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | #include /* for size_t */ 23 | 24 | #ifndef LIBSIXEL_SIXEL_H 25 | #define LIBSIXEL_SIXEL_H 26 | 27 | #define LIBSIXEL_VRTSION 1.0.3 28 | #define LIBSIXEL_ABI_VRTSION 1:0:0 29 | 30 | #define SIXEL_OUTPUT_PACKET_SIZE 1024 31 | #define SIXEL_PALETTE_MAX 256 32 | 33 | /* output character size */ 34 | enum characterSize { 35 | CSIZE_7BIT = 0, /* 7bit character */ 36 | CSIZE_8BIT = 1, /* 8bit character */ 37 | }; 38 | 39 | /* method for finding the largest dimention for splitting, 40 | * and sorting by that component */ 41 | enum methodForLargest { 42 | LARGE_AUTO = 0, /* choose automatically the method for finding the largest 43 | dimention */ 44 | LARGE_NORM = 1, /* simply comparing the range in RGB space */ 45 | LARGE_LUM = 2 /* transforming into luminosities before the comparison */ 46 | }; 47 | 48 | /* method for choosing a color from the box */ 49 | enum methodForRep { 50 | REP_AUTO = 0, /* choose automatically the method for selecting 51 | representative color from each box */ 52 | REP_CENTER_BOX = 1, /* choose the center of the box */ 53 | REP_AVERAGE_COLORS = 2, /* choose the average all the color 54 | in the box (specified in Heckbert's paper) */ 55 | REP_AVERAGE_PIXELS = 3 /* choose the averate all the pixels in the box */ 56 | }; 57 | 58 | /* method for diffusing */ 59 | enum methodForDiffuse { 60 | DIFFUSE_AUTO = 0, /* choose diffusion type automatically */ 61 | DIFFUSE_NONE = 1, /* don't diffuse */ 62 | DIFFUSE_ATKINSON = 2, /* diffuse with Bill Atkinson's method */ 63 | DIFFUSE_FS = 3, /* diffuse with Floyd-Steinberg method */ 64 | DIFFUSE_JAJUNI = 4, /* diffuse with Jarvis, Judice & Ninke method */ 65 | DIFFUSE_STUCKI = 5, /* diffuse with Stucki's method */ 66 | DIFFUSE_BURKES = 6 /* diffuse with Burkes' method */ 67 | }; 68 | 69 | /* quality modes */ 70 | enum qualityMode { 71 | QUALITY_AUTO = 0, /* choose quality mode automatically */ 72 | QUALITY_HIGH = 1, /* high quality */ 73 | QUALITY_LOW = 2, /* low quality */ 74 | }; 75 | 76 | /* built-in dither */ 77 | enum builtinDither { 78 | BUILTIN_MONO_DARK = 0, /* monochrome terminal with dark background */ 79 | BUILTIN_MONO_LIGHT = 1, /* monochrome terminal with dark background */ 80 | BUILTIN_XTERM16 = 2, /* xterm 16color */ 81 | BUILTIN_XTERM256 = 3, /* xterm 256color */ 82 | }; 83 | 84 | #ifndef LIBSIXEL_DITHER_H 85 | 86 | /* dither context object */ 87 | typedef struct sixel_dither {} sixel_dither_t; 88 | 89 | #endif /* SIXEL_DITHER_H */ 90 | 91 | #ifndef LIBSIXEL_OUTPUT_H 92 | 93 | typedef int (* sixel_write_function)(char *data, int size, void *priv); 94 | 95 | typedef struct sixel_output {} sixel_output_t; 96 | 97 | #endif /* LIBSIXEL_OUTPUT_H */ 98 | 99 | 100 | /* output context manipulation API */ 101 | 102 | #ifdef __cplusplus 103 | extern "C" { 104 | #endif 105 | 106 | /* create output context object */ 107 | sixel_output_t *const 108 | sixel_output_create(sixel_write_function /* in */ fn_write, /* callback function for output sixel */ 109 | void /* in */ *priv); /* private data given as 110 | 3rd argument of fn_write */ 111 | /* destroy output context object */ 112 | void 113 | sixel_output_destroy(sixel_output_t /* in */ *output); /* output context */ 114 | 115 | /* increment reference count of output context object (thread-unsafe) */ 116 | void 117 | sixel_output_ref(sixel_output_t /* in */ *output); /* output context */ 118 | 119 | /* decrement reference count of output context object (thread-unsafe) */ 120 | void 121 | sixel_output_unref(sixel_output_t /* in */ *output); /* output context */ 122 | 123 | int 124 | sixel_output_get_8bit_availability(sixel_output_t *output); 125 | 126 | void 127 | sixel_output_set_8bit_availability(sixel_output_t *output, int availability); 128 | 129 | #ifdef __cplusplus 130 | } 131 | #endif 132 | 133 | 134 | /* color quantization API */ 135 | 136 | #ifdef __cplusplus 137 | extern "C" { 138 | #endif 139 | 140 | /* create dither context object */ 141 | sixel_dither_t * 142 | sixel_dither_create(int /* in */ ncolors); /* number of colors */ 143 | 144 | /* get built-in dither context object */ 145 | sixel_dither_t * 146 | sixel_dither_get(int builtin_dither); /* ID of built-in dither object */ 147 | 148 | /* destroy dither context object */ 149 | void 150 | sixel_dither_destroy(sixel_dither_t *dither); /* dither context object */ 151 | 152 | /* increment reference count of dither context object (thread-unsafe) */ 153 | void 154 | sixel_dither_ref(sixel_dither_t *dither); /* dither context object */ 155 | 156 | /* decrement reference count of dither context object (thread-unsafe) */ 157 | void 158 | sixel_dither_unref(sixel_dither_t *dither); /* dither context object */ 159 | 160 | /* initialize internal palette from specified pixel buffer */ 161 | int 162 | sixel_dither_initialize(sixel_dither_t *dither, /* dither context object */ 163 | unsigned char /* in */ *data, /* sample image */ 164 | int /* in */ width, /* image width */ 165 | int /* in */ height, /* image height */ 166 | int /* in */ depth, /* pixel depth, now only '3' is supported */ 167 | int /* in */ method_for_largest, /* method for finding the largest dimention */ 168 | int /* in */ method_for_rep, /* method for choosing a color from the box */ 169 | int /* in */ quality_mode); /* quality of histgram processing */ 170 | 171 | /* set diffusion type, choose from enum methodForDiffuse */ 172 | void 173 | sixel_dither_set_diffusion_type(sixel_dither_t /* in */ *dither, /* dither context object */ 174 | int /* in */ method_for_diffuse); /* one of enum methodForDiffuse */ 175 | 176 | /* get number of palette colors */ 177 | int 178 | sixel_dither_get_num_of_palette_colors(sixel_dither_t /* in */ *dither); /* dither context object */ 179 | 180 | /* get number of histgram colors */ 181 | int 182 | sixel_dither_get_num_of_histgram_colors(sixel_dither_t /* in */ *dither); /* dither context object */ 183 | 184 | /* get palette */ 185 | unsigned char * 186 | sixel_dither_get_palette(sixel_dither_t /* in */ *dither); /* dither context object */ 187 | 188 | #ifdef __cplusplus 189 | } 190 | #endif 191 | 192 | /* converter API */ 193 | 194 | typedef void * (* sixel_allocator_function)(size_t size); 195 | 196 | #ifdef __cplusplus 197 | extern "C" { 198 | #endif 199 | 200 | /* convert pixels into sixel format and write it to output context */ 201 | int 202 | sixel_encode(unsigned char /* in */ *pixels, /* pixel bytes */ 203 | int /* in */ width, /* image width */ 204 | int /* in */ height, /* image height */ 205 | int /* in */ depth, /* pixel depth */ 206 | sixel_dither_t /* in */ *dither, /* dither context */ 207 | sixel_output_t /* in */ *context); /* output context */ 208 | 209 | /* convert sixel data into indexed pixel bytes and palette data */ 210 | int 211 | sixel_decode(unsigned char /* in */ *sixels, /* sixel bytes */ 212 | int /* in */ size, /* size of sixel bytes */ 213 | unsigned char /* out */ **pixels, /* decoded pixels */ 214 | int /* out */ *pwidth, /* image width */ 215 | int /* out */ *pheight, /* image height */ 216 | unsigned char /* out */ **palette, /* RGBA palette */ 217 | int /* out */ *ncolors, /* palette size (<= 256) */ 218 | sixel_allocator_function /* out */ allocator); /* malloc function */ 219 | 220 | #ifdef __cplusplus 221 | } 222 | #endif 223 | 224 | #endif /* LIBSIXEL_SIXEL_H */ 225 | 226 | /* emacs, -*- Mode: C; tab-width: 4; indent-tabs-mode: nil -*- */ 227 | /* vim: set expandtab ts=4 : */ 228 | /* EOF */ 229 | -------------------------------------------------------------------------------- /static/libsixel/tosixel.c: -------------------------------------------------------------------------------- 1 | /* 2 | * this file is derived from "sixel" original version (2014-3-2) 3 | * http://nanno.dip.jp/softlib/man/rlogin/sixel.tar.gz 4 | * 5 | * Initial developer of this file is kmiya@culti. 6 | * 7 | * He distributes it under very permissive license which permits 8 | * useing, copying, modification, redistribution, and all other 9 | * public activities without any restrictions. 10 | * 11 | * He declares this is compatible with MIT/BSD/GPL. 12 | * 13 | * Hayaki Saito (user@zuse.jp) modified this and re-licensed 14 | * it under the MIT license. 15 | * 16 | */ 17 | #include "config.h" 18 | #include 19 | #include 20 | #include 21 | 22 | #if defined(HAVE_INTTYPES_H) 23 | # include 24 | #endif 25 | 26 | #include "output.h" 27 | #include "dither.h" 28 | #include "image.h" 29 | #include "sixel.h" 30 | 31 | /* implementation */ 32 | 33 | static void 34 | advance(sixel_output_t *context, int nwrite) 35 | { 36 | if ((context->pos += nwrite) >= SIXEL_OUTPUT_PACKET_SIZE) { 37 | context->fn_write((char *)context->buffer, SIXEL_OUTPUT_PACKET_SIZE, context->priv); 38 | memcpy(context->buffer, 39 | context->buffer + SIXEL_OUTPUT_PACKET_SIZE, 40 | (context->pos -= SIXEL_OUTPUT_PACKET_SIZE)); 41 | } 42 | } 43 | 44 | 45 | static int 46 | PutFlash(sixel_output_t *const context) 47 | { 48 | int n; 49 | int ret; 50 | char buf[256]; 51 | int nwrite; 52 | 53 | #if defined(USE_VT240) /* VT240 Max 255 ? */ 54 | while (context->save_count > 255) { 55 | nwrite = spritf((char *)context->buffer + context->pos, "!255%c", context->save_pixel); 56 | if (nwrite <= 0) { 57 | return (-1); 58 | } 59 | advance(context, nwrite); 60 | context->save_count -= 255; 61 | } 62 | #endif /* defined(USE_VT240) */ 63 | 64 | if (context->save_count > 3) { 65 | /* DECGRI Graphics Repeat Introducer ! Pn Ch */ 66 | nwrite = sprintf((char *)context->buffer + context->pos, "!%d%c", context->save_count, context->save_pixel); 67 | if (nwrite <= 0) { 68 | return (-1); 69 | } 70 | advance(context, nwrite); 71 | } else { 72 | for (n = 0; n < context->save_count; n++) { 73 | context->buffer[context->pos] = (char)context->save_pixel; 74 | advance(context, 1); 75 | } 76 | } 77 | 78 | context->save_pixel = 0; 79 | context->save_count = 0; 80 | 81 | return 0; 82 | } 83 | 84 | 85 | static void 86 | PutPixel(sixel_output_t *const context, int pix) 87 | { 88 | if (pix < 0 || pix > 63) { 89 | pix = 0; 90 | } 91 | 92 | pix += '?'; 93 | 94 | if (pix == context->save_pixel) { 95 | context->save_count++; 96 | } else { 97 | PutFlash(context); 98 | context->save_pixel = pix; 99 | context->save_count = 1; 100 | } 101 | } 102 | 103 | 104 | static void 105 | PutPalet(sixel_output_t *context, sixel_image_t *im, int pal) 106 | { 107 | int nwrite; 108 | 109 | /* designate palette index */ 110 | if (context->active_palette != pal) { 111 | nwrite = sprintf((char *)context->buffer + context->pos, "#%d", context->conv_palette[pal]); 112 | advance(context, nwrite); 113 | context->active_palette = pal; 114 | } 115 | } 116 | 117 | 118 | static void 119 | NodeFree(sixel_output_t *const context) 120 | { 121 | sixel_node_t *np; 122 | 123 | while ((np = context->node_free) != NULL) { 124 | context->node_free = np->next; 125 | free(np); 126 | } 127 | } 128 | 129 | 130 | static void 131 | NodeDel(sixel_output_t *const context, sixel_node_t *np) 132 | { 133 | sixel_node_t *tp; 134 | 135 | if ((tp = context->node_top) == np) { 136 | context->node_top = np->next; 137 | } 138 | 139 | else { 140 | while (tp->next != NULL) { 141 | if (tp->next == np) { 142 | tp->next = np->next; 143 | break; 144 | } 145 | tp = tp->next; 146 | } 147 | } 148 | 149 | np->next = context->node_free; 150 | context->node_free = np; 151 | } 152 | 153 | 154 | static int 155 | NodeAdd(sixel_output_t *const context, int pal, int sx, int mx, unsigned char *map) 156 | { 157 | sixel_node_t *np, *tp, top; 158 | 159 | if ((np = context->node_free) != NULL) { 160 | context->node_free = np->next; 161 | } else if ((np = (sixel_node_t *)malloc(sizeof(sixel_node_t))) == NULL) { 162 | return (-1); 163 | } 164 | 165 | np->pal = pal; 166 | np->sx = sx; 167 | np->mx = mx; 168 | np->map = map; 169 | 170 | top.next = context->node_top; 171 | tp = ⊤ 172 | 173 | while (tp->next != NULL) { 174 | if (np->sx < tp->next->sx) { 175 | break; 176 | } else if (np->sx == tp->next->sx && np->mx > tp->next->mx) { 177 | break; 178 | } 179 | tp = tp->next; 180 | } 181 | 182 | np->next = tp->next; 183 | tp->next = np; 184 | context->node_top = top.next; 185 | 186 | return 0; 187 | } 188 | 189 | 190 | static int 191 | NodeLine(sixel_output_t *const context, int pal, int width, unsigned char *map) 192 | { 193 | int sx, mx, n; 194 | int ret; 195 | 196 | for (sx = 0; sx < width; sx++) { 197 | if (map[sx] == 0) { 198 | continue; 199 | } 200 | 201 | for (mx = sx + 1; mx < width; mx++) { 202 | if (map[mx] != 0) { 203 | continue; 204 | } 205 | 206 | for (n = 1; (mx + n) < width; n++) { 207 | if (map[mx + n] != 0) { 208 | break; 209 | } 210 | } 211 | 212 | if (n >= 10 || (mx + n) >= width) { 213 | break; 214 | } 215 | mx = mx + n - 1; 216 | } 217 | 218 | ret = NodeAdd(context, pal, sx, mx, map); 219 | if (ret != 0) { 220 | return ret; 221 | } 222 | sx = mx - 1; 223 | } 224 | 225 | return 0; 226 | } 227 | 228 | 229 | static int 230 | PutNode(sixel_output_t *const context, sixel_image_t *im, int x, 231 | sixel_node_t *np, int ncolors, int keycolor) 232 | { 233 | if (ncolors != 2 || keycolor == -1) { 234 | PutPalet(context, im, np->pal); 235 | } 236 | 237 | for (; x < np->sx; x++) { 238 | if (x != keycolor) { 239 | PutPixel(context, 0); 240 | } 241 | } 242 | 243 | for (; x < np->mx; x++) { 244 | if (x != keycolor) { 245 | PutPixel(context, np->map[x]); 246 | } 247 | } 248 | 249 | PutFlash(context); 250 | 251 | return x; 252 | } 253 | 254 | 255 | int 256 | LibSixel_LSImageToSixel(sixel_image_t *im, sixel_output_t *context) 257 | { 258 | int x, y, i, n, c; 259 | int maxPalet; 260 | int width, height; 261 | int len, pix, skip; 262 | int back = (-1); 263 | int ret; 264 | unsigned char *map; 265 | sixel_node_t *np; 266 | unsigned char list[SIXEL_PALETTE_MAX]; 267 | char buf[256]; 268 | int nwrite; 269 | 270 | width = im->sx; 271 | height = im->sy; 272 | context->pos = 0; 273 | 274 | maxPalet = im->dither->ncolors; 275 | if (maxPalet < 1) { 276 | return (-1); 277 | } 278 | back = im->dither->keycolor; 279 | len = maxPalet * width; 280 | context->active_palette = (-1); 281 | 282 | #if HAVE_CALLOC 283 | if ((map = (unsigned char *)calloc(len, sizeof(unsigned char))) == NULL) { 284 | return (-1); 285 | } 286 | #else 287 | if ((map = (unsigned char *)malloc(len)) == NULL) { 288 | return (-1); 289 | } 290 | memset(map, 0, len); 291 | #endif 292 | for (n = 0; n < maxPalet; n++) { 293 | context->conv_palette[n] = list[n] = n; 294 | } 295 | 296 | if (context->has_8bit_control) { 297 | nwrite = sprintf((char *)context->buffer, "\x90" "0;0;0" "q"); 298 | } else { 299 | nwrite = sprintf((char *)context->buffer, "\x1bP" "0;0;0" "q"); 300 | } 301 | if (nwrite <= 0) { 302 | return (-1); 303 | } 304 | advance(context, nwrite); 305 | nwrite = sprintf((char *)context->buffer + context->pos, "\"1;1;%d;%d", width, height); 306 | if (nwrite <= 0) { 307 | return (-1); 308 | } 309 | advance(context, nwrite); 310 | 311 | if (maxPalet != 2 || back == -1) { 312 | for (n = 0; n < maxPalet; n++) { 313 | /* DECGCI Graphics Color Introducer # Pc ; Pu; Px; Py; Pz */ 314 | nwrite = sprintf((char *)context->buffer + context->pos, "#%d;2;%d;%d;%d", 315 | context->conv_palette[n], 316 | (im->dither->palette[n * 3 + 0] * 100 + 127) / 255, 317 | (im->dither->palette[n * 3 + 1] * 100 + 127) / 255, 318 | (im->dither->palette[n * 3 + 2] * 100 + 127) / 255); 319 | if (nwrite <= 0) { 320 | return (-1); 321 | } 322 | advance(context, nwrite); 323 | if (nwrite <= 0) { 324 | return (-1); 325 | } 326 | } 327 | context->buffer[context->pos] = '\n'; 328 | advance(context, 1); 329 | } 330 | 331 | for (y = i = 0; y < height; y++) { 332 | for (x = 0; x < width; x++) { 333 | pix = im->pixels[y * width + x]; 334 | if (pix >= 0 && pix < maxPalet && pix != back) { 335 | map[pix * width + x] |= (1 << i); 336 | } 337 | } 338 | 339 | if (++i < 6 && (y + 1) < height) { 340 | continue; 341 | } 342 | 343 | for (n = 0; n < maxPalet; n++) { 344 | ret = NodeLine(context, n, width, map + n * width); 345 | if (ret != 0) { 346 | return ret; 347 | } 348 | } 349 | 350 | for (x = 0; (np = context->node_top) != NULL;) { 351 | if (x > np->sx) { 352 | /* DECGCR Graphics Carriage Return */ 353 | context->buffer[context->pos] = '$'; 354 | advance(context, 1); 355 | x = 0; 356 | } 357 | 358 | x = PutNode(context, im, x, np, maxPalet, back); 359 | NodeDel(context, np); 360 | np = context->node_top; 361 | 362 | while (np != NULL) { 363 | if (np->sx < x) { 364 | np = np->next; 365 | continue; 366 | } 367 | 368 | x = PutNode(context, im, x, np, maxPalet, back); 369 | NodeDel(context, np); 370 | np = context->node_top; 371 | } 372 | } 373 | 374 | /* DECGNL Graphics Next Line */ 375 | context->buffer[context->pos] = '-'; 376 | advance(context, 1); 377 | if (nwrite <= 0) { 378 | return (-1); 379 | } 380 | 381 | i = 0; 382 | memset(map, 0, len); 383 | } 384 | 385 | if (context->has_8bit_control) { 386 | context->buffer[context->pos] = '\x9c'; 387 | advance(context, 1); 388 | } else { 389 | context->buffer[context->pos] = '\x1b'; 390 | context->buffer[context->pos + 1] = '\\'; 391 | advance(context, 2); 392 | } 393 | if (nwrite <= 0) { 394 | return (-1); 395 | } 396 | 397 | /* flush buffer */ 398 | if (context->pos > 0) { 399 | context->fn_write((char *)context->buffer, context->pos, context->priv); 400 | } 401 | 402 | NodeFree(context); 403 | free(map); 404 | 405 | return 0; 406 | } 407 | 408 | int sixel_encode(unsigned char /* in */ *pixels, /* pixel bytes */ 409 | int /* in */ width, /* image width */ 410 | int /* in */ height, /* image height */ 411 | int /* in */ depth, /* pixel depth */ 412 | sixel_dither_t /* in */ *dither, /* dither context */ 413 | sixel_output_t /* in */ *context) /* output context */ 414 | { 415 | sixel_image_t *im; 416 | int ret; 417 | 418 | /* create intermidiate bitmap image */ 419 | im = sixel_create_image(pixels, width, height, depth, 1, dither); 420 | if (!im) { 421 | return (-1); 422 | } 423 | 424 | /* apply palette */ 425 | ret = sixel_apply_palette(im); 426 | if (ret != 0) { 427 | return ret; 428 | } 429 | 430 | LibSixel_LSImageToSixel(im, context); 431 | 432 | sixel_image_destroy(im); 433 | 434 | return 0; 435 | } 436 | 437 | 438 | /* emacs, -*- Mode: C; tab-width: 4; indent-tabs-mode: nil -*- */ 439 | /* vim: set expandtab ts=4 : */ 440 | /* EOF */ 441 | -------------------------------------------------------------------------------- /static/makefile: -------------------------------------------------------------------------------- 1 | CC ?= gcc 2 | #CC ?= clang 3 | 4 | CFLAGS ?= -Wall -Wextra -std=c99 -pedantic \ 5 | -O3 -pipe -s 6 | LDFLAGS ?= 7 | 8 | HDR = ../stb_image.h ../libnsgif.h ../libnsbmp.h ../lodepng.h 9 | SRC = ../libnsgif.c ../libnsbmp.c ../lodepng.c 10 | 11 | SIXEL_SRC = ./libsixel/dither.c ./libsixel/fromsixel.c ./libsixel/image.c \ 12 | ./libsixel/output.c ./libsixel/quant.c ./libsixel/tosixel.c 13 | SIXEL_OBJ = dither.o fromsixel.o image.o \ 14 | output.o quant.o tosixel.o 15 | 16 | DST = sdump 17 | 18 | all: $(DST) 19 | 20 | sdump: sdump.c $(HDR) $(SRC) $(SIXEL_SRC) 21 | # recommend to copy "conf.h" from libsixel to ./libsixel (after ./configure) 22 | $(CC) -c -O3 $(SIXEL_SRC) 23 | $(CC) $(CFLAGS) $(LDFLAGS) -I./libsixel \ 24 | $(SIXEL_OBJ) $(SRC) $< -o $@ 25 | rm *.o 26 | 27 | clean: 28 | rm -f $(DST) *.o 29 | -------------------------------------------------------------------------------- /static/sdump.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE for licence details. */ 2 | #define _XOPEN_SOURCE 600 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | enum { 20 | VERBOSE = false, 21 | BUFSIZE = 1024, 22 | MULTIPLER = 1024, 23 | }; 24 | 25 | /* error functions */ 26 | enum loglevel { 27 | DEBUG = 0, 28 | WARN, 29 | ERROR, 30 | FATAL, 31 | }; 32 | 33 | void logging(int loglevel, char *format, ...) 34 | { 35 | va_list arg; 36 | 37 | static const char *loglevel2str[] = { 38 | [DEBUG] = "DEBUG", 39 | [WARN] = "WARN", 40 | [ERROR] = "ERROR", 41 | [FATAL] = "FATAL", 42 | }; 43 | 44 | if (loglevel == DEBUG && !VERBOSE) 45 | return; 46 | 47 | fprintf(stderr, ">>%s<<\t", loglevel2str[loglevel]); 48 | va_start(arg, format); 49 | vfprintf(stderr, format, arg); 50 | va_end(arg); 51 | } 52 | 53 | /* wrapper of C functions */ 54 | int eopen(const char *path, int flag) 55 | { 56 | int fd; 57 | errno = 0; 58 | 59 | if ((fd = open(path, flag)) < 0) { 60 | logging(ERROR, "couldn't open \"%s\"\n", path); 61 | logging(ERROR, "open: %s\n", strerror(errno)); 62 | } 63 | return fd; 64 | } 65 | 66 | int eclose(int fd) 67 | { 68 | int ret = 0; 69 | errno = 0; 70 | 71 | if ((ret = close(fd)) < 0) 72 | logging(ERROR, "close: %s\n", strerror(errno)); 73 | 74 | return ret; 75 | } 76 | 77 | FILE *efopen(const char *path, char *mode) 78 | { 79 | FILE *fp; 80 | errno = 0; 81 | 82 | if ((fp = fopen(path, mode)) == NULL) { 83 | logging(ERROR, "cannot open \"%s\"\n", path); 84 | logging(ERROR, "fopen: %s\n", strerror(errno)); 85 | } 86 | return fp; 87 | } 88 | 89 | int efclose(FILE *fp) 90 | { 91 | int ret; 92 | errno = 0; 93 | 94 | if ((ret = fclose(fp)) < 0) 95 | logging(ERROR, "fclose: %s\n", strerror(errno)); 96 | 97 | return ret; 98 | } 99 | 100 | void *ecalloc(size_t nmemb, size_t size) 101 | { 102 | void *ptr; 103 | errno = 0; 104 | 105 | if ((ptr = calloc(nmemb, size)) == NULL) 106 | logging(ERROR, "calloc: %s\n", strerror(errno)); 107 | 108 | return ptr; 109 | } 110 | 111 | long int estrtol(const char *nptr, char **endptr, int base) 112 | { 113 | long int ret; 114 | errno = 0; 115 | 116 | ret = strtol(nptr, endptr, base); 117 | if (ret == LONG_MIN || ret == LONG_MAX) { 118 | logging(ERROR, "strtol: %s\n", strerror(errno)); 119 | return 0; 120 | } 121 | 122 | return ret; 123 | } 124 | 125 | int emkstemp(char *template) 126 | { 127 | int ret; 128 | errno = 0; 129 | 130 | if ((ret = mkstemp(template)) < 0) { 131 | logging(ERROR, "couldn't open \"%s\"\n", template); 132 | logging(ERROR, "mkstemp: %s\n", strerror(errno)); 133 | } 134 | 135 | return ret; 136 | } 137 | 138 | /* some useful functions */ 139 | int str2num(char *str) 140 | { 141 | if (str == NULL) 142 | return 0; 143 | 144 | return estrtol(str, NULL, 10); 145 | } 146 | 147 | /* image loading functions */ 148 | /* for png */ 149 | #include "../lodepng.h" 150 | 151 | /* for gif/bmp/(ico not supported) */ 152 | #include "../libnsgif.h" 153 | #include "../libnsbmp.h" 154 | 155 | #define STB_IMAGE_IMPLEMENTATION 156 | /* to remove math.h dependency */ 157 | #define STBI_NO_HDR 158 | #include "../stb_image.h" 159 | 160 | enum { 161 | CHECK_HEADER_SIZE = 8, 162 | BYTES_PER_PIXEL = 4, 163 | PNG_HEADER_SIZE = 8, 164 | MAX_FRAME_NUM = 128, /* limit of gif frames */ 165 | }; 166 | 167 | enum filetype_t { 168 | TYPE_JPEG, 169 | TYPE_PNG, 170 | TYPE_BMP, 171 | TYPE_GIF, 172 | TYPE_PNM, 173 | TYPE_UNKNOWN, 174 | }; 175 | 176 | struct image { 177 | /* normally use data[0], data[n] (n > 1) for animanion gif */ 178 | uint8_t *data[MAX_FRAME_NUM]; 179 | int width; 180 | int height; 181 | int channel; 182 | bool alpha; 183 | /* for animation gif */ 184 | int delay[MAX_FRAME_NUM]; 185 | int frame_count; /* normally 1 */ 186 | int loop_count; 187 | int current_frame; /* for yaimgfb */ 188 | }; 189 | 190 | unsigned char *file_into_memory(FILE *fp, size_t *data_size) 191 | { 192 | unsigned char *buffer; 193 | size_t n, size; 194 | 195 | fseek(fp, 0L, SEEK_END); 196 | size = ftell(fp); 197 | 198 | if ((buffer = ecalloc(1, size)) == NULL) 199 | return NULL; 200 | 201 | fseek(fp, 0L, SEEK_SET); 202 | if ((n = fread(buffer, 1, size, fp)) != size) { 203 | free(buffer); 204 | return NULL; 205 | } 206 | *data_size = size; 207 | 208 | return buffer; 209 | } 210 | 211 | bool load_jpeg(FILE *fp, struct image *img) 212 | { 213 | if ((img->data[0] = (uint8_t *) stbi_load_from_file(fp, &img->width, &img->height, &img->channel, 3)) == NULL) 214 | return false; 215 | 216 | return true; 217 | } 218 | 219 | bool load_png(FILE *fp, struct image *img) 220 | { 221 | unsigned char *mem; 222 | size_t size; 223 | 224 | if ((mem = file_into_memory(fp, &size)) == NULL) 225 | return false; 226 | 227 | if (lodepng_decode24(&img->data[0], (unsigned *) &img->width, (unsigned *) &img->height, mem, size) != 0) { 228 | free(mem); 229 | return false; 230 | } 231 | 232 | img->channel = 3; 233 | free(mem); 234 | return true; 235 | } 236 | 237 | /* libns{gif,bmp} functions */ 238 | void *gif_bitmap_create(int width, int height) 239 | { 240 | return calloc(width * height, BYTES_PER_PIXEL); 241 | } 242 | 243 | void gif_bitmap_set_opaque(void *bitmap, bool opaque) 244 | { 245 | (void) opaque; /* unused */ 246 | (void) bitmap; 247 | } 248 | 249 | bool gif_bitmap_test_opaque(void *bitmap) 250 | { 251 | (void) bitmap; /* unused */ 252 | return false; 253 | } 254 | 255 | unsigned char *gif_bitmap_get_buffer(void *bitmap) 256 | { 257 | return bitmap; 258 | } 259 | 260 | void gif_bitmap_destroy(void *bitmap) 261 | { 262 | free(bitmap); 263 | } 264 | 265 | void gif_bitmap_modified(void *bitmap) 266 | { 267 | (void) bitmap; /* unused */ 268 | return; 269 | } 270 | 271 | bool load_gif(FILE *fp, struct image *img) 272 | { 273 | gif_bitmap_callback_vt gif_callbacks = { 274 | gif_bitmap_create, 275 | gif_bitmap_destroy, 276 | gif_bitmap_get_buffer, 277 | gif_bitmap_set_opaque, 278 | gif_bitmap_test_opaque, 279 | gif_bitmap_modified 280 | }; 281 | size_t size; 282 | gif_result code; 283 | unsigned char *mem; 284 | gif_animation gif; 285 | int i; 286 | 287 | gif_create(&gif, &gif_callbacks); 288 | if ((mem = file_into_memory(fp, &size)) == NULL) 289 | return false; 290 | 291 | code = gif_initialise(&gif, size, mem); 292 | if (code != GIF_OK && code != GIF_WORKING) 293 | goto error_initialize_failed; 294 | 295 | img->width = gif.width; 296 | img->height = gif.height; 297 | img->channel = BYTES_PER_PIXEL; /* libnsgif always return 4bpp image */ 298 | size = img->width * img->height * img->channel; 299 | 300 | /* read animation gif */ 301 | img->frame_count = (gif.frame_count < MAX_FRAME_NUM) ? gif.frame_count: MAX_FRAME_NUM - 1; 302 | img->loop_count = gif.loop_count; 303 | 304 | for (i = 0; i < img->frame_count; i++) { 305 | code = gif_decode_frame(&gif, i); 306 | if (code != GIF_OK) 307 | goto error_decode_failed; 308 | 309 | if ((img->data[i] = (uint8_t *) ecalloc(1, size)) == NULL) 310 | goto error_decode_failed; 311 | memcpy(img->data[i], gif.frame_image, size); 312 | 313 | img->delay[i] = gif.frames[i].frame_delay; 314 | } 315 | 316 | gif_finalise(&gif); 317 | free(mem); 318 | return true; 319 | 320 | error_decode_failed: 321 | img->frame_count = i; 322 | for (i = 0; i < img->frame_count; i++) { 323 | free(img->data[i]); 324 | img->data[i] = NULL; 325 | } 326 | gif_finalise(&gif); 327 | error_initialize_failed: 328 | free(mem); 329 | return false; 330 | } 331 | 332 | void *bmp_bitmap_create(int width, int height, unsigned int state) 333 | { 334 | (void) state; /* unused */ 335 | return calloc(width * height, BYTES_PER_PIXEL); 336 | } 337 | 338 | unsigned char *bmp_bitmap_get_buffer(void *bitmap) 339 | { 340 | return bitmap; 341 | } 342 | 343 | void bmp_bitmap_destroy(void *bitmap) 344 | { 345 | free(bitmap); 346 | } 347 | 348 | size_t bmp_bitmap_get_bpp(void *bitmap) 349 | { 350 | (void) bitmap; /* unused */ 351 | return BYTES_PER_PIXEL; 352 | } 353 | 354 | bool load_bmp(FILE *fp, struct image *img) 355 | { 356 | bmp_bitmap_callback_vt bmp_callbacks = { 357 | bmp_bitmap_create, 358 | bmp_bitmap_destroy, 359 | bmp_bitmap_get_buffer, 360 | bmp_bitmap_get_bpp 361 | }; 362 | bmp_result code; 363 | size_t size; 364 | unsigned char *mem; 365 | bmp_image bmp; 366 | 367 | bmp_create(&bmp, &bmp_callbacks); 368 | if ((mem = file_into_memory(fp, &size)) == NULL) 369 | return false; 370 | 371 | code = bmp_analyse(&bmp, size, mem); 372 | if (code != BMP_OK) 373 | goto error_analyse_failed; 374 | 375 | code = bmp_decode(&bmp); 376 | if (code != BMP_OK) 377 | goto error_decode_failed; 378 | 379 | img->width = bmp.width; 380 | img->height = bmp.height; 381 | img->channel = BYTES_PER_PIXEL; /* libnsbmp always return 4bpp image */ 382 | 383 | size = img->width * img->height * img->channel; 384 | if ((img->data[0] = (uint8_t *) ecalloc(1, size)) == NULL) 385 | goto error_decode_failed; 386 | memcpy(img->data[0], bmp.bitmap, size); 387 | 388 | bmp_finalise(&bmp); 389 | free(mem); 390 | return true; 391 | 392 | error_decode_failed: 393 | bmp_finalise(&bmp); 394 | error_analyse_failed: 395 | free(mem); 396 | return false; 397 | } 398 | 399 | /* pnm functions */ 400 | inline int getint(FILE *fp) 401 | { 402 | int c, n = 0; 403 | 404 | do { 405 | c = fgetc(fp); 406 | } while (isspace(c)); 407 | 408 | while (isdigit(c)) { 409 | n = n * 10 + c - '0'; 410 | c = fgetc(fp); 411 | } 412 | return n; 413 | } 414 | 415 | inline uint8_t pnm_normalize(int c, int type, int max_value) 416 | { 417 | if (type == 1 || type == 4) 418 | return (c == 0) ? 0: 0xFF; 419 | else 420 | return 0xFF * c / max_value; 421 | } 422 | 423 | bool load_pnm(FILE *fp, struct image *img) 424 | { 425 | int size, type, c, count, max_value = 0; 426 | 427 | if (fgetc(fp) != 'P') 428 | return false; 429 | 430 | type = fgetc(fp) - '0'; 431 | img->channel = (type == 1 || type == 2 || type == 4 || type == 5) ? 1: 432 | (type == 3 || type == 6) ? 3: -1; 433 | 434 | if (img->channel == -1) 435 | return false; 436 | 437 | /* read header */ 438 | while ((c = fgetc(fp)) != EOF) { 439 | if (c == '#') 440 | while ((c = fgetc(fp)) != '\n'); 441 | 442 | if (isspace(c)) 443 | continue; 444 | 445 | if (isdigit(c)) { 446 | ungetc(c, fp); 447 | img->width = getint(fp); 448 | img->height = getint(fp); 449 | if (type != 1 && type != 4) 450 | max_value = getint(fp); 451 | break; 452 | } 453 | } 454 | 455 | size = img->width * img->height * img->channel; 456 | if ((img->data[0] = ecalloc(1, size)) == NULL) 457 | return false; 458 | 459 | /* read data */ 460 | count = 0; 461 | if (1 <= type && type <= 3) { 462 | while ((c = fgetc(fp)) != EOF) { 463 | if (c == '#') 464 | while ((c = fgetc(fp)) != '\n'); 465 | 466 | if (isspace(c)) 467 | continue; 468 | 469 | if (isdigit(c)) { 470 | ungetc(c, fp); 471 | *(img->data[0] + count++) = pnm_normalize(getint(fp), type, max_value); 472 | } 473 | } 474 | } 475 | else { 476 | while ((c = fgetc(fp)) != EOF) 477 | *(img->data[0] + count++) = pnm_normalize(c, type, max_value); 478 | } 479 | 480 | return true; 481 | } 482 | 483 | void init_image(struct image *img) 484 | { 485 | for (int i = 0; i < MAX_FRAME_NUM; i++) { 486 | img->data[i] = NULL; 487 | img->delay[i] = 0; 488 | } 489 | img->width = 0; 490 | img->height = 0; 491 | img->channel = 0; 492 | img->alpha = false; 493 | /* for animation gif */ 494 | img->frame_count = 1; 495 | img->loop_count = 0; 496 | img->current_frame = 0; 497 | } 498 | 499 | void free_image(struct image *img) 500 | { 501 | for (int i = 0; i < img->frame_count; i++) { 502 | free(img->data[i]); 503 | img->data[i] = NULL; 504 | } 505 | } 506 | 507 | enum filetype_t check_filetype(FILE *fp) 508 | { 509 | /* 510 | JPEG(JFIF): FF D8 511 | PNG : 89 50 4E 47 0D 0A 1A 0A (0x89 'P' 'N' 'G' '\r' '\n' 0x1A '\n') 512 | GIF : 47 49 46 (ASCII 'G' 'I' 'F') 513 | BMP : 42 4D (ASCII 'B' 'M') 514 | PNM : 50 [31|32|33|34|35|36] ('P' ['1' - '6']) 515 | */ 516 | uint8_t header[CHECK_HEADER_SIZE]; 517 | static uint8_t jpeg_header[] = {0xFF, 0xD8}; 518 | static uint8_t png_header[] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; 519 | static uint8_t gif_header[] = {0x47, 0x49, 0x46}; 520 | static uint8_t bmp_header[] = {0x42, 0x4D}; 521 | size_t size; 522 | 523 | if ((size = fread(header, 1, CHECK_HEADER_SIZE, fp)) != CHECK_HEADER_SIZE) { 524 | logging(ERROR, "couldn't read header\n"); 525 | return TYPE_UNKNOWN; 526 | } 527 | fseek(fp, 0L, SEEK_SET); 528 | 529 | if (memcmp(header, jpeg_header, 2) == 0) 530 | return TYPE_JPEG; 531 | else if (memcmp(header, png_header, 8) == 0) 532 | return TYPE_PNG; 533 | else if (memcmp(header, gif_header, 3) == 0) 534 | return TYPE_GIF; 535 | else if (memcmp(header, bmp_header, 2) == 0) 536 | return TYPE_BMP; 537 | else if (header[0] == 'P' && ('0' <= header[1] && header[1] <= '6')) 538 | return TYPE_PNM; 539 | else 540 | return TYPE_UNKNOWN; 541 | } 542 | 543 | bool load_image(const char *file, struct image *img) 544 | { 545 | int i; 546 | enum filetype_t type; 547 | FILE *fp; 548 | 549 | static bool (*loader[])(FILE *fp, struct image *img) = { 550 | [TYPE_JPEG] = load_jpeg, 551 | [TYPE_PNG] = load_png, 552 | [TYPE_GIF] = load_gif, 553 | [TYPE_BMP] = load_bmp, 554 | [TYPE_PNM] = load_pnm, 555 | }; 556 | 557 | if ((fp = efopen(file, "r")) == NULL) 558 | return false; 559 | 560 | if ((type = check_filetype(fp)) == TYPE_UNKNOWN) { 561 | logging(ERROR, "unknown file type: %s\n", file); 562 | goto image_load_error; 563 | } 564 | 565 | if (loader[type](fp, img)) { 566 | img->alpha = (img->channel == 2 || img->channel == 4) ? true: false; 567 | logging(DEBUG, "image width:%d height:%d channel:%d alpha:%s\n", 568 | img->width, img->height, img->channel, (img->alpha) ? "true": "false"); 569 | if (img->frame_count > 1) { 570 | logging(DEBUG, "frame:%d loop:%d\n", img->frame_count, img->loop_count); 571 | for (i = 0; i < img->frame_count; i++) 572 | logging(DEBUG, "delay[%u]:%u\n", i, img->delay[i]); 573 | } 574 | efclose(fp); 575 | return true; 576 | } 577 | 578 | image_load_error: 579 | logging(ERROR, "image load error: %s\n", file); 580 | //init_image(img); 581 | efclose(fp); 582 | return false; 583 | } 584 | 585 | /* inline functions for accessing member of struct image: 586 | never access member of struct image directly */ 587 | static inline int get_frame_count(struct image *img) 588 | { 589 | return img->frame_count; 590 | } 591 | 592 | static inline uint8_t *get_current_frame(struct image *img) 593 | { 594 | return img->data[img->current_frame]; 595 | } 596 | 597 | static inline int get_current_delay(struct image *img) 598 | { 599 | return img->delay[img->current_frame]; 600 | } 601 | 602 | static inline void increment_frame(struct image *img) 603 | { 604 | img->current_frame = (img->current_frame + 1) % img->frame_count; 605 | } 606 | 607 | static inline int get_image_width(struct image *img) 608 | { 609 | return img->width; 610 | } 611 | 612 | static inline int get_image_height(struct image *img) 613 | { 614 | return img->height; 615 | } 616 | 617 | static inline int get_image_channel(struct image *img) 618 | { 619 | return img->channel; 620 | } 621 | 622 | /* image proccessing functions: 623 | never use *_single functions directly */ 624 | static inline void get_rgb(struct image *img, uint8_t *data, int x, int y, uint8_t *r, uint8_t *g, uint8_t *b) 625 | { 626 | uint8_t *ptr; 627 | 628 | ptr = data + img->channel * (y * img->width + x); 629 | 630 | if (img->channel <= 2) { /* grayscale (+ alpha) */ 631 | *r = *g = *b = *ptr; 632 | } else { /* rgb (+ alpha) */ 633 | *r = *ptr; *g = *(ptr + 1); *b = *(ptr + 2); 634 | } 635 | } 636 | 637 | static inline void get_average(struct image *img, uint8_t *data, int x_from, int y_from, int x_to, int y_to, uint8_t pixel[]) 638 | { 639 | int cell_num; 640 | uint8_t r, g, b; 641 | uint16_t rsum, gsum, bsum; 642 | 643 | rsum = gsum = bsum = 0; 644 | for (int y = y_from; y < y_to; y++) { 645 | for (int x = x_from; x < x_to; x++) { 646 | get_rgb(img, data, x, y, &r, &g, &b); 647 | rsum += r; gsum += g; bsum += b; 648 | } 649 | } 650 | 651 | cell_num = (y_to - y_from) * (x_to - x_from); 652 | if (cell_num > 1) { 653 | rsum /= cell_num; gsum /= cell_num; bsum /= cell_num; 654 | } 655 | 656 | if (img->channel <= 2) 657 | *pixel++ = rsum; 658 | else { 659 | *pixel++ = rsum; *pixel++ = gsum; *pixel++ = bsum; 660 | } 661 | 662 | if (img->alpha) 663 | *pixel = 0; 664 | } 665 | 666 | uint8_t *rotate_image_single(struct image *img, uint8_t *data, int angle) 667 | { 668 | int x1, x2, y1, y2, r, dst_width, dst_height; 669 | uint8_t *rotated_data; 670 | long offset_dst, offset_src; 671 | 672 | static const int cos[3] = {0, -1, 0}; 673 | static const int sin[3] = {1, 0, -1}; 674 | 675 | int shift[3][3] = { 676 | /* x_shift, y_shift, sign */ 677 | {img->height - 1, 0 , -1}, 678 | {img->width - 1, img->height - 1, 1}, 679 | { 0, img->width - 1, -1} 680 | }; 681 | 682 | if (angle != 90 && angle != 180 && angle != 270) 683 | return NULL; 684 | /* r == 0: clockwise : (angle 90) */ 685 | /* r == 1: upside down : (angle 180) */ 686 | /* r == 2: counter clockwise: (angle 270) */ 687 | r = angle / 90 - 1; 688 | 689 | if (angle == 90 || angle == 270) { 690 | dst_width = img->height; 691 | dst_height = img->width; 692 | } else { 693 | dst_width = img->width; 694 | dst_height = img->height; 695 | } 696 | 697 | if ((rotated_data = (uint8_t *) ecalloc(dst_width * dst_height, img->channel)) == NULL) 698 | return NULL; 699 | 700 | logging(DEBUG, "rotated image: %dx%d size:%d\n", 701 | dst_width, dst_height, dst_width * dst_height * img->channel); 702 | 703 | for (y2 = 0; y2 < dst_height; y2++) { 704 | for (x2 = 0; x2 < dst_width; x2++) { 705 | x1 = ((x2 - shift[r][0]) * cos[r] - (y2 - shift[r][1]) * sin[r]) * shift[r][2]; 706 | y1 = ((x2 - shift[r][0]) * sin[r] + (y2 - shift[r][1]) * cos[r]) * shift[r][2]; 707 | offset_src = img->channel * (y1 * img->width + x1); 708 | offset_dst = img->channel * (y2 * dst_width + x2); 709 | memcpy(rotated_data + offset_dst, data + offset_src, img->channel); 710 | } 711 | } 712 | free(data); 713 | 714 | img->width = dst_width; 715 | img->height = dst_height; 716 | 717 | return rotated_data; 718 | } 719 | 720 | void rotate_image(struct image *img, int angle, bool rotate_all) 721 | { 722 | uint8_t *rotated_data; 723 | 724 | if (rotate_all) { 725 | for (int i = 0; i < img->frame_count; i++) 726 | if ((rotated_data = rotate_image_single(img, img->data[i], angle)) != NULL) 727 | img->data[i] = rotated_data; 728 | } else { 729 | if ((rotated_data = rotate_image_single(img, img->data[img->current_frame], angle)) != NULL) 730 | img->data[img->current_frame] = rotated_data; 731 | } 732 | } 733 | 734 | uint8_t *resize_image_single(struct image *img, uint8_t *data, int disp_width, int disp_height) 735 | { 736 | /* TODO: support enlarge */ 737 | int width_rate, height_rate, resize_rate; 738 | int dst_width, dst_height, y_from, x_from, y_to, x_to; 739 | uint8_t *resized_data, pixel[img->channel]; 740 | long offset_dst; 741 | 742 | width_rate = MULTIPLER * disp_width / img->width; 743 | height_rate = MULTIPLER * disp_height / img->height; 744 | resize_rate = (width_rate < height_rate) ? width_rate: height_rate; 745 | 746 | logging(DEBUG, "width_rate:%.2d height_rate:%.2d resize_rate:%.2d\n", 747 | width_rate, height_rate, resize_rate); 748 | 749 | /* only support shrink */ 750 | if ((resize_rate / MULTIPLER) >= 1) 751 | return NULL; 752 | 753 | /* FIXME: let the same num (img->width == fb->width), if it causes SEGV, remove "+ 1" */ 754 | dst_width = resize_rate * img->width / MULTIPLER + 1; 755 | dst_height = resize_rate * img->height / MULTIPLER; 756 | 757 | if ((resized_data = (uint8_t *) ecalloc(dst_width * dst_height, img->channel)) == NULL) 758 | return NULL; 759 | 760 | logging(DEBUG, "resized image: %dx%d size:%d\n", 761 | dst_width, dst_height, dst_width * dst_height * img->channel); 762 | 763 | for (int y = 0; y < dst_height; y++) { 764 | y_from = MULTIPLER * y / resize_rate; 765 | y_to = MULTIPLER * (y + 1) / resize_rate; 766 | for (int x = 0; x < dst_width; x++) { 767 | x_from = MULTIPLER * x / resize_rate; 768 | x_to = MULTIPLER * (x + 1) / resize_rate; 769 | get_average(img, data, x_from, y_from, x_to, y_to, pixel); 770 | offset_dst = img->channel * (y * dst_width + x); 771 | memcpy(resized_data + offset_dst, pixel, img->channel); 772 | } 773 | } 774 | free(data); 775 | 776 | img->width = dst_width; 777 | img->height = dst_height; 778 | 779 | return resized_data; 780 | } 781 | 782 | void resize_image(struct image *img, int disp_width, int disp_height, bool resize_all) 783 | { 784 | uint8_t *resized_data; 785 | 786 | if (resize_all) { 787 | for (int i = 0; i < img->frame_count; i++) 788 | if ((resized_data = resize_image_single(img, img->data[i], disp_width, disp_height)) != NULL) 789 | img->data[i] = resized_data; 790 | } else { 791 | if ((resized_data = resize_image_single(img, img->data[img->current_frame], disp_width, disp_height)) != NULL) 792 | img->data[img->current_frame] = resized_data; 793 | } 794 | } 795 | 796 | uint8_t *normalize_bpp_single(struct image *img, uint8_t *data, int bytes_per_pixel) 797 | { 798 | uint8_t *normalized_data, *src, *dst, r, g, b; 799 | 800 | if ((normalized_data = (uint8_t *) 801 | ecalloc(img->width * img->height, bytes_per_pixel)) == NULL) 802 | return NULL; 803 | 804 | if (img->channel <= 2) { /* grayscale (+ alpha) */ 805 | for (int y = 0; y < img->height; y++) { 806 | for (int x = 0; x < img->width; x++) { 807 | src = data + img->channel * (y * img->width + x); 808 | dst = normalized_data + bytes_per_pixel * (y * img->width + x); 809 | *dst = *src; *(dst + 1) = *src; *(dst + 2) = *src; 810 | } 811 | } 812 | } else { /* rgb (+ alpha) */ 813 | for (int y = 0; y < img->height; y++) { 814 | for (int x = 0; x < img->width; x++) { 815 | get_rgb(img, data, x, y, &r, &g, &b); 816 | dst = normalized_data + bytes_per_pixel * (y * img->width + x); 817 | *dst = r; *(dst + 1) = g; *(dst + 2) = b; 818 | } 819 | } 820 | } 821 | free(data); 822 | 823 | return normalized_data; 824 | } 825 | 826 | void normalize_bpp(struct image *img, int bytes_per_pixel, bool normalize_all) 827 | { 828 | uint8_t *normalized_data; 829 | 830 | /* XXX: now only support bytes_per_pixel == 3 */ 831 | if (bytes_per_pixel != 3) 832 | return; 833 | 834 | if (normalize_all) { 835 | for (int i = 0; i < img->frame_count; i++) 836 | if ((normalized_data = normalize_bpp_single(img, img->data[i], bytes_per_pixel)) != NULL) 837 | img->data[i] = normalized_data; 838 | } else { 839 | if ((normalized_data = normalize_bpp_single(img, img->data[img->current_frame], bytes_per_pixel)) != NULL) 840 | img->data[img->current_frame] = normalized_data; 841 | } 842 | } 843 | 844 | /* main functions */ 845 | #include "sixel.h" 846 | 847 | char temp_file[BUFSIZE]; 848 | 849 | enum { 850 | SIXEL_COLORS = 256, 851 | SIXEL_BPP = 3, 852 | TERM_WIDTH = 1280, 853 | TERM_HEIGHT = 1024, 854 | }; 855 | 856 | void usage() 857 | { 858 | printf("usage:\n" 859 | "\tsdump [-h] [-f] [-r angle] image\n" 860 | "\tcat image | sdump\n" 861 | "\twget -O - image_url | sdump\n" 862 | "options:\n" 863 | "\t-h: show this help\n" 864 | "\t-f: fit image to display\n" 865 | "\t-r: rotate image (90/180/270)\n" 866 | ); 867 | } 868 | 869 | void remove_temp_file() 870 | { 871 | extern char temp_file[BUFSIZE]; /* global */ 872 | remove(temp_file); 873 | } 874 | 875 | char *make_temp_file(const char *template) 876 | { 877 | extern char temp_file[BUFSIZE]; /* global */ 878 | int fd; 879 | ssize_t size, file_size = 0; 880 | char buf[BUFSIZE], *env; 881 | 882 | /* stdin is tty or not */ 883 | if (isatty(STDIN_FILENO)) { 884 | logging(ERROR, "stdin is neither pipe nor redirect\n"); 885 | return NULL; 886 | } 887 | 888 | /* prepare temp file */ 889 | memset(temp_file, 0, BUFSIZE); 890 | if ((env = getenv("TMPDIR")) != NULL) { 891 | snprintf(temp_file, BUFSIZE, "%s/%s", env, template); 892 | } else { 893 | snprintf(temp_file, BUFSIZE, "/tmp/%s", template); 894 | } 895 | 896 | if ((fd = emkstemp(temp_file)) < 0) 897 | return NULL; 898 | logging(DEBUG, "tmp file:%s\n", temp_file); 899 | 900 | /* register cleanup function */ 901 | if (atexit(remove_temp_file)) 902 | logging(ERROR, "atexit() failed\nmaybe temporary file remains...\n"); 903 | 904 | /* read data */ 905 | while ((size = read(STDIN_FILENO, buf, BUFSIZE)) > 0) { 906 | write(fd, buf, size); 907 | file_size += size; 908 | } 909 | eclose(fd); 910 | 911 | if (file_size == 0) { 912 | logging(ERROR, "stdin is empty\n"); 913 | return NULL; 914 | } 915 | 916 | return temp_file; 917 | } 918 | 919 | int sixel_write_callback(char *data, int size, void *priv) 920 | { 921 | /* 922 | for (int i = 0; i < size; i++) { 923 | if (i == (size - 1)) 924 | break; 925 | 926 | if (*(data + i) == 0x1B && *(data + i + 1) == 0x5C) { 927 | fprintf((FILE *) priv, "\033\033\\\033P\\"); 928 | break; 929 | } else { 930 | fwrite(data + i, 1, 1, fp); 931 | } 932 | } 933 | 934 | logging(DEBUG, "write callback() size:%d\n", size); 935 | return size; 936 | */ 937 | return fwrite(data, size, 1, (FILE *) priv); 938 | } 939 | 940 | void cleanup(sixel_dither_t *sixel_dither, sixel_output_t *sixel_context, struct image *img) 941 | { 942 | if (sixel_dither) 943 | sixel_dither_unref(sixel_dither); 944 | if (sixel_context) 945 | sixel_output_unref(sixel_context); 946 | free_image(img); 947 | } 948 | 949 | int main(int argc, char **argv) 950 | { 951 | const char *template = "sdump.XXXXXX"; 952 | char *file; 953 | bool resize = false; 954 | int angle = 0, opt; 955 | struct image img; 956 | sixel_output_t *sixel_context = NULL; 957 | sixel_dither_t *sixel_dither = NULL; 958 | 959 | /* check arg */ 960 | while ((opt = getopt(argc, argv, "hfr:")) != -1) { 961 | switch (opt) { 962 | case 'h': 963 | usage(); 964 | return EXIT_SUCCESS; 965 | case 'f': 966 | resize = true; 967 | break; 968 | case 'r': 969 | angle = str2num(optarg); 970 | break; 971 | default: 972 | break; 973 | } 974 | } 975 | 976 | /* open file */ 977 | if (optind < argc) 978 | file = argv[optind]; 979 | else 980 | file = make_temp_file(template); 981 | 982 | if (file == NULL) { 983 | logging(FATAL, "input file not found\n"); 984 | usage(); 985 | return EXIT_FAILURE; 986 | } 987 | 988 | /* init */ 989 | init_image(&img); 990 | 991 | if (load_image(file, &img) == false) { 992 | logging(FATAL, "couldn't load image\n"); 993 | return EXIT_FAILURE; 994 | } 995 | 996 | /* rotate/resize and draw */ 997 | /* TODO: support color reduction for 8bpp mode */ 998 | if (angle != 0) 999 | rotate_image(&img, angle, true); 1000 | 1001 | if (resize) 1002 | resize_image(&img, TERM_WIDTH, TERM_HEIGHT, true); 1003 | 1004 | /* sixel */ 1005 | /* XXX: libsixel only allows 3 bytes per pixel image, 1006 | we should convert bpp when bpp is 1 or 2 or 4 */ 1007 | if (get_image_channel(&img) != SIXEL_BPP) 1008 | normalize_bpp(&img, SIXEL_BPP, true); 1009 | 1010 | if ((sixel_dither = sixel_dither_create(SIXEL_COLORS)) == NULL) { 1011 | logging(ERROR, "couldn't create dither\n"); 1012 | goto error_occured; 1013 | } 1014 | 1015 | /* XXX: use first frame for dither initialize */ 1016 | if (sixel_dither_initialize(sixel_dither, get_current_frame(&img), get_image_width(&img), get_image_height(&img), 1017 | SIXEL_BPP, LARGE_AUTO, REP_AUTO, QUALITY_AUTO) != 0) { 1018 | logging(ERROR, "couldn't initialize dither\n"); 1019 | sixel_dither_unref(sixel_dither); 1020 | goto error_occured; 1021 | } 1022 | sixel_dither_set_diffusion_type(sixel_dither, DIFFUSE_AUTO); 1023 | 1024 | if ((sixel_context = sixel_output_create(sixel_write_callback, stdout)) == NULL) { 1025 | logging(ERROR, "couldn't create sixel context\n"); 1026 | goto error_occured; 1027 | } 1028 | sixel_output_set_8bit_availability(sixel_context, CSIZE_7BIT); 1029 | 1030 | printf("\0337"); /* save cursor position */ 1031 | for (int i = 0; i < get_frame_count(&img); i++) { 1032 | printf("\0338"); /* restore cursor position */ 1033 | sixel_encode(get_current_frame(&img), get_image_width(&img), get_image_height(&img), get_image_channel(&img), sixel_dither, sixel_context); 1034 | usleep(get_current_delay(&img) * 10000); /* gif delay 1 == 1/100 sec */ 1035 | increment_frame(&img); 1036 | } 1037 | 1038 | /* cleanup resource */ 1039 | cleanup(sixel_dither, sixel_context, &img); 1040 | return EXIT_SUCCESS; 1041 | 1042 | error_occured: 1043 | cleanup(sixel_dither, sixel_context, &img); 1044 | return EXIT_FAILURE;; 1045 | } 1046 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE for licence details. */ 2 | /* error functions */ 3 | enum loglevel_t { 4 | DEBUG = 0, 5 | WARN, 6 | ERROR, 7 | FATAL, 8 | }; 9 | 10 | void logging(enum loglevel_t loglevel, char *format, ...) 11 | { 12 | va_list arg; 13 | static const char *loglevel2str[] = { 14 | [DEBUG] = "DEBUG", 15 | [WARN] = "WARN", 16 | [ERROR] = "ERROR", 17 | [FATAL] = "FATAL", 18 | }; 19 | 20 | if (loglevel == DEBUG && !VERBOSE) 21 | return; 22 | 23 | fprintf(stderr, ">>%s<<\t", loglevel2str[loglevel]); 24 | va_start(arg, format); 25 | vfprintf(stderr, format, arg); 26 | va_end(arg); 27 | } 28 | 29 | /* wrapper of C functions */ 30 | int eopen(const char *path, int flag) 31 | { 32 | int fd; 33 | errno = 0; 34 | 35 | if ((fd = open(path, flag)) < 0) { 36 | logging(ERROR, "couldn't open \"%s\"\n", path); 37 | logging(ERROR, "open: %s\n", strerror(errno)); 38 | } 39 | return fd; 40 | } 41 | 42 | int eclose(int fd) 43 | { 44 | int ret = 0; 45 | errno = 0; 46 | 47 | if ((ret = close(fd)) < 0) 48 | logging(ERROR, "close: %s\n", strerror(errno)); 49 | 50 | return ret; 51 | } 52 | 53 | FILE *efopen(const char *path, char *mode) 54 | { 55 | FILE *fp; 56 | errno = 0; 57 | 58 | if ((fp = fopen(path, mode)) == NULL) { 59 | logging(ERROR, "couldn't open \"%s\"\n", path); 60 | logging(ERROR, "fopen: %s\n", strerror(errno)); 61 | } 62 | return fp; 63 | } 64 | 65 | int efclose(FILE *fp) 66 | { 67 | int ret; 68 | errno = 0; 69 | 70 | if ((ret = fclose(fp)) < 0) 71 | logging(ERROR, "fclose: %s\n", strerror(errno)); 72 | 73 | return ret; 74 | } 75 | 76 | void *ecalloc(size_t nmemb, size_t size) 77 | { 78 | void *ptr; 79 | errno = 0; 80 | 81 | if ((ptr = calloc(nmemb, size)) == NULL) 82 | logging(ERROR, "calloc: %s\n", strerror(errno)); 83 | 84 | return ptr; 85 | } 86 | 87 | long int estrtol(const char *nptr, char **endptr, int base) 88 | { 89 | long int ret; 90 | errno = 0; 91 | 92 | ret = strtol(nptr, endptr, base); 93 | if (ret == LONG_MIN || ret == LONG_MAX) { 94 | logging(ERROR, "strtol: %s\n", strerror(errno)); 95 | return 0; 96 | } 97 | 98 | return ret; 99 | } 100 | 101 | int emkstemp(char *template) 102 | { 103 | int ret; 104 | errno = 0; 105 | 106 | if ((ret = mkstemp(template)) < 0) { 107 | logging(ERROR, "couldn't open \"%s\"\n", template); 108 | logging(ERROR, "mkstemp: %s\n", strerror(errno)); 109 | } 110 | 111 | return ret; 112 | } 113 | 114 | int eselect(int max_fd, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *tv) 115 | { 116 | int ret; 117 | errno = 0; 118 | 119 | if ((ret = select(max_fd, readfds, writefds, errorfds, tv)) < 0) { 120 | if (errno == EINTR) 121 | return eselect(max_fd, readfds, writefds, errorfds, tv); 122 | else 123 | logging(ERROR, "select: %s\n", strerror(errno)); 124 | } 125 | return ret; 126 | } 127 | 128 | ssize_t ewrite(int fd, const void *buf, size_t size) 129 | { 130 | ssize_t ret; 131 | errno = 0; 132 | 133 | if ((ret = write(fd, buf, size)) < 0) { 134 | if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) 135 | return ewrite(fd, buf, size); 136 | else 137 | logging(ERROR, "write: %s\n", strerror(errno)); 138 | } else if (ret < (ssize_t) size) { 139 | return ret + ewrite(fd, (char *) buf + ret, size - ret); 140 | } 141 | return ret; 142 | } 143 | 144 | int esigaction(int signo, struct sigaction *act, struct sigaction *oact) 145 | { 146 | int ret; 147 | errno = 0; 148 | 149 | if ((ret = sigaction(signo, act, oact)) < 0) 150 | logging(ERROR, "sigaction: %s\n", strerror(errno)); 151 | 152 | return ret; 153 | } 154 | 155 | /* 156 | int estat(const char *restrict path, struct stat *restrict buf) 157 | { 158 | int ret; 159 | errno = 0; 160 | 161 | if ((ret = stat(path, buf)) < 0) 162 | logging(ERROR, "stat: %s\n", strerror(errno)); 163 | 164 | return ret; 165 | } 166 | 167 | void *emmap(void *addr, size_t len, int prot, int flag, int fd, off_t offset) 168 | { 169 | uint32_t *fp; 170 | errno = 0; 171 | 172 | if ((fp = (uint32_t *) mmap(addr, len, prot, flag, fd, offset)) == MAP_FAILED) 173 | logging(ERROR, "mmap: %s\n", strerror(errno)); 174 | 175 | return fp; 176 | } 177 | 178 | int emunmap(void *ptr, size_t len) 179 | { 180 | int ret; 181 | errno = 0; 182 | 183 | if ((ret = munmap(ptr, len)) < 0) 184 | logging(ERROR, "munmap: %s\n", strerror(errno)); 185 | 186 | return ret; 187 | } 188 | */ 189 | 190 | /* some useful functions */ 191 | int str2num(char *str) 192 | { 193 | if (str == NULL) 194 | return 0; 195 | 196 | return estrtol(str, NULL, 10); 197 | } 198 | 199 | /* 200 | static inline void swapint(int *a, int *b) 201 | { 202 | int tmp = *a; 203 | *a = *b; 204 | *b = tmp; 205 | } 206 | 207 | static inline int my_ceil(int val, int div) 208 | { 209 | return (val + div - 1) / div; 210 | } 211 | 212 | static inline uint32_t bit_reverse(uint32_t val, int bits) 213 | { 214 | uint32_t ret = val; 215 | int shift = bits - 1; 216 | 217 | for (val >>= 1; val; val >>= 1) { 218 | ret <<= 1; 219 | ret |= val & 1; 220 | shift--; 221 | } 222 | 223 | return ret <<= shift; 224 | } 225 | */ 226 | -------------------------------------------------------------------------------- /yaimg-sixel/makefile: -------------------------------------------------------------------------------- 1 | CC ?= gcc 2 | #CC ?= clang 3 | 4 | CFLAGS ?= -Wall -Wextra -std=c99 -pedantic \ 5 | -O3 -pipe -s 6 | #-Og -g -rdynamic #-pg 7 | LDFLAGS ?= -ljpeg -lpng -lsixel 8 | 9 | HDR = ../libnsgif.h ../libnsbmp.h \ 10 | ../util.h ../loader.h ../image.h \ 11 | ../sixel.h parsearg.h 12 | SRC = yaimg-sixel.c ../libnsgif.c ../libnsbmp.c 13 | DST = yaimg-sixel 14 | 15 | all: $(DST) 16 | 17 | yaimg-sixel: $(SRC) $(HDR) 18 | $(CC) $(CFLAGS) $(LDFLAGS) $(SRC) -o $@ 19 | 20 | clean: 21 | rm -f $(DST) 22 | -------------------------------------------------------------------------------- /yaimg-sixel/parsearg.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE for licence details. */ 2 | enum { 3 | MAX_ARGS = 128, 4 | }; 5 | 6 | struct parm_t { /* for parse_arg() */ 7 | int argc; 8 | char *argv[MAX_ARGS]; 9 | }; 10 | 11 | /* parse_arg functions */ 12 | void reset_parm(struct parm_t *pt) 13 | { 14 | int i; 15 | 16 | pt->argc = 0; 17 | for (i = 0; i < MAX_ARGS; i++) 18 | pt->argv[i] = NULL; 19 | } 20 | 21 | void add_parm(struct parm_t *pt, char *cp) 22 | { 23 | if (pt->argc >= MAX_ARGS) 24 | return; 25 | 26 | logging(DEBUG, "argv[%d]: %s\n", 27 | pt->argc, (cp == NULL) ? "NULL": cp); 28 | 29 | pt->argv[pt->argc] = cp; 30 | pt->argc++; 31 | } 32 | 33 | void parse_arg(char *buf, struct parm_t *pt, int delim, int (is_valid)(int c)) 34 | { 35 | /* 36 | v..........v d v.....v d v.....v ... d 37 | (valid char) (delimiter) 38 | argv[0] argv[1] argv[2] ... argv[argc - 1] 39 | */ 40 | int i, length; 41 | char *cp, *vp; 42 | 43 | if (buf == NULL) 44 | return; 45 | 46 | length = strlen(buf); 47 | logging(DEBUG, "parse_arg() buf length:%d\n", length); 48 | 49 | vp = NULL; 50 | for (i = 0; i < length; i++) { 51 | cp = &buf[i]; 52 | 53 | if (vp == NULL && is_valid(*cp)) 54 | vp = cp; 55 | 56 | if (*cp == delim) { 57 | *cp = '\0'; 58 | add_parm(pt, vp); 59 | vp = NULL; 60 | } 61 | 62 | if (i == (length - 1) && (vp != NULL || *cp == '\0')) 63 | add_parm(pt, vp); 64 | } 65 | 66 | logging(DEBUG, "argc:%d\n", pt->argc); 67 | } 68 | -------------------------------------------------------------------------------- /yaimg-sixel/yaimg-sixel.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE for licence details. */ 2 | #include "yaimg-sixel.h" 3 | #include "../util.h" 4 | #include "../loader.h" 5 | #include "../image.h" 6 | #include "../sixel.h" 7 | #include "parsearg.h" 8 | 9 | uint8_t *crop_image_single(struct tty_t *tty, struct image *img, uint8_t *data, 10 | int shift_x, int shift_y, int width, int height) 11 | { 12 | int offset; 13 | uint8_t r, g, b, *cropped_data; 14 | 15 | if ((cropped_data = (uint8_t *) ecalloc(width * height, img->channel)) == NULL) 16 | return NULL; 17 | 18 | for (int y = 0; y < height; y++) { 19 | if (y >= tty->height) 20 | break; 21 | 22 | for (int x = 0; x < width; x++) { 23 | if (x >= tty->width) 24 | break; 25 | 26 | get_rgb(img, data, x + shift_x, y + shift_y, &r, &g, &b); 27 | 28 | /* update copy buffer */ 29 | offset = img->channel * (y * width + x); 30 | *(cropped_data + offset + 0) = r; 31 | *(cropped_data + offset + 1) = g; 32 | *(cropped_data + offset + 2) = b; 33 | } 34 | } 35 | free(data); 36 | 37 | img->width = width; 38 | img->height = height; 39 | 40 | return cropped_data; 41 | } 42 | 43 | void crop_image(struct tty_t *tty, struct image *img, 44 | int offset_x, int offset_y, int shift_x, int shift_y, int width, int height, bool crop_all) 45 | { 46 | /* 47 | +- screen -----------------+ 48 | | ^ | 49 | | | offset_y | 50 | | v | 51 | | +- image --+ | 52 | |<------>| | | 53 | |offset_x| | | 54 | | | | | 55 | | +----------+ | 56 | +--------------|-----------+ 57 | | 58 | v 59 | +- image ----------------------+ 60 | | ^ | 61 | | | shift_y | 62 | | v | 63 | | +- view port + ^ | 64 | |<----->| | | | 65 | |shift_x| | | height| 66 | | | | | | 67 | | +------------+ v | 68 | | <- width -> | 69 | +------------------------------+ 70 | */ 71 | uint8_t *cropped_data; 72 | 73 | if (shift_x + width > img->width) 74 | width = img->width - shift_x; 75 | 76 | if (shift_y + height > img->height) 77 | height = img->height - shift_y; 78 | 79 | if (offset_x + width > tty->width) 80 | width = tty->width - offset_x; 81 | 82 | if (offset_y + height > tty->height) 83 | height = tty->height - offset_y; 84 | 85 | if (crop_all) { 86 | for (int i = 0; i < img->frame_count; i++) { 87 | if ((cropped_data = crop_image_single(tty, img, img->data[i], 88 | shift_x, shift_y, width, height)) != NULL) 89 | img->data[i] = cropped_data; 90 | } 91 | } else { 92 | if ((cropped_data = crop_image_single(tty, img, img->data[img->current_frame], 93 | shift_x, shift_y, width, height)) != NULL) 94 | img->data[img->current_frame] = cropped_data; 95 | } 96 | } 97 | 98 | void w3m_draw(struct tty_t *tty, struct image imgs[], struct parm_t *parm, int op) 99 | { 100 | int index, offset_x, offset_y, width, height, shift_x, shift_y, view_w, view_h; 101 | char *file; 102 | struct image *img; 103 | struct sixel_t sixel; 104 | 105 | logging(DEBUG, "w3m_%s()\n", (op == W3M_DRAW) ? "draw": "redraw"); 106 | 107 | if (parm->argc != 11) 108 | return; 109 | 110 | index = str2num(parm->argv[1]) - 1; /* 1 origin */ 111 | offset_x = str2num(parm->argv[2]); 112 | offset_y = str2num(parm->argv[3]); 113 | width = str2num(parm->argv[4]); 114 | height = str2num(parm->argv[5]); 115 | shift_x = str2num(parm->argv[6]); 116 | shift_y = str2num(parm->argv[7]); 117 | view_w = str2num(parm->argv[8]); 118 | view_h = str2num(parm->argv[9]); 119 | file = parm->argv[10]; 120 | 121 | if (index < 0) 122 | index = 0; 123 | else if (index >= MAX_IMAGE) 124 | index = MAX_IMAGE - 1; 125 | img = &imgs[index]; 126 | 127 | logging(DEBUG, "index:%d offset_x:%d offset_y:%d shift_x:%d shift_y:%d view_w:%d view_h:%d\n", 128 | index, offset_x, offset_y, shift_x, shift_y, view_w, view_h); 129 | 130 | if (op == W3M_DRAW) { 131 | if (get_current_frame(img)) { /* cleanup preloaded image */ 132 | free_image(img); 133 | init_image(img); 134 | } 135 | if (load_image(file, img) == false) 136 | return; 137 | 138 | if (!get_current_frame(img)) { 139 | logging(ERROR, "specify unloaded image? img[%d] is NULL\n", index); 140 | return; 141 | } 142 | img->already_drew = false; 143 | /* for ranger (python filer) */ 144 | //} 145 | //if (!img->already_drew) { /* op == W3M_REDRAW */ 146 | } else if (!img->already_drew) { /* op == W3M_REDRAW */ 147 | char buf[BUFSIZE]; 148 | int size; 149 | struct image new = *img; 150 | 151 | size = get_image_width(img) * get_image_height(img) * get_image_channel(img); 152 | if ((new.data[0] = ecalloc(size, 1)) == NULL) 153 | return; 154 | memcpy(new.data[0], get_current_frame(img), size); 155 | new.frame_count = 1; 156 | new.current_frame = 0; 157 | 158 | /* XXX: at first, we need to resize, then crop */ 159 | if (width != get_image_width(&new) || height != get_image_height(&new)) 160 | resize_image(&new, width, height, false); 161 | 162 | crop_image(tty, &new, offset_x, offset_y, shift_x, shift_y, 163 | (view_w ? view_w: width), (view_h ? view_h: height), false); 164 | 165 | /* cursor move */ 166 | snprintf(buf, BUFSIZE, "\033[%d;%dH", 167 | (offset_y / tty->cell_height) + 1, (offset_x / tty->cell_width) + 1); 168 | ewrite(tty->fd, buf, strlen(buf)); 169 | 170 | /* sixel */ 171 | if (!sixel_init(tty, &sixel, &new)) 172 | goto sixel_init_err; 173 | sixel_write(tty, &sixel, &new); 174 | sixel_die(&sixel); 175 | 176 | sixel_init_err: 177 | img->already_drew = true; 178 | free_image(&new); 179 | increment_frame(img); 180 | } 181 | } 182 | 183 | void w3m_stop() 184 | { 185 | logging(DEBUG, "w3m_stop()\n"); 186 | } 187 | 188 | void w3m_sync() 189 | { 190 | logging(DEBUG, "w3m_sync()\n"); 191 | } 192 | 193 | void w3m_nop() 194 | { 195 | logging(DEBUG, "w3m_nop()\n"); 196 | printf("\n"); 197 | } 198 | 199 | void w3m_getsize(struct tty_t *tty, struct image *img, const char *file) 200 | { 201 | int width, height; 202 | logging(DEBUG, "w3m_getsize()\n"); 203 | 204 | if (get_current_frame(img)) { /* cleanup preloaded image */ 205 | free_image(img); 206 | init_image(img); 207 | } 208 | 209 | if (load_image(file, img)) { 210 | /* XXX: we should consider cell alignment */ 211 | width = get_image_width(img); 212 | height = get_image_height(img); 213 | 214 | if ((width % tty->cell_width) != 0) 215 | width += tty->cell_width - (width % tty->cell_width); 216 | if ((height % tty->cell_height) != 0) 217 | height += tty->cell_height - (height % tty->cell_height); 218 | 219 | printf("%d %d\n", width, height); 220 | logging(DEBUG, "responce: %d %d\n", width, height); 221 | } else { 222 | printf("0 0\n"); 223 | logging(DEBUG, "responce: 0 0\n"); 224 | } 225 | } 226 | 227 | void w3m_clear(struct image img[], struct parm_t *parm) 228 | { 229 | logging(DEBUG, "w3m_clear()\n"); 230 | 231 | for (int i = 0; i < MAX_IMAGE; i++) 232 | img[i].already_drew = false; 233 | 234 | (void) img; 235 | (void) parm; 236 | } 237 | 238 | void sig_handler(int signo) 239 | { 240 | extern volatile sig_atomic_t window_resized; /* global */ 241 | 242 | if (signo == SIGWINCH) 243 | window_resized = true; 244 | } 245 | 246 | void set_terminal_size(struct tty_t *tty, int width, int height, int cols, int lines) 247 | { 248 | tty->cell_width = (width / cols); 249 | tty->cell_height = (height / lines); 250 | tty->width = width; 251 | /* XXX: to avoid drawing at the bottom line, 252 | shrink display height a little. +7 pixel is just for me */ 253 | tty->height = height - (tty->cell_height + 7); 254 | } 255 | 256 | int check_fds(fd_set *fds, struct timeval *tv, int fd) 257 | { 258 | FD_ZERO(fds); 259 | FD_SET(fd, fds); 260 | tv->tv_sec = 0; 261 | tv->tv_usec = SELECT_TIMEOUT; 262 | return eselect(fd + 1, fds, NULL, NULL, tv); 263 | } 264 | 265 | bool terminal_query(int ttyfd, int *height, int *width, const char *send_seq, const char *recv_format) 266 | { 267 | int ret, check_count; 268 | char buf[BUFSIZE], *ptr; 269 | ssize_t size, left, length; 270 | struct timeval tv; 271 | fd_set fds; 272 | 273 | length = strlen(send_seq); 274 | if ((size = ewrite(ttyfd, send_seq, length)) != length) { 275 | logging(DEBUG, "write error (data:%d != wrote:%d)\n", size, length); 276 | return false; 277 | } 278 | 279 | ptr = buf; 280 | left = BUFSIZE - 1; 281 | 282 | check_count = 0; 283 | while (check_count < SELECT_CHECK_LIMIT) { 284 | if ((ret = check_fds(&fds, &tv, ttyfd)) < 0) 285 | continue; 286 | 287 | if (FD_ISSET(ttyfd, &fds)) { 288 | /* FIXME: read is blocked!!! */ 289 | if ((size = read(ttyfd, ptr, left)) > 0) { 290 | *(ptr + size) = '\0'; 291 | logging(DEBUG, "buf: %s\n", buf); 292 | if (sscanf(buf, recv_format, height, width) == 2) 293 | return true; 294 | } 295 | ptr += size; 296 | left -= size; 297 | } 298 | check_count++; 299 | } 300 | return false; 301 | } 302 | 303 | bool get_tty(struct tty_t *tty) 304 | { 305 | char ttyname[L_ctermid]; 306 | 307 | /* get ttyname and open it */ 308 | if (!ctermid(ttyname) 309 | || (tty->fd = eopen(ttyname, O_RDWR)) < 0) { 310 | logging(ERROR, "couldn't open controlling terminal\n"); 311 | return false; 312 | } 313 | logging(DEBUG, "ttyname:%s fd:%d\n", ttyname, tty->fd); 314 | 315 | return true; 316 | } 317 | 318 | bool check_terminal_size(struct tty_t *tty) 319 | { 320 | int width, height, cols, lines; 321 | struct winsize ws; 322 | 323 | /* at first, we try to get pixel size from "struct winsize" */ 324 | if (ioctl(tty->fd, TIOCGWINSZ, &ws)) { 325 | logging(ERROR, "ioctl: TIOCGWINSZ failed\n"); 326 | } else if (ws.ws_xpixel == 0 || ws.ws_ypixel == 0) { 327 | logging(ERROR, "struct winsize has no pixel information\n"); 328 | } else { 329 | set_terminal_size(tty, ws.ws_xpixel, ws.ws_ypixel, ws.ws_col, ws.ws_row); 330 | logging(DEBUG, "width:%d height%d cols:%d lines:%d\n", 331 | ws.ws_xpixel, ws.ws_ypixel, ws.ws_col, ws.ws_row); 332 | logging(DEBUG, "terminal size set by winsize\n"); 333 | return true; 334 | } 335 | 336 | /* second, try dtterm sequence: 337 | CSI 14 t: request window size in pixels. 338 | -> responce: CSI 4 ; height ; width t 339 | CSI 18 t: request text area size in characters 340 | -> responce: CSI 8 ; height ; width t 341 | */ 342 | /* this function causes I/O block... */ 343 | if (terminal_query(tty->fd, &height, &width, "\033[14t", "\033[4;%d;%dt") 344 | && terminal_query(tty->fd, &lines, &cols, "\033[18t", "\033[8;%d;%dt")) { 345 | set_terminal_size(tty, width, height, cols, lines); 346 | logging(DEBUG, "width:%d height%d cols:%d lines:%d\n", width, height, cols, lines); 347 | logging(DEBUG, "terminal size set by dtterm sequence\n"); 348 | return true; 349 | } else { 350 | logging(ERROR, "no responce for dtterm sequence\n"); 351 | } 352 | 353 | /* finally, use default value */ 354 | set_terminal_size(tty, TERM_WIDTH, TERM_HEIGHT, 355 | TERM_WIDTH / CELL_WIDTH, TERM_HEIGHT / CELL_HEIGHT); 356 | logging(DEBUG, "terminal size set by default value\n"); 357 | 358 | return true; 359 | } 360 | 361 | 362 | int main(int argc, char *argv[]) 363 | { 364 | /* 365 | command line option 366 | -bg : background color (for transparent image?) 367 | -x : image position offset x 368 | -y : image position offset x 369 | -test : request display size (response "width height\n") 370 | -size : request image size (response "width height\n") 371 | -anim : number of max frame of animation image? 372 | -margin: margin of clear region? 373 | -debug : debug flag (not used) 374 | */ 375 | /* 376 | w3mimg protocol 377 | 0 1 2 .... 378 | +--+--+--+--+ ...... +--+--+ 379 | |op|; |args |\n| 380 | +--+--+--+--+ .......+--+--+ 381 | 382 | args is separeted by ';' 383 | op args 384 | 0; params draw image 385 | 1; params redraw image 386 | 2; -none- terminate drawing 387 | 3; -none- sync drawing 388 | 4; -none- nop, sync communication 389 | response '\n' 390 | 5; path get size of image, 391 | response " \n" 392 | 6; params(6) clear image 393 | 394 | params 395 | ;;;;;;;;; 396 | params(6) 397 | ;;; 398 | */ 399 | int i, op, optind; 400 | char buf[BUFSIZE], *cp; 401 | struct tty_t tty; 402 | struct image img[MAX_IMAGE]; 403 | struct parm_t parm; 404 | struct winsize ws; 405 | 406 | if (freopen(instance_log, "a", stderr) == NULL) 407 | logging(ERROR, "freopen (stderr to %s) faild\n", instance_log); 408 | 409 | logging(DEBUG, "--- new instance ---\n"); 410 | for (i = 0; i < argc; i++) 411 | logging(DEBUG, "argv[%d]:%s\n", i, argv[i]); 412 | logging(DEBUG, "argc:%d\n", argc); 413 | 414 | /* init */ 415 | for (i = 0; i < MAX_IMAGE; i++) 416 | init_image(&img[i]); 417 | 418 | /* register signal handler for window resize event */ 419 | struct sigaction sigact = { 420 | .sa_handler = sig_handler, 421 | .sa_flags = SA_RESTART, 422 | }; 423 | esigaction(SIGWINCH, &sigact, NULL); 424 | 425 | if (!get_tty(&tty)) 426 | 427 | goto release; 428 | /* FIXME: when w3m uses pipe(), read() in terminal_query() causes I/O block... 429 | w3mimg [-test|-size] -> ok, not blocked 430 | echo 0;1;4;92;183;64;0;0;183;64;/path/to/image.png | w3mimg -> blocked! */ 431 | if (QUERY_WINDOW_SIZE) { 432 | if (!check_terminal_size(&tty)) 433 | goto release; 434 | } else { 435 | if (ioctl(tty.fd, TIOCGWINSZ, &ws)) { 436 | logging(ERROR, "ioctl: TIOCGWINSZ failed\n"); 437 | set_terminal_size(&tty, TERM_WIDTH, TERM_HEIGHT, 438 | CELL_WIDTH, CELL_HEIGHT); 439 | } else { 440 | set_terminal_size(&tty, CELL_WIDTH * ws.ws_col, 441 | CELL_HEIGHT * ws.ws_row, ws.ws_col, ws.ws_row); 442 | } 443 | } 444 | logging(DEBUG, "terminal size width:%d height:%d cell_width:%d cell_height:%d\n", 445 | tty.width, tty.height, tty.cell_width, tty.cell_height); 446 | 447 | /* check args */ 448 | optind = 1; 449 | while (optind < argc) { 450 | if (strncmp(argv[optind], "-test", 5) == 0) { 451 | printf("%d %d\n", tty.width, tty.height); 452 | logging(DEBUG, "responce: %d %d\n", tty.width, tty.height); 453 | goto release; 454 | } 455 | else if (strncmp(argv[optind], "-size", 5) == 0 && ++optind < argc) { 456 | w3m_getsize(&tty, &img[0], argv[optind]); 457 | goto release; 458 | } 459 | optind++; 460 | } 461 | 462 | /* for avoiding simultaneous-write, reopen stder again 463 | w3m --+------------------+----------------------------------------------> (end) 464 | | fork()/pipe() |fork()/popen() | | 465 | | v v v 466 | | w3mimg -> (end) w3mimg -> (end) w3mimg -> (end) 467 | | (get image size) 468 | | | 469 | | v 470 | | w3mimg_instance.log 471 | v 472 | w3mimg -----------------------------------------------------------> (end) 473 | (drawing) 474 | | 475 | v 476 | w3mimg_sixel.log (file lock) 477 | */ 478 | if (freopen(log_file, "w", stderr) == NULL) 479 | logging(ERROR, "freopen (stderr to %s) faild\n", log_file); 480 | 481 | setvbuf(stderr, NULL, _IONBF, 0); 482 | setvbuf(stdout, NULL, _IONBF, 0); 483 | 484 | /* main loop */ 485 | while (fgets(buf, BUFSIZE, stdin) != NULL) { 486 | if (window_resized) { 487 | window_resized = false; 488 | if (ioctl(tty.fd, TIOCGWINSZ, &ws)) { 489 | logging(ERROR, "ioctl: TIOCGWINSZ failed\n"); 490 | set_terminal_size(&tty, TERM_WIDTH, TERM_HEIGHT, 491 | CELL_WIDTH, CELL_HEIGHT); 492 | } else { 493 | set_terminal_size(&tty, CELL_WIDTH * ws.ws_col, 494 | CELL_HEIGHT * ws.ws_row, ws.ws_col, ws.ws_row); 495 | } 496 | logging(DEBUG, "window resized!\n"); 497 | logging(DEBUG, "terminal size width:%d height:%d cell_width:%d cell_height:%d\n", 498 | tty.width, tty.height, tty.cell_width, tty.cell_height); 499 | } 500 | 501 | if ((cp = strchr(buf, '\n')) != NULL) 502 | *cp = '\0'; 503 | logging(DEBUG, "stdin: %s\n", buf); 504 | 505 | reset_parm(&parm); 506 | parse_arg(buf, &parm, ';', isgraph); 507 | 508 | if (parm.argc <= 0) 509 | continue; 510 | 511 | op = str2num(parm.argv[0]); 512 | if (op < 0 || op >= NUM_OF_W3M_FUNC) 513 | continue; 514 | 515 | switch (op) { 516 | case W3M_DRAW: 517 | case W3M_REDRAW: 518 | w3m_draw(&tty, img, &parm, op); 519 | break; 520 | case W3M_STOP: 521 | w3m_stop(); 522 | break; 523 | case W3M_SYNC: 524 | w3m_sync(); 525 | break; 526 | case W3M_NOP: 527 | w3m_nop(); 528 | break; 529 | case W3M_GETSIZE: 530 | if (parm.argc != 2) 531 | break; 532 | w3m_getsize(&tty, &img[0], parm.argv[1]); 533 | break; 534 | case W3M_CLEAR: 535 | w3m_clear(img, &parm); 536 | break; 537 | default: 538 | break; 539 | } 540 | } 541 | 542 | /* release */ 543 | release: 544 | for (i = 0; i < MAX_IMAGE; i++) 545 | free_image(&img[i]); 546 | 547 | fflush(stdout); 548 | fflush(stderr); 549 | 550 | efclose(stderr); 551 | 552 | if (tty.fd > 0) 553 | eclose(tty.fd); 554 | 555 | logging(DEBUG, "exiting...\n"); 556 | return EXIT_SUCCESS; 557 | } 558 | -------------------------------------------------------------------------------- /yaimg-sixel/yaimg-sixel.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE for licence details. */ 2 | #define _XOPEN_SOURCE 600 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | /* for Mac OS X? */ 20 | #define SIGWINCH 28 21 | 22 | enum w3m_op { 23 | W3M_DRAW = 0, 24 | W3M_REDRAW, 25 | W3M_STOP, 26 | W3M_SYNC, 27 | W3M_NOP, 28 | W3M_GETSIZE, 29 | W3M_CLEAR, 30 | NUM_OF_W3M_FUNC, 31 | }; 32 | 33 | enum { 34 | VERBOSE = false, /* if false, suppress "DEBUG" level logging */ 35 | BUFSIZE = 1024, 36 | MAX_IMAGE = 1024, 37 | /* default value */ 38 | CELL_WIDTH = 8, 39 | CELL_HEIGHT = 16, 40 | /* this values will be updated at window resize */ 41 | TERM_WIDTH = 1280, 42 | TERM_HEIGHT = 1024, 43 | /* for select */ 44 | SELECT_TIMEOUT = 100000, /* usec */ 45 | SELECT_CHECK_LIMIT = 4, 46 | /* experimental features: buggy! */ 47 | QUERY_WINDOW_SIZE = false, 48 | SIXEL_PENETRATE = false, 49 | }; 50 | 51 | struct tty_t { 52 | int fd; /* fd of current controlling terminal */ 53 | int width, height; /* terminal size (by pixel) */ 54 | int cell_width, cell_height; /* cell_size (by pixel) */ 55 | }; 56 | 57 | const char *instance_log = "/tmp/w3mimg-sixel.instance.log"; 58 | const char *log_file = "/tmp/w3mimg-sixel.log"; 59 | volatile sig_atomic_t window_resized = false; 60 | --------------------------------------------------------------------------------