M config.mk => config.mk +4 -1
@@ 33,9 33,12 @@ XRENDER = -lXrender
#PANGOINC = `pkg-config --cflags xft pango pangoxft`
#PANGOLIB = `pkg-config --libs xft pango pangoxft`
+# PNG images patch / PNG_IMAGES_PATCH
+SPNGLIBS = -lspng
+
# includes and libs
INCS = -I$(X11INC) -I$(FREETYPEINC) $(PANGOINC) $(BIDIINC)
-LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) -lm $(XRENDER) $(PANGOLIB) $(BIDILIBS)
+LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) -lm $(XRENDER) $(PANGOLIB) $(BIDILIBS) $(SPNGLIBS)
# flags
CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS) $(EXTRAFLAGS)
M => +114 -7
@@ 126,6 126,9 @@ static int inputw = 0, promptw;
static int passwd = 0;
#endif // PASSWORD_PATCH
static int lrpad; /* sum of left and right padding */
#if PNG_IMAGES_PATCH
static int tbpad; /* sum of top and bottom padding for images */
#endif // PNG_IMAGES_PATCH
#if BARPADDING_PATCH
static int vp; /* vertical padding for bar */
static int sp; /* side padding for bar */
@@ 138,6 141,10 @@ static struct item *items = NULL;
static struct item *matches, *matchend;
static struct item *prev, *curr, *next, *sel;
static int mon = -1, screen;
#if PNG_IMAGES_PATCH
static char *image_prefix = "PNG_IMAGE:";
static int image_size = -1; /* in pixels */
#endif // PNG_IMAGES_PATCH
#if PRINTINDEX_PATCH
static int print_index = 0;
#endif // PRINTINDEX_PATCH
@@ 185,12 192,34 @@ static Clr *scheme[SchemeLast];
#include "config.h"
static unsigned int
textw_clamp(const char *str, unsigned int n)
textw_clamp(const char *str, unsigned int n
#if PNG_IMAGES_PATCH
, unsigned int maxw, unsigned int maxh
#endif // PNG_IMAGES_PATCH
)
{
unsigned int w = drw_fontset_getwidth_clamp(drw, str, n) + lrpad;
unsigned int w;
#if PNG_IMAGES_PATCH
if (startswith(image_prefix, str) &&
(w = drw_getimagewidth_clamp(drw, str + strlen(image_prefix), maxw, maxh)))
return MIN(w + lrpad, n);
#endif // PNG_IMAGES_PATCH
w = drw_fontset_getwidth_clamp(drw, str, n) + lrpad;
return MIN(w, n);
}
#if PNG_IMAGES_PATCH
static unsigned int
texth_clamp(const char *str, unsigned int n, unsigned int maxw, unsigned int maxh)
{
unsigned int h;
if (startswith(image_prefix, str) &&
(h = drw_getimageheight_clamp(drw, str + strlen(image_prefix), maxw, maxh)))
return MIN(h + tbpad, n);
return MIN((unsigned int)bh, n);
}
#endif // PNG_IMAGES_PATCH
static void appenditem(struct item *item, struct item **list, struct item **last);
static void calcoffsets(void);
static void cleanup(void);
@@ 245,14 274,16 @@ calcoffsets(void)
int i, n, rpad = 0;
if (lines > 0) {
#if GRID_PATCH
#if PNG_IMAGES_PATCH
n = mh - bh;
#elif GRID_PATCH
if (columns)
n = lines * columns * bh;
else
n = lines * bh;
#else
n = lines * bh;
#endif // GRID_PATCH
#endif // PNG_IMAGES_PATCH / GRID_PATCH
} else {
#if NUMBERS_PATCH
rpad = TEXTW(numbers);
@@ 265,10 296,22 @@ calcoffsets(void)
}
/* calculate which items will begin the next page and previous page */
for (i = 0, next = curr; next; next = next->right)
#if PNG_IMAGES_PATCH
if ((i += (lines > 0)
? texth_clamp(next->text, n, mw - lrpad, image_size)
: textw_clamp(next->text, n, image_size, bh)) > n)
#else
if ((i += (lines > 0) ? bh : textw_clamp(next->text, n)) > n)
#endif // PNG_IMAGES_PATCH
break;
for (i = 0, prev = curr; prev && prev->left; prev = prev->left)
#if PNG_IMAGES_PATCH
if ((i += (lines > 0)
? texth_clamp(prev->left->text, n, mw - lrpad, image_size)
: textw_clamp(prev->left->text, n, image_size, bh)) > n)
#else
if ((i += (lines > 0) ? bh : textw_clamp(prev->left->text, n)) > n)
#endif // PNG_IMAGES_PATCH
break;
}
@@ 471,6 514,23 @@ drawitem(struct item *item, int x, int y, int w)
else
drw_setscheme(drw, scheme[SchemeNorm]);
#if PNG_IMAGES_PATCH
{
int vertical = lines > 0;
if (startswith(image_prefix, text)) {
char *path = text + strlen(image_prefix);
unsigned int image_width = vertical ? w - lrpad : image_size;
unsigned int image_height = vertical ? image_size : bh;
drw_image(drw, &x, &y, &image_width, &image_height,
lrpad, vertical ? tbpad : 0, path, vertical);
if (image_width && image_height) {
r = vertical ? y : x;
goto done_drawitem;
}
}
}
#endif // PNG_IMAGES_PATCH
r = drw_text(drw
#if EMOJI_HIGHLIGHT_PATCH
, x + ((iscomment == 6) ? temppadding : 0)
@@ 502,6 562,9 @@ drawitem(struct item *item, int x, int y, int w)
drawhighlights(item, x, y, w);
#endif // EMOJI_HIGHLIGHT_PATCH
#endif // HIGHLIGHT_PATCH
#if PNG_IMAGES_PATCH
done_drawitem:
#endif // PNG_IMAGES_PATCH
return r;
}
@@ 717,6 780,19 @@ drawmenu(void)
#else
/* draw vertical list */
#if PNG_IMAGES_PATCH
y = bh;
for (item = curr; item != next; item = item->right) {
#if DYNAMIC_HEIGHT_PATCH
i++;
#endif // DYNAMIC_HEIGHT_PATCH
#if VERTFULL_PATCH
y = drawitem(item, 0, y, mw);
#else
y = drawitem(item, x, y, mw - x);
#endif // VERTFULL_PATCH
}
#else
for (item = curr; item != next; item = item->right) {
#if DYNAMIC_HEIGHT_PATCH
i++;
@@ 727,6 803,7 @@ drawmenu(void)
drawitem(item, x, y += bh, mw - x);
#endif // VERTFULL_PATCH
}
#endif // PNG_IMAGES_PATCH
#if DYNAMIC_HEIGHT_PATCH
XResizeWindow(dpy, win, mw, (i + 1) * bh);
#endif // DYNAMIC_HEIGHT_PATCH
@@ 759,9 836,17 @@ drawmenu(void)
stw = TEXTW(">");
#endif // SYMBOLS_PATCH
#if TSV_PATCH && !SEPARATOR_PATCH
itw = textw_clamp(item->stext, mw - x - stw - rpad);
itw = textw_clamp(item->stext, mw - x - stw - rpad
#if PNG_IMAGES_PATCH
, image_size, bh
#endif // PNG_IMAGES_PATCH
);
#else
itw = textw_clamp(item->text, mw - x - stw - rpad);
itw = textw_clamp(item->text, mw - x - stw - rpad
#if PNG_IMAGES_PATCH
, image_size, bh
#endif // PNG_IMAGES_PATCH
);
#endif // TSV_PATCH
x = drawitem(item, x, 0, itw);
}
@@ 1769,7 1854,13 @@ setup(void)
bh = MAX(bh,lineheight); /* make a menu line AT LEAST 'lineheight' tall */
#endif // LINE_HEIGHT_PATCH
lines = MAX(lines, 0);
#if PNG_IMAGES_PATCH
if (image_size < 0)
image_size = (lines > 0) ? 2 * bh : 8 * bh;
mh = bh + ((lines > 0) ? MAX(lines * bh, image_size) : 0);
#else
mh = (lines + 1) * bh;
#endif // PNG_IMAGES_PATCH
#if CENTER_PATCH && PANGO_PATCH
promptw = (prompt && *prompt) ? TEXTWM(prompt) - lrpad / 4 : 0;
#elif CENTER_PATCH
@@ 1880,7 1971,11 @@ setup(void)
if (numwidthchecks || minstrlen < curstrlen) {
numwidthchecks = MAX(numwidthchecks - 1, 0);
minstrlen = MAX(minstrlen, curstrlen);
if ((tmp = textw_clamp(item->text, mw/3)) > inputw) {
if ((tmp = textw_clamp(item->text, mw/3
#if PNG_IMAGES_PATCH
, image_size, bh
#endif // PNG_IMAGES_PATCH
)) > inputw) {
inputw = tmp;
if (tmp == mw/3)
break;
@@ 2085,6 2180,9 @@ usage(void)
#if SEPARATOR_PATCH
"\n [-d separator] [-D separator]"
#endif // SEPARATOR_PATCH
#if PNG_IMAGES_PATCH
"\n [-ip image_prefix] [-is image_size]"
#endif // PNG_IMAGES_PATCH
"\n");
}
@@ 2351,6 2449,12 @@ main(int argc, char *argv[])
insert(text, strlen(text));
}
#endif // INITIALTEXT_PATCH
#if PNG_IMAGES_PATCH
else if (!strcmp(argv[i], "-ip")) /* image prefix */
image_prefix = argv[++i];
else if (!strcmp(argv[i], "-is")) /* max. image preview size (height or width) */
image_size = atoi(argv[++i]);
#endif // PNG_IMAGES_PATCH
else {
usage();
}
@@ 2402,6 2506,9 @@ main(int argc, char *argv[])
#else
lrpad = drw->fonts->h;
#endif // PANGO_PATCH
#if PNG_IMAGES_PATCH
tbpad = lrpad / 2;
#endif // PNG_IMAGES_PATCH
#if BARPADDING_PATCH
sp = sidepad;
M drw.c => drw.c +202 -0
@@ 6,6 6,11 @@
#include <X11/Xft/Xft.h>
#include "patches.h"
+
+#if PNG_IMAGES_PATCH
+#include <spng.h>
+#endif // PNG_IMAGES_PATCH
+
#include "drw.h"
#include "util.h"
@@ 647,6 652,203 @@ no_match:
}
#endif // PANGO_PATCH
+#if PNG_IMAGES_PATCH
+struct image_item {
+ const char *path;
+ int width;
+ int height;
+ char *buf;
+ Pixmap pixmap;
+ struct image_item *next;
+};
+
+static struct image_item *images = NULL;
+
+static struct image_item *
+load_image(Drw *drw, unsigned int maxw, unsigned int maxh, const char *path)
+{
+ FILE *png;
+ spng_ctx *ctx = NULL;
+ int ret = 0;
+ struct spng_ihdr ihdr;
+ struct spng_plte plte = {0};
+ struct spng_row_info row_info = {0};
+ char *spng_buf;
+ int fmt = SPNG_FMT_RGBA8;
+ int crop_width;
+ int crop_height;
+
+ struct image_item *image = ecalloc(1, sizeof(struct image_item));
+ image->path = path;
+ image->next = images;
+ images = image;
+
+ png = fopen(path, "rb");
+ if (png == NULL) {
+ fprintf(stderr, "error opening input file %s\n", path);
+ return NULL;
+ }
+
+ ctx = spng_ctx_new(0);
+ if (ctx == NULL) {
+ fprintf(stderr, "%s: spng_ctx_new() failed\n", path);
+ fclose(png);
+ return NULL;
+ }
+
+ spng_set_crc_action(ctx, SPNG_CRC_USE, SPNG_CRC_USE);
+
+ size_t limit = 1024 * 1024 * 64;
+ spng_set_chunk_limits(ctx, limit, limit);
+
+ spng_set_png_file(ctx, png);
+
+ ret = spng_get_ihdr(ctx, &ihdr);
+ if (ret) {
+ fprintf(stderr, "%s: spng_get_ihdr() error: %s\n", path, spng_strerror(ret));
+ spng_ctx_free(ctx);
+ fclose(png);
+ return NULL;
+ }
+
+ ret = spng_get_plte(ctx, &plte);
+ if (ret && ret != SPNG_ECHUNKAVAIL) {
+ fprintf(stderr, "%s: spng_get_plte() error: %s\n", path, spng_strerror(ret));
+ spng_ctx_free(ctx);
+ fclose(png);
+ return NULL;
+ }
+
+ size_t image_size, bytes_per_row;
+
+ ret = spng_decoded_image_size(ctx, fmt, &image_size);
+ if (ret) {
+ spng_ctx_free(ctx);
+ fclose(png);
+ return NULL;
+ }
+
+ spng_buf = malloc(image_size);
+ if (!spng_buf) {
+ spng_ctx_free(ctx);
+ fclose(png);
+ return NULL;
+ }
+
+ ret = spng_decode_image(ctx, NULL, 0, fmt, SPNG_DECODE_PROGRESSIVE);
+ if (ret) {
+ fprintf(stderr, "%s: progressive spng_decode_image() error: %s\n",
+ path, spng_strerror(ret));
+ free(spng_buf);
+ spng_ctx_free(ctx);
+ fclose(png);
+ return NULL;
+ }
+
+ bytes_per_row = image_size / ihdr.height;
+ crop_width = MIN(ihdr.width, maxw);
+ crop_height = MIN(ihdr.height, maxh);
+
+ do {
+ ret = spng_get_row_info(ctx, &row_info);
+ if (ret)
+ break;
+ ret = spng_decode_row(ctx, spng_buf + row_info.row_num * bytes_per_row, bytes_per_row);
+ } while (!ret && row_info.row_num < (unsigned int)crop_height);
+
+ if (ret != SPNG_EOI && row_info.row_num < (unsigned int)crop_height)
+ fprintf(stderr, "%s: progressive decode error: %s\n", path, spng_strerror(ret));
+
+ image->buf = calloc(ihdr.width * crop_height * 4, sizeof(char));
+ for (unsigned int i = 0; i < ihdr.width * (unsigned int)crop_height; i++) {
+ /* RGBA to BGRA */
+ image->buf[i*4+2] = spng_buf[i*4+0];
+ image->buf[i*4+1] = spng_buf[i*4+1];
+ image->buf[i*4+0] = spng_buf[i*4+2];
+ image->buf[i*4+3] = spng_buf[i*4+3];
+ }
+ image->width = crop_width;
+ image->height = crop_height;
+
+ XImage *img = XCreateImage(drw->dpy, CopyFromParent, DefaultDepth(drw->dpy, drw->screen),
+ ZPixmap, 0, image->buf, ihdr.width, crop_height, 32, 0);
+ image->pixmap = XCreatePixmap(drw->dpy, drw->root, crop_width, crop_height, 24);
+ XPutImage(drw->dpy, image->pixmap, drw->gc, img, 0, 0, 0, 0, crop_width, crop_height);
+ free(spng_buf);
+ spng_ctx_free(ctx);
+ fclose(png);
+ return image;
+}
+
+void
+drw_image(Drw *drw, int *x, int *y, unsigned int *w, unsigned int *h,
+ unsigned int lrpad, unsigned int tbpad, const char *path, int vertical)
+{
+ struct image_item *image = NULL;
+ int render = *x || *y;
+ int crop_width, crop_height;
+
+ for (struct image_item *item = images; item != NULL; item = item->next) {
+ if (!strcmp(item->path, path)) {
+ image = item;
+ if (!image->buf)
+ goto file_error;
+ break;
+ }
+ }
+
+ if (!image && !(image = load_image(drw, *w, *h, path)))
+ goto file_error;
+
+ if (!render) {
+ *w = image->width;
+ *h = image->height;
+ return;
+ }
+
+ crop_width = MIN(image->width, (int)*w);
+ crop_height = MIN(image->height, (int)*h);
+ if (vertical)
+ *h = crop_height;
+ else
+ *w = crop_width;
+
+ XSetForeground(drw->dpy, drw->gc, drw->scheme[ColBg].pixel);
+ XFillRectangle(drw->dpy, drw->drawable, drw->gc, *x, *y, *w + lrpad, *h + tbpad);
+ XCopyArea(drw->dpy, image->pixmap, drw->drawable, drw->gc, 0, 0,
+ crop_width, crop_height, *x + lrpad/2, *y + tbpad/2);
+
+ if (vertical)
+ *y += *h + tbpad;
+ else
+ *x += *w + lrpad;
+ return;
+
+file_error:
+ *w = *h = 0;
+}
+
+unsigned int
+drw_getimagewidth_clamp(Drw *drw, const char *path, unsigned int maxw, unsigned int maxh)
+{
+ int x = 0, y = 0;
+ unsigned int w = maxw, h = maxh;
+ if (drw && path && maxw && maxh)
+ drw_image(drw, &x, &y, &w, &h, 0, 0, path, 0);
+ return MIN(maxw, w);
+}
+
+unsigned int
+drw_getimageheight_clamp(Drw *drw, const char *path, unsigned int maxw, unsigned int maxh)
+{
+ int x = 0, y = 0;
+ unsigned int w = maxw, h = maxh;
+ if (drw && path && maxw && maxh)
+ drw_image(drw, &x, &y, &w, &h, 0, 0, path, 1);
+ return MIN(maxh, h);
+}
+#endif // PNG_IMAGES_PATCH
+
void
drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h)
{
M drw.h => drw.h +7 -0
@@ 101,6 101,13 @@ int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned in
int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert);
#endif // PANGO_PATCH
+#if PNG_IMAGES_PATCH
+/* Image abstraction */
+unsigned int drw_getimagewidth_clamp(Drw *drw, const char *path, unsigned int maxw, unsigned int maxh);
+unsigned int drw_getimageheight_clamp(Drw *drw, const char *path, unsigned int maxw, unsigned int maxh);
+void drw_image(Drw *drw, int *x, int *y, unsigned int *w, unsigned int *h, unsigned int lrpad, unsigned int tbpad, const char *path, int vertical);
+#endif // PNG_IMAGES_PATCH
+
/* Map functions */
void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h);
M patches.h => patches.h +3 -0
@@ 179,3 179,6 @@
/* Position and width options. */
#define XYW_PATCH 0
+
+/* PNG image preview support using libspng. */
+#define PNG_IMAGES_PATCH 1
M util.c => util.c +9 -0
@@ 4,6 4,7 @@
#include <stdlib.h>
#include <string.h>
+#include "patches.h"
#include "util.h"
void
@@ 34,3 35,11 @@ ecalloc(size_t nmemb, size_t size)
die("calloc:");
return p;
}
+
+#if PNG_IMAGES_PATCH
+int
+startswith(const char *prefix, const char *str)
+{
+ return strncmp(prefix, str, strlen(prefix)) == 0;
+}
+#endif // PNG_IMAGES_PATCH
M util.h => util.h +3 -0
@@ 11,3 11,6 @@
void die(const char *fmt, ...);
void *ecalloc(size_t nmemb, size_t size);
+#if PNG_IMAGES_PATCH
+int startswith(const char *prefix, const char *str);
+#endif // PNG_IMAGES_PATCH