/* img.c * Generic image decoding and PNG and JPG decoders. * (c) 2002 Karel 'Clock' Kulhavy * This is a part of the Links program, released under GPL. * Used in graphics mode of Links only TODO: odstranit zbytecne ditherovani z strip_optimized header_dimensions_known, protoze pozadi obrazku musi byt stejne jako pozadi stranky, a to se nikdy neditheruje, protoze je to vzdy jednolita barva. Kdyz uz to nepujde odstranit tak tam aspon dat fixne zaokrouhlovani. TODO: dodelat stripy do jpegu a png a tiff. */ #include "links.h" #ifdef G #define RESTART_SIZE 8192 /* Size of biggest chunk of compressed data that is processed in one run */ static struct g_object_image *global_goi; struct cached_image *global_cimg; int end_callback_hit; static int is_image_size_sane(ssize_t x, ssize_t y) { size_t a; if (x < 0 || y < 0) return 0; if (x >= MAXINT || y >= MAXINT) return 0; a = (size_t)x * (size_t)y * (drv->depth & 7); if (y && (size_t)a / (size_t)y / (drv->depth & 7) != (size_t)x) return 0; return a <= MAX_SIZE_T / 2; } /* mem_free(cimg->decoder) */ static void destroy_decoder (struct cached_image *cimg) { if (cimg->decoder){ switch(cimg->image_type){ case IM_PNG: png_destroy_decoder(cimg); break; #ifdef HAVE_JPEG case IM_JPG: jpeg_destroy_decoder(cimg); break; #endif /* #ifdef HAVE_JPEG */ case IM_GIF: gif_destroy_decoder(cimg); break; case IM_XBM: /* do nothing */ break; #ifdef HAVE_TIFF case IM_TIFF: tiff_destroy_decoder(cimg); break; #endif #ifdef HAVE_SVG case IM_SVG: svg_destroy_decoder(cimg); break; #endif #ifdef HAVE_WEBP case IM_WEBP: webp_destroy_decoder(cimg); break; #endif #ifdef HAVE_AVIF case IM_AVIF: avif_destroy_decoder(cimg); break; #endif } mem_free(cimg->decoder); } } static void mem_free_buffer(struct cached_image *cimg) { mem_free(cimg->buffer); } static void img_destruct_image(struct g_object *object) { struct g_object_image *goi = get_struct(object, struct g_object_image, goti.go); if (goi->orig_src) mem_free(goi->orig_src); if (goi->alt) mem_free(goi->alt); if (goi->name) mem_free(goi->name); if (goi->src) mem_free(goi->src); release_image_map(goi->goti.map); if (goi->list_entry.next) del_from_list(goi); if (goi->goti.go.xw && goi->goti.go.yw) { /* At least one dimension is zero */ deref_cached_image(goi->cimg); } mem_free(goi); } /* Frees all data allocated by cached_image including cached image itself */ void img_destruct_cached_image(struct cached_image *cimg) { switch (cimg->state){ case 0: case 1: case 2: case 3: case 9: case 11: break; case 12: case 14: if (cimg->gamma_table) mem_free(cimg->gamma_table); if (cimg->bmp_used){ drv->unregister_bitmap(&(cimg->bmp)); } if (cimg->strip_optimized){ if (cimg->dregs) mem_free(cimg->dregs); }else{ mem_free_buffer(cimg); } /*-fallthrough*/ case 8: case 10: destroy_decoder(cimg); break; case 13: case 15: drv->unregister_bitmap(&(cimg->bmp)); break; #ifdef DEBUG default: fprintf(stderr, "img_destruct_cached_image: state=%d\n", cimg->state); internal_error("Invalid state in struct cached_image"); #endif /* #ifdef DEBUG */ } mem_free(cimg->url); mem_free(cimg); } /* You throw in a vertical dimension of image and it returns * new dimension according to the aspect ratio and user-set image * scaling factor. When scaling factor is 100% and screen pixels * are non-square, the picture will be always in one dimension * untouched and in the second _ENLARGED_. So that no information * in the picture will be lost. * Input may be <0. In this case output=input * Input may be 0. In this case output=0. * If input is >0 the output is also >0. */ static ssize_t img_scale_h(unsigned scale, ssize_t in) { ssize_t out; /* We assume unsigned long holds at least 32 bits */ unsigned long pre; if (in<=0) return in; pre=((unsigned long)(aspect<65536UL?65536UL:aspect)*scale+128)>>8; out=(size_t)(((unsigned long)in*pre+12800UL)/25600UL); if (out<1) out=1; return out; } static ssize_t img_scale_v(unsigned scale, ssize_t in) { ssize_t out; unsigned long divisor; if (in<=0) return in; divisor=(100*(aspect>=65536UL?65536UL:aspect)+128)>>8; out=(ssize_t)(((unsigned long)in*(scale*256)+(divisor>>1))/divisor); if (out<1) out=1; return out; } /* Returns height (pixels) for prescribed width (pixels). Honours aspect. */ static ssize_t width2height(double width_px, double width_mm, double height_mm) { ssize_t height_px; if (width_px<=0) return 0; height_px=(ssize_t)((height_mm*width_px*65536)/(aspect*width_mm)); if (height_px<1) height_px=1; return height_px; } /* Returns width (pixels) for prescribed height (pixels). Honours aspect. */ static ssize_t height2width(double height_px, double width_mm, double height_mm) { ssize_t width_px; if (height_px<=0) return 0; width_px=(ssize_t)((width_mm*height_px*aspect)/(65536*height_mm)); if (width_px<1) width_px=1; return width_px; } /* Compute 8-bit background for filling buffer with cimg->*_gamma * (performs rounding) */ void compute_background_8(struct cached_image *cimg, unsigned char rgb[3]) { unsigned short red, green, blue; round_color_sRGB_to_48(&red, &green, &blue, cimg->background_color); rgb[0] = ags_16_to_8(red, (float)(cimg->red_gamma/(float)(user_gamma tcc_hack))); rgb[1] = ags_16_to_8(green, (float)(cimg->green_gamma/(float)(user_gamma tcc_hack))); rgb[2] = ags_16_to_8(blue, (float)(cimg->blue_gamma/(float)(user_gamma tcc_hack))); } /* updates cimg state when header dimensions are know. Only allowed to be called * in state 8 and 10. * Allocates right amount of memory into buffer, formats it (with background or * zeroes, depens on buffer_bytes_per_pixel). Updates dimensions (xww and yww) * according to newly known header dimensions. Fills in gamma_stamp, bmp_used * (zero because we not bother with generating bitmap here) * and rows_added. * Resets strip_optimized if image will be scaled or * Allocates dregs if on exit strip_optimized is nonzero. * Allocates and computes gamma_table, otherwise * sets gamma_table to NULL. Also doesn't make gamma table if image contains less * than 1024 pixels (it would be probably a waste of time). * Output state is always 12 (from input state 8) or 14 (from input state 10). * * The caller must have set the following elements of cimg: * width * height * buffer_bytes_per_pixel * red_gamma * green_gamma * blue_gamma * strip_optimized */ int header_dimensions_known(struct cached_image *cimg) { unsigned short red, green, blue; #ifdef DEBUG if ((cimg->state^8)&13){ fprintf(stderr,"cimg->state=%d\n",cimg->state); internal_error("Invalid state in header_dimensions_known"); } #endif /* #ifdef DEBUG */ if (cimg->width<1||cimg->height<1){ /*fprintf(stderr,"width=%d height=%d\n",cimg->width, cimg->height);*/ return 1; } if (!is_image_size_sane(cimg->width, cimg->height)) { return 1; } if (cimg->wanted_xw<0){ /* Unspecified width */ if (cimg->wanted_yw<0){ /* Unspecified neither width nor height */ cimg->xww=img_scale_h(cimg->scale, cimg->width); cimg->yww=img_scale_v(cimg->scale, cimg->height); }else{ /* Unspecified width specified height */ cimg->xww=height2width(cimg->yww, cimg->width, cimg->height); if (cimg->xww<=0) cimg->xww=1; } }else{ /* Specified width */ if (cimg->wanted_yw<0){ /* Unspecified height, specified width */ cimg->yww=width2height(cimg->xww, cimg->width, cimg->height); if (cimg->yww<=0) cimg->yww=1; }else if (cimg->wanted_xyw_meaning==MEANING_AUTOSCALE){ /* Specified height and width and autoscale meant */ /* Try first to nail the height */ cimg->yww=cimg->wanted_yw; cimg->xww=height2width(cimg->yww, cimg->width, cimg->height); if (cimg->xww>cimg->wanted_xw) { /* Width too much, we nail the width */ cimg->xww=cimg->wanted_xw; cimg->yww=width2height(cimg->xww, cimg->width, cimg->height); } /* Some sanity checks */ if (cimg->xww<=0) cimg->xww=1; if (cimg->yww<=0) cimg->yww=1; } } if (!is_image_size_sane(cimg->xww, cimg->yww)) { cimg->xww = cimg->width; cimg->yww = cimg->height; } #ifdef HAVE_SVG if (cimg->image_type == IM_SVG) { /* SVG images are scaled using the cairo library, not the Links scaler */ cimg->width = cimg->xww; cimg->height = cimg->yww; } #endif if (cimg->width!=cimg->xww||cimg->height!=cimg->yww) cimg->strip_optimized=0; cimg->gamma_stamp=gamma_stamp; if (cimg->strip_optimized){ struct bitmap tmpbmp; unsigned short *buf_16; ssize_t i; tmpbmp.x = (int)cimg->width; tmpbmp.y = 1; /* No buffer, bitmap is valid from the very beginning */ cimg->bmp.x = (int)cimg->width; cimg->bmp.y = (int)cimg->height; if (drv->get_empty_bitmap(&(cimg->bmp))) { cimg->dregs = NULL; goto skip_img; } if ((size_t)cimg->width > MAX_SIZE_T / sizeof(*buf_16) / 3) overalloc(); buf_16 = mem_alloc(sizeof(*buf_16) * 3 * (size_t)cimg->width); round_color_sRGB_to_48(&red, &green, &blue , cimg->background_color); mix_one_color_48(buf_16,cimg->width, red, green, blue); #ifdef DEBUG if (cimg->height<=0){ fprintf(stderr,"cimg->height=%ld\n", (long)cimg->height); internal_error("Invalid cimg->height in strip_optimized section of header_dimensions_known"); } #endif /* #ifdef DEBUG */ /* The skip is uninitialized here and is read by dither_start * but is not used in any malicious way so it doesn't matter */ tmpbmp.data = cimg->bmp.data; tmpbmp.skip = cimg->bmp.skip; cimg->dregs=dither_images?dither_start(buf_16,&tmpbmp):NULL; tmpbmp.data=(unsigned char *)tmpbmp.data+cimg->bmp.skip; if (cimg->dregs) for (i=cimg->height-1;i;i--){ dither_restart(buf_16,&tmpbmp,cimg->dregs); tmpbmp.data=(unsigned char *)tmpbmp.data+cimg->bmp.skip; } else for (i=cimg->height-1;i;i--){ (*round_fn)(buf_16,&tmpbmp); tmpbmp.data=(unsigned char *)tmpbmp.data+cimg->bmp.skip; } mem_free(buf_16); skip_img: drv->register_bitmap(&(cimg->bmp)); if(cimg->dregs) memset(cimg->dregs,0,cimg->width*sizeof(*cimg->dregs)*3); cimg->bmp_used=1; /* Nonzero value */ /* This ensures the dregs are none and because strip * optimization is unusable in interlaced pictures, * this saves the zeroing out at the beginning of the * decoder itself. */ }else { cimg->rows_added=1; cimg->bmp_used=0; if (cimg->width && (size_t)cimg->width * (size_t)cimg->height / (size_t)cimg->width != (size_t)cimg->height) overalloc(); cimg->buffer = mem_alloc_mayfail((size_t)cimg->width * (size_t)cimg->height * (size_t)cimg->buffer_bytes_per_pixel); if (!cimg->buffer) return 1; if (cimg->buffer_bytes_per_pixel==4 ||cimg->buffer_bytes_per_pixel==4 *sizeof(unsigned short)) { /* Make the buffer contain full transparency */ memset(cimg->buffer, 0, (size_t)cimg->width * (size_t)cimg->height * (size_t)cimg->buffer_bytes_per_pixel); }else{ /* Fill the buffer with background color */ if (cimg->buffer_bytes_per_pixel > 4){ /* 16-bit */ unsigned short red, green, blue; round_color_sRGB_to_48(&red, &green, &blue, cimg->background_color); red=ags_16_to_16(red, (float)(cimg->red_gamma / (float)(user_gamma tcc_hack))); green=ags_16_to_16(green, (float)(cimg->green_gamma / (float)(user_gamma tcc_hack))); blue=ags_16_to_16(blue, (float)(cimg->blue_gamma / (float)(user_gamma tcc_hack))); mix_one_color_48((unsigned short *)cimg->buffer, cimg->width*cimg->height, red, green, blue); } else { unsigned char rgb[3]; /* 8-bit */ compute_background_8(cimg, rgb); mix_one_color_24(cimg->buffer, cimg->width * cimg->height, rgb[0], rgb[1], rgb[2]); } } } if (cimg->buffer_bytes_per_pixel<=4&&cimg->width*cimg->height>=1024){ make_gamma_table(cimg); }else if (cimg->buffer_bytes_per_pixel>=6&&cimg->width*cimg->height>=262144){ make_gamma_table(cimg); }else cimg->gamma_table=NULL; cimg->state|=4; /* Update state */ return 0; } /* Fills "tmp" buffer with the resulting data and does not free the input * buffer. May be called only in states 12 and 14 of cimg */ static unsigned short *buffer_to_16(unsigned short *tmp, struct cached_image *cimg, unsigned char *buffer, ssize_t height) { unsigned short red, green, blue; #ifdef DEBUG if (cimg->state != 12 && cimg->state != 14){ fprintf(stderr, "cimg->state=%d\n", cimg->state); internal_error("invalid state in buffer_to_16"); } #endif /* #ifdef DEBUG */ switch (cimg->buffer_bytes_per_pixel) { case 3: if (cimg->gamma_table) { agx_24_to_48_table(tmp, buffer, cimg->width*height, cimg->gamma_table); } else { agx_24_to_48(tmp, buffer, cimg->width * height, (float)((float)(user_gamma tcc_hack) / cimg->red_gamma), (float)((float)(user_gamma tcc_hack) / cimg->green_gamma), (float)((float)(user_gamma tcc_hack) / cimg->blue_gamma)); } break; case 3 * sizeof(unsigned short): if (cimg->gamma_table) { agx_48_to_48_table(tmp, (unsigned short *)buffer, cimg->width * height, cimg->gamma_table); } else { agx_48_to_48(tmp, (unsigned short *)buffer, cimg->width * height, (float)((float)(user_gamma tcc_hack) / cimg->red_gamma), (float)((float)(user_gamma tcc_hack) / cimg->green_gamma), (float)((float)(user_gamma tcc_hack) / cimg->blue_gamma)); } break; /* Alpha's: */ case 4: round_color_sRGB_to_48(&red,&green,&blue,cimg->background_color); if (cimg->gamma_table) { agx_and_uc_32_to_48_table(tmp, buffer, cimg->width * height, cimg->gamma_table, red, green, blue); } else { agx_and_uc_32_to_48(tmp,buffer, cimg->width*height, (float)((float)(user_gamma tcc_hack) / cimg->red_gamma), (float)((float)(user_gamma tcc_hack) / cimg->green_gamma), (float)((float)(user_gamma tcc_hack) / cimg->blue_gamma), red, green, blue); } break; case 4 * sizeof(unsigned short): round_color_sRGB_to_48(&red, &green, &blue, cimg->background_color); if (cimg->gamma_table) { agx_and_uc_64_to_48_table(tmp, (unsigned short *)buffer, cimg->width * height, cimg->gamma_table, red, green, blue); } else { agx_and_uc_64_to_48(tmp, (unsigned short *)buffer, cimg->width * height, (float)((float)(user_gamma tcc_hack) / cimg->red_gamma), (float)((float)(user_gamma tcc_hack) / cimg->green_gamma), (float)((float)(user_gamma tcc_hack) / cimg->blue_gamma), red,green,blue); } break; #ifdef DEBUG default: internal_error("buffer_to_16: unknown mem organization"); #endif /* #ifdef DEBUG */ } return tmp; } /* Returns allocated buffer with the resulting data and does not free the input * buffer. May be called only in states 12 and 14 of cimg * use_strip: 1 if the image is already registered and prepare_strip and * commit_strip is to be used * 0: if the image is not yet registered and instead one big register_bitmap * will be used eventually * dregs must be externally allocated and contain required value or must be * NULL. * if !dregs then rounding is performed instead of dithering. * dregs are not freed. * bottom dregs are placed back into dregs. * Before return the bitmap will be in registered state and changes will be * commited. * height must be >=1 !!! */ void buffer_to_bitmap_incremental(struct cached_image *cimg, unsigned char *buffer, ssize_t height, ssize_t yoff, int *dregs, int use_strip) { #define max_height 16 /* max_height must be at least 1 */ unsigned short *tmp; struct bitmap tmpbmp; ssize_t add1 = 0, add2; #ifdef DEBUG if (cimg->state!=12&&cimg->state!=14){ fprintf(stderr,"cimg->state=%d\n",cimg->state); internal_error("Invalid state in buffer_to_bitmap_incremental\n"); } if (height<1){ fprintf(stderr,"height=%ld\n", (long)height); internal_error("Invalid height in buffer_to_bitmap_incremental\n"); } if (cimg->width<1||cimg->height<1){ fprintf(stderr,"cimg->width=%ld, cimg->height=%ld\n", (long)cimg->width, (long)cimg->height); internal_error("Invalid cimg->width x cimg->height in\ buffer_to_bitmap_incremental"); } #endif /* #ifdef DEBUG */ if ((size_t)cimg->width > MAX_SIZE_T / (size_t)max_height / 3 / sizeof(*tmp)) overalloc(); tmp=mem_alloc((size_t)cimg->width * (size_t)(height < max_height ? height : max_height) * 3 * sizeof(*tmp)); /* Prepare a fake bitmap for dithering */ tmpbmp.x = (int)cimg->width; if (!use_strip){ tmpbmp.data=(unsigned char *)cimg->bmp.data+cimg->bmp.skip*yoff; add1=cimg->bmp.skip*max_height; } add2=cimg->buffer_bytes_per_pixel*cimg->width*max_height; not_enough: tmpbmp.y = (int)(height < max_height ? height : max_height); if (use_strip) { tmpbmp.data = drv->prepare_strip(&(cimg->bmp), (int)yoff, tmpbmp.y); if (!tmpbmp.data) goto prepare_failed; } tmpbmp.skip=cimg->bmp.skip; buffer_to_16(tmp, cimg, buffer, tmpbmp.y); if (dregs) { dither_restart(tmp, &tmpbmp, dregs); } else { (*round_fn)(tmp, &tmpbmp); } if (use_strip) { prepare_failed: drv->commit_strip(&(cimg->bmp), (int)yoff, tmpbmp.y); } height-=tmpbmp.y; if (!height) goto end; buffer+=add2; yoff+=tmpbmp.y; tmpbmp.data=(unsigned char *)tmpbmp.data+add1; /* This has no effect if use_strip but it's faster * to add to bogus value than to play with * conditional jumps. */ goto not_enough; end: mem_free(tmp); if (!use_strip) drv->register_bitmap(&(cimg->bmp)); } /* Takes the buffer and resamples the data into the bitmap. Automatically * destroys the previous bitmap. Must be called only when cimg->buffer is valid. * Sets bmp_used to non-zero. * If gamma_table is used, it must be still allocated here (take care if you * want to destroy gamma table and call buffer_to_bitmap, first call buffer_to_bitmap * and then destroy gamma_table). */ static void buffer_to_bitmap(struct cached_image *cimg) { unsigned short *tmp, *tmp1; ssize_t ix, iy, ox, oy; int gonna_be_smart; int *dregs; #ifdef DEBUG if(cimg->state!=12&&cimg->state!=14){ fprintf(stderr,"cimg->state=%d\n",cimg->state); internal_error("buffer_to_bitmap called in invalid state"); } if (cimg->strip_optimized) internal_error("strip_optimized in buffer_to_bitmap"); if (cimg->width<1||cimg->height<1){ fprintf(stderr,"cimg->width=%ld, cimg->height=%ld\n", (long)cimg->width, (long)cimg->height); internal_error("Invalid cimg->width x cimg->height in buffer_to_bitmap"); } #endif /* #ifdef DEBUG */ if (!cimg->rows_added) return; /* Here of course width and height must be already filled */ cimg->rows_added=0; ix=cimg->width; iy=cimg->height; ox=cimg->xww; oy=cimg->yww; if (ix==ox&&iy==oy) gonna_be_smart=1; else{ gonna_be_smart=0; if (ix && (size_t)ix * (size_t)iy / (size_t)ix != (size_t)iy) overalloc(); if ((size_t)ix * (size_t)iy > MAX_SIZE_T / sizeof(*tmp) / 3) overalloc(); tmp = mem_alloc_mayfail((size_t)ix * (size_t)iy * 3 * sizeof(*tmp)); if (tmp) buffer_to_16(tmp, cimg, cimg->buffer, iy); if (!cimg->decoder){ mem_free_buffer(cimg); cimg->buffer=NULL; } /* Scale the image to said size */ #ifdef DEBUG if (ox<=0||oy<=0){ internal_error("ox or oy <=0 before resampling image"); } #endif /* #ifdef DEBUG */ if (tmp && (ix!=ox || iy!=oy)) { /* We must really scale */ tmp1=tmp; scale_color(tmp1,ix,iy,&tmp,ox,oy); } } if (cimg->bmp_used) drv->unregister_bitmap(&cimg->bmp); cimg->bmp.x = (int)ox; cimg->bmp.y = (int)oy; if (drv->get_empty_bitmap(&(cimg->bmp))) { if (!gonna_be_smart) { if (tmp) { mem_free(tmp); } } goto bitmap_failed; } if (gonna_be_smart){ if (dither_images) { if ((size_t)cimg->width > MAX_SIZE_T / 3 / sizeof(*dregs)) overalloc(); dregs = mem_calloc(sizeof(*dregs) * 3 * (size_t)cimg->width); } else { dregs = NULL; } buffer_to_bitmap_incremental(cimg, cimg->buffer, cimg->height, 0, dregs, 0); if (dregs) mem_free(dregs); }else{ if (tmp) { if (dither_images) dither(tmp, &cimg->bmp); else (*round_fn)(tmp, &cimg->bmp); mem_free(tmp); } else { int i; unsigned char *ptr = cimg->bmp.data; for (i = 0; i < cimg->bmp.y; i++) { memset(ptr, 0, cimg->bmp.x * (drv->depth & 7)); ptr += cimg->bmp.skip; } } bitmap_failed: drv->register_bitmap(&(cimg->bmp)); } cimg->bmp_used=1; /* Indicate that the bitmap is valid. The value is just any nonzero value */ cimg->rows_added=0; /* Indicate the bitmap is up-to-date */ } /* Performs state transition for end of stream or error in image or * end of image */ void img_end(struct cached_image *cimg) { switch(cimg->state){ case 12: case 14: if (cimg->strip_optimized){ if (cimg->dregs) mem_free(cimg->dregs); } else{ buffer_to_bitmap(cimg); mem_free_buffer(cimg); } if (cimg->gamma_table) mem_free(cimg->gamma_table); /*-fallthrough*/ case 8: case 10: destroy_decoder(cimg); case 0: case 1: case 2: case 3: case 9: case 11: case 13: case 15: break; #ifdef DEBUG default: fprintf(stderr,"state=%d\n",cimg->state); internal_error("Invalid state encountered in end"); #endif /* #ifdef DEBUG */ } cimg->state|=1; } static void r3l0ad(struct cached_image *cimg, struct g_object_image *goi) { cimg->eof_hit=0; cimg->last_count=goi->af->rq->ce->count; cimg->last_count2=goi->af->rq->ce->count2; cimg->gamma_stamp=gamma_stamp; switch(cimg->state){ case 8: case 10: destroy_decoder(cimg); case 1: case 3: case 9: case 11: case 0: case 2: break; case 12: if (cimg->gamma_table) mem_free(cimg->gamma_table); destroy_decoder(cimg); if (cimg->strip_optimized){ if (cimg->dregs) mem_free(cimg->dregs); }else{ mem_free_buffer(cimg); } if (cimg->bmp_used){ case 13: drv->unregister_bitmap(&cimg->bmp); } cimg->xww=img_scale_h(cimg->scale, cimg->wanted_xw<0?32:cimg->wanted_xw); cimg->yww=img_scale_v(cimg->scale, cimg->wanted_yw<0?32:cimg->wanted_yw); break; case 14: if (cimg->gamma_table) mem_free(cimg->gamma_table); destroy_decoder(cimg); if (cimg->strip_optimized){ if (cimg->dregs) mem_free(cimg->dregs); }else{ mem_free_buffer(cimg); } if (cimg->bmp_used){ case 15: drv->unregister_bitmap(&cimg->bmp); } break; #ifdef DEBUG default: fprintf(stderr,"cimg->state=%d\n",cimg->state); internal_error("Invalid state in r3l0ad()"); #endif /* #ifdef DEBUG */ } cimg->state&=2; } /* Returns 1 if match. * If doesn't return 1 then returns 0 */ static inline int dtest(unsigned char *templat, unsigned char *test) { return !casestrcmp(templat, test); } /* This may be called only in state 0 or 2 */ static void type(struct cached_image *cimg, unsigned char *content_type, unsigned char *data /* at least 4 bytes */) { #ifdef DEBUG if (cimg->state!=0&&cimg->state!=2){ fprintf(stderr,"cimg->state=%d\n",cimg->state); internal_error("Invalid state encountered in type()"); } #endif /* #ifdef DEBUG */ #ifdef HAVE_JPEG if (data[0] == 0xff && data[1] == 0xd8) goto have_jpeg; #endif #ifdef HAVE_TIFF if (data[0] == 'I' && data[1] == 'I') goto have_tiff; if (data[0] == 'M' && data[1] == 'M') goto have_tiff; #endif #ifdef HAVE_SVG if (data[0] == '<' && data[1] == '?') goto have_svg; #endif #ifdef HAVE_WEBP if (data[0] == 'R' && data[1] == 'I' && data[2] == 'F' && data[3] == 'F') goto have_webp; #endif #if 0 if (data[4] == 'f' && data[5] == 't' && data[6] == 'y' && data[7] == 'p' && (memcmp(data + 8, "avi", 3) && (data[11] == 'f' || data[11] == 's' || data[11] == 'o')) || (data[8] == 'm' && (data[9] == 'i' || data[9] == 's') && data[10] == 'f' && data[11] == '1') || memcmp(data + 8, "miaf", 4) || memcmp(data + 8, "MA1B", 4)) goto have_avif; #endif if (data[0] == 0x89 && data[1] == 'P' && data[2] == 'N' && data[3] == 'G') goto have_png; if (data[0] == 'G' && data[1] == 'I' && data[2] == 'F') goto have_gif; #ifdef HAVE_JPEG if (dtest(cast_uchar "image/jpeg",content_type) || dtest(cast_uchar "image/jpg",content_type) || dtest(cast_uchar "image/jpe",content_type) || dtest(cast_uchar "image/pjpe",content_type) || dtest(cast_uchar "image/pjpeg",content_type) || dtest(cast_uchar "image/pjpg",content_type)) { have_jpeg: cimg->image_type=IM_JPG; jpeg_start(cimg); } else #endif /* #ifdef HAVE_JPEG */ if (dtest(cast_uchar "image/png",content_type) || dtest(cast_uchar "image/x-png",content_type)) { have_png: cimg->image_type=IM_PNG; png_start(cimg); } else if (dtest(cast_uchar "image/gif",content_type)){ have_gif: cimg->image_type=IM_GIF; gif_start(cimg); } else if (dtest(cast_uchar "image/x-xbitmap",content_type)){ cimg->image_type=IM_XBM; xbm_start(cimg); } else #ifdef HAVE_TIFF if (dtest(cast_uchar "image/tiff",content_type) || dtest(cast_uchar "image/tif",content_type)) { have_tiff: cimg->image_type=IM_TIFF; tiff_start(cimg); } else #endif /* #ifdef HAVE_TIFF */ #ifdef HAVE_SVG if (dtest(cast_uchar "image/svg+xml",content_type) || dtest(cast_uchar "image/svg",content_type)) { have_svg: cimg->image_type=IM_SVG; svg_start(cimg); } else #endif /* #ifdef HAVE_SVG */ #ifdef HAVE_WEBP if (dtest(cast_uchar "image/webp",content_type)) { have_webp: cimg->image_type=IM_WEBP; webp_start(cimg); } else #endif /* #ifdef HAVE_WEBP */ #ifdef HAVE_AVIF if (dtest(cast_uchar "image/avif",content_type)) { /*have_avif:*/ cimg->image_type=IM_AVIF; avif_start(cimg); } else #endif /* #ifdef HAVE_AVIF */ { /* Error */ img_end(cimg); return; } cimg->state|=8; /* Advance the state according to the table in links-doc.html */ cimg->last_length=0; } /* Doesn't print anything. Downloads more data if available. * Sets up cimg->reparse and cimg->xww and cimg->yww accordingly to * the state of the decoder. When changing xww and yww also changes xw and yw * in g_object_image. * return value 1 means the data were chopped and the caller shall not redraw * (because it would be too slow and because we are probably choked * up with the data) */ static int img_process_download(struct g_object_image *goi, struct f_data_c *fdatac) { unsigned char *data, *ctype; size_t total_len; struct cached_image *cimg = goi->cimg; int chopped=0; #ifdef DEBUG if (!goi->af) internal_error("NULL goi->af in process_download\n"); if (cimg->state>=16){ /* Negative don't occur because it's unsigned char */ fprintf(stderr,"cimg->state=%d\n",cimg->state); internal_error("Invalid cimg->state in img_process_download\n"); } #endif /* #ifdef DEBUG */ if (!goi->af->rq) return 0; if (get_file(goi->af->rq, &data, &total_len)) goto end; if (total_len < 4) goto end; if (total_len > MAXINT) total_len = MAXINT; /*fprintf(stderr, "processing: %s\n", goi->af->rq->ce->url);*/ if (goi->af->rq->ce->count2!=cimg->last_count2|| (goi->af->rq->ce->count!=cimg->last_count && cimg->eof_hit) || (cimg->state>=12&&gamma_stamp!=cimg->gamma_stamp)){ /* Reload */ r3l0ad(cimg,goi); } /*if (!goi->af->rq->ce->head) goto end;*/ /* Mikulas: head muze byt NULL*/ /* Mikulas: tak se to zpracuje a nebude se skakat na konec, kdyz je to NULL */ if (cimg->state==0||cimg->state==2){ /* Type still unknown */ ctype=get_content_type(goi->af->rq->ce->head, goi->af->rq->url); if (!ctype) ctype = stracpy(cast_uchar "application/octet-stream"); type(cimg,ctype,data); mem_free(ctype); } /* Now, if we are in state where decoder is running (8, 10, 12, 14), we may feed * some data into it. */ if (!((cimg->state^8)&9)){ int length = (int)total_len; if (length<=cimg->last_length) goto end; /* No new data */ data+=cimg->last_length; length-=cimg->last_length; if (length>RESTART_SIZE){ length=RESTART_SIZE; chopped=1; if (fdatac) { refresh_image(fdatac, &goi->goti.go, 0); } } /* Decoder has been already started */ switch(cimg->image_type){ case IM_PNG: png_restart(cimg,data,length); break; #ifdef HAVE_JPEG case IM_JPG: jpeg_restart(cimg,data,length); break; #endif /* #ifdef HAVE_JPEG */ case IM_XBM: xbm_restart(cimg,data,length); break; case IM_GIF: gif_restart(data,length); break; #ifdef HAVE_TIFF case IM_TIFF: tiff_restart(cimg,data,length); break; #endif /* #ifdef HAVE_TIFF */ #ifdef HAVE_SVG case IM_SVG: svg_restart(cimg,data,length); break; #endif /* #ifdef HAVE_SVG */ #ifdef HAVE_WEBP case IM_WEBP: webp_restart(cimg,data,length); break; #endif /* #ifdef HAVE_WEBP */ #ifdef HAVE_AVIF case IM_AVIF: avif_restart(cimg,data,length); break; #endif /* #ifdef HAVE_AVIF */ #ifdef DEBUG default: fprintf(stderr,"cimg->image_type=%d\n",cimg->state); internal_error("Invalid image_type encountered when processing data in\ img_process_download.\n"); #endif /* #ifdef DEBUG */ } cimg->last_length+=length; } end: /* Test end */ if (!is_entry_used(goi->af->rq->ce) && (goi->af->rq->state < 0 ||(goi->af->rq->ce&&goi->af->rq->stat.state<0))){ /* We must not perform end with chopped because some * unprocessed data still wait for us :) */ if (!chopped){ if (!((cimg->state^8)&9)) { #ifdef HAVE_TIFF if (cimg->image_type==IM_TIFF) tiff_finish(cimg); #endif #ifdef HAVE_SVG if (cimg->image_type==IM_SVG) svg_finish(cimg); #endif #ifdef HAVE_WEBP if (cimg->image_type==IM_WEBP) webp_finish(cimg); #endif #ifdef HAVE_AVIF if (cimg->image_type==IM_AVIF) avif_finish(cimg); #endif } cimg->eof_hit=1; if (goi->af->rq->ce) cimg->last_count=goi->af->rq->ce->count; img_end(cimg); } } else if (!chopped) { if (fdatac) { if (f_is_finished(fdatac->f_data)) { refresh_image(fdatac, &goi->goti.go, 2000); } else { /* * Fix a bug - if we have a text file with html and built-in image using * the data:// url. If we press '\' to toggle the view from text to * html, the image is not displayed correctly. We need to reset * fdatac->done to force re-parse. */ fdatac->done = 0; } } } return chopped; } /* Input: rgb (sRGB) triplet (0...255) * Returns a color that is very contrasty on that background sRGB color */ int get_foreground(int rgb) { int r,g,b; r=(rgb>>16)&255; g=(rgb>>8)&255; b=rgb&255; r=r<128?255:0; g=g<128?255:0; b=b<128?255:0; return (r<<16)|(g<<8)|b; } static void draw_frame_mark(struct graphics_device *dev, int x, int y, int xw, int yw, long bg, long fg, int broken) { #ifdef DEBUG if (xw<1||yw<1) internal_error("zero dimension in draw_frame_mark"); #endif /* #ifdef DEBUG */ if (broken == 1){ /* Draw between ( 0 and 1/4 ) and ( 3/4 and 1 ) of each * side (0-1) */ int xl, xh, yl, yh; xh=xw-(xl=xw>>2); yh=yw-(yl=yw>>2); /* Draw full sides and the box inside */ drv->draw_hline(dev,x,y,x+xl,fg); drv->draw_hline(dev,x+xl,y,x+xh,bg); drv->draw_hline(dev,x+xh,y,x+xw,fg); if (yw>=1){ if (yw>=2){ drv->draw_vline(dev,x,y+1,y+yl,fg); drv->draw_vline(dev,x,y+yl,y+yh,bg); drv->draw_vline(dev,x,y+yh,y+yw-1,fg); if (xw>=1){ if (xw>=2){ drv->fill_area(dev, x+1,y+1,x+xw-1,y+yw-1, bg); } drv->draw_vline(dev,x+xw-1,y+1,y+yl,fg); drv->draw_vline(dev,x+xw-1,y+yl,y+yh,bg); drv->draw_vline(dev,x+xw-1,y+yh,y+yw-1,fg); } } drv->draw_hline(dev,x,y+yw-1,x+xl,fg); drv->draw_hline(dev,x+xl,y+yw-1,x+xh,bg); drv->draw_hline(dev,x+xh,y+yw-1,x+xw,fg); } }else { /* Draw full sides and the box inside */ drv->draw_hline(dev,x,y,x+xw,fg); if (yw>=1){ if (yw>=2){ drv->draw_vline(dev,x,y+1,y+yw-1,fg); if (xw>=1){ if (xw>=2){ if (broken < 2) drv->fill_area(dev, x+1,y+1,x+xw-1,y+yw-1, bg); } drv->draw_vline(dev,x+xw-1,y+1, y+yw-1,fg); } } drv->draw_hline(dev,x,y+yw-1,x+xw,fg); } if (broken == 2 && xw > 2 && yw > 2) { draw_frame_mark(dev, x + 1, y + 1, xw - 2, yw - 2, bg, fg, 3); } } } static long image_backgronud(struct cached_image *cimg) { if (!cimg) return dip_get_color_sRGB(0x00c0c0c0); return dip_get_color_sRGB(cimg->background_color); } static long image_foregronud(struct cached_image *cimg) { if (!cimg) return dip_get_color_sRGB(0x00000000); return dip_get_color_sRGB(get_foreground(cimg->background_color)); } /* Entry is allowed only in states 12, 13, 14, 15 * Draws the picture from bitmap. * Before doing so, ensures that bitmap is present and if not, converts it from * the buffer. */ static void draw_picture(struct f_data_c *fdatac, struct g_object_image *goi, int x, int y) { struct graphics_device *dev = fdatac->ses->term->dev; struct cached_image *cimg = goi->cimg; struct rect saved; #ifdef DEBUG if (goi->cimg->state < 12 || goi->cimg->state >= 16) { fprintf(stderr, "cimg->state=%d\n", cimg->state); internal_error("Invalid cimg->state in draw_picture"); } #endif /* #ifdef DEBUG */ if (!(cimg->state & 1)) { if (!cimg->bmp_used) buffer_to_bitmap(cimg); } #ifdef DEBUG else if (!cimg->bmp_used) { fprintf(stderr, "cimg->state=%d\n", cimg->state); internal_error("Nonexistent bitmap in said cimg->state in draw_picture"); } #endif /* #ifdef DEBUG */ restrict_clip_area(dev, &saved, x, y, x + goi->goti.go.xw, y + goi->goti.go.yw); drv->draw_bitmap(dev, &cimg->bmp, x, y); if (cimg->bmp.x < goi->goti.go.xw || cimg->bmp.y < goi->goti.go.yw) { long bg = image_backgronud(cimg); drv->fill_area(dev, x + cimg->bmp.x, y, x + goi->goti.go.xw, y + cimg->bmp.y, bg); drv->fill_area(dev, x, y + cimg->bmp.y, x + goi->goti.go.xw, y + goi->goti.go.yw, bg); } set_clip_area(dev, &saved); } /* Ensures in buffer there is not newer picture than in bitmap. Allowed to be * called only in state 12, 13, 14, 15. */ static void update_bitmap(struct cached_image *cimg) { #ifdef DEBUG if (cimg->state < 12 || cimg->state >= 16) { fprintf(stderr, "cimg->state=%d\n", cimg->state); internal_error("Invalid state in update_bitmap"); } #endif /* #ifdef DEBUG */ if (!(cimg->state & 1) && !cimg->strip_optimized && cimg->rows_added) buffer_to_bitmap(cimg); } /* Draws the image at x,y. Is called from other C sources. */ static void img_draw_image(struct f_data_c *fdatac, struct g_object *goi_, int x, int y) { struct g_object_image *goi = get_struct(goi_, struct g_object_image, goti.go); struct cached_image *cimg = goi->cimg; struct rect r; /* refresh_image(fdatac, goi, 1000); To sem asi napsal mikulas jako * navod, jak se vola to refresh_image. Nicmene ja jsem milostive * usoudil, ze zadnejch 1000, ale 0. */ if (!(goi->goti.go.xw && goi->goti.go.yw)) return; /* At least one dimension is zero */ memcpy(&r, &fdatac->ses->term->dev->clip, sizeof(struct rect)); if (fdatac->vs->g_display_link && fdatac->active && fdatac->vs->current_link != -1 && fdatac->vs->current_link == goi->goti.link_num) { long fg = image_foregronud(cimg); draw_frame_mark(fdatac->ses->term->dev, x, y, goi->goti.go.xw, goi->goti.go.yw, fg, fg, 2); restrict_clip_area(fdatac->ses->term->dev, &r, x + 2, y + 2, x + goi->goti.go.xw - 2, y + goi->goti.go.yw - 2); } global_goi=goi; global_cimg=goi->cimg; if (img_process_download(goi, fdatac)) goto ret; /* Choked with data, will not * draw. */ /* Now we will only draw... */ if (!cimg || cimg->state < 12) { long fg = image_foregronud(cimg); long bg = image_backgronud(cimg); draw_frame_mark(fdatac->ses->term->dev, x, y, goi->goti.go.xw, goi->goti.go.yw, bg, fg, !cimg || cimg->state & 1); } else { #ifdef DEBUG if (cimg->state >= 16) internal_error("Invalid state in img_draw_image: %d", cimg->state); #endif update_bitmap(cimg); draw_picture(fdatac, goi, x, y); } ret: set_clip_area(fdatac->ses->term->dev, &r); #ifdef LINKS_TESTMODE_IMAGE_AUTO_EXIT if (cimg->state & 1) terminate_loop = 1; #endif } /* Prior to calling this function you have to fill out * image -> xw (<0 means not known) * image -> yw (<0 means not known) * image -> xyw meaning (MEANING_AUTOSCALE or MEANING_DIMS) * image -> background * * The URL will not be freed. */ static void find_or_make_cached_image(struct g_object_image *image, unsigned char *url, int scale) { struct cached_image *cimg; if (!(cimg = find_cached_image(image->background, url, image->goti.go.xw, image->goti.go.yw, image->xyw_meaning, scale, aspect))){ /* We have to make a new image cache entry */ cimg = mem_alloc(sizeof(*cimg)); cimg->background_color = image->background; #ifdef DEBUG if (!url) internal_error("NULL url as argument of\ find_or_make_cached_image"); #endif /* #ifdef DEBUG */ cimg->scale = scale; cimg->aspect = aspect; cimg->url = stracpy(url); cimg->wanted_xw = image->goti.go.xw; cimg->wanted_yw = image->goti.go.yw; cimg->wanted_xyw_meaning = image->xyw_meaning; cimg->xww = image->goti.go.xw < 0 ? img_scale_h(cimg->scale, 32) : cimg->wanted_xw; cimg->yww = image->goti.go.yw < 0 ? img_scale_v(cimg->scale, 32) : cimg->wanted_yw; cimg->state=0; /* width, height, image_type, buffer, buffer_bytes_per_pixel, red_gamma, * green_gamma, blue_gamma, gamma_stamp, bitmap, last_length, rows_added, * and decoder is invalid in both state 0 and state 2. Thus is need no to * be filled in. */ /* last_count2 is unitialized */ cimg->eof_hit = 0; cimg->last_count = -1; cimg->last_count2 = -1; if (cimg->wanted_xw >= 0 && cimg->wanted_yw >=0) cimg->state |= 2; add_image_to_cache(cimg); } global_cimg = image->cimg = cimg; } /* The original (unscaled, in pixels pace) size is requested in im->xsize and im->ysize. * <0 means unknown. Autoscale is requested in autoscale. When autoscale is on, * the requested dimensions are not scaled and they mean maximum allowed * dimensions. */ struct g_object_image *insert_image(struct g_part *p, struct image_description *im) { struct g_object_image *image; struct cached_image *cimg; int retval; ssize_t xw, yw; image=mem_calloc(sizeof(struct g_object_image)); global_goi = image; image->goti.go.mouse_event = g_text_mouse; image->goti.go.draw = img_draw_image; image->goti.go.destruct = img_destruct_image; image->goti.go.get_list = NULL; image->goti.link_num = im->link_num; image->goti.link_order = im->link_order; image->goti.map = NULL; /* image->goti.go.x is already filled image->goti.go.y is already filled */ if (im->align == AL_MIDDLE) image->goti.go.y = G_OBJ_ALIGN_MIDDLE; if (im->align == AL_TOP) image->goti.go.y = G_OBJ_ALIGN_TOP; if (im->autoscale_x && im->autoscale_y) { /* Autoscale requested */ xw = im->autoscale_x; yw = im->autoscale_y; image->goti.go.xw = (int)xw; image->goti.go.yw = (int)yw; image->xyw_meaning = MEANING_AUTOSCALE; }else{ /* Autoscale not requested */ xw = img_scale_h(d_opt->image_scale, im->xsize); yw = img_scale_v(d_opt->image_scale, im->ysize); image->goti.go.xw = (int)xw; image->goti.go.yw = (int)yw; image->xyw_meaning = MEANING_DIMS; } if (xw >= 0 && yw >= 0) { if (!is_image_size_sane(xw, yw)) { mem_free(image); return NULL; } } /* Put the data for javascript inside */ image->id = current_f_data->n_images++; image->name = stracpy(im->name); image->alt = stracpy(im->alt); image->orig_src = stracpy(im->src); image->border = im->border; image->vspace = im->vspace; image->hspace = im->hspace; image->src = stracpy(im->url); if (!(image->goti.go.xw && image->goti.go.yw)) { /* At least one is zero */ if (image->goti.go.xw < 0) image->goti.go.xw = 0; if (image->goti.go.yw < 0) image->goti.go.yw = 0; if (im->insert_flag) add_to_list(current_f_data->images, image); else image->list_entry.prev = image->list_entry.next = NULL; return image; } /* image->parent is already filled */ image->af = request_additional_file(current_f_data, im->url); if (image->goti.go.xw < 0 || image->goti.go.yw < 0) image->af->unknown_image_size = 1; image->background = hack_rgb(p->root->bg->sRGB); /* This supplies the result into image->cimg and global_cimg */ find_or_make_cached_image(image, im->url, d_opt->image_scale); cimg = global_cimg; next_chunk: retval = img_process_download(image, NULL); if (retval && !(cimg->state & 4)) goto next_chunk; image->goti.go.xw = (int)image->cimg->xww; image->goti.go.yw = (int)image->cimg->yww; if (cimg->state == 0 || cimg->state == 8 || image->af->unknown_image_size) if (image->af->need_reparse != -1) image->af->need_reparse = 1; if (im->insert_flag) add_to_list(current_f_data->images, image); else image->list_entry.prev = image->list_entry.next = NULL; return image; } #ifdef JS void change_image (struct g_object_image *goi, unsigned char *url, unsigned char *src, struct f_data *fdata) { /*struct cached_image *cimg;*/ global_goi = goi; mem_free(goi->src); goi->src = stracpy(url); if (goi->orig_src) mem_free(goi->orig_src); goi->orig_src = stracpy(src); if (!(goi->goti.go.xw && goi->goti.go.yw)) return; deref_cached_image(goi->cimg); goi->af = request_additional_file(fdata, url); goi->af->need_reparse = -1; find_or_make_cached_image(goi, url, fdata->opt.image_scale); /* Automatically sets up global_cimg */ refresh_image(fdata->fd, &goi->goti.go, 0); } #endif #endif int known_image_type(unsigned char *type) { #ifdef G if (!casestrcmp(type, cast_uchar "image/png")) return 1; if (!casestrcmp(type, cast_uchar "image/x-png")) return 1; if (!casestrcmp(type, cast_uchar "image/gif")) return 1; if (!casestrcmp(type, cast_uchar "image/x-xbitmap")) return 1; #ifdef HAVE_JPEG if (!casestrcmp(type, cast_uchar "image/jpeg")) return 1; if (!casestrcmp(type, cast_uchar "image/jpg")) return 1; if (!casestrcmp(type, cast_uchar "image/jpe")) return 1; if (!casestrcmp(type, cast_uchar "image/pjpe")) return 1; if (!casestrcmp(type, cast_uchar "image/pjpeg")) return 1; if (!casestrcmp(type, cast_uchar "image/pjpg")) return 1; #endif #ifdef HAVE_TIFF if (!casestrcmp(type, cast_uchar "image/tiff")) return 1; if (!casestrcmp(type, cast_uchar "image/tif")) return 1; #endif #ifdef HAVE_SVG if (!casestrcmp(type, cast_uchar "image/svg+xml")) return 1; if (!casestrcmp(type, cast_uchar "image/svg")) return 1; #endif #ifdef HAVE_WEBP if (!casestrcmp(type, cast_uchar "image/webp")) return 1; #endif #ifdef HAVE_AVIF if (!casestrcmp(type, cast_uchar "image/avif")) return 1; #endif #endif return 0; }