diff --git a/image-sixel.c b/image-sixel.c index e2ab71307..fd25c6e37 100644 --- a/image-sixel.c +++ b/image-sixel.c @@ -485,7 +485,13 @@ sixel_to_image(struct sixel_image *si) } } sixel_size_in_cells(si, &sx, &sy); - im = image_create(si->x, si->y, sx, sy, pixels); + if ((uint64_t)sx * si->xpixel > UINT_MAX || + (uint64_t)sy * si->ypixel > UINT_MAX) { + free(pixels); + return (NULL); + } + im = image_create(si->x, si->y, sx * si->xpixel, sy * si->ypixel, + sx, sy, pixels); if (im == NULL) free(pixels); return (im); @@ -743,12 +749,29 @@ sixel_from_image(struct image *im, u_int ox, u_int oy, u_int cells_x, u_int x, y, sx, sy, sourcex, sourcey; u_int sourcex0, sourcey0, sourcewidth, sourceheight; u_int r, g, b, colour, i; + uint64_t destination_width, destination_height; + uint64_t content_width, content_height, x0, x1, y0, y1; - if ((uint64_t)cells_x * xpixel > UINT_MAX || - (uint64_t)cells_y * ypixel > UINT_MAX) + destination_width = (uint64_t)im->sx * xpixel; + destination_height = (uint64_t)im->sy * ypixel; + if (destination_width > UINT_MAX || destination_height > UINT_MAX) return (NULL); - sx = cells_x * xpixel; - sy = cells_y * ypixel; + content_width = ((uint64_t)im->width * destination_width + + im->canvas_width - 1) / im->canvas_width; + content_height = ((uint64_t)im->height * destination_height + + im->canvas_height - 1) / im->canvas_height; + x0 = (uint64_t)ox * xpixel; + y0 = (uint64_t)oy * ypixel; + x1 = ((uint64_t)ox + cells_x) * xpixel; + y1 = ((uint64_t)oy + cells_y) * ypixel; + if (x1 > content_width) + x1 = content_width; + if (y1 > content_height) + y1 = content_height; + if (x1 <= x0 || y1 <= y0) + return (NULL); + sx = x1 - x0; + sy = y1 - y0; if (sx == 0 || sy == 0 || sx > SIXEL_WIDTH_LIMIT || sy > SIXEL_HEIGHT_LIMIT) return (NULL); diff --git a/image.c b/image.c index eb82a265f..104377b04 100644 --- a/image.c +++ b/image.c @@ -132,14 +132,19 @@ image_sample(struct image *im, uint64_t sample_x, uint64_t sample_y, uint64_t brightness = 0, count = 0; u_int x, y, x0, x1, y0, y1; - x0 = sample_x * im->width / sample_columns; - x1 = (sample_x + 1) * im->width / sample_columns; - y0 = sample_y * im->height / sample_rows; - y1 = (sample_y + 1) * im->height / sample_rows; + x0 = sample_x * im->canvas_width / sample_columns; + x1 = ((sample_x + 1) * im->canvas_width + sample_columns - 1) / + sample_columns; + y0 = sample_y * im->canvas_height / sample_rows; + y1 = ((sample_y + 1) * im->canvas_height + sample_rows - 1) / + sample_rows; if (x1 <= x0) x1 = x0 + 1; if (y1 <= y0) y1 = y0 + 1; + count = (uint64_t)(x1 - x0) * (y1 - y0); + if (x0 >= im->width || y0 >= im->height) + return; if (x1 > im->width) x1 = im->width; if (y1 > im->height) @@ -155,7 +160,6 @@ image_sample(struct image *im, uint64_t sample_x, uint64_t sample_y, brightness += ((2126ULL * pixel[0] + 7152ULL * pixel[1] + 722ULL * pixel[2]) / 10000) * pixel[3] / 255; - count++; } } if (count == 0) @@ -206,11 +210,13 @@ image_find(u_int id) } struct image * -image_create(u_int width, u_int height, u_int sx, u_int sy, u_char *pixels) +image_create(u_int width, u_int height, u_int canvas_width, + u_int canvas_height, u_int sx, u_int sy, u_char *pixels) { struct image *im; - if (width == 0 || height == 0 || sx == 0 || sy == 0 || pixels == NULL) + if (width == 0 || height == 0 || canvas_width < width || + canvas_height < height || sx == 0 || sy == 0 || pixels == NULL) return (NULL); if ((uint64_t)width * height * 4 > SIZE_MAX) return (NULL); @@ -227,6 +233,8 @@ image_create(u_int width, u_int height, u_int sx, u_int sy, u_char *pixels) im->references = 1; im->width = width; im->height = height; + im->canvas_width = canvas_width; + im->canvas_height = canvas_height; im->sx = sx; im->sy = sy; im->stride = (size_t)width * 4; @@ -234,8 +242,9 @@ image_create(u_int width, u_int height, u_int sx, u_int sy, u_char *pixels) im->pixels = pixels; RB_INSERT(images, &images, im); - log_debug("%s: image %u is %ux%u pixels, %ux%u cells", __func__, - im->id, width, height, sx, sy); + log_debug("%s: image %u is %ux%u pixels on %ux%u canvas, " + "%ux%u cells", __func__, im->id, width, height, canvas_width, + canvas_height, sx, sy); return (im); } @@ -305,14 +314,16 @@ image_get_pixel_rectangle(const struct image *im, u_int x, u_int y, if (height > im->sy - y) height = im->sy - y; - *px = (uint64_t)x * im->width / im->sx; - *py = (uint64_t)y * im->height / im->sy; - x1 = (uint64_t)(x + width) * im->width / im->sx; - y1 = (uint64_t)(y + height) * im->height / im->sy; - if (*px >= im->width) - *px = im->width - 1; - if (*py >= im->height) - *py = im->height - 1; + *px = (uint64_t)x * im->canvas_width / im->sx; + *py = (uint64_t)y * im->canvas_height / im->sy; + x1 = ((uint64_t)(x + width) * im->canvas_width + im->sx - 1) / + im->sx; + y1 = ((uint64_t)(y + height) * im->canvas_height + im->sy - 1) / + im->sy; + if (*px >= im->width || *py >= im->height) { + *px = *py = 0; + return; + } if (x1 <= *px) x1 = *px + 1; if (y1 <= *py) diff --git a/regress/image-support.sh b/regress/image-support.sh index 82564ca4d..05eabc128 100755 --- a/regress/image-support.sh +++ b/regress/image-support.sh @@ -69,10 +69,12 @@ $TMUX copy-mode -t:4 || exit 1 $TMUX send-keys -t:4 -X history-top || exit 1 $TMUX capture-pane -pt:4 >$TMP || exit 1 -# A client without an image feature gets the brightness-based ASCII backend. +# A 26-pixel raster occupies two default 16-pixel cells, but the final cell +# remains only partially filled instead of stretching the raster to 32 pixels. +# A client without an image feature exposes this in the sampled text backend. $TMUX kill-server 2>/dev/null $TMUX2 new-session -d -x 10 -y 4 " - printf '\033_Ga=T,q=2,f=32,s=2,v=2,c=2,r=2;/wAA/wD/AP8AAP///////w==\033\\' + printf '\033Pq\"1;1;26;26#0;2;100;100;100#0!26~-!26~-!26~-!26~-!26B\033\\' sleep 10" || exit 1 $TMUX2 set -g status off || exit 1 $TMUX new-session -d -x 10 -y 4 || exit 1 @@ -80,9 +82,8 @@ $TMUX set -g status off || exit 1 $TMUX send-keys -l "$TMUX2 attach-session" || exit 1 $TMUX send-keys Enter || exit 1 sleep 1 -$TMUX capture-pane -pS0 -E1 >$TMP || exit 1 -[ "$(sed -n 1p $TMP)" = ".*" ] || exit 1 -[ "$(sed -n 2p $TMP)" = " @" ] || exit 1 +$TMUX capture-pane -pS0 -E0 >$TMP || exit 1 +grep -q '^#=' $TMP || exit 1 # A selection redraw uses the single-cell path. It must leave ASCII image # cells visible (graphical clients skip these cells to avoid erasing pixels). @@ -92,8 +93,7 @@ $TMUX2 send-keys -X start-of-line || exit 1 $TMUX2 send-keys -X begin-selection || exit 1 $TMUX2 send-keys -X cursor-right || exit 1 sleep 1 -$TMUX capture-pane -pS0 -E1 >$TMP || exit 1 -sed -n 1p $TMP | grep -q '^\.\*' || exit 1 -sed -n 2p $TMP | grep -q '^ @' || exit 1 +$TMUX capture-pane -pS0 -E0 >$TMP || exit 1 +grep -q '^#=' $TMP || exit 1 exit 0 diff --git a/tmux.h b/tmux.h index c553b4feb..ca2b282f1 100644 --- a/tmux.h +++ b/tmux.h @@ -1061,6 +1061,8 @@ struct image { u_int references; u_int width; u_int height; + u_int canvas_width; + u_int canvas_height; u_int sx; u_int sy; size_t stride; @@ -4255,7 +4257,8 @@ char *regsub(const char *, const char *, const char *, int); #ifdef ENABLE_IMAGES /* image.c */ -struct image *image_create(u_int, u_int, u_int, u_int, u_char *); +struct image *image_create(u_int, u_int, u_int, u_int, u_int, u_int, + u_char *); struct image *image_find(u_int); void image_ref(u_int); void image_free(u_int);