]> git.neil.brown.name Git - edlib.git/commitdiff
ncurses: display low-res images
authorNeilBrown <neil@brown.name>
Sun, 7 May 2023 06:11:19 +0000 (16:11 +1000)
committerNeilBrown <neil@brown.name>
Sat, 20 May 2023 00:10:49 +0000 (10:10 +1000)
Each character cell can be 2 pixel.  This allows low-res image display.

Signed-off-by: NeilBrown <neil@brown.name>
DOC/TODO.md
Makefile
display-ncurses.c
tests.d/02-presenter

index 7c1afa4d08dbb50ef16f7abcd58575c8b81c9978..8f8ccc86c105d2e01833fe48f81d6734da3d5e69 100644 (file)
@@ -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.
index b5df98c3ff07a908eab87866be597ba8c914f517..a13c349d9475535954c9ebff7fd2964a4738a272 100644 (file)
--- 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
index d5c33368a2cde8741c230b85b13172439380c6a0..f7b85ec457de658adfa56bd1dcf9f24ab908568f 100644 (file)
 #include <ctype.h>
 #include <signal.h>
 #include <sys/ioctl.h>
-#include <term.h>
 #include <netdb.h>
 
+#include <wand/MagickWand.h>
+#ifdef __CHECKER__
+// enums confuse sparse...
+#define MagickBooleanType int
+#endif
+
+#include <term.h>
+
 #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);
index 20392df8902fd4fce1d9ff37a8daf39dfe7fa523..b6a85ae46ef45759cd1da5ac0222ade9200a32b2 100644 (file)
@@ -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->"