From 137c4a3bbaba1eb093782226fdfaad39ddef1de0 Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Sun, 7 May 2023 16:11:19 +1000 Subject: [PATCH] ncurses: display low-res images Each character cell can be 2 pixel. This allows low-res image display. Signed-off-by: NeilBrown --- DOC/TODO.md | 7 +- Makefile | 4 +- display-ncurses.c | 203 ++++++++++++++++++++++++++++++++++++++++++- tests.d/02-presenter | 64 +++++++------- 4 files changed, 241 insertions(+), 37 deletions(-) diff --git a/DOC/TODO.md b/DOC/TODO.md index 7c1afa4d..8f8ccc86 100644 --- a/DOC/TODO.md +++ b/DOC/TODO.md @@ -52,6 +52,9 @@ Current priorities Bugs to be fixed ---------------- +- [ ] renderline *knows* about scaling and when it places the cursor + in an image, it gets it wrong for ncurses. It should ask about + scaling. - [ ] notmuch shouldn't clear tag:new until *all* views on search have closed. - [X] I think attaching a path starting ~/ to an email fails. @@ -391,7 +394,7 @@ Module features - [ ] add full list of colour names (to lib-colourmap) - [ ] allow a pane to require 'true-colour' and discover number of colours available Colour map gets changed when it becomes the focus. -- [ ] merge 'catpic' code to draw low-res images. +- [X] merge 'catpic' code to draw low-res images. - [ ] When only 16 colours, maybe add underline when insufficient contrast available. - [ ] automatically ensure the fg colour contrasts with bg, unless explicitly disabled. If bg is bright, reduce fg brightness. If bg is dark, reduce saturation. @@ -704,7 +707,7 @@ Module features - [ ] detect and hide cited text - [ ] maybe detect "-----Original Message-----" as indicating cited text - [ ] Make long to/cc headers truncate unless selected. -- [ ] display image on ncurses. +- [X] display image on ncurses. - [ ] Make addresses active (menu?) to allow adding to a saved search with options and/or/andnot. Also "mail to" or "save".. - [ ] Allow any selection to be added to a saved search. diff --git a/Makefile b/Makefile index b5df98c3..a13c349d 100644 --- a/Makefile +++ b/Makefile @@ -97,8 +97,8 @@ pypkg=$(shell pkg-config --atleast-version=3.8 python3 && echo python3-embed || LIBS-lang-python = $(shell pkg-config --libs $(pypkg)) INC-lang-python = $(shell pkg-config --cflags $(pypkg)) -LIBS-display-ncurses = $(shell pkg-config --libs panelw ncursesw) -INC-display-ncurses = $(shell pkg-config --cflags panelw ncursesw) +LIBS-display-ncurses = $(shell pkg-config --libs panelw ncursesw MagickWand) +INC-display-ncurses = $(shell pkg-config --cflags panelw ncursesw MagickWand) -Wno-strict-prototypes O/display-ncurses.o : md5.h LIBS-lib-aspell = -laspell diff --git a/display-ncurses.c b/display-ncurses.c index d5c33368..f7b85ec4 100644 --- a/display-ncurses.c +++ b/display-ncurses.c @@ -32,9 +32,16 @@ #include #include #include -#include #include +#include +#ifdef __CHECKER__ +// enums confuse sparse... +#define MagickBooleanType int +#endif + +#include + #include "core.h" #ifdef RECORD_REPLAY @@ -955,6 +962,196 @@ DEF_CMD(nc_draw_text) return 1; } +DEF_CMD(nc_draw_image) +{ + /* 'str' identifies the image. Options are: + * file:filename - load file from fs + * comm:command - run command collecting bytes + * 'num' is '16' if image should be stretched to fill pane + * Otherwise it is the 'or' of + * 0,1,2 for left/middle/right in x direction + * 0,4,8 for top/middle/bottom in y direction + * only one of these can be used as image will fill pane + * in other direction. + * If 'x' and 'y' are both positive, draw cursor box at + * p->cx, p->cy of a size so that 'x' will fit across and + * 'y' will fit down. + */ + struct pane *p = ci->home; + struct display_data *dd = p->data; + int x = 0, y = 0; + bool stretch = ci->num & 16; + int pos = ci->num; + int w = ci->focus->w, h = ci->focus->h * 2; + int cx = -1, cy = -1; + MagickBooleanType status; + MagickWand *wd; + unsigned char *buf; + int i, j; + + if (!ci->str) + return Enoarg; + if (strncmp(ci->str, "file:", 5) == 0) { + wd = NewMagickWand(); + status = MagickReadImage(wd, ci->str + 5); + if (status == MagickFalse) { + DestroyMagickWand(wd); + return Efail; + } + } else if (strncmp(ci->str, "comm:", 5) == 0) { + struct call_return cr; + wd = NewMagickWand(); + cr = call_ret(bytes, ci->str+5, ci->focus, 0, NULL, ci->str2); + if (!cr.s) { + DestroyMagickWand(wd); + return Efail; + } + status = MagickReadImageBlob(wd, cr.s, cr.i); + free(cr.s); + if (status == MagickFalse) { + DestroyMagickWand(wd); + return Efail; + } + } else + return Einval; + + MagickAutoOrientImage(wd); + if (!stretch) { + int ih = MagickGetImageHeight(wd); + int iw = MagickGetImageWidth(wd); + + if (iw <= 0 || iw <= 0) { + DestroyMagickWand(wd); + return Efail; + } + if (iw * h > ih * w) { + /* Image is wider than space, use less height */ + ih = ih * w / iw; + switch(pos & (8+4)) { + case 4: /* center */ + y = (h - ih) / 2; break; + case 8: /* bottom */ + y = h - ih; break; + } + /* Keep 'h' even! */ + h = ((ih+1)/2) * 2; + } else { + /* image is too tall, use less width */ + iw = iw * h / ih; + switch (pos & (1+2)) { + case 1: /* center */ + x = (w - iw) / 2; break; + case 2: /* right */ + x = w - iw ; break; + } + w = iw; + } + } + MagickAdaptiveResizeImage(wd, w, h); + buf = malloc(h * w * 4); + MagickExportImagePixels(wd, 0, 0, w, h, "RGBA", CharPixel, buf); + + if (ci->x > 0 && ci->y > 0 && ci->focus->cx >= 0) { + /* We want a cursor */ + cx = x + ci->focus->cx; + cy = y + ci->focus->cy; + } + for (i = 0; i < h; i+= 2) { + static wint_t hilo = 0x2580; /* L'▀' */ + for (j = 0; j < w ; j+= 1) { + unsigned char *p1 = buf + i*w*4 + j*4; + unsigned char *p2 = buf + (i+1)*w*4 + j*4; + int rgb1[3] = { p1[0]*1000/255, p1[1]*1000/255, p1[2]*1000/255 }; + int rgb2[3] = { p2[0]*1000/255, p2[1]*1000/255, p2[2]*1000/255 }; + int fg = find_col(dd, rgb1); + int bg = find_col(dd, rgb2); + + if (p1[3] < 128 || p2[3] < 128) { + /* transparent */ + cchar_t cc; + short f,b; + struct pane *pn2 = ci->focus; + PANEL *pan = pane_panel(pn2, NULL); + + while (!pan && pn2->parent != pn2) { + pn2 = pn2->parent; + pan = pane_panel(pn2, NULL); + } + if (pan) { + wgetbkgrnd(panel_window(pan), &cc); + pair_content(cc.ext_color, &f, &b); + if (p1[3] < 128) + fg = b; + if (p2[3] < 128) + bg = b; + } + } + /* FIXME this doesn't work because + * render-line knows too much and gets it wrong. + */ + if (cx == x+j && cy == y + (i/2)) + ncurses_text(ci->focus, p, 'X', 0, + to_pair(dd, 0, 0), + x+j, y+(i/2), 1); + else + ncurses_text(ci->focus, p, hilo, 0, + to_pair(dd, fg, bg), + x+j, y+(i/2), 0); + + } + } + free(buf); + + DestroyMagickWand(wd); + + pane_damaged(ci->home, DAMAGED_POSTORDER); + + return 1; +} + +DEF_CMD(nc_image_size) +{ + MagickBooleanType status; + MagickWand *wd; + int ih, iw; + + if (!ci->str) + return Enoarg; + if (strncmp(ci->str, "file:", 5) == 0) { + wd = NewMagickWand(); + status = MagickReadImage(wd, ci->str + 5); + if (status == MagickFalse) { + DestroyMagickWand(wd); + return Efail; + } + } else if (strncmp(ci->str, "comm:", 5) == 0) { + struct call_return cr; + wd = NewMagickWand(); + cr = call_ret(bytes, ci->str+5, ci->focus, 0, NULL, ci->str2); + if (!cr.s) { + DestroyMagickWand(wd); + return Efail; + } + status = MagickReadImageBlob(wd, cr.s, cr.i); + free(cr.s); + if (status == MagickFalse) { + DestroyMagickWand(wd); + return Efail; + } + } else + return Einval; + + MagickAutoOrientImage(wd); + ih = MagickGetImageHeight(wd); + iw = MagickGetImageWidth(wd); + + DestroyMagickWand(wd); + comm_call(ci->comm2, "callback:size", ci->focus, + 0, NULL, NULL, 0, NULL, NULL, + iw, ih); + return 1; +} + DEF_CMD(nc_refresh_size) { struct pane *p = ci->home; @@ -1560,6 +1757,10 @@ void edlib_init(struct pane *ed safe) key_add(nc_map, "Draw:clear", &nc_clear); key_add(nc_map, "Draw:text-size", &nc_text_size); key_add(nc_map, "Draw:text", &nc_draw_text); + + key_add(nc_map, "Draw:image", &nc_draw_image); + key_add(nc_map, "Draw:image-size", &nc_image_size); + key_add(nc_map, "Refresh:size", &nc_refresh_size); key_add(nc_map, "Refresh:postorder", &nc_refresh_post); key_add(nc_map, "Paste:get", &nc_get_paste); diff --git a/tests.d/02-presenter b/tests.d/02-presenter index 20392df8..b6a85ae4 100644 --- a/tests.d/02-presenter +++ b/tests.d/02-presenter @@ -128,41 +128,41 @@ Display 80,30 6A0EA60D8101143E81B030368AA1A1C7 41,0 Key ":C-V" Display 80,30 723F0458401FBB2B8EE91FF2E330C364 41,14 Key ":C-V" -Display 80,30 79595F464D732CA0ABB1D5E2A670B03B 41,23 +Display 80,30 3B328DBDA68F45B61927CFF26C04DE2B 41,23 Key ":C-V" Display 80,30 58B83018BB0E7C6BF86FEEA853B7D0E7 41,23 Key ":C-V" -Display 80,30 D99B4726C0BA5CA9CB3F6142A5980DCC 41,19 +Display 80,30 8AF4F30D9AE7F72B6C2CE83062FB8A1B 41,19 Key ":C-V" -Display 80,30 DDB596455E4FB24A6E66846286A718A8 41,25 +Display 80,30 3DC89B674296F8F0EEBA32AF913A0CCB 41,25 Key ":C-V" -Display 80,30 341A976477CC9D0790C8BF0CA9822966 41,19 +Display 80,30 CB4B810BA885BAFC7B6926DE70E4AE0A 41,19 Key ":C-V" -Display 80,30 8AB3BED5D441E01FC81A72999ECBA8C9 41,25 +Display 80,30 E0BC8AEBBCBA9E2DCD119CC0365D32F4 41,25 Key ":C-V" -Display 80,30 4A32411D13D3B26FA19FB50B472C7CAA 41,21 +Display 80,30 BE456097FB843B20B3B7A0440F3A8B67 41,21 Key ":C-V" -Display 80,30 E0B009206406E0F14A8356834AD89824 41,20 +Display 80,30 CD005511873C7F4F5BA64CB38611F7BD 41,20 Key ":C-V" -Display 80,30 9A397612361034D694752C596C3357CB 41,27 +Display 80,30 1D95A578C44DC390B601563886C6A6C4 41,27 Key ":A-v" -Display 80,30 217C027F44331EEB28331150E0CC89BA 41,19 +Display 80,30 D28AEA7D77B6FEEADA52BF66ADB0820D 41,19 Key ":A-v" -Display 80,30 BF728199F869F41990D07497FCF8C8E7 41,4 +Display 80,30 63BCC4810C580652636EEBC1EA9BB70D 41,4 Key ":A-v" -Display 80,30 764F696B3B0F1398A085C42DFCEEB984 41,3 +Display 80,30 DF3166618B75991D58669065A0FC7E6E 41,3 Key ":A-v" -Display 80,30 8B40FCD3A5EAF0746FAB8FA70D61ED15 41,6 +Display 80,30 24988827E9C260FC7732215D39F34703 41,6 Key ":C-X" -Display 80,30 3F367CE9D7CEE7E4C12F5C81CCB7BE21 41,6 +Display 80,30 B98A84BD3FFB4073BEA3FF502ABB2C6D 41,6 Key "-o" -Display 80,30 8B40FCD3A5EAF0746FAB8FA70D61ED15 41,6 +Display 80,30 24988827E9C260FC7732215D39F34703 41,6 Key "- " -Display 80,30 7F1A3C4B9AF526FDF87F0DED4B5D3924 41,6 +Display 80,30 C98F2DAD7602B9373411CAB1180C5F32 41,6 Key "- " Display 80,30 26AAA5FF0FB53CE6F09E28737E89F4CC 41,6 Key ":Backspace" -Display 80,30 DD6D405AEE5B2D4F6503DB02394A317F 41,6 +Display 80,30 C57B318B84914EB7A5AA55B1FCF756DD 41,6 Key ":Backspace" Display 80,30 FDAE252AB12E082CC914F06CBE18846A 41,6 Key ":C-X" @@ -174,33 +174,33 @@ Display 80,30 596A9EBEFD49BEF6BBB115E42E64F9B5 41,0 Key ":C-V" Display 80,30 15A6375488AAAB2A50F6537A57C69FF4 41,14 Key ":C-V" -Display 80,30 23908995811429680EB53B41399FAF7B 41,23 +Display 80,30 141DB06AD827E40CEA0DB459D70F26B1 41,23 Key ":C-S" -Display 80,30 F608E450DDD730F144D1A8D569103A16 70,0 +Display 80,30 B0170AB49201ACFEEBAADB56BD92580C 70,0 Key "-e" -Display 80,30 B039C6296C9EB955D1626797AFD33E1B 71,0 -Display 80,30 E3DA2CD0DC52171F14CDE6020E8DBA4C 71,0 +Display 80,30 2E14A636535200CAA1A824EB3AE7DEB4 71,0 +Display 80,30 9E0F7CEA7310D628BEB25CF97031EA5A 71,0 Key "-m" -Display 80,30 0F2C3D854158665969A211A1098299B2 72,0 +Display 80,30 7BDACD6E9BC1D42692F10A131891B30A 72,0 Key "-p" -Display 80,30 9E79066979B0A8773C848AE5F5D83AA5 73,0 -Display 80,30 5204E6D4AD398441CF51E38B81E9AFD3 73,0 +Display 80,30 4E6FA27F57A29A1EDEE4403C5B20B40E 73,0 +Display 80,30 B66FC1265DE66359945402C362BC6BB8 73,0 Key ":Enter" -Display 80,30 FD4151E497AA682CCEFAC0FBBA4EBF1B 52,13 +Display 80,30 E82BA22E6B4C3593A91600521C28DAF7 52,13 Key ":C-L" -Display 80,30 DC8E40C3AB17F1986F9421F4ED4D8782 52,13 +Display 80,30 0B0303CA8A43537FFC33265CB28BD319 52,13 Key ":C-S" -Display 80,30 82BB743859F3892171E6B939E6FF7411 70,0 +Display 80,30 BC1AFEA3CF728F32D35D4BA2AF7C4FCE 70,0 Key ":C-S" -Display 80,30 009B860F851CC71954093C70D05C2B46 73,0 -Display 80,30 AFDC3770D58F4E93FDA772D309464BE7 73,0 +Display 80,30 E8E27AF5E40D19C8D32D176112DCA3DC 73,0 +Display 80,30 4BFE535B8BDFAA26E4663769EACD822E 73,0 Key ":C-S" -Display 80,30 706B19FCDA756055EF05D9A3AF6FB206 72,0 -Display 80,30 48EC1AE6318A704EA5D4B6FB0013AA2D 72,0 +Display 80,30 48046EAA95F9A3BD2B63B546651EAE24 72,0 +Display 80,30 5CC9B8DB70BF934ED27A97327CBB5171 72,0 Key ":Enter" -Display 80,30 69DB653BD93BAE6093F2BE2C1F2AD1CE 52,21 +Display 80,30 5C36EDC7853F89170F73B5F164533E92 52,21 Key ":C-V" -Display 80,30 825CEC0E5A930BD1BFF9E5ABF1DE92BB 41,25 +Display 80,30 B7E0CD0828B88C82E16A55C7B2BC0F7A 41,25 Key ":C-V" Display 80,30 A2958EB3DA369084F3AA0A8C220474CF 41,20 Key ":A->" -- 2.39.5