From e37953f220347276cf2929578b90984007871ce3 Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Fri, 22 Sep 2023 15:41:44 +1000 Subject: [PATCH] Draw:image: support scale and crop. ->num if non-zero scales the image. ->num2 if non zero sets absolute height and ->num becomes absolute ->width. ->x and ->y set top-left corner. Signed-off-by: NeilBrown --- DOC/Calls | 7 +++++ DOC/Developer/06-rendering.md | 12 ++++----- DOC/TODO.md | 1 + display-ncurses.c | 47 ++++++++++++++++++++++++++++++-- display-x11-xcb.c | 51 ++++++++++++++++++++++++++++++++--- python/display-pygtk.py | 46 ++++++++++++++++++++++++++++--- 6 files changed, 149 insertions(+), 15 deletions(-) diff --git a/DOC/Calls b/DOC/Calls index 30932d52..04350e98 100644 --- a/DOC/Calls +++ b/DOC/Calls @@ -134,6 +134,13 @@ Draw an image on the pane (or ancestor which as been cleared). the purpose of cursor positioning. If these are present and p->cx,cy are not negative, draw a cursor at p->cx,cy highlighting the relevant cell. +- num, num2 if both positive, give width and height to scale to, + over-riding the default scaling. + If only num is positive, then it is a scale factor *1024 + to be applied to the image. +- x,y give a top-left pixel in the scaled image to display. Only + this pixel and those to right and below might be shown. + Negative values allow a margin between pane edge and this image. ## all-displays diff --git a/DOC/Developer/06-rendering.md b/DOC/Developer/06-rendering.md index 92d21175..dcc3f3e0 100644 --- a/DOC/Developer/06-rendering.md +++ b/DOC/Developer/06-rendering.md @@ -25,9 +25,9 @@ given. ### Refresh size -The first step in the sequence ensure all panes have he right size. If +The first step in the sequence ensure all panes have the right size. If one pane changes size for any reason, such as a top level window being -resized, other may need to adjust to this change. A pane that notices +resized, others may need to adjust to this change. A pane that notices its size has changed, or might need to change, sets DAMAGED_SIZE with the pane_damage() interface. @@ -37,11 +37,11 @@ depth of zero will have their size adjusted to match the parent, which will cause DAMAGED_SIZE to be set on them. Any pane with a larger depth will just have DAMAGED_SIZE set. -When pane is thus requested to handle a resize, the sequence starts +When a pane is thus requested to handle a resize, the sequence starts again from the root looking for panes that need to handle a resize. It -should quickly deal with all pane. It finds that it needs to resize more -than 1000 panes, it assumes that some pane keeps setting DAMAGED_SIZE on -itself, and it aborts the loop. +should quickly deal with all pane. If it finds that it needs to resize +more than 1000 panes, it assumes that some pane keeps setting +DAMAGED_SIZE on itself, and it aborts the loop. ### Refresh view diff --git a/DOC/TODO.md b/DOC/TODO.md index 5fd1d681..e7e1a65d 100644 --- a/DOC/TODO.md +++ b/DOC/TODO.md @@ -250,6 +250,7 @@ Module features ### lib-server +- [ ] catch broken-pipe errors when sending to sock - [ ] ctrl-z in elc doesn't ask edlib to release the terminal - [ ] do we need both .term and .disp? When are they different? diff --git a/display-ncurses.c b/display-ncurses.c index a0102d04..69cd0fe7 100644 --- a/display-ncurses.c +++ b/display-ncurses.c @@ -1015,6 +1015,11 @@ DEF_CMD(nc_draw_image) * the purpose of cursor positioning. If these are present and * p->cx,cy are not negative, draw a cursor at p->cx,cy highlighting * the relevant cell. + * + * num,num2, if both positive, override the automatic scaling. + * The image is scaled to this many pixels. + * x,y is top-left pixel in the scaled image to start display at. + * Negative values allow a margin between pane edge and this image. */ struct pane *p = ci->home; struct display_data *dd = p->data; @@ -1022,6 +1027,9 @@ DEF_CMD(nc_draw_image) const char *mode = ci->str2 ?: ""; bool stretch = strchr(mode, 'S'); int w = ci->focus->w, h = ci->focus->h * 2; + int pw = w, ph = h; + int xo = 0, yo = 0; + int cix, ciy; int cx = -1, cy = -1; MagickBooleanType status; MagickWand *wd; @@ -1055,7 +1063,20 @@ DEF_CMD(nc_draw_image) return Einval; MagickAutoOrientImage(wd); - if (!stretch) { + if (ci->num > 0 && ci->num2 > 0) { + w = ci->num; + h = ci->num2; + } else if (ci->num > 0) { + int ih = MagickGetImageHeight(wd); + int iw = MagickGetImageWidth(wd); + + if (iw <= 0 || iw <= 0) { + DestroyMagickWand(wd); + return Efail; + } + w = iw * ci->num / 1024; + h = ih * ci->num / 1024; + } else if (!stretch) { int ih = MagickGetImageHeight(wd); int iw = MagickGetImageWidth(wd); @@ -1086,8 +1107,28 @@ DEF_CMD(nc_draw_image) } } MagickAdaptiveResizeImage(wd, w, h); + cix = ci->x; + ciy = ci->y; + if (cix < 0) { + xo -= cix; + pw += cix; + cix = 0; + } + if (ciy < 0) { + yo -= ciy; + ph += ciy; + ciy = 0; + } + if (w - cix <= pw) + w -= cix; + else + w = pw; + if (h - ciy <= ph) + h -= ciy; + else + h = ph; buf = malloc(h * w * 4); - MagickExportImagePixels(wd, 0, 0, w, h, "RGBA", CharPixel, buf); + MagickExportImagePixels(wd, cix, ciy, w, h, "RGBA", CharPixel, buf); if (ci->focus->cx >= 0 && strchr(mode, ':')) { /* We want a cursor */ @@ -1352,6 +1393,8 @@ static struct pane *ncurses_init(struct pane *ed safe, dd->scr_file = f; dd->is_xterm = (term && strstarts(term, "xterm")); + attr_set_str(&p->attrs, "Display:pixels", "1x2"); + set_screen(p); ncurses_start(p); diff --git a/display-x11-xcb.c b/display-x11-xcb.c index 237cf3b0..e9cb9560 100644 --- a/display-x11-xcb.c +++ b/display-x11-xcb.c @@ -825,13 +825,20 @@ DEF_CMD(xcb_draw_image) * the purpose of cursor positioning. If these are present and * p->cx,cy are not negative, draw a cursor at p->cx,cy highlighting * the relevant cell. + * + * num,num2, if both positive, override the automatic scaling. + * The image is scaled to this many pixels. + * x,y is top-left pixel in the scaled image to start display at. + * Negative values allow a margin between pane edge and this image. */ struct xcb_data *xd = ci->home->data; const char *mode = ci->str2 ?: ""; bool stretch = strchr(mode, 'S'); - int w = ci->focus->w, h = ci->focus->h; + int w, h; int x = 0, y = 0; + int pw, ph; int xo, yo; + int cix, ciy; int stride; struct panes *ps; MagickBooleanType status; @@ -875,7 +882,22 @@ DEF_CMD(xcb_draw_image) return Einval; MagickAutoOrientImage(wd); - if (!stretch) { + w = ci->focus->w; + h = ci->focus->h; + if (ci->num > 0 && ci->num2 > 0) { + w = ci->num; + h = ci->num2; + } else if (ci->num > 0) { + int ih = MagickGetImageHeight(wd); + int iw = MagickGetImageWidth(wd); + + if (iw <= 0 || iw <= 0) { + DestroyMagickWand(wd); + return Efail; + } + w = iw * ci->num / 1024; + h = ih * ci->num / 1024; + } else if (!stretch) { int ih = MagickGetImageHeight(wd); int iw = MagickGetImageWidth(wd); @@ -905,6 +927,28 @@ DEF_CMD(xcb_draw_image) } } MagickAdaptiveResizeImage(wd, w, h); + pw = ci->focus->w; + ph = ci->focus->h; + cix = ci->x; + ciy = ci->y; + if (cix < 0) { + xo -= cix; + pw += cix; + cix = 0; + } + if (ciy < 0) { + yo -= ciy; + ph += ciy; + ciy = 0; + } + if (w - cix <= pw) + w -= cix; + else + w = pw; + if (h - ciy <= ph) + h -= ciy; + else + h = ph; stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, w); buf = malloc(h * stride); // Cairo expects 32bit values with A in the high byte, then RGB. @@ -913,7 +957,8 @@ DEF_CMD(xcb_draw_image) fmt[0] = ('A'<<24) | ('R' << 16) | ('G' << 8) | ('B' << 0); fmt[1] = 0; - MagickExportImagePixels(wd, 0, 0, w, h, (char*)fmt, CharPixel, buf); + MagickExportImagePixels(wd, cix, ciy, w, h, + (char*)fmt, CharPixel, buf); surface = cairo_image_surface_create_for_data(buf, CAIRO_FORMAT_ARGB32, w, h, stride); cairo_set_source_surface(ps->ctx, surface, x + xo, y + yo); diff --git a/python/display-pygtk.py b/python/display-pygtk.py index aa8e5cbd..a9882c3b 100644 --- a/python/display-pygtk.py +++ b/python/display-pygtk.py @@ -276,7 +276,7 @@ class EdDisplay(edlib.Pane): return True - def handle_image(self, key, focus, str1, str2, **a): + def handle_image(self, key, focus, num, num2, str1, str2, xy, **a): "handle:Draw:image" self.damaged(edlib.DAMAGED_POSTORDER) # 'str1' identifies the image. Options are: @@ -297,6 +297,11 @@ class EdDisplay(edlib.Pane): # the purpose of cursor positioning. If these are present and # focus.cx,cy are not negative, draw a cursor at cx,cy highlighting # the relevant cell. + # num,num2, if both positive, override the automatic scaling. + # The image is scaled to this many pixels. + # If num2 <=0, then num is 1024 times a scale factor + # x,y is top-left pixel in the scaled image to start display at. + # Negative values allow a margin between pane edge and this image. if not str1: return edlib.Enoarg mode = str2 if str2 else "" @@ -314,10 +319,17 @@ class EdDisplay(edlib.Pane): return edlib.Einval except: # create a red error image - pb = Gdk.Pixbuf(Gdk.COLORSPACE_RGB, False, 8, w, h) + pb = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, + False, 8, w, h) pb.fill(0xff000000) - if not stretch: + if num > 0 and num2 > 0: + w = num + h = num2 + elif num > 0: + w = pb.get_width() * num / 1024 + h = pb.get_height() * num / 1024 + elif not stretch: if pb.get_width() * h > pb.get_height() * w: # image is wider than space, reduce height h2 = pb.get_height() * w / pb.get_width() @@ -334,9 +346,35 @@ class EdDisplay(edlib.Pane): elif 'L' not in mode: x = (w - w2) / 2 w = w2 - scale = pb.scale_simple(w, h, GdkPixbuf.InterpType.HYPER) pm, xo, yo, pbg = self.find_pixmap(focus, True) + sh = h / pb.get_height() + sw = w / pb.get_width() + + pw = focus.w + ph = focus.h + cix, ciy = xy + if cix < 0: + xo -= cix + pw += cix + cix = 0 + if ciy < 0: + yo -= ciy + ph += ciy + ciy = 0 + if w - cix <= pw: + w -= cix + else: + w = pw + if h - ciy <= ph: + h -= ciy + else: + h = ph + cr = cairo.Context(pm) + scale = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, + False, 8, w, h) + pb.scale(scale, 0,0, w, h, -cix, -ciy, sw, sh, + GdkPixbuf.InterpType.BILINEAR) Gdk.cairo_set_source_pixbuf(cr, scale, x + xo, y + yo) cr.paint() -- 2.39.5