meh-0.3/000075500000000000000000000000001203347577000121335ustar00rootroot00000000000000meh-0.3/AUTHORS000064400000000000000000000000571203347577000132050ustar00rootroot00000000000000meh authors John Hawthorn (jhawthor@uvic.ca) meh-0.3/BUGS000064400000000000000000000005211203347577000126140ustar00rootroot00000000000000 meh does not currently support display depths under 24 bits. This should be fixed in the next release. Some WM's, though very few, allow meh's window to be resized to a width of 0. meh is likely to crash in this case. A workaround will be found for the next release. Bugs may be sent to jhawthor@uvic.ca and are greatly appreciated. meh-0.3/COPYING000064400000000000000000000020721203347577000131670ustar00rootroot00000000000000MIT License --- Copyright (c) 2008-2009 John Hawthorn 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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. meh-0.3/Makefile000064400000000000000000000015441203347577000135770ustar00rootroot00000000000000 TARGET := meh TESTTARGET := test/test SRCFILES := $(wildcard src/*.c) OBJFILES := $(SRCFILES:%.c=%.o) DEPFILES := $(OBJFILES:%.o=%.d) TESTCLEAN := $(TESTTARGET) $(TESTTARGET).d $(TESTTARGET).o CLEANFILES := $(CLEANFILES) $(DEPFILES) $(OBJFILES) test/test.o test/test.d test/test $(TARGET) LIBS ?= -lX11 -lXext -ljpeg -lpng -lgif PREFIX ?= /usr/local BINDIR = $(PREFIX)/bin # User configuration CONFIG ?= ../config -include configs/$(CONFIG).mk CFLAGS ?= -O3 -DNDEBUG meh: $(OBJFILES) $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LIBS) test: $(TESTTARGET) ./$(TESTTARGET) test/test: test/test.o $(filter-out src/main.o, $(OBJFILES)) $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LIBS) -include $(DEPFILES) %.o: %.c Makefile $(CC) $(CFLAGS) -MMD -MP -MT "$*.d" -c -o $@ $< install: install -Dm 755 meh $(BINDIR) # Clean clean: $(RM) $(CLEANFILES) .PHONY: clean test meh-0.3/NEWS000064400000000000000000000003521203347577000126320ustar00rootroot00000000000000 meh 0.3 - Preliminary caching support. Improved responsiveness. PNG alpha support. meh 0.2 - Imagemagick support. netpbm support. Better downscaling. Bug fixes. meh 0.1 - First release. JPEG, PNG, GIF and BMP formats. XSHM support. meh-0.3/README000064400000000000000000000025051203347577000130150ustar00rootroot00000000000000meh 0.3 John Hawthorn (jhawthor@uvic.ca) --- Installation To compile type make To install type make install Installation prefix can be changed by the PREFIX variable make install PREFIX=/usr default prefix is /usr/local Configuration can be changed in config.mk --- Copyright (c) 2008-2009 John Hawthorn 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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. --- meh-0.3/THANKS000064400000000000000000000000721203347577000130450ustar00rootroot00000000000000 Clarke Brunsdon Tarpman Jared "PRINCESSJRNORMAN" Norman meh-0.3/configs/000075500000000000000000000000001203347577000135635ustar00rootroot00000000000000meh-0.3/configs/debug.mk000064400000000000000000000000371203347577000152020ustar00rootroot00000000000000CFLAGS += -O0 -g -Wall -Wextra meh-0.3/configs/gprof.mk000064400000000000000000000000211203347577000152220ustar00rootroot00000000000000CFLAGS += -g -pg meh-0.3/src/000075500000000000000000000000001203347577000127225ustar00rootroot00000000000000meh-0.3/src/bmp.c000064400000000000000000000072231203347577000136500ustar00rootroot00000000000000 #include #include #include "meh.h" struct rgb_t{ unsigned char r, g, b; }; struct bmp_t{ struct image img; FILE *f; unsigned long bitmapoffset; int compression; int bpp; int ncolors; struct rgb_t *colours; unsigned int rowwidth; }; static unsigned short getshort(FILE *f){ unsigned short ret; ret = getc(f); ret = ret | (getc(f) << 8); return ret; } static unsigned long getlong(FILE *f){ unsigned short ret; ret = getc(f); ret = ret | (getc(f) << 8); ret = ret | (getc(f) << 16); ret = ret | (getc(f) << 24); return ret; } struct image *bmp_open(FILE *f){ struct bmp_t *b; unsigned long headersize; rewind(f); if(getc(f) != 'B' || getc(f) != 'M') return NULL; b = malloc(sizeof(struct bmp_t)); b->f = f; fseek(f, 10, SEEK_SET); b->bitmapoffset = getlong(f); fseek(f, 14, SEEK_SET); headersize = getlong(f); if(headersize == 12){ /* OS/2 v1 */ b->ncolors = 0; fseek(f, 18, SEEK_SET); b->img.bufwidth = getshort(f); b->img.bufheight = getshort(f); b->compression = 0; }else{ fseek(f, 18, SEEK_SET); b->img.bufwidth = getlong(f); b->img.bufheight = getlong(f); fseek(f, 28, SEEK_SET); b->bpp = getshort(f); fseek(f, 30, SEEK_SET); b->compression = getlong(f); fseek(f, 46, SEEK_SET); b->ncolors = getlong(f); } if(!b->ncolors){ b->ncolors = 1 << b->bpp; } if(b->compression){ fprintf(stderr, "unsupported compression method %i\n", b->compression); return NULL; } if(b->bpp >= 16){ b->rowwidth = b->img.bufwidth * b->bpp / 8; b->colours = NULL; }else{ int i; b->colours = malloc(b->ncolors * sizeof(struct rgb_t)); fseek(f, 14+headersize, SEEK_SET); for(i = 0; i < b->ncolors; i++){ b->colours[i].b = getc(f); b->colours[i].g = getc(f); b->colours[i].r = getc(f); if(headersize != 12) getc(f); } b->rowwidth = (b->img.bufwidth * b->bpp + 7) / 8; } if(b->rowwidth & 3){ b->rowwidth += 4 - (b->rowwidth & 3); } b->img.fmt = &bmp; return (struct image *)b; } static void rgb16(unsigned char *buf, unsigned short c){ int i; for(i = 0; i < 3; i++){ *buf++ = ((c >> (i * 5)) & 0x1f) << 3; } } static int readrow(struct image *img, unsigned char *row, unsigned char *buf){ struct bmp_t *b = (struct bmp_t *)img; unsigned int x, i = 0; if(b->bpp == 24 || b->bpp == 32){ for(x = 0; x < img->bufwidth * 3; x+=3){ buf[x + 2] = row[i++]; buf[x + 1] = row[i++]; buf[x + 0] = row[i++]; if(b->bpp == 32) i++; } }else if(b->bpp == 16){ for(x = 0; x < img->bufwidth * 3; x+=3){ unsigned short c; c = row[i++]; c |= row[i++] << 8; rgb16(&buf[x], c); } }else if(b->bpp <= 8){ int mask; int pixelsperbit = 8 / b->bpp; mask = ~((~0) << b->bpp); for(x = 0; x < img->bufwidth; x++){ unsigned char c = ((row[i / pixelsperbit]) >> ((8 - ((i+1) % pixelsperbit) * b->bpp)) % 8) & mask; *buf++ = b->colours[c].r; *buf++ = b->colours[c].g; *buf++ = b->colours[c].b; i++; } }else{ fprintf(stderr, "bad bpp %i\n", b->bpp); return 1; } return 0; } int bmp_read(struct image *img){ struct bmp_t *b = (struct bmp_t *)img; unsigned int i, y; unsigned int dy; unsigned char *row; FILE *f = b->f; row = malloc(b->rowwidth); dy = img->bufwidth * 3; i = img->bufheight * dy; fseek(f, b->bitmapoffset, SEEK_SET); for(y = img->bufheight; y; y--){ i -= dy; if(fread(row, 1, b->rowwidth, f) != b->rowwidth || readrow(img, row, &img->buf[i])){ free(row); return 1; } } free(row); img->state |= LOADED | SLOWLOADED; return 0; } void bmp_close(struct image *img){ struct bmp_t *b = (struct bmp_t *)img; free(b->colours); fclose(b->f); } struct imageformat bmp = { bmp_open, NULL, bmp_read, bmp_close }; meh-0.3/src/gif.c000064400000000000000000000032561203347577000136410ustar00rootroot00000000000000 #include #include #include #include #include #include #include #include "meh.h" struct gif_t{ struct image img; FILE *f; GifFileType *gif; }; static int isgif(FILE *f){ return (getc(f) == 'G' && getc(f) == 'I' && getc(f) == 'F'); } static struct image *gif_open(FILE *f){ struct gif_t *g; GifFileType *gif; rewind(f); if(!isgif(f)) return NULL; /* HACK HACK HACK */ rewind(f); lseek(fileno(f), 0L, SEEK_SET); if(!(gif = DGifOpenFileHandle(fileno(f)))){ /* HACK AND HOPE */ rewind(f); lseek(fileno(f), 0L, SEEK_SET); return NULL; } g = malloc(sizeof(struct gif_t)); g->f = f; g->gif = gif; g->img.bufwidth = gif->SWidth; g->img.bufheight = gif->SHeight; g->img.fmt = &giflib; return (struct image *)g; } static int gif_read(struct image *img){ unsigned int i, j = 0; struct gif_t *g = (struct gif_t *)img; GifColorType *colormap; SavedImage *s; if(DGifSlurp(g->gif) == GIF_ERROR){ PrintGifError(); return 1; } s = &g->gif->SavedImages[0]; if(s->ImageDesc.ColorMap) colormap = s->ImageDesc.ColorMap->Colors; else if(g->gif->SColorMap) colormap = g->gif->SColorMap->Colors; else{ PrintGifError(); return 1; } for(i = 0; i < img->bufwidth * img->bufheight; i++){ unsigned char idx = s->RasterBits[i]; img->buf[j++] = colormap[idx].Red; img->buf[j++] = colormap[idx].Green; img->buf[j++] = colormap[idx].Blue; } img->state |= LOADED | SLOWLOADED; return 0; } void gif_close(struct image *img){ struct gif_t *g = (struct gif_t *)img; DGifCloseFile(g->gif); fclose(g->f); } struct imageformat giflib = { gif_open, NULL, gif_read, gif_close }; meh-0.3/src/imagemagick.c000064400000000000000000000021101203347577000153160ustar00rootroot00000000000000 #define _GNU_SOURCE #include #include #include #include #include #include "meh.h" struct image *imagemagick_open(FILE *f){ int tmpfd[2]; if(pipe(tmpfd)){ perror("pipe"); exit(EXIT_FAILURE); } int pid; if(!(pid = fork())){ close(tmpfd[0]); int origfd = fileno(f); if(lseek(origfd, 0, SEEK_SET) != 0){ perror("lseek"); exit(EXIT_FAILURE); } char *argv[6]; argv[0] = "convert"; argv[1] = "-depth"; argv[2] = "255"; asprintf(&argv[3], "fd:%i", origfd); asprintf(&argv[4], "ppm:fd:%i", tmpfd[1]); argv[5] = NULL; #ifdef NDEBUG /* STFU OMFG */ freopen("/dev/null", "w", stdout); freopen("/dev/null", "w", stderr); #endif execvp(argv[0], argv); perror("exec"); exit(EXIT_FAILURE); }else{ close(tmpfd[1]); FILE *ftmp; if(!(ftmp = fdopen(tmpfd[0], "rb"))){ perror("fopen"); exit(EXIT_FAILURE); } struct image *img = netpbm.open(ftmp); if(!img) return NULL; fclose(f); return img; } } struct imageformat imagemagick = { imagemagick_open, NULL, NULL, NULL }; meh-0.3/src/jpeg.c000064400000000000000000000072421203347577000140200ustar00rootroot00000000000000 #include #include #include #include #include #include "jpeglib.h" #include "meh.h" struct error_mgr{ struct jpeg_error_mgr pub; jmp_buf jmp_buffer; }; struct jpeg_t{ struct image img; FILE *f; struct jpeg_decompress_struct cinfo; struct error_mgr jerr; }; static void error_exit(j_common_ptr cinfo){ (void) cinfo; printf("\nerror!\n"); exit(1); } static void error_longjmp(j_common_ptr cinfo){ struct error_mgr *myerr = (struct error_mgr *)cinfo->err; (*cinfo->err->output_message)(cinfo); longjmp(myerr->jmp_buffer, 1); } /* TODO progressive */ static struct image *jpeg_open(FILE *f){ struct jpeg_t *j; rewind(f); if(getc(f) != 0xff || getc(f) != 0xd8) return NULL; j = malloc(sizeof(struct jpeg_t)); j->f = f; j->cinfo.err = jpeg_std_error(&j->jerr.pub); j->jerr.pub.error_exit = error_longjmp; if (setjmp(j->jerr.jmp_buffer)) { return NULL; } j->jerr.pub.error_exit = error_exit; j->img.fmt = &libjpeg; return (struct image *)j; } void jpeg_prep(struct image *img){ struct jpeg_t *j = (struct jpeg_t *)img; jpeg_create_decompress(&j->cinfo); rewind(j->f); jpeg_stdio_src(&j->cinfo, j->f); jpeg_read_header(&j->cinfo, TRUE); /* parameters */ j->cinfo.do_fancy_upsampling = 0; j->cinfo.do_block_smoothing = 0; j->cinfo.quantize_colors = 0; j->cinfo.dct_method = JDCT_FASTEST; j->cinfo.scale_denom = (img->state & LOADED) ? 1 : 8; /* TODO: This should be changed done only for large jpegs */ jpeg_calc_output_dimensions(&j->cinfo); j->img.bufwidth = j->cinfo.output_width; j->img.bufheight = j->cinfo.output_height; } static int jpeg_read(struct image *img){ struct jpeg_t *j = (struct jpeg_t *)img; unsigned int row_stride; int a = 0, b; unsigned int x, y; j->jerr.pub.error_exit = error_longjmp; if(setjmp(j->jerr.jmp_buffer)){ return 1; /* ERROR */ } row_stride = j->cinfo.output_width * j->cinfo.output_components; jpeg_start_decompress(&j->cinfo); if(j->cinfo.output_components == 3){ JSAMPROW rows[2]; rows[0] = img->buf; rows[1] = rows[0] + row_stride; for(y = 0; y < j->cinfo.output_height;){ int n = jpeg_read_scanlines(&j->cinfo, rows, 2); y += n; rows[0] = rows[n-1] + row_stride; rows[1] = rows[0] + row_stride; } }else if(j->cinfo.output_components == 1){ JSAMPARRAY buffer = (*j->cinfo.mem->alloc_sarray)((j_common_ptr)&j->cinfo, JPOOL_IMAGE, row_stride, 4); for(y = 0; y < j->cinfo.output_height; ){ int n = jpeg_read_scanlines(&j->cinfo, buffer, 4); for(b = 0; b < n; b++){ for(x = 0; x < j->cinfo.output_width; x++){ img->buf[a++] = buffer[b][x]; img->buf[a++] = buffer[b][x]; img->buf[a++] = buffer[b][x]; } } y += n; } }else if(j->cinfo.output_components == 4){ JSAMPARRAY buffer = (*j->cinfo.mem->alloc_sarray)((j_common_ptr)&j->cinfo, JPOOL_IMAGE, row_stride, 4); for(y = 0; y < j->cinfo.output_height; ){ int n = jpeg_read_scanlines(&j->cinfo, buffer, 4); for(b = 0; b < n; b++){ for(x = 0; x < j->cinfo.output_width; x++){ int tmp = buffer[b][x*4 + 3]; img->buf[a++] = buffer[b][x*4] * tmp / 255; img->buf[a++] = buffer[b][x*4 + 1] * tmp / 255; img->buf[a++] = buffer[b][x*4 + 2] * tmp / 255; } } y += n; } }else{ fprintf(stderr, "Unsupported number of output components: %u\n", j->cinfo.output_components); return 1; } jpeg_finish_decompress(&j->cinfo); img->state |= LOADED; if(j->cinfo.scale_denom == 1) img->state |= SLOWLOADED; return 0; } void jpeg_close(struct image *img){ struct jpeg_t *j = (struct jpeg_t *)img; jpeg_destroy_decompress(&j->cinfo); fclose(j->f); } struct imageformat libjpeg = { jpeg_open, jpeg_prep, jpeg_read, jpeg_close }; meh-0.3/src/main.c000064400000000000000000000144171203347577000140210ustar00rootroot00000000000000 #define _GNU_SOURCE #include #include #include #include #include #include #include "meh.h" #define MODE_NORM 0 #define MODE_LIST 1 #define MODE_CTL 2 static int mode; /* Supported Formats */ static struct imageformat *formats[] = { &libjpeg, &bmp, &libpng, &netpbm, &giflib, /* HACK! make gif last (uses read()) */ &imagemagick, NULL }; static void usage(){ printf("USAGE: meh [FILE1 [FILE2 [...]]]\n"); printf(" meh -list [LISTFILE] : Treat file as list of images. Defaults to stdin.\n"); printf(" meh -ctl : Display files as they are received on stdin\n"); printf(" meh -v : Print version and exit.\n"); exit(EXIT_FAILURE); } static struct image *newimage(FILE *f){ struct image *img = NULL; struct imageformat **fmt = formats; for(fmt = formats; *fmt; fmt++){ if((img = (*fmt)->open(f))){ img->state = NONE; img->backend = NULL; return img; } } return NULL; } /* For MODE_CTL */ static int ctlfd; static int imageslen; static int imageidx; static char **images; int width = 0, height = 0; struct image *curimg = NULL; struct image *nextimg = NULL; struct image *previmg = NULL; static void freeimage(struct image *img){ if(img){ backend_free(img); if(img->buf) free(img->buf); free(img); } } static int incidx(int i){ return ++i >= imageslen ? 0 : i; } static int decidx(int i){ return --i < 0 ? imageslen - 1 : i; } static int (*direction)(int) = incidx; void key_reload(){ freeimage(curimg); curimg = NULL; } void key_next(){ if(mode != MODE_CTL){ if(curimg) curimg->state &= LOADED | SLOWLOADED; freeimage(previmg); previmg = curimg; curimg = nextimg; nextimg = NULL; if(curimg){ imageidx = curimg->idx; }else{ imageidx = (direction = incidx)(imageidx); } } } void key_prev(){ if(mode != MODE_CTL){ if(curimg) curimg->state &= LOADED | SLOWLOADED; freeimage(nextimg); nextimg = curimg; curimg = previmg; previmg = NULL; if(curimg){ imageidx = curimg->idx; }else{ imageidx = (direction = decidx)(imageidx); } } } void key_quit(){ exit(EXIT_SUCCESS); } void key_action(){ puts(images[imageidx]); fflush(stdout); } struct image *imageopen2(char *filename){ struct image *i; FILE *f; if((f = fopen(filename, "rb"))){ if((i = newimage(f))){ return i; } else fprintf(stderr, "Invalid format '%s'\n", filename); }else{ fprintf(stderr, "Cannot open '%s'\n", filename); } return NULL; } static int doredraw(struct image **i, int idx, int (*dir)(int), int dstates){ if(!*i){ if(mode == MODE_CTL){ if(images[0] == NULL) return 0; if(!(*i = imageopen2(images[0]))){ images[0] = NULL; return 0; } }else{ int firstimg = idx; while(!*i){ if((*i = imageopen2(images[idx]))){ break; } idx = dir(idx); if(idx == firstimg){ fprintf(stderr, "No valid images to view\n"); exit(EXIT_FAILURE); } } } (*i)->idx = idx; (*i)->buf = NULL; (*i)->state = NONE; return 1; }else{ imgstate state = (*i)->state; if(!(state & LOADED) || ((state & (SLOWLOADED | DRAWN)) == (DRAWN)) ){ if((*i)->fmt->prep){ (*i)->fmt->prep(*i); } backend_setaspect((*i)->bufwidth, (*i)->bufheight); if((*i)->buf) free((*i)->buf); (*i)->buf = malloc(3 * (*i)->bufwidth * (*i)->bufheight); TDEBUG_START if((*i)->fmt->read(*i)){ fprintf(stderr, "read error!\n"); } TDEBUG_END("read"); if(((*i)->state & LOADED) && ((*i)->state & SLOWLOADED)){ /* We are done with the format's methods */ (*i)->fmt->close(*i); } (*i)->state &= LOADED | SLOWLOADED; /* state should be set by format */ assert((*i)->state & LOADED); return 1; }else if(width && height){ if((dstates & SCALED) && (state & LOADED) && !(state & SCALED)){ backend_prepare(*i, width, height, !!(state & SCALED)); (*i)->state &= LOADED | SLOWLOADED | SCALED | SLOWSCALED; /* state should be set by backend */ assert((*i)->state & SCALED); /* state should not be drawn so that we will draw later (also assures correct return value) */ assert(!((*i)->state & DRAWN)); } } if((dstates & DRAWN) && ((*i)->state & SCALED) && !(state & DRAWN)){ backend_draw(*i, width, height); (*i)->state |= DRAWN; return 1; } } return 0; } static void readlist(FILE *f){ int lsize = 16; imageslen = 0; images = NULL; while(!feof(f)){ images = realloc(images, lsize * sizeof(char *)); while(imageslen < lsize && !feof(f)){ char *line = NULL; size_t slen = 0; ssize_t read; read = getline(&line, &slen, f); if(read > 1){ line[read-1] = '\0'; images[imageslen++] = line; }else if(line){ free(line); } } lsize *= 2; } if(!imageslen){ fprintf(stderr, "No images to view\n"); exit(EXIT_FAILURE); } } int setup_fds(fd_set *fds){ FD_ZERO(fds); if(mode == MODE_CTL) FD_SET(ctlfd, fds); /* STDIN */ return ctlfd; } int process_fds(fd_set *fds){ if(FD_ISSET(ctlfd, fds)){ assert(mode == MODE_CTL); size_t slen = 0; ssize_t read; if(images[0]){ free(images[0]); } freeimage(curimg); curimg = NULL; images[0] = NULL; if((read = getline(images, &slen, stdin)) == -1){ exit(EXIT_SUCCESS); } images[0][read-1] = '\0'; return 1; } return 0; } int process_idle(){ if(mode == MODE_CTL && images[0] == NULL){ return 0; }else{ int ret = doredraw(&curimg, imageidx, direction, ~0); imageidx = curimg->idx; if(!ret){ ret = doredraw(&nextimg, incidx(imageidx), incidx, LOADED | SLOWLOADED); } if(!ret){ ret = doredraw(&previmg, decidx(imageidx), decidx, LOADED | SLOWLOADED); } return ret; } } int main(int argc, char *argv[]){ if(argc < 2) usage(); if(!strcmp(argv[1], "-ctl")){ if(argc != 2) usage(); mode = MODE_CTL; images = malloc(sizeof(char *)); images[0] = NULL; imageslen = 1; imageidx = 0; ctlfd = 0; }else if(!strcmp(argv[1], "-list")){ mode = MODE_LIST; if(argc == 2){ readlist(stdin); }else if(argc == 3){ FILE *f = fopen(argv[2], "r"); readlist(f); fclose(f); }else{ usage(); } }else if(!strcmp(argv[1], "-v")){ printf("meh version 0.3\n"); return 0; }else{ mode = MODE_NORM; images = &argv[1]; imageslen = argc-1; imageidx = 0; } backend_init(); backend_run(); return 0; } meh-0.3/src/meh.h000064400000000000000000000031351203347577000136460ustar00rootroot00000000000000#ifndef MEH_H #define MEH_H MEH_H #include struct image; struct imageformat{ struct image *(*open)(FILE *); void (*prep)(struct image *); int (*read)(struct image *); void (*close)(struct image *); }; typedef enum{ NONE = 0, LOADED = 1, SLOWLOADED = 2, SCALED = 4, SLOWSCALED = 8, DRAWN = 16 } imgstate; struct image{ unsigned char *buf; unsigned int bufwidth, bufheight; struct imageformat *fmt; imgstate state; int idx; void *backend; }; /* backend */ void backend_init(); void backend_free(struct image *img); void backend_setaspect(unsigned int w, unsigned int h); void backend_prepare(struct image *img, unsigned int width, unsigned int height, int fast); void backend_draw(struct image *img, unsigned int width, unsigned int height); void backend_run(); /* key actions for backend */ void key_reload(); void key_next(); void key_prev(); void key_quit(); void key_action(); /* callbacks from backend */ int setup_fds(fd_set *fds); int process_fds(fd_set *fds); int process_idle(); #ifdef TDEBUG #define TDEBUG_START \ struct timeval t0;\ struct timeval t1;\ gettimeofday(&t0, NULL); #define TDEBUG_END(x) \ gettimeofday(&t1, NULL); \ printf("%s: %li e2 us\n", (x), ((t1.tv_sec - t0.tv_sec) * 1000000 + t1.tv_usec - t0.tv_usec) / 100); #else #define TDEBUG_START #define TDEBUG_END(x) #endif /* Supported Formats */ extern struct imageformat libjpeg; extern struct imageformat giflib; extern struct imageformat libpng; extern struct imageformat bmp; extern struct imageformat netpbm; extern struct imageformat imagemagick; extern int width, height; extern struct image *curimg; #endif meh-0.3/src/netpbm.c000064400000000000000000000053261203347577000143610ustar00rootroot00000000000000 #include #include #include #include "meh.h" struct netpbm_t{ struct image img; FILE *f; char format; unsigned int maxval; }; void skipspace(FILE *f){ int c; for(;;){ c = fgetc(f); while(isspace(c)) c = fgetc(f); if(c == '#') while(fgetc(f) != '\n'); else break; } ungetc(c, f); } struct image *netpbm_open(FILE *f){ struct netpbm_t *b; rewind(f); char format; if(fgetc(f) != 'P') return NULL; format = fgetc(f); if(format > '6' || format < '1') return NULL; b = malloc(sizeof(struct netpbm_t)); b->format = format; skipspace(f); fscanf(f, "%u", &b->img.bufwidth); skipspace(f); fscanf(f, "%u", &b->img.bufheight); if(format == '1' || format == '4'){ b->maxval = 1; }else{ skipspace(f); fscanf(f, "%u", &b->maxval); } /* whitespace character */ fgetc(f); b->f = f; b->img.fmt = &netpbm; return (struct image *)b; } static unsigned char readvali(struct netpbm_t *b){ skipspace(b->f); int val; fscanf(b->f, "%i", &val); return val * 255 / b->maxval; } static unsigned char readvalb(struct netpbm_t *b){ if(b->maxval == 65535){ int val = fgetc(b->f) << 8; val |= fgetc(b->f); return val * 255 / b->maxval; }else{ int val = fgetc(b->f); return val * 255 / b->maxval; } } int netpbm_read(struct image *img){ struct netpbm_t *b = (struct netpbm_t *)img; FILE *f = b->f; int a = 0; int left = img->bufwidth * img->bufheight; int j, c, val; if(b->format == '1'){ while(left--){ skipspace(f); val = fgetc(f); val = val == '1' ? 0 : 255; img->buf[a++] = val; img->buf[a++] = val; img->buf[a++] = val; } }else if(b->format == '2'){ while(left--){ val = readvali(b); img->buf[a++] = val; img->buf[a++] = val; img->buf[a++] = val; } }else if(b->format == '3'){ while(left--){ img->buf[a++] = readvali(b); img->buf[a++] = readvali(b); img->buf[a++] = readvali(b); } }else if(b->format == '4'){ while(left){ c = fgetc(f); for(j = 0; j < 8 && left; j++){ int val = (c & 1) ? 0 : 255; img->buf[a++] = val; img->buf[a++] = val; img->buf[a++] = val; c >>= 1; left--; } } }else if(b->format == '5'){ while(left--){ val = readvalb(b); img->buf[a++] = val; img->buf[a++] = val; img->buf[a++] = val; } }else if(b->format == '6'){ if(b->maxval == 255){ fread(img->buf, 1, left * 3, f); }else{ while(left--){ img->buf[a++] = readvalb(b); img->buf[a++] = readvalb(b); img->buf[a++] = readvalb(b); } } } img->state |= LOADED | SLOWLOADED; return 0; } void netpbm_close(struct image *img){ struct netpbm_t *b = (struct netpbm_t *)img; fclose(b->f); } struct imageformat netpbm = { netpbm_open, NULL, netpbm_read, netpbm_close }; meh-0.3/src/png.c000064400000000000000000000062501203347577000136550ustar00rootroot00000000000000#include #include #include "meh.h" struct png_t{ struct image img; FILE *f; png_structp png_ptr; png_infop info_ptr; png_infop end_info; int numpasses; }; static int ispng(FILE *f){ unsigned char buf[8]; if(fread(buf, 1, 8, f) != 8) return 0; return png_check_sig(buf, 8); } struct image *png_open(FILE *f){ struct png_t *p; rewind(f); if(!ispng(f)) return NULL; p = malloc(sizeof(struct png_t)); if((p->png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)) == NULL){ free(p); return NULL; } if((p->info_ptr = png_create_info_struct(p->png_ptr)) == NULL){ png_destroy_read_struct(&p->png_ptr, (png_infopp)NULL, (png_infopp)NULL); free(p); return NULL; } if((p->end_info = png_create_info_struct(p->png_ptr)) == NULL){ png_destroy_read_struct(&p->png_ptr, &p->info_ptr, (png_infopp)NULL); free(p); return NULL; } if(setjmp(png_jmpbuf(p->png_ptr))){ png_destroy_read_struct(&p->png_ptr, &p->info_ptr, &p->end_info); free(p); return NULL; } p->f = f; rewind(f); png_init_io(p->png_ptr, f); png_read_info(p->png_ptr, p->info_ptr); p->img.bufwidth = png_get_image_width(p->png_ptr, p->info_ptr); p->img.bufheight = png_get_image_height(p->png_ptr, p->info_ptr); p->img.fmt = &libpng; return (struct image *)p; } int png_read(struct image *img){ unsigned int y; png_bytepp row_pointers; struct png_t *p = (struct png_t *)img; if(setjmp(png_jmpbuf(p->png_ptr))){ png_destroy_read_struct(&p->png_ptr, &p->info_ptr, &p->end_info); return 1; } { int color_type = png_get_color_type(p->png_ptr, p->info_ptr); int bit_depth = png_get_bit_depth(p->png_ptr, p->info_ptr); if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_expand(p->png_ptr); if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand(p->png_ptr); if (png_get_valid(p->png_ptr, p->info_ptr, PNG_INFO_tRNS)) png_set_expand(p->png_ptr); if (bit_depth == 16) png_set_strip_16(p->png_ptr); if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(p->png_ptr); //png_set_strip_alpha(p->png_ptr); png_color_16 my_background = {.red = 0xff, .green = 0xff, .blue = 0xff}; png_color_16p image_background; if(png_get_bKGD(p->png_ptr, p->info_ptr, &image_background)) png_set_background(p->png_ptr, image_background, PNG_BACKGROUND_GAMMA_FILE, 1, 1.0); else png_set_background(p->png_ptr, &my_background, PNG_BACKGROUND_GAMMA_SCREEN, 2, 1.0); if(png_get_interlace_type(p->png_ptr, p->info_ptr) == PNG_INTERLACE_ADAM7) p->numpasses = png_set_interlace_handling(p->png_ptr); else p->numpasses = 1; png_read_update_info(p->png_ptr, p->info_ptr); } row_pointers = (png_bytepp)malloc(img->bufheight * sizeof(png_bytep)); for(y = 0; y < img->bufheight; y++) row_pointers[y] = img->buf + y * img->bufwidth * 3; png_read_image(p->png_ptr, row_pointers); free(row_pointers); img->state |= LOADED | SLOWLOADED; return 0; } void png_close(struct image *img){ struct png_t *p = (struct png_t *)img; png_destroy_read_struct(&p->png_ptr, &p->info_ptr, &p->end_info); fclose(p->f); } struct imageformat libpng = { png_open, NULL, png_read, png_close }; meh-0.3/src/scale.c000064400000000000000000000107611203347577000141620ustar00rootroot00000000000000 #include #include #include #include #include "meh.h" #define GETVAL0(c) ((ibuf[x0 + (c)] * (ur) + ibuf[x1 + (c)] * (u)) * (vr) >> 20) #define GETVAL(c) (( \ ( \ ibuf[x0 + (c)] * (ur) + \ ibuf[x1 + (c)] * (u) \ ) * (vr) + \ ( \ (ibufn[x0 + (c)]) * (ur) + \ (ibufn[x1 + (c)]) * (u)\ ) * (v)) >> 20) #define XLOOP(F) \ for(x = 0; x < width*4;){ \ const unsigned int x0 = a[x++];\ const unsigned int x1 = a[x++];\ const unsigned int u = a[x++];\ const unsigned int ur = a[x++];\ *newBuf++ = F(2);\ *newBuf++ = F(1);\ *newBuf++ = F(0);\ newBuf++;\ } #define YITER \ const unsigned int bufy = (y << 10) * img->bufheight / height;\ const unsigned int v = (bufy & 1023);\ const unsigned int vr = 1023^v;\ ibuf = &img->buf[y * img->bufheight / height * img->bufwidth * 3];\ ibufn = ibuf + dy; /* * Super sampling scale. Down only. */ static void superscale(struct image *img, unsigned int width, unsigned int height, unsigned int bytesperline, char* __restrict__ newBuf){ uint32_t x, y, i; unsigned char * __restrict__ ibuf; ibuf = &img->buf[0]; TDEBUG_START unsigned int divx[bytesperline]; unsigned int divy[bytesperline]; memset(divx, 0, sizeof divx); memset(divy, 0, sizeof divy); for(x = 0; x < img->bufwidth; x++){ divx[x * width / img->bufwidth]++; } for(y = 0; y < img->bufheight; y++){ divy[y * height / img->bufheight]++; } unsigned int tmp[width * 4]; unsigned int *xoff[img->bufwidth]; for(x = 0; x < img->bufwidth; x++){ xoff[x] = tmp + (x * width / img->bufwidth) * 3; } unsigned int y0; unsigned int * __restrict__ dest; for(y = 0; y < img->bufheight;){ unsigned int ydiv = divy[y * height / img->bufheight]; char * __restrict__ ydest = &newBuf[bytesperline * (y * height / img->bufheight)]; memset(tmp, 0, sizeof tmp); ibuf = &img->buf[y * img->bufwidth * 3]; for(y0 = y + ydiv; y < y0; y++){ for(x = 0; x < img->bufwidth; x++){ dest = xoff[x]; for(i = 0; i < 3; i++){ *dest++ += *ibuf++; } } } unsigned int * __restrict__ src = tmp; for(x = 0; x < width; x++){ ydest[2] = *src++ / ydiv / divx[x]; ydest[1] = *src++ / ydiv / divx[x]; ydest[0] = *src++ / ydiv / divx[x]; ydest += 4; } } TDEBUG_END("superscale") } /* * Bilinear scale. Used for up only. */ static void bilinearscale(struct image *img, unsigned int width, unsigned int height, unsigned int bytesperline, char* __restrict__ newBuf){ unsigned int x, y; const unsigned char * __restrict__ ibuf; const unsigned char * __restrict__ ibufn; const unsigned char * const bufend = &img->buf[img->bufwidth * img->bufheight * 3]; const unsigned int jdy = bytesperline / 4 - width; const unsigned int dy = img->bufwidth * 3; TDEBUG_START unsigned int a[width * 4]; { unsigned int dx = (img->bufwidth << 10) / width; unsigned int bufx = img->bufwidth / width; for(x = 0; x < width * 4;){ if((bufx >> 10) >= img->bufwidth - 1){ a[x++] = (img->bufwidth - 1) * 3; a[x++] = (img->bufwidth - 1) * 3; a[x++] = 0; a[x++] = 1023 ^ (bufx & 1023); }else{ a[x++] = (bufx >> 10) * 3; a[x++] = ((bufx >> 10) + 1) * 3; a[x++] = (bufx & 1023); a[x++] = 1023 ^ (bufx & 1023); } bufx += dx; } } y = 0; ibuf = img->buf; ibufn = img->buf + dy; for(;;){ YITER if(ibufn + dy > bufend){ break; } XLOOP(GETVAL) newBuf += jdy; y++; } for(;;){ YITER if(ibufn > bufend){ break; } XLOOP(GETVAL0) newBuf += jdy; y++; } TDEBUG_END("bilinearscale") } void scale(struct image *img, unsigned int width, unsigned int height, unsigned int bytesperline, char* __restrict__ newBuf){ if(width < img->bufwidth){ superscale(img, width, height, bytesperline, newBuf); }else{ bilinearscale(img, width, height, bytesperline, newBuf); } } /* * Nearest neighbour. Fast up and down. */ void nearestscale(struct image *img, unsigned int width, unsigned int height, unsigned int bytesperline, char* __restrict__ newBuf){ unsigned int x, y; unsigned char * __restrict__ ibuf; unsigned int jdy = bytesperline / 4 - width; unsigned int dx = (img->bufwidth << 10) / width; TDEBUG_START for(y = 0; y < height; y++){ unsigned int bufx = img->bufwidth / width; ibuf = &img->buf[y * img->bufheight / height * img->bufwidth * 3]; for(x = 0; x < width; x++){ *newBuf++ = (ibuf[(bufx >> 10)*3+2]); *newBuf++ = (ibuf[(bufx >> 10)*3+1]); *newBuf++ = (ibuf[(bufx >> 10)*3+0]); newBuf++; bufx += dx; } newBuf += jdy; } TDEBUG_END("nearestscale") } meh-0.3/src/scale.h000064400000000000000000000005011203347577000141560ustar00rootroot00000000000000#ifndef SCALE_H #define SCALE_H SCALE_H /* scale */ void scale(struct image *img, unsigned int width, unsigned int height, unsigned int bytesperline, char* __restrict__ newBuf); void nearestscale(struct image *img, unsigned int width, unsigned int height, unsigned int bytesperline, char* __restrict__ newBuf); #endif meh-0.3/src/xlib.c000064400000000000000000000143411203347577000140270ustar00rootroot00000000000000 #include #include #include #include #include #include #include #include #include #include #include "meh.h" #include "scale.h" struct data_t{ XImage *ximg; }; /* Globals */ static Display *display; static int screen; static Window window; static GC gc; static int xfd; static int xshm = 0; static XShmSegmentInfo *shminfo; static XImage *ximage(struct image *img, unsigned int width, unsigned int height, int fast){ int depth; XImage *ximg = NULL; Visual *vis; depth = DefaultDepth(display, screen); vis = DefaultVisual(display, screen); if(depth >= 24){ if(xshm){ shminfo = malloc(sizeof(XShmSegmentInfo)); if(!(ximg = XShmCreateImage(display, CopyFromParent, depth, ZPixmap, NULL, shminfo, width, height ))){ fprintf(stderr, "XShm problems\n"); exit(1); } if((shminfo->shmid = shmget(IPC_PRIVATE, ximg->bytes_per_line * ximg->height, IPC_CREAT|0777)) == -1){ fprintf(stderr, "XShm problems\n"); exit(1); } if((shminfo->shmaddr = ximg->data = shmat(shminfo->shmid, 0, 0)) == (void *)-1){ fprintf(stderr, "XShm problems\n"); exit(1); } shminfo->readOnly = False; if(!XShmAttach(display, shminfo)){ fprintf(stderr, "XShm problems, falling back to to XImage\n"); xshm = 0; } } if(!xshm){ ximg = XCreateImage(display, CopyFromParent, depth, ZPixmap, 0, NULL, width, height, 32, 0 ); ximg->data = malloc(ximg->bytes_per_line * ximg->height); XInitImage(ximg); } (fast ? nearestscale : scale)(img, ximg->width, ximg->height, ximg->bytes_per_line, ximg->data); }else{ /* TODO other depths */ fprintf(stderr, "This program does not yet support display depths <24.\n"); exit(1); return NULL; } return ximg; } void backend_prepare(struct image *img, unsigned int width, unsigned int height, int fast){ struct data_t *data = img->backend = malloc(sizeof (struct data_t)); if(width * img->bufheight > height * img->bufwidth){ data->ximg = ximage(img, img->bufwidth * height / img->bufheight, height, fast); }else{ data->ximg = ximage(img, width, img->bufheight * width / img->bufwidth, fast); } img->state |= SCALED; if(!fast) img->state |= SLOWSCALED; } void backend_draw(struct image *img, unsigned int width, unsigned int height){ assert(((struct data_t *)img->backend)); assert(((struct data_t *)img->backend)->ximg); XImage *ximg = ((struct data_t *)img->backend)->ximg; XRectangle rects[2]; int yoffset, xoffset; xoffset = (width - ximg->width) / 2; yoffset = (height - ximg->height) / 2; if(xoffset || yoffset){ rects[0].x = rects[0].y = 0; if(xoffset){ rects[0].width = rects[1].width = xoffset; rects[0].height = rects[1].height = height; rects[1].x = xoffset + ximg->width; rects[1].y = 0; rects[1].width = width - rects[1].x; }else if(yoffset){ rects[0].width = rects[1].width = width; rects[0].height = yoffset; rects[1].x = 0; rects[1].y = yoffset + ximg->height; rects[1].height = height - rects[1].y; } XFillRectangles(display, window, gc, rects, 2); } if(xshm){ XShmPutImage(display, window, gc, ximg, 0, 0, xoffset, yoffset, ximg->width, ximg->height, False); }else{ XPutImage(display, window, gc, ximg, 0, 0, xoffset, yoffset, ximg->width, ximg->height); } XFlush(display); } void backend_free(struct image *img){ assert(img); if(img->backend){ XImage *ximg = ((struct data_t *)img->backend)->ximg; if(ximg){ if(xshm){ XShmDetach(display, shminfo); XDestroyImage(ximg); shmdt(shminfo->shmaddr); shmctl(shminfo->shmid, IPC_RMID, 0); free(shminfo); }else{ XDestroyImage(ximg); } } img->backend = NULL; } } void backend_setaspect(unsigned int w, unsigned int h){ XSizeHints *hints = XAllocSizeHints(); //hints->flags = PAspect; hints->flags = 0; hints->min_aspect.x = hints->max_aspect.x = w; hints->min_aspect.y = hints->max_aspect.y = h; XSetWMNormalHints(display, window, hints); XFlush(display); XFree(hints); } /* Alt-F4 silent. Keeps people happy */ static int xquit(Display *d){ (void)d; exit(EXIT_SUCCESS); return 0; } void backend_init(){ display = XOpenDisplay (NULL); xfd = ConnectionNumber(display); assert(display); screen = DefaultScreen(display); window = XCreateWindow(display, DefaultRootWindow(display), 0, 0, 640, 480, 0, DefaultDepth(display, screen), InputOutput, CopyFromParent, 0, NULL); backend_setaspect(1, 1); gc = XCreateGC(display, window, 0, NULL); XMapRaised(display, window); XSelectInput(display, window, StructureNotifyMask | ExposureMask | KeyPressMask); XFlush(display); XSetIOErrorHandler(xquit); XFlush(display); } void handlekeypress(XEvent *event){ KeySym key = XLookupKeysym(&event->xkey, 0); switch(key){ case XK_Escape: case XK_q: key_quit(); break; case XK_Return: key_action(); break; case XK_j: key_next(); break; case XK_k: key_prev(); break; case XK_r: key_reload(); break; } } void handleevent(XEvent *event){ switch(event->type){ case ConfigureNotify: if(width != event->xconfigure.width || height != event->xconfigure.height){ width = event->xconfigure.width; height = event->xconfigure.height; if(curimg){ backend_free(curimg); curimg->state &= ~(DRAWN | SCALED | SLOWSCALED); /* Some window managers need reminding */ backend_setaspect(curimg->bufwidth, curimg->bufheight); } } break; case Expose: if(curimg){ curimg->state &= ~DRAWN; } break; case KeyPress: handlekeypress(event); break; } } void backend_run(){ fd_set fds; struct timeval tv0 = {0, 0}; struct timeval *tv = &tv0; for(;;){ int maxfd = setup_fds(&fds); FD_SET(xfd, &fds); if(xfd > maxfd) maxfd = xfd; int ret = select(maxfd+1, &fds, NULL, NULL, tv); if(ret == -1){ perror("select failed\n"); } if(process_fds(&fds)){ tv = &tv0; } if(XPending(display)){ tv = &tv0; XEvent event; do{ XNextEvent(display, &event); handleevent(&event); }while(XPending(display)); }else if(ret == 0){ if(!process_idle()){ /* If we get here everything has been drawn in full or we need more input to continue */ tv = NULL; } } } } meh-0.3/test/000075500000000000000000000000001203347577000131125ustar00rootroot00000000000000meh-0.3/test/test.c000064400000000000000000000032011203347577000142310ustar00rootroot00000000000000#include "stdio.h" #include "stdlib.h" #include "sys/time.h" #include "../src/meh.h" #define TESTRUNS 20 #define STARTTEST(name) int test_##name(){ testname = #name; testsrun++; do #define ENDTEST while(0); testspassed++; return 0;} #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++) #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); #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;} char *testname = NULL; int testsrun = 0, testspassed = 0; typedef int (*test_t)(); STARTTEST(scale){ struct image img; img.bufwidth = 1280*2; img.bufheight = 1024*2; img.buf = malloc(img.bufwidth*img.bufheight*4); char *to = malloc(1280*1024*4); STARTTIME{ scale(&img, 1280, 1024, 1280, to); }ENDTIME free(to); free(img.buf); }ENDTEST STARTTEST(nearestscale){ struct image img; img.bufwidth = 1280*2; img.bufheight = 1024*2; img.buf = malloc(img.bufwidth*img.bufheight*4); char *to = malloc(1280*1024*4); STARTTIME{ nearestscale(&img, 1280, 1024, 1280, to); }ENDTIME free(to); free(img.buf); }ENDTEST test_t tests[] = { test_scale, test_nearestscale, NULL }; void summary(){ printf("%i tests run: %i passed %i failed\n", testsrun, testspassed, testsrun - testspassed); } int main(){ test_t *test = tests; for(; *test; test++) (*test)(); summary(); return 0; } meh-0.3/test/test.h000064400000000000000000000000601203347577000142360ustar00rootroot00000000000000 #define STARTTIME #define ENDTIME #define