├── .gitignore ├── AUTHORS ├── BUGS ├── COPYING ├── Makefile ├── NEWS ├── README ├── THANKS ├── configs ├── debug.mk └── gprof.mk ├── doc └── meh.1 ├── src ├── bmp.c ├── gif.c ├── imagemagick.c ├── jpeg.c ├── main.c ├── meh.h ├── netpbm.c ├── png.c ├── scale.c ├── scale.h └── xlib.c └── test ├── test.c └── test.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.d 3 | meh 4 | test/test 5 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | meh authors 2 | 3 | John Hawthorn (jhawthor@uvic.ca) 4 | 5 | -------------------------------------------------------------------------------- /BUGS: -------------------------------------------------------------------------------- 1 | 2 | meh does not currently support display depths under 24 bits. This should be 3 | fixed in the next release. 4 | 5 | Some WM's, though very few, allow meh's window to be resized to a width of 0. 6 | meh is likely to crash in this case. A workaround will be found for the next 7 | release. 8 | 9 | Bugs may be sent to jhawthor@uvic.ca and are greatly appreciated. 10 | 11 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | --- 4 | 5 | Copyright (c) 2008-2009 John Hawthorn 6 | 7 | Permission is hereby granted, free of charge, to any person 8 | obtaining a copy of this software and associated documentation 9 | files (the "Software"), to deal in the Software without 10 | restriction, including without limitation the rights to use, 11 | copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following 14 | conditions: 15 | 16 | The above copyright notice and this permission notice shall be 17 | included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 21 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 23 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 24 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 26 | OTHER DEALINGS IN THE SOFTWARE. 27 | 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | TARGET := meh 3 | TESTTARGET := test/test 4 | 5 | SRCFILES := $(wildcard src/*.c) 6 | OBJFILES := $(SRCFILES:%.c=%.o) 7 | DEPFILES := $(OBJFILES:%.o=%.d) 8 | TESTCLEAN := $(TESTTARGET) $(TESTTARGET).d $(TESTTARGET).o 9 | CLEANFILES := $(CLEANFILES) $(DEPFILES) $(OBJFILES) test/test.o test/test.d test/test $(TARGET) 10 | LIBS ?= -lX11 -lXext -ljpeg -lpng -lgif 11 | PREFIX ?= /usr/local 12 | BINDIR = $(PREFIX)/bin 13 | DATAROOTDIR = $(PREFIX)/share 14 | MANDIR = $(DATAROOTDIR)/man 15 | 16 | # User configuration 17 | CONFIG ?= ../config 18 | -include configs/$(CONFIG).mk 19 | 20 | CFLAGS ?= -O3 -DNDEBUG 21 | 22 | meh: $(OBJFILES) 23 | $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LIBS) 24 | 25 | test: $(TESTTARGET) 26 | ./$(TESTTARGET) 27 | 28 | test/test: test/test.o $(filter-out src/main.o src/xlib.o, $(OBJFILES)) 29 | $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LIBS) 30 | 31 | -include $(DEPFILES) 32 | 33 | %.o: %.c Makefile 34 | $(CC) $(CFLAGS) -MMD -MP -MT "$*.d" -c -o $@ $< 35 | 36 | install: 37 | install -Dm 755 meh $(DESTDIR)$(BINDIR)/meh 38 | install -D doc/meh.1 $(DESTDIR)$(MANDIR)/man1/meh.1 39 | 40 | # Clean 41 | clean: 42 | $(RM) $(CLEANFILES) 43 | 44 | .PHONY: clean test 45 | 46 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | 2 | meh 0.3 - Preliminary caching support. Improved responsiveness. PNG alpha support. 3 | meh 0.2 - Imagemagick support. netpbm support. Better downscaling. Bug fixes. 4 | meh 0.1 - First release. JPEG, PNG, GIF and BMP formats. XSHM support. 5 | 6 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | meh 0.3 2 | John Hawthorn (jhawthor@uvic.ca) 3 | 4 | --- 5 | 6 | Installation 7 | 8 | To compile type 9 | make 10 | 11 | To install type 12 | make install 13 | 14 | 15 | Installation prefix can be changed by the PREFIX variable 16 | make install PREFIX=/usr 17 | default prefix is /usr/local 18 | 19 | Configuration can be changed in config.mk 20 | 21 | --- 22 | Copyright (c) 2008-2009 John Hawthorn 23 | 24 | Permission is hereby granted, free of charge, to any person 25 | obtaining a copy of this software and associated documentation 26 | files (the "Software"), to deal in the Software without 27 | restriction, including without limitation the rights to use, 28 | copy, modify, merge, publish, distribute, sublicense, and/or sell 29 | copies of the Software, and to permit persons to whom the 30 | Software is furnished to do so, subject to the following 31 | conditions: 32 | 33 | The above copyright notice and this permission notice shall be 34 | included in all copies or substantial portions of the Software. 35 | 36 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 37 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 38 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 39 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 40 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 41 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 42 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 43 | OTHER DEALINGS IN THE SOFTWARE. 44 | 45 | --- 46 | -------------------------------------------------------------------------------- /THANKS: -------------------------------------------------------------------------------- 1 | 2 | Clarke Brunsdon 3 | Tarpman 4 | Jared "PRINCESSJRNORMAN" Norman 5 | 6 | -------------------------------------------------------------------------------- /configs/debug.mk: -------------------------------------------------------------------------------- 1 | CFLAGS += -O0 -g -Wall -Wextra 2 | -------------------------------------------------------------------------------- /configs/gprof.mk: -------------------------------------------------------------------------------- 1 | CFLAGS += -g -pg 2 | -------------------------------------------------------------------------------- /doc/meh.1: -------------------------------------------------------------------------------- 1 | .TH MEH 1 "2013-12-28" 2 | .SH NAME 3 | meh \- simple, minimalist, super fast image viewer 4 | .SH SYNOPSIS 5 | .B meh 6 | .IR FILE ... 7 | .br 8 | .B meh \-list 9 | .RI [ LISTFILE ] 10 | .br 11 | .B meh \-ctl 12 | .br 13 | .B meh \-v 14 | .SH DESCRIPTION 15 | .B meh 16 | is a small, simple, super fast image viewer using raw XLib. 17 | It is similar to 18 | .BR feh (1), 19 | but faster and simpler. 20 | .LP 21 | .B meh 22 | can use ImageMagick's 23 | .BR convert (1) 24 | to view almost 200 file formats, though it is slower for these formats. 25 | Built-in formats are JPEG, PNG, BMP, and netpbm. 26 | .SH OPTIONS 27 | .TP 28 | .BR \-list \ [\fILISTFILE\fR] 29 | Treat \fILISTFILE\fR as list of images. Defaults to stdin. 30 | .TP 31 | .B \-ctl 32 | Display files as they are received on stdin. 33 | .TP 34 | .B \-v 35 | Print version and exit. 36 | -------------------------------------------------------------------------------- /src/bmp.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include "meh.h" 6 | 7 | struct rgb_t{ 8 | unsigned char r, g, b; 9 | }; 10 | 11 | struct bmp_t{ 12 | struct image img; 13 | FILE *f; 14 | unsigned long bitmapoffset; 15 | int compression; 16 | int bpp; 17 | int ncolors; 18 | struct rgb_t *colours; 19 | unsigned int rowwidth; 20 | }; 21 | 22 | static unsigned short getshort(FILE *f){ 23 | unsigned short ret; 24 | ret = getc(f); 25 | ret = ret | (getc(f) << 8); 26 | return ret; 27 | } 28 | 29 | static unsigned long getlong(FILE *f){ 30 | unsigned short ret; 31 | ret = getc(f); 32 | ret = ret | (getc(f) << 8); 33 | ret = ret | (getc(f) << 16); 34 | ret = ret | (getc(f) << 24); 35 | return ret; 36 | } 37 | 38 | struct image *bmp_open(FILE *f){ 39 | struct bmp_t *b; 40 | unsigned long headersize; 41 | 42 | rewind(f); 43 | if(getc(f) != 'B' || getc(f) != 'M') 44 | return NULL; 45 | 46 | b = malloc(sizeof(struct bmp_t)); 47 | b->f = f; 48 | 49 | fseek(f, 10, SEEK_SET); 50 | b->bitmapoffset = getlong(f); 51 | 52 | fseek(f, 14, SEEK_SET); 53 | headersize = getlong(f); 54 | 55 | if(headersize == 12){ /* OS/2 v1 */ 56 | b->ncolors = 0; 57 | fseek(f, 18, SEEK_SET); 58 | b->img.bufwidth = getshort(f); 59 | b->img.bufheight = getshort(f); 60 | b->compression = 0; 61 | }else{ 62 | fseek(f, 18, SEEK_SET); 63 | b->img.bufwidth = getlong(f); 64 | b->img.bufheight = getlong(f); 65 | 66 | fseek(f, 28, SEEK_SET); 67 | b->bpp = getshort(f); 68 | 69 | fseek(f, 30, SEEK_SET); 70 | b->compression = getlong(f); 71 | 72 | fseek(f, 46, SEEK_SET); 73 | b->ncolors = getlong(f); 74 | } 75 | 76 | if(!b->ncolors){ 77 | b->ncolors = 1 << b->bpp; 78 | } 79 | 80 | if(b->compression){ 81 | fprintf(stderr, "unsupported compression method %i\n", b->compression); 82 | return NULL; 83 | } 84 | 85 | if(b->bpp >= 16){ 86 | b->rowwidth = b->img.bufwidth * b->bpp / 8; 87 | b->colours = NULL; 88 | }else{ 89 | int i; 90 | b->colours = malloc(b->ncolors * sizeof(struct rgb_t)); 91 | fseek(f, 14+headersize, SEEK_SET); 92 | for(i = 0; i < b->ncolors; i++){ 93 | b->colours[i].b = getc(f); 94 | b->colours[i].g = getc(f); 95 | b->colours[i].r = getc(f); 96 | if(headersize != 12) 97 | getc(f); 98 | } 99 | b->rowwidth = (b->img.bufwidth * b->bpp + 7) / 8; 100 | } 101 | if(b->rowwidth & 3){ 102 | b->rowwidth += 4 - (b->rowwidth & 3); 103 | } 104 | 105 | b->img.fmt = &bmp; 106 | 107 | return (struct image *)b; 108 | } 109 | 110 | static void rgb16(unsigned char *buf, unsigned short c){ 111 | int i; 112 | for(i = 0; i < 3; i++){ 113 | *buf++ = ((c >> (i * 5)) & 0x1f) << 3; 114 | } 115 | } 116 | 117 | static int readrow(struct image *img, unsigned char *row, unsigned char *buf){ 118 | struct bmp_t *b = (struct bmp_t *)img; 119 | unsigned int x, i = 0; 120 | if(b->bpp == 24 || b->bpp == 32){ 121 | for(x = 0; x < img->bufwidth * 3; x+=3){ 122 | buf[x + 2] = row[i++]; 123 | buf[x + 1] = row[i++]; 124 | buf[x + 0] = row[i++]; 125 | if(b->bpp == 32) 126 | i++; 127 | } 128 | }else if(b->bpp == 16){ 129 | for(x = 0; x < img->bufwidth * 3; x+=3){ 130 | unsigned short c; 131 | c = row[i++]; 132 | c |= row[i++] << 8; 133 | rgb16(&buf[x], c); 134 | } 135 | }else if(b->bpp <= 8){ 136 | int mask; 137 | int pixelsperbit = 8 / b->bpp; 138 | mask = ~((~0) << b->bpp); 139 | for(x = 0; x < img->bufwidth; x++){ 140 | unsigned char c = ((row[i / pixelsperbit]) >> ((8 - ((i+1) % pixelsperbit) * b->bpp)) % 8) & mask; 141 | *buf++ = b->colours[c].r; 142 | *buf++ = b->colours[c].g; 143 | *buf++ = b->colours[c].b; 144 | i++; 145 | } 146 | }else{ 147 | fprintf(stderr, "bad bpp %i\n", b->bpp); 148 | return 1; 149 | } 150 | return 0; 151 | } 152 | 153 | int bmp_read(struct image *img){ 154 | struct bmp_t *b = (struct bmp_t *)img; 155 | unsigned int i, y; 156 | unsigned int dy; 157 | unsigned char *row; 158 | FILE *f = b->f; 159 | 160 | row = malloc(b->rowwidth); 161 | dy = img->bufwidth * 3; 162 | i = img->bufheight * dy; 163 | 164 | fseek(f, b->bitmapoffset, SEEK_SET); 165 | for(y = img->bufheight; y; y--){ 166 | i -= dy; 167 | if(fread(row, 1, b->rowwidth, f) != b->rowwidth || readrow(img, row, &img->buf[i])){ 168 | free(row); 169 | return 1; 170 | } 171 | } 172 | 173 | free(row); 174 | 175 | img->state |= LOADED | SLOWLOADED; 176 | 177 | return 0; 178 | } 179 | 180 | void bmp_close(struct image *img){ 181 | struct bmp_t *b = (struct bmp_t *)img; 182 | free(b->colours); 183 | fclose(b->f); 184 | } 185 | 186 | struct imageformat bmp = { 187 | bmp_open, 188 | NULL, 189 | bmp_read, 190 | bmp_close 191 | }; 192 | 193 | -------------------------------------------------------------------------------- /src/gif.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | 12 | #include "meh.h" 13 | 14 | struct gif_t{ 15 | struct image img; 16 | FILE *f; 17 | GifFileType *gif; 18 | }; 19 | 20 | static int isgif(FILE *f){ 21 | return (getc(f) == 'G' && getc(f) == 'I' && getc(f) == 'F'); 22 | } 23 | 24 | static struct image *gif_open(FILE *f){ 25 | struct gif_t *g; 26 | GifFileType *gif; 27 | 28 | rewind(f); 29 | if(!isgif(f)) 30 | return NULL; 31 | 32 | /* HACK HACK HACK */ 33 | rewind(f); 34 | lseek(fileno(f), 0L, SEEK_SET); 35 | #if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5 36 | if(!(gif = DGifOpenFileHandle(fileno(f), NULL))){ 37 | #else 38 | if(!(gif = DGifOpenFileHandle(fileno(f)))){ 39 | #endif 40 | /* HACK AND HOPE */ 41 | rewind(f); 42 | lseek(fileno(f), 0L, SEEK_SET); 43 | return NULL; 44 | } 45 | g = malloc(sizeof(struct gif_t)); 46 | g->f = f; 47 | g->gif = gif; 48 | 49 | g->img.bufwidth = gif->SWidth; 50 | g->img.bufheight = gif->SHeight; 51 | 52 | g->img.fmt = &giflib; 53 | 54 | return (struct image *)g; 55 | } 56 | 57 | static int gif_read(struct image *img){ 58 | unsigned int i, j = 0; 59 | struct gif_t *g = (struct gif_t *)img; 60 | GifColorType *colormap; 61 | SavedImage *s; 62 | int ret; 63 | 64 | if((ret = DGifSlurp(g->gif)) != GIF_OK) 65 | goto error; 66 | 67 | s = &g->gif->SavedImages[0]; 68 | 69 | if(s->ImageDesc.ColorMap) 70 | colormap = s->ImageDesc.ColorMap->Colors; 71 | else if(g->gif->SColorMap) 72 | colormap = g->gif->SColorMap->Colors; 73 | else 74 | goto error; 75 | 76 | for(i = 0; i < img->bufwidth * img->bufheight; i++){ 77 | unsigned char idx = s->RasterBits[i]; 78 | img->buf[j++] = colormap[idx].Red; 79 | img->buf[j++] = colormap[idx].Green; 80 | img->buf[j++] = colormap[idx].Blue; 81 | } 82 | 83 | img->state |= LOADED | SLOWLOADED; 84 | 85 | return 0; 86 | error: 87 | #if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5 88 | fprintf(stderr, "GIFLIB: %s\n", GifErrorString(ret)); 89 | #elif defined(GIFLIB_MAJOR) && defined(GIFLIB_MINOR) && ((GIFLIB_MAJOR == 4 && GIFLIB_MINOR >= 2) || GIFLIB_MAJOR > 4) 90 | fprintf(stderr, "GIFLIB: %s\n", GifErrorString()); 91 | #else 92 | PrintGifError(); 93 | #endif 94 | return 1; 95 | } 96 | 97 | void gif_close(struct image *img){ 98 | struct gif_t *g = (struct gif_t *)img; 99 | #if defined(GIFLIB_MAJOR) && defined(GIFLIB_MINOR) && (GIFLIB_MAJOR == 5 && GIFLIB_MINOR >= 1) 100 | int ret; 101 | #endif 102 | 103 | #if defined(GIFLIB_MAJOR) && defined(GIFLIB_MINOR) && (GIFLIB_MAJOR == 5 && GIFLIB_MINOR >= 1) 104 | DGifCloseFile(g->gif,&ret); 105 | if(ret != GIF_OK) { 106 | #if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5 107 | fprintf(stderr, "GIFLIB: %s\n", GifErrorString(ret)); 108 | #elif defined(GIFLIB_MAJOR) && defined(GIFLIB_MINOR) && ((GIFLIB_MAJOR == 4 && GIFLIB_MINOR >= 2) || GIFLIB_MAJOR > 4) 109 | fprintf(stderr, "GIFLIB: %s\n", GifErrorString()); 110 | #else 111 | PrintGifError(); 112 | #endif 113 | } 114 | #else 115 | DGifCloseFile(g->gif); 116 | #endif 117 | fclose(g->f); 118 | } 119 | 120 | struct imageformat giflib = { 121 | gif_open, 122 | NULL, 123 | gif_read, 124 | gif_close 125 | }; 126 | 127 | -------------------------------------------------------------------------------- /src/imagemagick.c: -------------------------------------------------------------------------------- 1 | 2 | #define _GNU_SOURCE 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "meh.h" 11 | 12 | struct image *imagemagick_open(FILE *f){ 13 | int tmpfd[2]; 14 | if(pipe(tmpfd)){ 15 | perror("pipe"); 16 | exit(EXIT_FAILURE); 17 | } 18 | 19 | int pid; 20 | if(!(pid = fork())){ 21 | close(tmpfd[0]); 22 | int origfd = fileno(f); 23 | if(lseek(origfd, 0, SEEK_SET) != 0){ 24 | perror("lseek"); 25 | exit(EXIT_FAILURE); 26 | } 27 | 28 | char *argv[6]; 29 | 30 | argv[0] = "convert"; 31 | argv[1] = "-depth"; 32 | argv[2] = "255"; 33 | if((asprintf(&argv[3], "fd:%i", origfd) < 0) || (asprintf(&argv[4], "ppm:fd:%i", tmpfd[1]) < 0)){ 34 | fprintf(stderr, "Out of memory"); 35 | exit(EXIT_FAILURE); 36 | } 37 | argv[5] = NULL; 38 | 39 | #ifdef NDEBUG 40 | /* STFU OMFG */ 41 | FILE *unused __attribute__((unused)); 42 | unused = freopen("/dev/null", "w", stdout); 43 | unused = freopen("/dev/null", "w", stderr); 44 | #endif 45 | 46 | execvp(argv[0], argv); 47 | 48 | perror("exec"); 49 | exit(EXIT_FAILURE); 50 | }else{ 51 | close(tmpfd[1]); 52 | FILE *ftmp; 53 | if(!(ftmp = fdopen(tmpfd[0], "rb"))){ 54 | perror("fopen"); 55 | exit(EXIT_FAILURE); 56 | } 57 | struct image *img = netpbm.open(ftmp); 58 | if(!img) 59 | return NULL; 60 | fclose(f); 61 | return img; 62 | } 63 | } 64 | 65 | struct imageformat imagemagick = { 66 | imagemagick_open, 67 | NULL, 68 | NULL, 69 | NULL 70 | }; 71 | 72 | -------------------------------------------------------------------------------- /src/jpeg.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "jpeglib.h" 9 | 10 | #include "meh.h" 11 | 12 | struct error_mgr{ 13 | struct jpeg_error_mgr pub; 14 | jmp_buf jmp_buffer; 15 | }; 16 | 17 | struct jpeg_t{ 18 | struct image img; 19 | FILE *f; 20 | struct jpeg_decompress_struct cinfo; 21 | struct error_mgr jerr; 22 | }; 23 | 24 | static void error_exit(j_common_ptr cinfo){ 25 | (void) cinfo; 26 | printf("\nerror!\n"); 27 | exit(1); 28 | } 29 | 30 | static void error_longjmp(j_common_ptr cinfo){ 31 | struct error_mgr *myerr = (struct error_mgr *)cinfo->err; 32 | (*cinfo->err->output_message)(cinfo); 33 | longjmp(myerr->jmp_buffer, 1); 34 | } 35 | 36 | /* TODO progressive */ 37 | static struct image *jpeg_open(FILE *f){ 38 | struct jpeg_t *j; 39 | 40 | rewind(f); 41 | if(getc(f) != 0xff || getc(f) != 0xd8) 42 | return NULL; 43 | 44 | j = malloc(sizeof(struct jpeg_t)); 45 | j->f = f; 46 | 47 | j->cinfo.err = jpeg_std_error(&j->jerr.pub); 48 | j->jerr.pub.error_exit = error_longjmp; 49 | if (setjmp(j->jerr.jmp_buffer)) { 50 | return NULL; 51 | } 52 | 53 | j->jerr.pub.error_exit = error_exit; 54 | 55 | j->img.fmt = &libjpeg; 56 | 57 | return (struct image *)j; 58 | } 59 | 60 | void jpeg_prep(struct image *img){ 61 | struct jpeg_t *j = (struct jpeg_t *)img; 62 | 63 | /* We've previously loaded this image, clean that up*/ 64 | if(img->state & LOADED) 65 | jpeg_destroy_decompress(&j->cinfo); 66 | 67 | jpeg_create_decompress(&j->cinfo); 68 | rewind(j->f); 69 | jpeg_stdio_src(&j->cinfo, j->f); 70 | jpeg_read_header(&j->cinfo, TRUE); 71 | 72 | /* parameters */ 73 | j->cinfo.do_fancy_upsampling = 0; 74 | j->cinfo.do_block_smoothing = 0; 75 | j->cinfo.quantize_colors = 0; 76 | j->cinfo.dct_method = JDCT_FASTEST; 77 | j->cinfo.scale_denom = (img->state & LOADED) ? 1 : 8; /* TODO: This should be changed done only for large jpegs */ 78 | 79 | jpeg_calc_output_dimensions(&j->cinfo); 80 | 81 | j->img.bufwidth = j->cinfo.output_width; 82 | j->img.bufheight = j->cinfo.output_height; 83 | } 84 | 85 | 86 | static int jpeg_read(struct image *img){ 87 | struct jpeg_t *j = (struct jpeg_t *)img; 88 | unsigned int row_stride; 89 | int a = 0, b; 90 | unsigned int x, y; 91 | 92 | j->jerr.pub.error_exit = error_longjmp; 93 | if(setjmp(j->jerr.jmp_buffer)){ 94 | return 1; /* ERROR */ 95 | } 96 | 97 | row_stride = j->cinfo.output_width * j->cinfo.output_components; 98 | 99 | jpeg_start_decompress(&j->cinfo); 100 | 101 | 102 | if(j->cinfo.output_components == 3){ 103 | JSAMPROW rows[2]; 104 | rows[0] = img->buf; 105 | rows[1] = rows[0] + row_stride; 106 | for(y = 0; y < j->cinfo.output_height;){ 107 | int n = jpeg_read_scanlines(&j->cinfo, rows, 2); 108 | y += n; 109 | rows[0] = rows[n-1] + row_stride; 110 | rows[1] = rows[0] + row_stride; 111 | } 112 | }else if(j->cinfo.output_components == 1){ 113 | JSAMPARRAY buffer = (*j->cinfo.mem->alloc_sarray)((j_common_ptr)&j->cinfo, JPOOL_IMAGE, row_stride, 4); 114 | for(y = 0; y < j->cinfo.output_height; ){ 115 | int n = jpeg_read_scanlines(&j->cinfo, buffer, 4); 116 | for(b = 0; b < n; b++){ 117 | for(x = 0; x < j->cinfo.output_width; x++){ 118 | img->buf[a++] = buffer[b][x]; 119 | img->buf[a++] = buffer[b][x]; 120 | img->buf[a++] = buffer[b][x]; 121 | } 122 | } 123 | y += n; 124 | } 125 | }else if(j->cinfo.output_components == 4){ 126 | JSAMPARRAY buffer = (*j->cinfo.mem->alloc_sarray)((j_common_ptr)&j->cinfo, JPOOL_IMAGE, row_stride, 4); 127 | for(y = 0; y < j->cinfo.output_height; ){ 128 | int n = jpeg_read_scanlines(&j->cinfo, buffer, 4); 129 | for(b = 0; b < n; b++){ 130 | for(x = 0; x < j->cinfo.output_width; x++){ 131 | int tmp = buffer[b][x*4 + 3]; 132 | img->buf[a++] = buffer[b][x*4] * tmp / 255; 133 | img->buf[a++] = buffer[b][x*4 + 1] * tmp / 255; 134 | img->buf[a++] = buffer[b][x*4 + 2] * tmp / 255; 135 | } 136 | } 137 | y += n; 138 | } 139 | }else{ 140 | fprintf(stderr, "Unsupported number of output components: %u\n", j->cinfo.output_components); 141 | return 1; 142 | } 143 | jpeg_finish_decompress(&j->cinfo); 144 | 145 | img->state |= LOADED; 146 | if(j->cinfo.scale_denom == 1) 147 | img->state |= SLOWLOADED; 148 | 149 | return 0; 150 | } 151 | 152 | void jpeg_close(struct image *img){ 153 | struct jpeg_t *j = (struct jpeg_t *)img; 154 | jpeg_destroy_decompress(&j->cinfo); 155 | fclose(j->f); 156 | } 157 | 158 | struct imageformat libjpeg = { 159 | jpeg_open, 160 | jpeg_prep, 161 | jpeg_read, 162 | jpeg_close 163 | }; 164 | 165 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | 2 | #define _GNU_SOURCE 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "meh.h" 12 | 13 | #define MODE_NORM 0 14 | #define MODE_LIST 1 15 | #define MODE_CTL 2 16 | static int mode; 17 | 18 | /* Supported Formats */ 19 | static struct imageformat *formats[] = { 20 | &libjpeg, 21 | &bmp, 22 | &libpng, 23 | &netpbm, 24 | &giflib, /* HACK! make gif last (uses read()) */ 25 | &imagemagick, 26 | NULL 27 | }; 28 | 29 | 30 | static void usage(){ 31 | printf("USAGE: meh [FILE1 [FILE2 [...]]]\n"); 32 | printf(" meh -list [LISTFILE] : Treat file as list of images. Defaults to stdin.\n"); 33 | printf(" meh -ctl : Display files as they are received on stdin\n"); 34 | printf(" meh -v : Print version and exit.\n"); 35 | exit(EXIT_FAILURE); 36 | } 37 | 38 | static struct image *newimage(FILE *f){ 39 | struct image *img = NULL; 40 | struct imageformat **fmt = formats; 41 | for(fmt = formats; *fmt; fmt++){ 42 | if((img = (*fmt)->open(f))){ 43 | img->state = NONE; 44 | img->backend = NULL; 45 | return img; 46 | } 47 | } 48 | return NULL; 49 | } 50 | 51 | /* For MODE_CTL */ 52 | static int ctlfd; 53 | 54 | static int imageslen; 55 | static int imageidx; 56 | static char **images; 57 | 58 | int width = 0, height = 0; 59 | struct image *curimg = NULL; 60 | 61 | struct image *nextimg = NULL; 62 | struct image *previmg = NULL; 63 | 64 | static void freeimage(struct image *img){ 65 | if(img){ 66 | backend_free(img); 67 | if(img->buf) 68 | free(img->buf); 69 | free(img); 70 | } 71 | } 72 | 73 | static int incidx(int i){ 74 | return ++i >= imageslen ? 0 : i; 75 | } 76 | 77 | static int decidx(int i){ 78 | return --i < 0 ? imageslen - 1 : i; 79 | } 80 | 81 | static int (*direction)(int) = incidx; 82 | 83 | void key_reload(){ 84 | freeimage(curimg); 85 | curimg = NULL; 86 | } 87 | void key_next(){ 88 | if(mode != MODE_CTL){ 89 | if(curimg) 90 | curimg->state &= LOADED | SLOWLOADED; 91 | freeimage(previmg); 92 | previmg = curimg; 93 | curimg = nextimg; 94 | nextimg = NULL; 95 | if(curimg){ 96 | imageidx = curimg->idx; 97 | }else{ 98 | imageidx = (direction = incidx)(imageidx); 99 | } 100 | } 101 | } 102 | void key_prev(){ 103 | if(mode != MODE_CTL){ 104 | if(curimg) 105 | curimg->state &= LOADED | SLOWLOADED; 106 | freeimage(nextimg); 107 | nextimg = curimg; 108 | curimg = previmg; 109 | previmg = NULL; 110 | if(curimg){ 111 | imageidx = curimg->idx; 112 | }else{ 113 | imageidx = (direction = decidx)(imageidx); 114 | } 115 | } 116 | } 117 | void key_quit(){ 118 | exit(EXIT_SUCCESS); 119 | } 120 | 121 | void key_action(){ 122 | puts(images[imageidx]); 123 | fflush(stdout); 124 | } 125 | 126 | struct image *imageopen2(char *filename){ 127 | struct image *i; 128 | FILE *f; 129 | if((f = fopen(filename, "rb"))){ 130 | if((i = newimage(f))){ 131 | return i; 132 | } 133 | else 134 | fprintf(stderr, "Invalid format '%s'\n", filename); 135 | }else{ 136 | fprintf(stderr, "Cannot open '%s'\n", filename); 137 | } 138 | return NULL; 139 | } 140 | 141 | static int doredraw(struct image **i, int idx, int (*dir)(int), int dstates){ 142 | if(!*i){ 143 | if(mode == MODE_CTL){ 144 | if(images[0] == NULL) 145 | return 0; 146 | if(!(*i = imageopen2(images[0]))){ 147 | images[0] = NULL; 148 | return 0; 149 | } 150 | }else{ 151 | int firstimg = idx; 152 | while(!*i){ 153 | if((*i = imageopen2(images[idx]))){ 154 | break; 155 | } 156 | idx = dir(idx); 157 | if(idx == firstimg){ 158 | fprintf(stderr, "No valid images to view\n"); 159 | exit(EXIT_FAILURE); 160 | } 161 | } 162 | } 163 | 164 | (*i)->idx = idx; 165 | (*i)->buf = NULL; 166 | (*i)->state = NONE; 167 | return 1; 168 | }else{ 169 | imgstate state = (*i)->state; 170 | if(!(state & LOADED) || ((state & (SLOWLOADED | DRAWN)) == (DRAWN)) ){ 171 | if((*i)->fmt->prep){ 172 | (*i)->fmt->prep(*i); 173 | } 174 | backend_setaspect((*i)->bufwidth, (*i)->bufheight); 175 | 176 | if((*i)->buf) 177 | free((*i)->buf); 178 | (*i)->buf = malloc(3 * (*i)->bufwidth * (*i)->bufheight); 179 | 180 | TDEBUG_START 181 | if((*i)->fmt->read(*i)){ 182 | fprintf(stderr, "read error!\n"); 183 | } 184 | TDEBUG_END("read"); 185 | 186 | if(((*i)->state & LOADED) && ((*i)->state & SLOWLOADED)){ 187 | /* We are done with the format's methods */ 188 | (*i)->fmt->close(*i); 189 | } 190 | 191 | (*i)->state &= LOADED | SLOWLOADED; 192 | 193 | /* state should be set by format */ 194 | assert((*i)->state & LOADED); 195 | return 1; 196 | }else if(width && height){ 197 | if((dstates & SCALED) && (state & LOADED) && !(state & SCALED)){ 198 | backend_prepare(*i, width, height, !!(state & SCALED)); 199 | (*i)->state &= LOADED | SLOWLOADED | SCALED | SLOWSCALED; 200 | 201 | /* state should be set by backend */ 202 | assert((*i)->state & SCALED); 203 | 204 | /* state should not be drawn so that we will draw later (also assures correct return value) */ 205 | assert(!((*i)->state & DRAWN)); 206 | } 207 | } 208 | if((dstates & DRAWN) && ((*i)->state & SCALED) && !(state & DRAWN)){ 209 | backend_draw(*i, width, height); 210 | (*i)->state |= DRAWN; 211 | return 1; 212 | } 213 | } 214 | return 0; 215 | } 216 | 217 | static void readlist(FILE *f){ 218 | int lsize = 16; 219 | imageslen = 0; 220 | images = NULL; 221 | while(!feof(f)){ 222 | images = realloc(images, lsize * sizeof(char *)); 223 | while(imageslen < lsize && !feof(f)){ 224 | char *line = NULL; 225 | size_t slen = 0; 226 | ssize_t read; 227 | read = getline(&line, &slen, f); 228 | if(read > 1){ 229 | line[read-1] = '\0'; 230 | images[imageslen++] = line; 231 | }else if(line){ 232 | free(line); 233 | } 234 | } 235 | lsize *= 2; 236 | } 237 | if(!imageslen){ 238 | fprintf(stderr, "No images to view\n"); 239 | exit(EXIT_FAILURE); 240 | } 241 | } 242 | 243 | int setup_fds(fd_set *fds){ 244 | FD_ZERO(fds); 245 | if(mode == MODE_CTL) 246 | FD_SET(ctlfd, fds); /* STDIN */ 247 | return ctlfd; 248 | } 249 | 250 | int process_fds(fd_set *fds){ 251 | if(FD_ISSET(ctlfd, fds)){ 252 | assert(mode == MODE_CTL); 253 | size_t slen = 0; 254 | ssize_t read; 255 | if(images[0]){ 256 | free(images[0]); 257 | } 258 | freeimage(curimg); 259 | curimg = NULL; 260 | images[0] = NULL; 261 | if((read = getline(images, &slen, stdin)) == -1){ 262 | exit(EXIT_SUCCESS); 263 | } 264 | images[0][read-1] = '\0'; 265 | return 1; 266 | } 267 | return 0; 268 | } 269 | 270 | int process_idle(){ 271 | if(mode == MODE_CTL && images[0] == NULL){ 272 | return 0; 273 | }else{ 274 | int ret = doredraw(&curimg, imageidx, direction, ~0); 275 | imageidx = curimg->idx; 276 | if(!ret){ 277 | ret = doredraw(&nextimg, incidx(imageidx), incidx, LOADED | SLOWLOADED); 278 | } 279 | if(!ret){ 280 | ret = doredraw(&previmg, decidx(imageidx), decidx, LOADED | SLOWLOADED); 281 | } 282 | return ret; 283 | } 284 | } 285 | 286 | int main(int argc, char *argv[]){ 287 | if(argc < 2) 288 | usage(); 289 | 290 | if(!strcmp(argv[1], "-ctl")){ 291 | if(argc != 2) 292 | usage(); 293 | mode = MODE_CTL; 294 | images = malloc(sizeof(char *)); 295 | images[0] = NULL; 296 | imageslen = 1; 297 | imageidx = 0; 298 | ctlfd = 0; 299 | }else if(!strcmp(argv[1], "-list")){ 300 | mode = MODE_LIST; 301 | if(argc == 2){ 302 | readlist(stdin); 303 | }else if(argc == 3){ 304 | FILE *f = fopen(argv[2], "r"); 305 | readlist(f); 306 | fclose(f); 307 | }else{ 308 | usage(); 309 | } 310 | }else if(!strcmp(argv[1], "-v")){ 311 | printf("meh version 0.3\n"); 312 | return 0; 313 | }else{ 314 | mode = MODE_NORM; 315 | images = &argv[1]; 316 | imageslen = argc-1; 317 | imageidx = 0; 318 | } 319 | backend_init(); 320 | backend_run(); 321 | 322 | return 0; 323 | } 324 | 325 | 326 | -------------------------------------------------------------------------------- /src/meh.h: -------------------------------------------------------------------------------- 1 | #ifndef MEH_H 2 | #define MEH_H MEH_H 3 | 4 | #include 5 | #include /* for fd_set */ 6 | 7 | struct image; 8 | 9 | struct imageformat{ 10 | struct image *(*open)(FILE *); 11 | void (*prep)(struct image *); 12 | int (*read)(struct image *); 13 | void (*close)(struct image *); 14 | }; 15 | 16 | typedef enum{ 17 | NONE = 0, 18 | LOADED = 1, 19 | SLOWLOADED = 2, 20 | SCALED = 4, 21 | SLOWSCALED = 8, 22 | DRAWN = 16 23 | } imgstate; 24 | 25 | struct image{ 26 | unsigned char *buf; 27 | unsigned int bufwidth, bufheight; 28 | struct imageformat *fmt; 29 | imgstate state; 30 | int idx; 31 | void *backend; 32 | }; 33 | 34 | 35 | /* backend */ 36 | void backend_init(); 37 | void backend_free(struct image *img); 38 | void backend_setaspect(unsigned int w, unsigned int h); 39 | void backend_prepare(struct image *img, unsigned int width, unsigned int height, int fast); 40 | void backend_draw(struct image *img, unsigned int width, unsigned int height); 41 | void backend_run(); 42 | 43 | /* key actions for backend */ 44 | void key_reload(); 45 | void key_next(); 46 | void key_prev(); 47 | void key_quit(); 48 | void key_action(); 49 | 50 | /* callbacks from backend */ 51 | int setup_fds(fd_set *fds); 52 | int process_fds(fd_set *fds); 53 | int process_idle(); 54 | 55 | #ifdef TDEBUG 56 | #define TDEBUG_START \ 57 | struct timeval t0;\ 58 | struct timeval t1;\ 59 | gettimeofday(&t0, NULL); 60 | #define TDEBUG_END(x) \ 61 | gettimeofday(&t1, NULL); \ 62 | printf("%s: %li e2 us\n", (x), ((t1.tv_sec - t0.tv_sec) * 1000000 + t1.tv_usec - t0.tv_usec) / 100); 63 | #else 64 | #define TDEBUG_START 65 | #define TDEBUG_END(x) 66 | #endif 67 | 68 | /* Supported Formats */ 69 | extern struct imageformat libjpeg; 70 | extern struct imageformat giflib; 71 | extern struct imageformat libpng; 72 | extern struct imageformat bmp; 73 | extern struct imageformat netpbm; 74 | extern struct imageformat imagemagick; 75 | 76 | extern int width, height; 77 | extern struct image *curimg; 78 | 79 | #endif 80 | 81 | -------------------------------------------------------------------------------- /src/netpbm.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | 6 | #include "meh.h" 7 | 8 | struct netpbm_t{ 9 | struct image img; 10 | FILE *f; 11 | char format; 12 | unsigned int maxval; 13 | }; 14 | 15 | void skipspace(FILE *f){ 16 | int c; 17 | for(;;){ 18 | c = fgetc(f); 19 | while(isspace(c)) 20 | c = fgetc(f); 21 | if(c == '#') 22 | while(fgetc(f) != '\n'); 23 | else 24 | break; 25 | } 26 | ungetc(c, f); 27 | } 28 | 29 | struct image *netpbm_open(FILE *f){ 30 | struct netpbm_t *b; 31 | rewind(f); 32 | 33 | char format; 34 | if(fgetc(f) != 'P') 35 | return NULL; 36 | format = fgetc(f); 37 | if(format > '6' || format < '1') 38 | return NULL; 39 | 40 | b = malloc(sizeof(struct netpbm_t)); 41 | b->format = format; 42 | 43 | skipspace(f); 44 | if(fscanf(f, "%u", &b->img.bufwidth) != 1) goto err; 45 | skipspace(f); 46 | if(fscanf(f, "%u", &b->img.bufheight) != 1) goto err; 47 | if(format == '1' || format == '4'){ 48 | b->maxval = 1; 49 | }else{ 50 | skipspace(f); 51 | if(fscanf(f, "%u", &b->maxval) != 1) goto err; 52 | } 53 | 54 | /* whitespace character */ 55 | fgetc(f); 56 | 57 | b->f = f; 58 | b->img.fmt = &netpbm; 59 | 60 | return (struct image *)b; 61 | 62 | err: 63 | free(b); 64 | return NULL; 65 | } 66 | 67 | static unsigned char readvali(struct netpbm_t *b){ 68 | skipspace(b->f); 69 | int val; 70 | int unused = fscanf(b->f, "%i", &val); 71 | return val * 255 / b->maxval; 72 | } 73 | 74 | static unsigned char readvalb(struct netpbm_t *b){ 75 | if(b->maxval == 65535){ 76 | int val = fgetc(b->f) << 8; 77 | val |= fgetc(b->f); 78 | return val * 255 / b->maxval; 79 | }else{ 80 | int val = fgetc(b->f); 81 | return val * 255 / b->maxval; 82 | } 83 | } 84 | 85 | int netpbm_read(struct image *img){ 86 | struct netpbm_t *b = (struct netpbm_t *)img; 87 | FILE *f = b->f; 88 | int a = 0; 89 | 90 | int left = img->bufwidth * img->bufheight; 91 | int j, c, val; 92 | 93 | if(b->format == '1'){ 94 | while(left--){ 95 | skipspace(f); 96 | val = fgetc(f); 97 | val = val == '1' ? 0 : 255; 98 | img->buf[a++] = val; 99 | img->buf[a++] = val; 100 | img->buf[a++] = val; 101 | } 102 | }else if(b->format == '2'){ 103 | while(left--){ 104 | val = readvali(b); 105 | img->buf[a++] = val; 106 | img->buf[a++] = val; 107 | img->buf[a++] = val; 108 | } 109 | }else if(b->format == '3'){ 110 | while(left--){ 111 | img->buf[a++] = readvali(b); 112 | img->buf[a++] = readvali(b); 113 | img->buf[a++] = readvali(b); 114 | } 115 | }else if(b->format == '4'){ 116 | while(left){ 117 | c = fgetc(f); 118 | for(j = 0; j < 8 && left; j++){ 119 | int val = (c & 1) ? 0 : 255; 120 | img->buf[a++] = val; 121 | img->buf[a++] = val; 122 | img->buf[a++] = val; 123 | c >>= 1; 124 | left--; 125 | } 126 | } 127 | }else if(b->format == '5'){ 128 | while(left--){ 129 | val = readvalb(b); 130 | img->buf[a++] = val; 131 | img->buf[a++] = val; 132 | img->buf[a++] = val; 133 | } 134 | }else if(b->format == '6'){ 135 | if(b->maxval == 255){ 136 | int unused = fread(img->buf, 1, left * 3, f); 137 | }else{ 138 | while(left--){ 139 | img->buf[a++] = readvalb(b); 140 | img->buf[a++] = readvalb(b); 141 | img->buf[a++] = readvalb(b); 142 | } 143 | } 144 | } 145 | 146 | img->state |= LOADED | SLOWLOADED; 147 | return 0; 148 | } 149 | 150 | void netpbm_close(struct image *img){ 151 | struct netpbm_t *b = (struct netpbm_t *)img; 152 | fclose(b->f); 153 | } 154 | 155 | struct imageformat netpbm = { 156 | netpbm_open, 157 | NULL, 158 | netpbm_read, 159 | netpbm_close 160 | }; 161 | 162 | -------------------------------------------------------------------------------- /src/png.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "meh.h" 5 | 6 | 7 | struct png_t{ 8 | struct image img; 9 | FILE *f; 10 | png_structp png_ptr; 11 | png_infop info_ptr; 12 | png_infop end_info; 13 | int numpasses; 14 | }; 15 | 16 | static int ispng(FILE *f){ 17 | unsigned char buf[8]; 18 | if(fread(buf, 1, 8, f) != 8) 19 | return 0; 20 | return !png_sig_cmp(buf, 1, 8); 21 | } 22 | 23 | struct image *png_open(FILE *f){ 24 | struct png_t *p; 25 | 26 | rewind(f); 27 | if(!ispng(f)) 28 | return NULL; 29 | 30 | p = malloc(sizeof(struct png_t)); 31 | if((p->png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)) == NULL){ 32 | free(p); 33 | return NULL; 34 | } 35 | if((p->info_ptr = png_create_info_struct(p->png_ptr)) == NULL){ 36 | png_destroy_read_struct(&p->png_ptr, (png_infopp)NULL, (png_infopp)NULL); 37 | free(p); 38 | return NULL; 39 | } 40 | if((p->end_info = png_create_info_struct(p->png_ptr)) == NULL){ 41 | png_destroy_read_struct(&p->png_ptr, &p->info_ptr, (png_infopp)NULL); 42 | free(p); 43 | return NULL; 44 | } 45 | if(setjmp(png_jmpbuf(p->png_ptr))){ 46 | png_destroy_read_struct(&p->png_ptr, &p->info_ptr, &p->end_info); 47 | free(p); 48 | return NULL; 49 | } 50 | 51 | p->f = f; 52 | rewind(f); 53 | png_init_io(p->png_ptr, f); 54 | 55 | png_read_info(p->png_ptr, p->info_ptr); 56 | 57 | p->img.bufwidth = png_get_image_width(p->png_ptr, p->info_ptr); 58 | p->img.bufheight = png_get_image_height(p->png_ptr, p->info_ptr); 59 | 60 | p->img.fmt = &libpng; 61 | 62 | return (struct image *)p; 63 | } 64 | 65 | int png_read(struct image *img){ 66 | unsigned int y; 67 | png_bytepp row_pointers; 68 | struct png_t *p = (struct png_t *)img; 69 | 70 | if(setjmp(png_jmpbuf(p->png_ptr))){ 71 | png_destroy_read_struct(&p->png_ptr, &p->info_ptr, &p->end_info); 72 | return 1; 73 | } 74 | 75 | { 76 | int color_type = png_get_color_type(p->png_ptr, p->info_ptr); 77 | int bit_depth = png_get_bit_depth(p->png_ptr, p->info_ptr); 78 | if (color_type == PNG_COLOR_TYPE_PALETTE) 79 | png_set_expand(p->png_ptr); 80 | if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) 81 | png_set_expand(p->png_ptr); 82 | if (png_get_valid(p->png_ptr, p->info_ptr, PNG_INFO_tRNS)) 83 | png_set_expand(p->png_ptr); 84 | if (bit_depth == 16) 85 | png_set_strip_16(p->png_ptr); 86 | if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) 87 | png_set_gray_to_rgb(p->png_ptr); 88 | //png_set_strip_alpha(p->png_ptr); 89 | 90 | png_color_16 my_background = {.red = 0xff, .green = 0xff, .blue = 0xff}; 91 | png_color_16p image_background; 92 | 93 | if(png_get_bKGD(p->png_ptr, p->info_ptr, &image_background)) 94 | png_set_background(p->png_ptr, image_background, PNG_BACKGROUND_GAMMA_FILE, 1, 1.0); 95 | else 96 | png_set_background(p->png_ptr, &my_background, PNG_BACKGROUND_GAMMA_SCREEN, 2, 1.0); 97 | 98 | if(png_get_interlace_type(p->png_ptr, p->info_ptr) == PNG_INTERLACE_ADAM7) 99 | p->numpasses = png_set_interlace_handling(p->png_ptr); 100 | else 101 | p->numpasses = 1; 102 | png_read_update_info(p->png_ptr, p->info_ptr); 103 | } 104 | 105 | row_pointers = (png_bytepp)malloc(img->bufheight * sizeof(png_bytep)); 106 | for(y = 0; y < img->bufheight; y++) 107 | row_pointers[y] = img->buf + y * img->bufwidth * 3; 108 | 109 | png_read_image(p->png_ptr, row_pointers); 110 | free(row_pointers); 111 | 112 | img->state |= LOADED | SLOWLOADED; 113 | 114 | return 0; 115 | } 116 | 117 | void png_close(struct image *img){ 118 | struct png_t *p = (struct png_t *)img; 119 | png_destroy_read_struct(&p->png_ptr, &p->info_ptr, &p->end_info); 120 | fclose(p->f); 121 | } 122 | 123 | struct imageformat libpng = { 124 | png_open, 125 | NULL, 126 | png_read, 127 | png_close 128 | }; 129 | 130 | 131 | -------------------------------------------------------------------------------- /src/scale.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "meh.h" 7 | 8 | 9 | #define GETVAL0(c) ((ibuf[x0 + (c)] * (ur) + ibuf[x1 + (c)] * (u)) * (vr) >> 20) 10 | #define GETVAL(c) (( \ 11 | ( \ 12 | ibuf[x0 + (c)] * (ur) + \ 13 | ibuf[x1 + (c)] * (u) \ 14 | ) * (vr) + \ 15 | ( \ 16 | (ibufn[x0 + (c)]) * (ur) + \ 17 | (ibufn[x1 + (c)]) * (u)\ 18 | ) * (v)) >> 20) 19 | 20 | #define XLOOP(F) \ 21 | for(x = 0; x < width*4;){ \ 22 | const unsigned int x0 = a[x++];\ 23 | const unsigned int x1 = a[x++];\ 24 | const unsigned int u = a[x++];\ 25 | const unsigned int ur = a[x++];\ 26 | *newBuf++ = F(2);\ 27 | *newBuf++ = F(1);\ 28 | *newBuf++ = F(0);\ 29 | newBuf++;\ 30 | } 31 | 32 | #define YITER \ 33 | const unsigned int bufy = (y << 10) * img->bufheight / height;\ 34 | const unsigned int v = (bufy & 1023);\ 35 | const unsigned int vr = 1023^v;\ 36 | ibuf = &img->buf[y * img->bufheight / height * img->bufwidth * 3];\ 37 | ibufn = ibuf + dy; 38 | 39 | /* 40 | * Super sampling scale. Down only. 41 | */ 42 | static void superscale(struct image *img, unsigned int width, unsigned int height, unsigned int bytesperline, char* __restrict__ newBuf){ 43 | uint32_t x, y, i; 44 | unsigned char * __restrict__ ibuf; 45 | ibuf = &img->buf[0]; 46 | 47 | TDEBUG_START 48 | 49 | unsigned int divx[width]; 50 | unsigned int divy[height]; 51 | memset(divx, 0, sizeof divx); 52 | memset(divy, 0, sizeof divy); 53 | for(x = 0; x < img->bufwidth; x++){ 54 | divx[x * width / img->bufwidth]++; 55 | } 56 | for(y = 0; y < img->bufheight; y++){ 57 | divy[y * height / img->bufheight]++; 58 | } 59 | 60 | unsigned int tmp[width * 4]; 61 | 62 | unsigned int *xoff[img->bufwidth]; 63 | for(x = 0; x < img->bufwidth; x++){ 64 | xoff[x] = tmp + (x * width / img->bufwidth) * 3; 65 | } 66 | 67 | unsigned int y0; 68 | unsigned int * __restrict__ dest; 69 | for(y = 0; y < img->bufheight;){ 70 | unsigned int ydiv = divy[y * height / img->bufheight]; 71 | char * __restrict__ ydest = &newBuf[bytesperline * (y * height / img->bufheight)]; 72 | memset(tmp, 0, sizeof tmp); 73 | ibuf = &img->buf[y * img->bufwidth * 3]; 74 | for(y0 = y + ydiv; y < y0; y++){ 75 | for(x = 0; x < img->bufwidth; x++){ 76 | dest = xoff[x]; 77 | for(i = 0; i < 3; i++){ 78 | *dest++ += *ibuf++; 79 | } 80 | } 81 | } 82 | unsigned int * __restrict__ src = tmp; 83 | for(x = 0; x < width; x++){ 84 | ydest[2] = *src++ / ydiv / divx[x]; 85 | ydest[1] = *src++ / ydiv / divx[x]; 86 | ydest[0] = *src++ / ydiv / divx[x]; 87 | ydest += 4; 88 | } 89 | } 90 | 91 | TDEBUG_END("superscale") 92 | } 93 | 94 | /* 95 | * Bilinear scale. Used for up only. 96 | */ 97 | static void bilinearscale(struct image *img, unsigned int width, unsigned int height, unsigned int bytesperline, char* __restrict__ newBuf){ 98 | unsigned int x, y; 99 | const unsigned char * __restrict__ ibuf; 100 | const unsigned char * __restrict__ ibufn; 101 | const unsigned char * const bufend = &img->buf[img->bufwidth * img->bufheight * 3]; 102 | const unsigned int jdy = bytesperline / 4 - width; 103 | const unsigned int dy = img->bufwidth * 3; 104 | 105 | TDEBUG_START 106 | 107 | unsigned int a[width * 4]; 108 | { 109 | unsigned int dx = (img->bufwidth << 10) / width; 110 | unsigned int bufx = img->bufwidth / width; 111 | for(x = 0; x < width * 4;){ 112 | if((bufx >> 10) >= img->bufwidth - 1){ 113 | a[x++] = (img->bufwidth - 1) * 3; 114 | a[x++] = (img->bufwidth - 1) * 3; 115 | a[x++] = 0; 116 | a[x++] = 1023 ^ (bufx & 1023); 117 | }else{ 118 | a[x++] = (bufx >> 10) * 3; 119 | a[x++] = ((bufx >> 10) + 1) * 3; 120 | a[x++] = (bufx & 1023); 121 | a[x++] = 1023 ^ (bufx & 1023); 122 | } 123 | bufx += dx; 124 | } 125 | } 126 | y = 0; 127 | ibuf = img->buf; 128 | ibufn = img->buf + dy; 129 | for(;;){ 130 | YITER 131 | if(ibufn + dy > bufend){ 132 | break; 133 | } 134 | XLOOP(GETVAL) 135 | newBuf += jdy; 136 | y++; 137 | } 138 | for(;;){ 139 | YITER 140 | if(ibufn > bufend){ 141 | break; 142 | } 143 | XLOOP(GETVAL0) 144 | newBuf += jdy; 145 | y++; 146 | } 147 | 148 | TDEBUG_END("bilinearscale") 149 | } 150 | 151 | void scale(struct image *img, unsigned int width, unsigned int height, unsigned int bytesperline, char* __restrict__ newBuf){ 152 | if(width < img->bufwidth){ 153 | superscale(img, width, height, bytesperline, newBuf); 154 | }else{ 155 | bilinearscale(img, width, height, bytesperline, newBuf); 156 | } 157 | } 158 | 159 | /* 160 | * Nearest neighbour. Fast up and down. 161 | */ 162 | void nearestscale(struct image *img, unsigned int width, unsigned int height, unsigned int bytesperline, char* __restrict__ newBuf){ 163 | unsigned int x, y; 164 | unsigned char * __restrict__ ibuf; 165 | unsigned int jdy = bytesperline / 4 - width; 166 | unsigned int dx = (img->bufwidth << 10) / width; 167 | 168 | TDEBUG_START 169 | 170 | for(y = 0; y < height; y++){ 171 | unsigned int bufx = img->bufwidth / width; 172 | ibuf = &img->buf[y * img->bufheight / height * img->bufwidth * 3]; 173 | 174 | for(x = 0; x < width; x++){ 175 | *newBuf++ = (ibuf[(bufx >> 10)*3+2]); 176 | *newBuf++ = (ibuf[(bufx >> 10)*3+1]); 177 | *newBuf++ = (ibuf[(bufx >> 10)*3+0]); 178 | newBuf++; 179 | bufx += dx; 180 | } 181 | newBuf += jdy; 182 | } 183 | 184 | TDEBUG_END("nearestscale") 185 | } 186 | 187 | 188 | -------------------------------------------------------------------------------- /src/scale.h: -------------------------------------------------------------------------------- 1 | #ifndef SCALE_H 2 | #define SCALE_H SCALE_H 3 | 4 | /* scale */ 5 | void scale(struct image *img, unsigned int width, unsigned int height, unsigned int bytesperline, char* __restrict__ newBuf); 6 | void nearestscale(struct image *img, unsigned int width, unsigned int height, unsigned int bytesperline, char* __restrict__ newBuf); 7 | 8 | #endif 9 | 10 | -------------------------------------------------------------------------------- /src/xlib.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "meh.h" 15 | #include "scale.h" 16 | 17 | struct data_t{ 18 | XImage *ximg; 19 | XShmSegmentInfo *shminfo; 20 | }; 21 | 22 | /* Globals */ 23 | static Display *display; 24 | static int screen; 25 | static Window window; 26 | static GC gc; 27 | 28 | static int xfd; 29 | 30 | static int xshm = 0; 31 | 32 | static void ximage(struct data_t *data, struct image *img, unsigned int width, unsigned int height, int fast){ 33 | int depth; 34 | XImage *ximg = NULL; 35 | XShmSegmentInfo *shminfo = NULL; 36 | 37 | depth = DefaultDepth(display, screen); 38 | 39 | if(depth >= 24){ 40 | if(xshm){ 41 | shminfo = malloc(sizeof(XShmSegmentInfo)); 42 | if(!(ximg = XShmCreateImage(display, 43 | CopyFromParent, depth, 44 | ZPixmap, 45 | NULL, 46 | shminfo, 47 | width, height 48 | ))){ 49 | fprintf(stderr, "XShm problems\n"); 50 | exit(1); 51 | } 52 | if((shminfo->shmid = shmget(IPC_PRIVATE, ximg->bytes_per_line * ximg->height, IPC_CREAT|0777)) == -1){ 53 | fprintf(stderr, "XShm problems\n"); 54 | exit(1); 55 | } 56 | if((shminfo->shmaddr = ximg->data = shmat(shminfo->shmid, 0, 0)) == (void *)-1){ 57 | fprintf(stderr, "XShm problems\n"); 58 | exit(1); 59 | } 60 | shminfo->readOnly = False; 61 | if(!XShmAttach(display, shminfo)){ 62 | fprintf(stderr, "XShm problems, falling back to to XImage\n"); 63 | xshm = 0; 64 | } 65 | }else{ 66 | ximg = XCreateImage(display, 67 | CopyFromParent, depth, 68 | ZPixmap, 0, 69 | NULL, 70 | width, height, 71 | 32, 0 72 | ); 73 | ximg->data = malloc(ximg->bytes_per_line * ximg->height); 74 | XInitImage(ximg); 75 | } 76 | (fast ? nearestscale : scale)(img, ximg->width, ximg->height, ximg->bytes_per_line, ximg->data); 77 | }else{ 78 | /* TODO other depths */ 79 | fprintf(stderr, "This program does not yet support display depths <24.\n"); 80 | exit(1); 81 | } 82 | 83 | data->ximg = ximg; 84 | data->shminfo = shminfo; 85 | } 86 | 87 | void backend_prepare(struct image *img, unsigned int width, unsigned int height, int fast){ 88 | struct data_t *data = img->backend = malloc(sizeof (struct data_t)); 89 | if(width * img->bufheight > height * img->bufwidth){ 90 | ximage(data, img, img->bufwidth * height / img->bufheight, height, fast); 91 | }else{ 92 | ximage(data, img, width, img->bufheight * width / img->bufwidth, fast); 93 | } 94 | 95 | img->state |= SCALED; 96 | if(!fast) 97 | img->state |= SLOWSCALED; 98 | } 99 | 100 | void backend_draw(struct image *img, unsigned int width, unsigned int height){ 101 | assert(((struct data_t *)img->backend)); 102 | assert(((struct data_t *)img->backend)->ximg); 103 | 104 | XImage *ximg = ((struct data_t *)img->backend)->ximg; 105 | 106 | XRectangle rects[2]; 107 | int yoffset, xoffset; 108 | xoffset = (width - ximg->width) / 2; 109 | yoffset = (height - ximg->height) / 2; 110 | if(xoffset || yoffset){ 111 | rects[0].x = rects[0].y = 0; 112 | if(xoffset){ 113 | rects[0].width = rects[1].width = xoffset; 114 | rects[0].height = rects[1].height = height; 115 | rects[1].x = xoffset + ximg->width; 116 | rects[1].y = 0; 117 | rects[1].width = width - rects[1].x; 118 | }else if(yoffset){ 119 | rects[0].width = rects[1].width = width; 120 | rects[0].height = yoffset; 121 | rects[1].x = 0; 122 | rects[1].y = yoffset + ximg->height; 123 | rects[1].height = height - rects[1].y; 124 | } 125 | XFillRectangles(display, window, gc, rects, 2); 126 | } 127 | if(xshm){ 128 | XShmPutImage(display, window, gc, ximg, 0, 0, xoffset, yoffset, ximg->width, ximg->height, False); 129 | }else{ 130 | XPutImage(display, window, gc, ximg, 0, 0, xoffset, yoffset, ximg->width, ximg->height); 131 | } 132 | XFlush(display); 133 | } 134 | 135 | void backend_free(struct image *img){ 136 | assert(img); 137 | if(img->backend){ 138 | struct data_t *data = (struct data_t *)img->backend; 139 | XImage *ximg = data->ximg; 140 | if(ximg){ 141 | if(xshm && data->shminfo){ 142 | XShmDetach(display, data->shminfo); 143 | XDestroyImage(ximg); 144 | shmdt(data->shminfo->shmaddr); 145 | shmctl(data->shminfo->shmid, IPC_RMID, 0); 146 | free(data->shminfo); 147 | }else{ 148 | XDestroyImage(ximg); 149 | } 150 | } 151 | img->backend = NULL; 152 | } 153 | } 154 | 155 | 156 | void backend_setaspect(unsigned int w, unsigned int h){ 157 | XSizeHints *hints = XAllocSizeHints(); 158 | //hints->flags = PAspect; 159 | hints->flags = 0; 160 | hints->min_aspect.x = hints->max_aspect.x = w; 161 | hints->min_aspect.y = hints->max_aspect.y = h; 162 | XSetWMNormalHints(display, window, hints); 163 | XFlush(display); 164 | XFree(hints); 165 | } 166 | 167 | /* Alt-F4 silent. Keeps people happy */ 168 | static int xquit(Display *d){ 169 | (void)d; 170 | exit(EXIT_SUCCESS); 171 | return 0; 172 | } 173 | 174 | void backend_init(){ 175 | display = XOpenDisplay(NULL); 176 | if(!display){ 177 | fprintf(stderr, "Can't open X display.\n"); 178 | exit(EXIT_FAILURE); 179 | } 180 | xfd = ConnectionNumber(display); 181 | screen = DefaultScreen(display); 182 | 183 | window = XCreateWindow(display, DefaultRootWindow(display), 0, 0, 640, 480, 0, DefaultDepth(display, screen), InputOutput, CopyFromParent, 0, NULL); 184 | backend_setaspect(1, 1); 185 | gc = XCreateGC(display, window, 0, NULL); 186 | 187 | XSelectInput(display, window, StructureNotifyMask | ExposureMask | KeyPressMask); 188 | XMapRaised(display, window); 189 | XFlush(display); 190 | XSetIOErrorHandler(xquit); 191 | XFlush(display); 192 | } 193 | 194 | void handlekeypress(XEvent *event){ 195 | KeySym key = XLookupKeysym(&event->xkey, 0); 196 | switch(key){ 197 | case XK_Escape: 198 | case XK_q: 199 | key_quit(); 200 | break; 201 | case XK_Return: 202 | key_action(); 203 | break; 204 | case XK_j: 205 | case XK_l: 206 | case XK_Right: 207 | case XK_Down: 208 | key_next(); 209 | break; 210 | case XK_k: 211 | case XK_h: 212 | case XK_Left: 213 | case XK_Up: 214 | key_prev(); 215 | break; 216 | case XK_r: 217 | key_reload(); 218 | break; 219 | case XK_0: 220 | if(curimg) 221 | XResizeWindow(display, window, curimg->bufwidth, curimg->bufheight); 222 | break; 223 | } 224 | } 225 | 226 | 227 | void handleevent(XEvent *event){ 228 | switch(event->type){ 229 | /* Might not get ConfigureNotify, for example if there's no window manager */ 230 | case MapNotify: 231 | if (!width || !height) 232 | { 233 | XWindowAttributes attr; 234 | XGetWindowAttributes(event->xmap.display, event->xmap.window, &attr); 235 | width = attr.width; 236 | height = attr.height; 237 | } 238 | break; 239 | case ConfigureNotify: 240 | if(width != event->xconfigure.width || height != event->xconfigure.height){ 241 | width = event->xconfigure.width; 242 | height = event->xconfigure.height; 243 | if(curimg){ 244 | backend_free(curimg); 245 | curimg->state &= ~(DRAWN | SCALED | SLOWSCALED); 246 | 247 | /* Some window managers need reminding */ 248 | backend_setaspect(curimg->bufwidth, curimg->bufheight); 249 | } 250 | } 251 | break; 252 | case Expose: 253 | if(curimg){ 254 | curimg->state &= ~DRAWN; 255 | } 256 | break; 257 | case KeyPress: 258 | handlekeypress(event); 259 | break; 260 | } 261 | } 262 | 263 | void backend_run(){ 264 | fd_set fds; 265 | struct timeval tv0 = {0, 0}; 266 | struct timeval *tv = &tv0; 267 | 268 | for(;;){ 269 | int maxfd = setup_fds(&fds); 270 | FD_SET(xfd, &fds); 271 | if(xfd > maxfd) 272 | maxfd = xfd; 273 | int ret = select(maxfd+1, &fds, NULL, NULL, tv); 274 | if(ret == -1){ 275 | perror("select failed\n"); 276 | } 277 | if(process_fds(&fds)){ 278 | tv = &tv0; 279 | } 280 | if(XPending(display)){ 281 | tv = &tv0; 282 | XEvent event; 283 | do{ 284 | XNextEvent(display, &event); 285 | handleevent(&event); 286 | }while(XPending(display)); 287 | }else if(ret == 0){ 288 | if(!process_idle()){ 289 | /* If we get here everything has been drawn in full or we need more input to continue */ 290 | tv = NULL; 291 | } 292 | } 293 | } 294 | } 295 | 296 | 297 | -------------------------------------------------------------------------------- /test/test.c: -------------------------------------------------------------------------------- 1 | #include "stdio.h" 2 | #include "stdlib.h" 3 | #include "sys/time.h" 4 | 5 | #include "../src/meh.h" 6 | #include "../src/scale.h" 7 | 8 | #define TESTRUNS 20 9 | #define STARTTEST(name) int test_##name(){ testname = #name; testsrun++; do 10 | #define ENDTEST while(0); testspassed++; return 0;} 11 | #define STARTTIME struct timeval tvs[2];int _i, _j; for(_i = 0; _i < 2; gettimeofday(&tvs[_i], NULL), _i++){for(_j = 0; _j < (_i ? TESTRUNS : 3); _j++) 12 | #define ENDTIME }long int mselapsed = (tvs[1].tv_sec - tvs[0].tv_sec) * 1000000L + (tvs[1].tv_usec - tvs[0].tv_usec); printf("%s: average time: %li.%.3li ms\n", testname, mselapsed/1000/TESTRUNS, (mselapsed/TESTRUNS)%1000); 13 | #define assert(x) if(!(x)){fprintf(stderr, "test \"%s\" failed\n assert(%s) was false\n at %s:%i\n\n", testname, #x, __FILE__ ,__LINE__);return -1;} 14 | 15 | char *testname = NULL; 16 | int testsrun = 0, testspassed = 0; 17 | 18 | typedef int (*test_t)(); 19 | 20 | STARTTEST(scale){ 21 | struct image img; 22 | img.bufwidth = 1280*2; 23 | img.bufheight = 1024*2; 24 | img.buf = malloc(img.bufwidth*img.bufheight*4); 25 | char *to = malloc(1280*1024*4); 26 | STARTTIME{ 27 | scale(&img, 1280, 1024, 1280, to); 28 | }ENDTIME 29 | free(to); 30 | free(img.buf); 31 | }ENDTEST 32 | 33 | STARTTEST(nearestscale){ 34 | struct image img; 35 | img.bufwidth = 1280*2; 36 | img.bufheight = 1024*2; 37 | img.buf = malloc(img.bufwidth*img.bufheight*4); 38 | char *to = malloc(1280*1024*4); 39 | STARTTIME{ 40 | nearestscale(&img, 1280, 1024, 1280, to); 41 | }ENDTIME 42 | free(to); 43 | free(img.buf); 44 | }ENDTEST 45 | 46 | test_t tests[] = { 47 | test_scale, 48 | test_nearestscale, 49 | NULL 50 | }; 51 | 52 | void summary(){ 53 | printf("%i tests run: %i passed %i failed\n", testsrun, testspassed, testsrun - testspassed); 54 | } 55 | 56 | int main(){ 57 | test_t *test = tests; 58 | for(; *test; test++) 59 | (*test)(); 60 | summary(); 61 | return 0; 62 | } 63 | 64 | -------------------------------------------------------------------------------- /test/test.h: -------------------------------------------------------------------------------- 1 | 2 | #define STARTTIME 3 | #define ENDTIME 4 | 5 | #define 6 | 7 | --------------------------------------------------------------------------------