/* * Image mode for QEmacs. * * Copyright (c) 2002-2003 Fabrice Bellard. * Copyright (c) 2003-2024 Charlie Gordon. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "qe.h" #include "avformat.h" #define SCROLL_MHEIGHT 10 typedef struct ImageBuffer ImageBuffer; typedef struct ImageState ImageState; typedef struct ImageBufferState ImageBufferState; struct ImageBufferState { QEModeData base; ImageBuffer *ib; }; struct ImageBuffer { int pix_fmt; int width; int height; int interleaved; /* just an info to tell if the image is interleaved */ int alpha_info; /* see FF_ALPHA_xxx constants */ AVPicture pict; /* NOTE: data[] fields are temporary */ }; struct ImageState { QEModeData base; ImageBufferState *ibs; QEBitmap *disp_bmp; int x, y; /* position of the center of the image in window */ int w, h; /* displayed size */ int xfactor_num, xfactor_den; int yfactor_num, yfactor_den; /* XXX: should also have a current zone with mark(x/y) and cur(x/y) */ QEColor background_color; /* transparent to display tiles */ }; int qe_bitmap_format_to_pix_fmt(int format) { int dst_pix_fmt; switch (format) { case QEBITMAP_FORMAT_YUV420P: dst_pix_fmt = PIX_FMT_YUV420P; break; case QEBITMAP_FORMAT_RGB555: dst_pix_fmt = PIX_FMT_RGB555; break; default: case QEBITMAP_FORMAT_RGB565: dst_pix_fmt = PIX_FMT_RGB565; break; case QEBITMAP_FORMAT_RGB24: dst_pix_fmt = PIX_FMT_RGB24; break; case QEBITMAP_FORMAT_RGBA32: dst_pix_fmt = PIX_FMT_RGBA32; break; } return dst_pix_fmt; } static void image_callback(EditBuffer *b, void *opaque, int arg, enum LogOperation op, int offset, int size); void draw_alpha_grid(EditState *s, int x1, int y1, int w, int h) { int state, x, y; unsigned int color; for (y = 0; y < h; y += 16) { for (x = 0; x < w; x += 16) { state = (x ^ y) & 16; if (state) color = QERGB(0x94, 0x94, 0x94); else color = QERGB(0x64, 0x64, 0x64); fill_rectangle(s->screen, x1 + x, y1 + y, 16, 16, color); } } } static ImageState *image_get_state(EditState *e, int status) { return qe_get_window_mode_data(e, &image_mode, status); } /* transp: 0x94 and 0x64, 16x16 grid */ static void image_display(EditState *s) { ImageState *is = image_get_state(s, 0); int x, y; if (!is) return; if (s->display_invalid) { if (is->disp_bmp) { x = is->x + (s->width - is->w) / 2; y = is->y + (s->height - is->h) / 2; fill_window_slack(s, x, y, is->w, is->h, QERGB(0x00, 0x00, 0x00)); bmp_draw(s->screen, is->disp_bmp, s->xleft + x, s->ytop + y, is->w, is->h, 0, 0, 0); } s->display_invalid = 0; } } #if 1 static int gcd(int a, int b) { int c; while (1) { c = a % b; if (c == 0) return b; a = b; b = c; } } /* resize current image using the current factors */ static void image_resize(EditState *s) { ImageState *is = image_get_state(s, 1); ImageBuffer *ib; int d, w, h; if (!is) return; ib = is->ibs->ib; /* simplify factors */ d = gcd(is->xfactor_num, is->xfactor_den); is->xfactor_num /= d; is->xfactor_den /= d; d = gcd(is->yfactor_num, is->yfactor_den); is->yfactor_num /= d; is->yfactor_den /= d; w = ((long long)ib->width * (long long)is->xfactor_num) / is->xfactor_den; h = ((long long)ib->height * (long long)is->yfactor_num) / is->yfactor_den; if (w < 1) w = 1; if (h < 1) h = 1; /* if no resize needed, exit */ if (w == is->w && h == is->h) return; edit_invalidate(s, 0); } static void image_normal_size(EditState *s) { ImageState *is = image_get_state(s, 1); if (!is) return; is->xfactor_num = 1; is->xfactor_den = 1; is->yfactor_num = 1; is->yfactor_den = 1; image_resize(s); } /* increase or decrease image size by percent */ static void image_mult_size(EditState *s, int percent) { ImageState *is = image_get_state(s, 1); if (!is) return; is->xfactor_num *= (100 + percent); is->xfactor_den *= 100; is->yfactor_num *= (100 + percent); is->yfactor_den *= 100; image_resize(s); } static void image_set_size(EditState *s, int w, int h) { ImageState *is = image_get_state(s, 1); ImageBuffer *ib; if (!is) return; ib = is->ibs->ib; if (w < 1 || h < 1) { put_error(s, "Invalid image size"); return; } is->xfactor_num = w; is->xfactor_den = ib->width; is->yfactor_num = h; is->yfactor_den = ib->height; image_resize(s); } #endif static int image_mode_probe(ModeDef *mode, ModeProbeData *pd) { AVProbeData avpd; AVImageFormat *fmt; avpd.filename = pd->filename; avpd.buf = pd->buf; avpd.buf_size = pd->buf_size; fmt = av_probe_image_format(&avpd); if (!fmt) return 0; else return 100; } static void image_mode_free(EditBuffer *b, void *state) { ImageBufferState *ibs = state; image_free(&ibs->ib); } /* allocate a new image at the end of the buffer */ static ImageBuffer *image_allocate(int pix_fmt, int width, int height) { unsigned char *ptr; int size; ImageBuffer *ib; ib = qe_mallocz(ImageBuffer); if (!ib) return NULL; size = avpicture_get_size(pix_fmt, width, height); if (size < 0) goto fail; ptr = qe_malloc_array(u8, size); if (!ptr) { fail: qe_free(&ib); return NULL; } avpicture_fill(&ib->pict, ptr, pix_fmt, width, height); ib->pix_fmt = pix_fmt; ib->width = width; ib->height = height; return ib; } static void image_free(ImageBuffer **ibp) { if (*ibp) { qe_free(&(*ibp)->pict.data[0]); qe_free(ibp); } } static int read_image_cb(void *opaque, AVImageInfo *info) { ImageBufferState *ibs = opaque; ImageBuffer *ib; int i; ib = image_allocate(info->pix_fmt, info->width, info->height); if (!ib) return AVERROR_NOMEM; ibs->ib = ib; ib->interleaved = info->interleaved; for (i = 0; i < 4; i++) { info->pict.linesize[i] = ib->pict.linesize[i]; info->pict.data[i] = ib->pict.data[i]; } return 0; } static int image_buffer_load(EditBuffer *b, FILE *f) { ByteIOContext pb1, *pb = &pb1; ImageBufferState *ibs; int ret; ibs = qe_get_buffer_mode_data(b, &image_mode, NULL); if (!ibs) return; /* start loading the image */ ret = url_fopen(pb, b->filename, URL_RDONLY); if (ret < 0) return -1; ret = av_read_image(pb, b->filename, NULL, read_image_cb, ibs); url_fclose(pb); if (ret) { return -1; } else { ImageBuffer *ib = ibs->ib; ib->alpha_info = img_get_alpha_info(&ib->pict, ib->pix_fmt, ib->width, ib->height); return 0; } } static int set_new_image(EditBuffer *b, ImageBuffer *ib) { ImageBufferState *ibs = qe_get_buffer_mode_data(b, &image_mode, NULL); if (!ibs) return -1; image_free(&ibs->ib); ibs->ib = ib; /* XXX: should signal all windows that image changed? */ eb_invalidate_raw_data(b); b->modified = 1; /* not really */ return 0; } static int image_buffer_save(EditBuffer *b, int start, int end, const char *filename) { ByteIOContext pb1, *pb = &pb1; ImageBufferState *ibs = qe_get_buffer_mode_data(b, &image_mode, NULL); ImageBuffer *ib; ImageBuffer *ib1 = NULL; int ret, dst_pix_fmt, loss; AVImageFormat *fmt; AVImageInfo info; if (!ibs || !ibs->ib) return -1; ib = ibs->ib; /* find image format */ fmt = guess_image_format(filename); if (!fmt) return -1; /* find the best image format */ dst_pix_fmt = avcodec_find_best_pix_fmt(fmt->supported_pixel_formats, ib->pix_fmt, ib->alpha_info, &loss); if (dst_pix_fmt < 0) return -1; /* convert to new format if needed */ if (dst_pix_fmt != ib->pix_fmt) { ib1 = image_allocate(dst_pix_fmt, ib->width, ib->height); if (!ib1) return -1; if (img_convert(&ib1->pict, ib1->pix_fmt, &ib->pict, ib->pix_fmt, ib->width, ib->height) < 0) { image_free(&ib1); return -1; } set_new_image(b, ib1); ib = ib1; } /* start saving the image */ ret = url_fopen(pb, filename, URL_WRONLY); if (ret < 0) return -1; memset(&info, 0, sizeof(AVImageInfo)); info.pix_fmt = ib->pix_fmt; info.width = ib->width; info.height = ib->height; info.pict = ib->pict; av_write_image(pb, fmt, &info); url_fclose(pb); return 0; } static void image_buffer_close(EditBuffer *b) { ImageBufferState *ibs = qe_get_buffer_mode_data(b, &image_mode, NULL); if (ibs) { image_free(&ibs->ib); } } static void update_bmp(EditState *s) { ImageState *is = image_get_state(s, 1); ImageBuffer *ib; QEPicture pict; AVPicture avpict; ImageBuffer *ib1; int dst_pix_fmt; int i; if (!is) return; ib = is->ibs->ib; bmp_free(s->screen, &is->disp_bmp); /* combine with the appropriate background if alpha is present */ ib1 = NULL; if (ib->alpha_info) { int x, y, state, linesize; unsigned int r, g, b, a, bg_r, bg_g, bg_b, v; uint8_t *d, *d1; ib1 = image_allocate(PIX_FMT_RGBA32, is->w, is->h); if (!ib1) goto next; img_convert(&ib1->pict, ib1->pix_fmt, &ib->pict, ib->pix_fmt, ib->width, ib->height); linesize = ib1->pict.linesize[0]; d1 = ib1->pict.data[0]; bg_r = (is->background_color >> 16) & 0xff; bg_g = (is->background_color >> 8) & 0xff; bg_b = (is->background_color) & 0xff; for (y = 0; y < is->h; y++) { d = d1; for (x = 0; x < is->w; x++) { if (is->background_color == 0) { state = (x ^ y) & 16; if (state) { bg_r = bg_g = bg_b = 0x94; } else { bg_r = bg_g = bg_b = 0x64; } } v = ((uint32_t *)d)[0]; a = (v >> 24) & 0xff; r = (v >> 16) & 0xff; g = (v >> 8) & 0xff; b = (v) & 0xff; r = (bg_r * (256 - a) + r * a) >> 8; g = (bg_g * (256 - a) + g * a) >> 8; b = (bg_b * (256 - a) + b * a) >> 8; ((uint32_t *)d)[0] = (0xff << 24) | (r << 16) | (g << 8) | b; d += 4; } d1 += linesize; } ib = ib1; next: ; } /* create the displayed bitmap and put the image in it */ is->disp_bmp = bmp_alloc(s->screen, is->w, is->h, 0); bmp_lock(s->screen, is->disp_bmp, &pict, 0, 0, is->w, is->h); for (i = 0; i < 4; i++) { avpict.data[i] = pict.data[i]; avpict.linesize[i] = pict.linesize[i]; } dst_pix_fmt = qe_bitmap_format_to_pix_fmt(is->disp_bmp->format); #if 0 printf("dst=%s src=%s\n", avcodec_get_pix_fmt_name(dst_pix_fmt), avcodec_get_pix_fmt_name(ib->pix_fmt)); #endif if (img_convert(&avpict, dst_pix_fmt, &ib->pict, ib->pix_fmt, ib->width, ib->height) < 0) { printf("Cannot convert from %s to %s\n", avcodec_get_pix_fmt_name(ib->pix_fmt), avcodec_get_pix_fmt_name(dst_pix_fmt)); } bmp_unlock(s->screen, is->disp_bmp); if (ib1) image_free(ib1); edit_invalidate(s, 0); } static int image_mode_init(EditState *s, EditBuffer *b, int flags) { if (s) { if (flags & MODEF_NEWINSTANCE) { ImageState *is = qe_get_window_mode_data(s, &image_mode, 0); ImageBufferState *ibs = qe_get_buffer_mode_data(b, &image_mode, NULL); ImageBuffer *ib; if (!ibs || !is) return -1; ib = qe_mallocz(ImageBuffer); if (!ib) return -1; is->ibs = ibs; ibs->ib = ib; is->w = ib->width; is->h = ib->height; is->xfactor_num = 1; is->xfactor_den = 1; is->yfactor_num = 1; is->yfactor_den = 1; is->background_color = 0; /* display tiles */ } update_bmp(s); eb_add_callback(s->b, image_callback, s, 1); } return 0; } static void update_pos(EditState *s, int dx, int dy) { ImageState *is = image_get_state(s, 1); int delta; if (!is) return; is->x += dx; delta = (s->width - is->w) / 2; if (delta < 0) { if (is->x + delta > 0) is->x = -delta; else if (is->x + delta + is->w < s->width) is->x = s->width - is->w - delta; } else { is->x = 0; } is->y += dy; delta = (s->height - is->h) / 2; if (delta < 0) { if (is->y + delta > 0) is->y = -delta; else if (is->y + delta + is->h < s->height) is->y = s->height - is->h - delta; } else { is->y = 0; } edit_invalidate(s, 0); } static void image_move_left_right(EditState *s, int disp) { int d; /* move 10% */ d = s->width / 10; if (d < 1) d = 1; update_pos(s, -disp * d, 0); } static void image_move_up_down(EditState *s, int disp) { int d; /* move 10% */ d = s->height / 10; if (d < 1) d = 1; update_pos(s, 0, -disp * d); } static void image_scroll_up_down(EditState *s, int dir) { int d; d = SCROLL_MHEIGHT; if (abs(dir) == 2) { /* move 50% */ d = s->height / 2; dir /= 2; } if (d < 1) d = 1; update_pos(s, 0, -dir * d); } static void image_mode_close(EditState *s) { ImageState *is = image_get_state(s, 0); if (!is) return; bmp_free(s->screen, &is->disp_bmp); eb_free_callback(s->b, image_callback, s); } /* when the image is modified, reparse it */ static void image_callback(EditBuffer *b, void *opaque, int arg, enum LogOperation op, int offset, int size) { // EditState *s = opaque; // update_bmp(s); } static int img_rotate(AVPicture *dst, AVPicture *src, int pix_fmt, int w, int h) { int x, y, dlinesize, bpp; uint8_t *d, *d1; const uint8_t *s, *s1; switch (pix_fmt) { case PIX_FMT_GRAY8: case PIX_FMT_PAL8: bpp = 1; break; case PIX_FMT_RGB24: case PIX_FMT_BGR24: bpp = 3; break; case PIX_FMT_RGBA32: bpp = 4; break; default: return -1; } s1 = src->data[0]; dlinesize = dst->linesize[0]; d1 = dst->data[0] + (h - 1) * bpp; for (y = 0; y < h; y++) { s = s1; d = d1; switch (pix_fmt) { case PIX_FMT_PAL8: case PIX_FMT_GRAY8: for (x = 0; x < w; x++) { d[0] = s[0]; s++; d += dlinesize; } break; case PIX_FMT_RGB24: case PIX_FMT_BGR24: for (x = 0; x < w; x++) { d[0] = s[0]; d[1] = s[1]; d[2] = s[2]; s += 3; d += dlinesize; } break; case PIX_FMT_RGBA32: for (x = 0; x < w; x++) { ((uint32_t *)d)[0] = ((uint32_t *)s)[0]; s += 4; d += dlinesize; } break; default: break; } s1 += src->linesize[0]; d1 -= bpp; } if (pix_fmt == PIX_FMT_PAL8) { /* copy palette */ memcpy(dst->data[1], src->data[1], 256 * sizeof(uint32_t)); } return 0; } static void image_rotate(EditState *e) { ImageState *is = image_get_state(e, 1); ImageBuffer *ib; int ret, w, h, pix_fmt; ImageBuffer *ib1; if (!is) return; ib = is->ibs->ib; pix_fmt = ib->pix_fmt; w = ib->width; h = ib->height; ib1 = image_allocate(pix_fmt, h, w); if (!ib1) return; /* need to reget image because buffer address may have changed */ ret = img_rotate(&ib1->pict, &ib->pict, pix_fmt, w, h); if (ret < 0) { /* remove temporary image */ image_free(ib1); put_error(e, "Format '%s' not supported yet in rotate", avcodec_get_pix_fmt_name(pix_fmt)); return; } ib1->alpha_info = ib->alpha_info; set_new_image(e->b, ib1); /* temporary */ is->w = h; is->h = w; /* suppress that and use callback */ update_bmp(e); } static void image_set_background_color(EditState *e, const char *color_str) { ImageState *is = image_get_state(s, 0); if (!is) return; css_get_color(&is->background_color, color_str); update_bmp(e); } static void image_convert(EditState *e, const char *pix_fmt_str) { ImageState *is = image_get_state(s, 0); ImageBuffer *ib; int ret, new_pix_fmt, i, loss; ImageBuffer *ib1; const char *name; if (!is) return; ib = is->ibs->ib; for (i = 0; i < PIX_FMT_NB; i++) { name = avcodec_get_pix_fmt_name(i); if (strequal(name, pix_fmt_str)) goto found; } put_error(e, "Unknown pixel format"); return; found: new_pix_fmt = i; ib1 = image_allocate(new_pix_fmt, ib->width, ib->height); if (!ib1) return; ret = img_convert(&ib1->pict, ib1->pix_fmt, &ib->pict, ib->pix_fmt, ib->width, ib->height); if (ret < 0) { /* remove temporary image */ image_free(ib1); put_error(e, "Conversion from '%s' to '%s' not supported yet", avcodec_get_pix_fmt_name(ib->pix_fmt), avcodec_get_pix_fmt_name(new_pix_fmt)); return; } else { char buf[128]; loss = avcodec_get_pix_fmt_loss(new_pix_fmt, ib->pix_fmt, ib->alpha_info); if (loss != 0) { buf[0] = '\0'; if (loss & FF_LOSS_RESOLUTION) pstrcat(buf, sizeof(buf), " res"); if (loss & FF_LOSS_DEPTH) pstrcat(buf, sizeof(buf), " depth"); if (loss & FF_LOSS_COLORSPACE) pstrcat(buf, sizeof(buf), " colorspace"); if (loss & FF_LOSS_ALPHA) pstrcat(buf, sizeof(buf), " alpha"); if (loss & FF_LOSS_COLORQUANT) pstrcat(buf, sizeof(buf), " colorquant"); if (loss & FF_LOSS_CHROMA) pstrcat(buf, sizeof(buf), " chroma"); put_status(e, "Warning: data loss:%s", buf); } } ib1->alpha_info = img_get_alpha_info(&ib1->pict, ib1->pix_fmt, ib1->width, ib1->height); set_new_image(e->b, ib1); /* suppress that and use callback */ update_bmp(e); } void image_mode_line(EditState *s, buf_t *out) { EditBuffer *b = s->b; ImageState *is = image_get_state(s, 0); ImageBuffer *ib; char alpha_mode; if (!is) return; ib = is->ib; basic_mode_line(s, out, '-'); if (ib->alpha_info & FF_ALPHA_SEMI_TRANSP) alpha_mode = 'A'; else if (ib->alpha_info & FF_ALPHA_TRANSP) alpha_mode = 'T'; else alpha_mode = ' '; buf_printf(out, "--%dx%d %s %c%c", ib->width, ib->height, avcodec_get_pix_fmt_name(ib->pix_fmt), alpha_mode, ib->interleaved ? 'I' : ' '); } static void pixel_format_complete(CompleteState *cp, CompleteFunc enumerate) { int i; const char *name; for (i = 0; i < PIX_FMT_NB; i++) { name = avcodec_get_pix_fmt_name(i); (*enumerate)(cp, name, CT_IGLOB); } } /* specific image commands */ static const CmdDef image_commands[] = { CMD0( "image-rotate", "t", "", image_rotate) CMD2( "image-convert", "c", "", image_convert, ESs, "s{New pixel format: }[pixel-format]|pixel-format|") CMD2( "image-set-background-color", "b", "", image_set_background_color, ESs, "s{Background color (use 'transparent' for tiling): }") #if 1 CMD0( "image-normal-size", "n", "", image_normal_size) CMD1( "image-double-size", ">", "", image_mult_size, 100) CMD1( "image-halve-size", "<", "", image_mult_size, -50) CMD1( "image-larger-10", ".", "", image_mult_size, 10) CMD1( "image-smaller-10", ",", "", image_mult_size, -10) CMD2( "image-set-display-size", "S", "", image_set_size, ESii, "n{Displayed width: }" "n{Displayed height: }") #endif }; static EditBufferDataType image_data_type = { "image", image_buffer_load, image_buffer_save, image_buffer_close, }; /* additional mode specific bindings */ static const char * const image_bindings[] = { "f", "toggle-full-screen", NULL }; static ModeDef image_mode = { .name = "image", .buffer_instance_size = sizeof(ImageBufferState), .window_instance_size = sizeof(ImageState), .mode_probe = image_mode_probe, .mode_init = image_mode_init, .mode_close = image_mode_close, .mode_free = image_mode_free, .display = image_display, .move_up_down = image_move_up_down, .move_left_right = image_move_left_right, .scroll_up_down = image_scroll_up_down, .data_type = &image_data_type, .get_mode_line = image_mode_line, .bindings = image_bindings, }; static CompletionDef pixel_format_completion = { "pixel-format", pixel_format_complete }; static int image_init(QEmacsState *qs) { av_register_all(); qe_register_data_type(qs, &image_data_type); qe_register_mode(qs, &image_mode, MODEF_DATATYPE | MODEF_VIEW); qe_register_commands(qs, &image_mode, image_commands, countof(image_commands)); qe_register_completion(qs, &pixel_format_completion); return 0; } qe_module_init(image_init);