/* dip.c * Digital Image Processing routines * (c) 2000-2002 Karel 'Clock' Kulhavy * This file is a part of the Links program, released under GPL. * This does various utilities for digital image processing including font * rendering. */ #include "cfg.h" #ifdef G #include "links.h" #ifdef HAVE_MATH_H #include #endif /* HAVE_MATH_H */ /* #define this if you want to report missing letters to stderr. * Leave it commented up for normal operation and releases! */ /* #define REPORT_UNKNOWN 1 */ unsigned aspect=65536; /* aspect=65536 for 320x240 * aspect=157286 for 640x200 (tall pixels) * Defines aspect ratio of screen pixels. * aspect=(196608*xsize+ysize<<1)/(ysize<<1); * Default is 65536 because we assume square pixel * when not specified otherwise. Aspect is ratio of * the pixel height (in milimeters) to pixel width, * multiplied by 65536. */ /* Limitation: No input image's dimension may exceed 2^(32-1-8) pixels. */ /* Each input byte represents 1 byte (gray). The question whether 0 is * black or 255 is black doesn't matter. */ /* These constants represent contrast-enhancement and sharpen filter (which is one filter * together) that is applied onto the letters to enhance their appearance at low height. * They were determined by experiment for several values, interpolated, checked and tuned. * If you change them, don't wonder if the letters start to look strange. * The numers in the comments denote which height the line applies for. */ static_const float fancy_constants[64]={ (float)0,(float)3, /* 1 */ (float).1,(float)3, /* 2 */ (float).2,(float)3, /* 3 */ (float).3,(float)2.9, /* 4 */ (float).4,(float)2.7, /* 5 */ (float).4,(float)2.5, /* 6 */ (float).4,(float)2, /* 7 */ (float).5,(float)2, /* 8 */ (float).4,(float)2, /* 9 */ (float).38,(float)1.9, /* 10 */ (float).36,(float)1.8, /* 11 */ (float).33,(float)1.7, /* 12 */ (float).30,(float)1.6, /* 13 */ (float).25,(float)1.5, /* 14 */ (float).20,(float)1.5, /* 15 */ (float).15,(float)1.5, /* 16 */ (float).14,(float)1.5, /* 17 */ (float).13,(float)1.5, /* 18 */ (float).12,(float)1.5, /* 19 */ (float).12,(float)1.5, /* 20 */ (float).12,(float)1.5, /* 21 */ (float).12,(float)1.5, /* 22 */ (float).11,(float)1.5, /* 23 */ (float).10,(float)1.4, /* 24 */ (float).09,(float)1.3, /* 25 */ (float).08,(float)1.3, /* 26 */ (float).04,(float)1.2, /* 27 */ (float).04,(float)1.2, /* 28 */ (float).02,(float)1.1, /* 29 */ (float).02,(float)1.1, /* 30 */ (float).01,(float)1, /* 31 */ (float).01,(float)1 /* 32 */ }; /* This shall be hopefully reasonably fast and portable * We assume ix is <65536. If not, the letters will be smaller in * horizontal dimension (because of overflow) but this will not cause * a segfault. 65536 pixels wide bitmaps are not normal and will anyway * exhaust the memory. */ int compute_width(int ix, int iy, int required_height) { int width; unsigned long reg; reg=(unsigned long)aspect*(unsigned long)required_height; if (reg>=0x1000000UL) { /* It's big */ reg=(reg+32768)>>16; width=(int)((reg*ix+(iy>>1))/iy); }else{ /* It's small */ reg=(reg+128)>>8; iy<<=8; width=(int)((reg*ix+(iy>>1))/iy); } if (width<1) width=1; return width; } static struct lru font_cache; /* This is a cache for screen-ready colored bitmaps * of lettrs and/or alpha channels for these (are the * same size, only one byte per pixel and are used * for letters with an image under them ) */ #if defined(__i686__) || defined(__athlon__) || defined(__SSE2__) || defined(__x86_64__) || defined(__aarch64__) || defined(__alpha) || defined(__hppa) /* * On modern processors it is faster to do this in floating point. * Do it only if we are sure that the coprocessor is present. */ typedef double scale_t; #define USE_FP_SCALE #elif defined(HAVE_LONG_LONG) typedef unsigned long long scale_t; #else /* * This may overflow, but systems without long long are very old * and we will rather accept overflow on big images (65536 pixels) * than slowing down the process unacceptably with possibly emulated FPU. */ typedef unsigned long scale_t; #endif /* Each input byte represents 1 byte (gray). The question whether 0 is * black or 255 is black doesn't matter. */ static void add_col_gray(unsigned *my_restrict col_buf, unsigned char *my_restrict ptr, size_t line_skip, size_t n, unsigned weight) { for (;n;n--) { *col_buf+=weight*(*ptr); ptr+=line_skip; col_buf++; } } /* We assume unsigned short holds at least 16 bits. */ static void add_row_gray(unsigned *my_restrict row_buf, unsigned char *my_restrict ptr, size_t n, unsigned weight) { for (;n;n--) { *row_buf+=weight**ptr; ptr++; row_buf++; } } /* line_skip is in pixels. The column contains the whole pixels (R G B) * We assume unsigned short holds at least 16 bits. */ #if defined(__ICC) && defined(USE_FP_SCALE) /* ICC misoptimizes this function when inlining it */ ATTR_NOINLINE #endif static void add_col_color(scale_t *my_restrict col_buf, #ifdef __GNUC__ #if __GNUC__ == 3 volatile #endif #endif unsigned short *my_restrict ptr, size_t line_skip, size_t n, ulonglong weight) { if (!weight) return; #ifndef USE_FP_SCALE if (weight < 0x10000) { unsigned short weight_16 = (unsigned short)weight; for (;n;n--) { col_buf[0]+=(scale_t)((unsigned)weight_16*(unsigned)ptr[0]); col_buf[1]+=(scale_t)((unsigned)weight_16*(unsigned)ptr[1]); col_buf[2]+=(scale_t)((unsigned)weight_16*(unsigned)ptr[2]); ptr+=line_skip; col_buf+=3; } } else #endif { scale_t w = (scale_t)(longlong)weight; for (;n;n--) { col_buf[0]+=w*ptr[0]; col_buf[1]+=w*ptr[1]; col_buf[2]+=w*ptr[2]; ptr+=line_skip; col_buf+=3; } } } /* n is in pixels. pixel is 3 unsigned shorts in series */ /* We assume unsigned short holds at least 16 bits. */ static void add_row_color(scale_t *my_restrict row_buf, #ifdef __GNUC__ #if __GNUC__ == 3 volatile #endif #endif unsigned short *my_restrict ptr, size_t n, ulonglong weight) { if (!weight) return; #ifndef USE_FP_SCALE if (weight < 0x10000) { unsigned short weight_16 = (unsigned short)weight; for (;n;n--) { row_buf[0]+=(scale_t)((unsigned)weight_16*(unsigned)ptr[0]); row_buf[1]+=(scale_t)((unsigned)weight_16*(unsigned)ptr[1]); row_buf[2]+=(scale_t)((unsigned)weight_16*(unsigned)ptr[2]); ptr+=3; row_buf+=3; } } else #endif { scale_t w = (scale_t)(longlong)weight; for (;n;n--) { row_buf[0]+=w*ptr[0]; row_buf[1]+=w*ptr[1]; row_buf[2]+=w*ptr[2]; ptr+=3; row_buf+=3; } } } /* We assume unsigned holds at least 32 bits */ static void emit_and_bias_col_gray(unsigned *my_restrict col_buf, unsigned char *my_restrict out, size_t line_skip, size_t n, unsigned weight) { unsigned half=weight>>1; for (;n;n--) { *out=*col_buf/weight; out+=line_skip; *col_buf++=half; } } /* We assume unsigned holds at least 32 bits */ static void emit_and_bias_row_gray(unsigned *my_restrict row_buf, unsigned char *my_restrict out, size_t n, unsigned weight) { unsigned half=weight>>1; for (;n;n--) { *out++=*row_buf/weight; *row_buf++=half; } } /* We assume unsigned holds at least 32 bits */ static void bias_buf_gray(unsigned *my_restrict col_buf, size_t n, unsigned half) { for (;n;n--) *col_buf++=half; } /* We assume unsigned holds at least 32 bits */ static void bias_buf_color(scale_t *my_restrict col_buf, size_t n, scale_t half) { for (;n;n--) { col_buf[0]=half; col_buf[1]=half; col_buf[2]=half; col_buf+=3; } /* System activated */ } #ifdef USE_FP_SCALE #define op(x) ((x) * inv_weight) #else #define op(x) (sizeof(unsigned long) < sizeof(scale_t) && (x) == (unsigned long)(x) ? (unsigned long)(x) / weight : (x) / weight) #endif /* line skip is in pixels. Pixel is 3*unsigned short */ /* We assume unsigned holds at least 32 bits */ /* We assume unsigned short holds at least 16 bits. */ static void emit_and_bias_col_color(scale_t *my_restrict col_buf, unsigned short *my_restrict out, size_t line_skip, size_t n, unsigned weight) { scale_t half=(scale_t)weight / 2; #ifdef USE_FP_SCALE scale_t inv_weight = (scale_t)1 / weight; #endif for (;n;n--) { out[0]=(unsigned short)op(col_buf[0]); col_buf[0]=half; out[1]=(unsigned short)op(col_buf[1]); col_buf[1]=half; /* The following line is an enemy of the State and will be * prosecuted according to the Constitution of The United States * Cap. 20/3 ix. Sel. Bill 12/1920 * Moses 12/20 Erizea farizea 2:2:1:14 */ out[2]=(unsigned short)op(col_buf[2]); col_buf[2]=half; out+=line_skip; col_buf+=3; } } /* n is in pixels. pixel is 3 unsigned shorts in series. */ /* We assume unsigned holds at least 32 bits */ /* We assume unsigned short holds at least 16 bits. */ static void emit_and_bias_row_color(scale_t *my_restrict row_buf, unsigned short *my_restrict out, size_t n, unsigned weight) { scale_t half=(scale_t)weight / 2; #ifdef USE_FP_SCALE scale_t inv_weight = (scale_t)1 / weight; #endif for (;n;n--) { out[0]=(unsigned short)op(row_buf[0]); row_buf[0]=half; out[1]=(unsigned short)op(row_buf[1]); row_buf[1]=half; out[2]=(unsigned short)op(row_buf[2]); row_buf[2]=half; out+=3; row_buf+=3; } } #undef op /* For enlargement only -- does linear filtering. * Allocates output and frees input. * We assume unsigned holds at least 32 bits */ static void enlarge_gray_horizontal(unsigned char *in, size_t ix, size_t y, unsigned char **out, size_t ox) { unsigned *col_buf; size_t total; size_t out_pos,in_pos,in_begin,in_end; unsigned half = (unsigned)((ox - 1) >> 1); unsigned char *outptr; unsigned char *inptr; if (ox && ox * y / ox != y) overalloc(); if (ox * y > MAX_SIZE_T) overalloc(); outptr=mem_alloc(ox * y); inptr=in; *out=outptr; if (ix==1) { /* Dull copying */ for (;y;y--) { memset(outptr,*inptr,ox); outptr+=ox; inptr++; } mem_free(in); }else{ total=(ix-1)*(ox-1); if (y > MAX_SIZE_T / sizeof(*col_buf)) overalloc(); col_buf=mem_alloc(y * sizeof(*col_buf)); bias_buf_gray(col_buf, y, half); out_pos=0; in_pos=0; again: in_begin=in_pos; in_end=in_pos+ox-1; add_col_gray(col_buf, inptr, ix, y, (unsigned)(in_end - out_pos)); add_col_gray(col_buf, inptr + 1, ix, y, (unsigned)(out_pos - in_begin)); emit_and_bias_col_gray(col_buf, outptr, ox, y, (unsigned)(ox - 1)); outptr++; out_pos+=ix-1; if (out_pos>in_end) { in_pos=in_end; inptr++; } if (out_pos>total) { mem_free(in); mem_free(col_buf); return; } goto again; } /* Rohan, oh Rohan... */ /* ztracena zeme :) */ } static inline longlong multiply_int(size_t a, size_t b) { #ifndef HAVE_LONG_LONG volatile #endif longlong result = (ulonglong)a * (ulonglong)b; #if !defined(__TINYC__) && !defined(HAVE_LONG_LONG) if (result / a != (longlong)b) { /*fprintf(stderr, "%lld, %lld, %d, %d\n", result / a, result, a, b);*/ overflow(); } #endif #ifndef HAVE_LONG_LONG if (result == result + 1 || result == result - 1) overflow(); #endif return result; } /* For enlargement only -- does linear filtering * Frees input and allocates output. * We assume unsigned holds at least 32 bits */ static void enlarge_color_horizontal(unsigned short *in, size_t ix, size_t y, unsigned short **outa, size_t ox) { #ifdef HAVE_OPENMP int use_omp; #endif int n_threads; size_t alloc_size; scale_t *col_buf; size_t a; size_t skip=3*ix; size_t oskip=3*ox; unsigned short *out; if (!in) { *outa = NULL; return; } if (ix==ox) { *outa=in; return; } if (ox && ox * y / ox != y) overalloc(); if (ox * y > MAX_SIZE_T / 3 / sizeof(*out)) overalloc(); out = mem_alloc_mayfail(sizeof(*out) * 3 * ox * y); *outa=out; if (!out) { mem_free(in); return; } if (ix==1) { unsigned short *inp = in; for (;y;y--,inp+=3) for (a=ox;a;a--,out+=3) { out[0]=inp[0]; out[1]=inp[1]; out[2]=inp[2]; } mem_free(in); return; } multiply_int(ix-1,ox-1); n_threads = omp_start(); #ifdef HAVE_OPENMP use_omp = !OPENMP_NONATOMIC & (n_threads > 1) & (ox >= 24); if (!use_omp) n_threads = 1; #endif if (y > (MAX_SIZE_T - SMP_ALIGN + 1) / 3 / sizeof(*col_buf)) overalloc(); alloc_size = y * 3 * sizeof(*col_buf); alloc_size = (alloc_size + SMP_ALIGN - 1) & ~(SMP_ALIGN - 1); if (alloc_size > MAX_SIZE_T / n_threads) overalloc(); col_buf = mem_alloc_mayfail(alloc_size * n_threads); if (!col_buf) goto skip_omp; #ifdef HAVE_OPENMP #pragma omp parallel default(none) firstprivate(col_buf,alloc_size,in,out,ix,ox,y,skip,oskip) if (use_omp) #endif { scale_t *thread_col_buf; ssize_t out_idx; thread_col_buf = (scale_t *)((char *)col_buf + alloc_size * omp_get_thread_num()); bias_buf_color(thread_col_buf, y, (scale_t)(ox - 1) / 2); #ifdef HAVE_OPENMP #pragma omp for nowait #endif for (out_idx = 0; out_idx <= (ssize_t)(ox - 1); out_idx++) { ulonglong out_pos, in_pos, in_end; size_t in_idx; out_pos = (ulonglong)out_idx * (ix - 1); if (out_idx) in_idx = (out_pos - 1) / (ox - 1); else in_idx = 0; in_pos = (ulonglong)in_idx * (ox - 1); in_end = in_pos + (ox - 1); add_col_color(thread_col_buf, in + in_idx * 3, skip, y, in_end - out_pos); add_col_color(thread_col_buf, in + (in_idx + 1) * 3, skip, y, out_pos - in_pos); emit_and_bias_col_color(thread_col_buf, out + out_idx * 3, oskip, y, (unsigned)(ox - 1)); } } skip_omp: omp_end(); mem_free(in); if (col_buf) mem_free(col_buf); else { mem_free(out); *outa = NULL; } } /* Works for both enlarging and diminishing. Linear resample, no low pass. * Automatically mem_frees the "in" and allocates "out". */ /* We assume unsigned holds at least 32 bits */ static void scale_gray_horizontal(unsigned char *in, size_t ix, size_t y, unsigned char **out, size_t ox) { unsigned *col_buf; size_t total=ix * ox; size_t out_pos, in_pos, in_begin, in_end, out_end; unsigned char *outptr; unsigned char *inptr; if (ix MAX_SIZE_T) overalloc(); outptr=mem_alloc(ox*y); inptr=in; *out=outptr; if (y > MAX_SIZE_T / sizeof(*col_buf)) overalloc(); col_buf = mem_alloc(y * sizeof(*col_buf)); bias_buf_gray(col_buf, y, (unsigned)(ix >> 1)); out_pos=0; in_pos=0; again: in_begin=in_pos; in_end=in_pos+ox; out_end=out_pos+ix; if (in_beginout_end)in_end=out_end; add_col_gray(col_buf, inptr, ix, y, (unsigned)(in_end - in_begin)); in_end=in_pos+ox; if (out_end>=in_end) { in_pos=in_end; inptr++; } if (out_end<=in_end) { emit_and_bias_col_gray(col_buf, outptr, ox, y, (unsigned)ix); out_pos=out_pos+ix; outptr++; } if (out_pos==total) { mem_free(in); mem_free(col_buf); return; } goto again; } /* Works for both enlarging and diminishing. Linear resample, no low pass. * Does only one color component. * Frees ina and allocates outa. * If ox*3<=ix, and display_optimize, performs optimization for LCD. */ static void scale_color_horizontal(unsigned short *in, size_t ix, size_t y, unsigned short **outa, size_t ox) { #ifdef HAVE_OPENMP int use_omp; #endif int n_threads; size_t alloc_size; scale_t *col_buf; size_t skip = 3 * ix; size_t oskip = 3 * ox; unsigned short *out; if (!in) { *outa = NULL; return; } if (ix==ox) { *outa=in; return; } if (ix MAX_SIZE_T / 3 / sizeof(*out)) overalloc(); out = mem_alloc_mayfail(sizeof(*out) * 3 * ox * y); *outa=out; if (!out) { mem_free(in); return; } n_threads = omp_start(); #ifdef HAVE_OPENMP use_omp = !OPENMP_NONATOMIC & (n_threads > 1) & (ox >= 24); if (!use_omp) n_threads = 1; #endif if (y > (MAX_SIZE_T - SMP_ALIGN + 1) / 3 / sizeof(*col_buf)) overalloc(); alloc_size = y * 3 * sizeof(*col_buf); alloc_size = (alloc_size + SMP_ALIGN - 1) & ~(SMP_ALIGN - 1); if (alloc_size > MAX_SIZE_T / n_threads) overalloc(); col_buf = mem_alloc_mayfail(alloc_size * n_threads); if (!col_buf) goto skip_omp; #ifdef HAVE_OPENMP #pragma omp parallel default(none) firstprivate(col_buf,alloc_size,in,out,ix,ox,y,skip,oskip) if (use_omp) #endif { scale_t *thread_col_buf; ssize_t out_idx; thread_col_buf = (scale_t *)((char *)col_buf + alloc_size * omp_get_thread_num()); bias_buf_color(thread_col_buf, y, (scale_t)ix / 2); #ifdef HAVE_OPENMP #pragma omp for nowait #endif for (out_idx = 0; out_idx < (ssize_t)ox; out_idx++) { ulonglong out_pos, out_end, in_pos; size_t in_idx; out_pos = (ulonglong)out_idx * ix; out_end = out_pos + ix; in_idx = out_pos / ox; in_pos = (ulonglong)in_idx * ox; do { ulonglong in_begin, in_end; in_begin = in_pos; in_end = in_pos + ox; if (in_begin < out_pos) in_begin = out_pos; if (in_end > out_end) in_end = out_end; add_col_color(thread_col_buf, in + in_idx * 3, skip, y, in_end - in_begin); in_idx++; in_pos += ox; } while (in_pos < out_end); emit_and_bias_col_color(thread_col_buf, out + out_idx * 3, oskip, y, (unsigned)ix); } } skip_omp: omp_end(); mem_free(in); if (col_buf) mem_free(col_buf); else { mem_free(out); *outa = NULL; } } /* For magnification only. Does linear filtering. */ /* We assume unsigned holds at least 32 bits */ static void enlarge_gray_vertical(unsigned char *in, size_t x, size_t iy, unsigned char **out, size_t oy) { unsigned *row_buf; size_t total; size_t out_pos,in_pos,in_begin,in_end; unsigned half = (unsigned)((oy - 1) >> 1); unsigned char *outptr; unsigned char *inptr; if (iy==1) { if (x && x * oy / x != oy) overalloc(); if (x * oy > MAX_SIZE_T) overalloc(); outptr = mem_alloc(oy * x); *out=outptr; for(;oy;oy--,outptr+=x) memcpy(outptr,in,x); mem_free(in); } else if (iy==oy) { *out=in; }else{ if (x && x * oy / x != oy) overalloc(); if (x * oy > MAX_SIZE_T) overalloc(); outptr = mem_alloc(oy * x); inptr=in; *out=outptr; total=(iy-1)*(oy-1); if (x > MAX_SIZE_T / sizeof(*row_buf)) overalloc(); row_buf=mem_alloc(x * sizeof(*row_buf)); bias_buf_gray(row_buf, x, half); out_pos=0; in_pos=0; again: in_begin=in_pos; in_end=in_pos+oy-1; add_row_gray(row_buf, inptr, x, (unsigned)(in_end - out_pos)); add_row_gray(row_buf, inptr + x, x, (unsigned)(out_pos - in_begin)); emit_and_bias_row_gray(row_buf, outptr, x, (unsigned)(oy - 1)); outptr+=x; out_pos+=iy-1; if (out_pos>in_end) { in_pos=in_end; inptr+=x; } if (out_pos>total) { mem_free(in); mem_free(row_buf); return; } goto again; } } /* For magnification only. Does linear filtering */ /* We assume unsigned holds at least 32 bits */ static void enlarge_color_vertical(unsigned short *in, size_t x, size_t iy, unsigned short **outa, size_t oy) { #ifdef HAVE_OPENMP int use_omp; #endif int n_threads; size_t alloc_size; scale_t *row_buf; unsigned short *out; if (!in) { *outa = NULL; return; } if (iy==oy) { *outa=in; return; } /* Rivendell */ if (x && x * oy / x != oy) overalloc(); if (x * oy > MAX_SIZE_T / 3 / sizeof(*out)) overalloc(); out = mem_alloc_mayfail(sizeof(*out) * 3 * oy * x); *outa=out; if (!out) { mem_free(in); return; } if (iy==1) { for (;oy;oy--) { memcpy(out,in,3*x*sizeof(*out)); out+=3*x; } mem_free(in); return; } multiply_int(iy-1,oy-1); n_threads = omp_start(); #ifdef HAVE_OPENMP use_omp = (!OPENMP_NONATOMIC | !(x & 3)) & (n_threads > 1) & (oy >= 24); if (!use_omp) n_threads = 1; #endif if (x > (MAX_SIZE_T - SMP_ALIGN + 1) / 3 / sizeof(*row_buf)) overalloc(); alloc_size = x * 3 * sizeof(*row_buf); alloc_size = (alloc_size + SMP_ALIGN - 1) & ~(SMP_ALIGN - 1); if (alloc_size > MAX_SIZE_T / n_threads) overalloc(); row_buf = mem_alloc_mayfail(alloc_size * n_threads); if (!row_buf) goto skip_omp; #ifdef HAVE_OPENMP #pragma omp parallel default(none) firstprivate(row_buf,alloc_size,in,out,x,iy,oy) if (use_omp) #endif { scale_t *thread_row_buf; ssize_t out_idx; thread_row_buf = (scale_t *)((char *)row_buf + alloc_size * omp_get_thread_num()); bias_buf_color(thread_row_buf,x,(scale_t)(oy-1) / 2); #ifdef HAVE_OPENMP #pragma omp for nowait #endif for (out_idx = 0; out_idx <= (ssize_t)(oy - 1); out_idx++) { ulonglong out_pos, in_pos, in_end; size_t in_idx; out_pos = (ulonglong)out_idx * (iy - 1); if (out_idx) in_idx = (out_pos - 1) / (oy - 1); else in_idx = 0; in_pos = (ulonglong)in_idx * (oy - 1); in_end = in_pos + oy - 1; add_row_color(thread_row_buf, in + in_idx * 3 * x, x, in_end - out_pos); add_row_color(thread_row_buf, in + (in_idx + 1) * 3 * x, x, out_pos - in_pos); emit_and_bias_row_color(thread_row_buf, out + out_idx * 3 * x, x, (unsigned)(oy - 1)); } } skip_omp: omp_end(); mem_free(in); if (row_buf) mem_free(row_buf); else { mem_free(out); *outa = NULL; } } /* Both enlarges and diminishes. Linear filtering. * Automatically allocates output and frees input. * We assume unsigned holds at least 32 bits */ static void scale_gray_vertical(unsigned char *in, size_t x, size_t iy, unsigned char **out, size_t oy) { unsigned *row_buf; size_t total=iy*oy; size_t out_pos, in_pos, in_begin, in_end, out_end; unsigned char *outptr; unsigned char *inptr; /* Snow White, Snow White... */ if (iy MAX_SIZE_T) overalloc(); outptr=mem_alloc(x*oy); inptr=in; *out=outptr; if (x > MAX_SIZE_T / sizeof(*row_buf)) overalloc(); row_buf = mem_calloc(x * sizeof(*row_buf)); bias_buf_gray(row_buf, x, (unsigned)(iy >> 1)); out_pos=0; in_pos=0; again: in_begin=in_pos; in_end=in_pos+oy; out_end=out_pos+iy; if (in_beginout_end)in_end=out_end; add_row_gray(row_buf, inptr, x, (unsigned)(in_end - in_begin)); in_end=in_pos+oy; if (out_end>=in_end) { in_pos=in_end; inptr+=x; } if (out_end<=in_end) { emit_and_bias_row_gray(row_buf, outptr, x, (unsigned)iy); out_pos=out_pos+iy; outptr+=x; } if (out_pos==total) { mem_free(in); mem_free(row_buf); return; } goto again; } /* Both enlarges and diminishes. Linear filtering. Sizes are in pixels. Sizes are not in bytes. 1 pixel=3 unsigned shorts. We assume unsigned short can hold at least 16 bits. We assume unsigned holds at least 32 bits. */ static void scale_color_vertical(unsigned short *in, size_t x, size_t iy, unsigned short **outa, size_t oy) { #ifdef HAVE_OPENMP int use_omp; #endif int n_threads; size_t alloc_size; scale_t *row_buf; unsigned short *out; if (!in) { *outa = NULL; return; } if (iy==oy) { *outa=in; return; } if (iy MAX_SIZE_T / 3 / sizeof(*out)) overalloc(); out = mem_alloc_mayfail(sizeof(*out) * 3 * oy * x); *outa=out; if (!out) { mem_free(in); return; } n_threads = omp_start(); #ifdef HAVE_OPENMP use_omp = (!OPENMP_NONATOMIC | !(x & 3)) & (n_threads > 1) & (oy >= 24); if (!use_omp) n_threads = 1; #endif if (x > (MAX_SIZE_T - SMP_ALIGN + 1) / 3 / sizeof(*row_buf)) overalloc(); alloc_size = x * 3 * sizeof(*row_buf); alloc_size = (alloc_size + SMP_ALIGN - 1) & ~(SMP_ALIGN - 1); if (alloc_size > MAX_SIZE_T / n_threads) overalloc(); row_buf = mem_alloc_mayfail(alloc_size * n_threads); if (!row_buf) goto skip_omp; #ifdef HAVE_OPENMP #pragma omp parallel default(none) firstprivate(row_buf,alloc_size,in,out,x,iy,oy) if (use_omp) #endif { scale_t *thread_row_buf; ssize_t out_idx; thread_row_buf = (scale_t *)((char *)row_buf + alloc_size * omp_get_thread_num()); bias_buf_color(thread_row_buf,x,(scale_t)iy / 2); #ifdef HAVE_OPENMP #pragma omp for nowait #endif for (out_idx = 0; out_idx < (ssize_t)oy; out_idx++) { ulonglong out_pos, out_end, in_pos; size_t in_idx; out_pos = (ulonglong)out_idx * iy; out_end = out_pos + iy; in_idx = out_pos / oy; in_pos = (ulonglong)in_idx * oy; do { ulonglong in_begin, in_end; in_begin = in_pos; in_end = in_pos + oy; if (in_begin < out_pos) in_begin = out_pos; if (in_end > out_end) in_end = out_end; add_row_color(thread_row_buf, in + in_idx * 3 * x, x, in_end - in_begin); in_idx++; in_pos += oy; } while (in_pos < out_end); emit_and_bias_row_color(thread_row_buf, out + out_idx * 3 * x, x, (unsigned)iy); } } skip_omp: omp_end(); mem_free(in); if (row_buf) mem_free(row_buf); else { mem_free(out); *outa = NULL; } } /* Scales grayscale 8-bit map. Both enlarges and diminishes. Uses either low * pass or bilinear filtering. Automatically mem_frees the "in". * Automatically allocates "out". */ static void scale_gray(unsigned char *in, size_t ix, size_t iy, unsigned char **out, size_t ox, size_t oy) { unsigned char *intermediate_buffer; if (!ix||!iy) { if (in) mem_free(in); if (ox && ox * oy / ox != oy) overalloc(); if (ox * oy > MAX_SIZE_T) overalloc(); *out = mem_calloc(ox * oy); return; } if (ix*oy=1. * Performs realloc onto the buffer after decimation to save memory. */ static void decimate_3(unsigned short **data0, size_t x, size_t y) { unsigned short *data=*data0; unsigned short *ahead=data; size_t i, futuresize; if (!data) return; if (x && (size_t)x * (size_t)y / (size_t)x != (size_t)y) overalloc(); if ((size_t)x * (size_t)y > MAX_SIZE_T / 3 / sizeof(**data0)) overalloc(); futuresize = x * y * 3 * sizeof(**data0); #ifdef DEBUG if (!(x>0&&y>0)) internal_error("zero width or height in decimate_3"); #endif /* #Ifdef DEBUG */ if (display_optimize==1) { if (x==1) { for (;y;y--,ahead+=9,data+=3) { data[0]=(ahead[0]+ahead[0]+ahead[3])/3; data[1]=(ahead[1]+ahead[4]+ahead[7])/3; data[2]=(ahead[5]+ahead[8]+ahead[8])/3; } }else{ for (;y;y--) { data[0]=(ahead[0]+ahead[0]+ahead[3])/3; data[1]=(ahead[1]+ahead[4]+ahead[7])/3; data[2]=(ahead[5]+ahead[8]+ahead[11])/3; for (ahead+=9,data+=3,i=x-2;i;i--,ahead+=9,data+=3) { data[0]=(ahead[-3]+ahead[0]+ahead[3])/3; data[1]=(ahead[1]+ahead[4]+ahead[7])/3; data[2]=(ahead[5]+ahead[8]+ahead[11])/3; } data[0]=(ahead[-3]+ahead[0]+ahead[3])/3; data[1]=(ahead[1]+ahead[4]+ahead[7])/3; data[2]=(ahead[5]+ahead[8]+ahead[8])/3; ahead+=9,data+=3; } } }else{ /* display_optimize==2 */ if (x==1) { for (;y;y--,ahead+=9,data+=3) { data[0]=(ahead[3]+ahead[6]+ahead[6])/3; data[1]=(ahead[1]+ahead[4]+ahead[7])/3; data[2]=(ahead[2]+ahead[2]+ahead[5])/3; } }else{ for (;y;y--) { data[0]=(ahead[3]+ahead[6]+ahead[9])/3; data[1]=(ahead[1]+ahead[4]+ahead[7])/3; data[2]=(ahead[2]+ahead[2]+ahead[5])/3; for (ahead+=9,data+=3,i=x-2;i;i--,ahead+=9,data+=3) { data[0]=(ahead[3]+ahead[6]+ahead[9])/3; data[1]=(ahead[1]+ahead[4]+ahead[7])/3; data[2]=(ahead[-1]+ahead[2]+ahead[5])/3; } data[0]=(ahead[3]+ahead[6]+ahead[6])/3; data[1]=(ahead[1]+ahead[4]+ahead[7])/3; data[2]=(ahead[-1]+ahead[2]+ahead[5])/3; ahead+=9,data+=3; } } } *data0=mem_realloc(*data0,futuresize); } /* Scales color 48-bits-per-pixel bitmap. Both enlarges and diminishes. Uses * either low pass or bilinear filtering. The memory organization for both * input and output are red, green, blue. All three of them are unsigned shorts 0-65535. * Allocates output and frees input * We assume unsigned short holds at least 16 bits. */ void scale_color(unsigned short *in, size_t ix, size_t iy, unsigned short **out, size_t ox, size_t oy) { unsigned short *intermediate_buffer; int do_optimize; size_t ox0 = ox; if (!ix||!iy) { if (in) mem_free(in); if (ox && ox * oy / ox != oy) overalloc(); if (ox * oy > MAX_SIZE_T / 3 / sizeof(**out)) overalloc(); *out = mem_calloc_mayfail(ox * oy * sizeof(**out) * 3); return; } if (display_optimize&&ox*3<=ix) { do_optimize=1; ox0=ox; ox*=3; }else do_optimize=0; if (ix*oy=2) { cmask=255-mask; dest[0]=(mask*r255+cmask*r0+127)/255; dest[1]=(mask*g255+cmask*g0+127)/255; dest[2]=(mask*b255+cmask*b0+127)/255; }else{ if (mask) { dest[0]=r255; dest[1]=g255; dest[2]=b255; }else{ dest[0]=r0; dest[1]=g0; dest[2]=b0; } } dest+=3; } } /* We assume unsigned short holds at least 16 bits. */ void agx_and_uc_32_to_48_table(unsigned short *my_restrict dest, const unsigned char *my_restrict src, size_t length, unsigned short *my_restrict table, unsigned short rb, unsigned short gb, unsigned short bb) { unsigned char alpha, calpha; unsigned short ri, gi, bi; for (;length;length--) { ri=table[src[0]]; gi=table[src[1]+256]; bi=table[src[2]+512]; alpha=src[3]; src+=4; if (((alpha+1)&255)>=2) { calpha=255-alpha; dest[0]=(ri*alpha+calpha*rb+127)/255; dest[1]=(gi*alpha+calpha*gb+127)/255; dest[2]=(bi*alpha+calpha*bb+127)/255; } else { if (alpha) { dest[0]=ri; dest[1]=gi; dest[2]=bi; } else { dest[0]=rb; dest[1]=gb; dest[2]=bb; } } dest+=3; } } /* src is a block of four-bytes RGBA. All bytes are gamma corrected. length is * number of pixels. output is input powered to the given gamma, passed into * dest. src and dest may be identical and it will work. rb, gb, bb are 0-65535 * in linear monitor output photon space */ /* We assume unsigned short holds at least 16 bits. */ void agx_and_uc_32_to_48(unsigned short *my_restrict dest, const unsigned char *my_restrict src, size_t length, float red_gamma, float green_gamma, float blue_gamma, unsigned short rb, unsigned short gb, unsigned short bb) { float_double r,g,b; unsigned char alpha, calpha; unsigned short ri,gi,bi; const float_double inv_255=(float_double)(1/255.); for (;length;length--) { r=src[0]; g=src[1]; b=src[2]; alpha=src[3]; src+=4; r*=inv_255; g*=inv_255; b*=inv_255; r=fd_pow(r,red_gamma); g=fd_pow(g,green_gamma); b=fd_pow(b,blue_gamma); ri=(unsigned)((r*65535)+(float_double)0.5); gi=(unsigned)((g*65535)+(float_double)0.5); bi=(unsigned)((b*65535)+(float_double)0.5); cmd_limit_16(ri); cmd_limit_16(gi); cmd_limit_16(bi); if (((alpha+1)&255)>=2) { calpha=255-alpha; dest[0]=(unsigned short)((ri*alpha+calpha*rb+127U)/255U); dest[1]=(unsigned short)((gi*alpha+calpha*gb+127U)/255U); dest[2]=(unsigned short)((bi*alpha+calpha*bb+127U)/255U); }else{ if (alpha) { dest[0]=ri; dest[1]=gi; dest[2]=bi; }else{ dest[0]=rb; dest[1]=gb; dest[2]=bb; } } dest+=3; } } /* src is a block of four-bytes RGBA. All bytes are gamma corrected. length is * number of pixels. output is input powered to the given gamma, passed into * dest. src and dest may be identical and it will work. rb, gb, bb are 0-65535 * in linear monitor output photon space. alpha 255 means full image no background. */ /* We assume unsigned short holds at least 16 bits. */ void agx_and_uc_64_to_48(unsigned short *my_restrict dest, const unsigned short *my_restrict src, size_t length, float red_gamma, float green_gamma, float blue_gamma, unsigned short rb, unsigned short gb, unsigned short bb) { float_double r,g,b; unsigned short alpha, calpha; unsigned short ri,gi,bi; const float_double inv_65535=(float_double)(1/65535.); for (;length;length--) { r=src[0]; g=src[1]; b=src[2]; alpha=src[3]; src+=4; r*=inv_65535; g*=inv_65535; b*=inv_65535; r=fd_pow(r,red_gamma); g=fd_pow(g,green_gamma); b=fd_pow(b,blue_gamma); ri=(unsigned short)(r*65535+(float_double)0.5); gi=(unsigned short)(g*65535+(float_double)0.5); bi=(unsigned short)(b*65535+(float_double)0.5); cmd_limit_16(ri); cmd_limit_16(gi); cmd_limit_16(bi); if (((alpha+1)&65535)>=2) { calpha=65535-alpha; dest[0]=(unsigned short)((ri*(unsigned)alpha+(unsigned)calpha*rb+32767U)/65535U); dest[1]=(unsigned short)((gi*(unsigned)alpha+(unsigned)calpha*gb+32767U)/65535U); dest[2]=(unsigned short)((bi*(unsigned)alpha+(unsigned)calpha*bb+32767U)/65535U); }else{ if (alpha) { dest[0]=ri; dest[1]=gi; dest[2]=bi; }else{ dest[0]=rb; dest[1]=gb; dest[2]=bb; } } dest+=3; } } /* src is a block of four-bytes RGBA. All bytes are gamma corrected. length is * number of pixels. output is input powered to the given gamma, passed into * dest. src and dest may be identical and it will work. rb, gb, bb are 0-65535 * in linear monitor output photon space. alpha 255 means full image no background. * We assume unsigned short holds at least 16 bits. */ void agx_and_uc_64_to_48_table(unsigned short *my_restrict dest, const unsigned short *my_restrict src, size_t length, unsigned short *my_restrict gamma_table, unsigned short rb, unsigned short gb, unsigned short bb) { unsigned short alpha, calpha; unsigned short ri,gi,bi; for (;length;length--) { ri=gamma_table[*src]; gi=gamma_table[src[1]+65536]; bi=gamma_table[src[2]+131072]; alpha=src[3]; src+=4; if (((alpha+1)&65535)>=2) { calpha=65535-alpha; dest[0]=(ri*alpha+calpha*rb+32767)/65535; dest[1]=(gi*alpha+calpha*gb+32767)/65535; dest[2]=(bi*alpha+calpha*bb+32767)/65535; }else{ if (alpha) { dest[0]=ri; dest[1]=gi; dest[2]=bi; }else{ dest[0]=rb; dest[1]=gb; dest[2]=bb; } } dest+=3; } } /* src is a block of three-bytes. All bytes are gamma corrected. length is * number of triplets. output is input powered to the given gamma, passed into * dest. src and dest may be identical and it will work. * We assume unsigned short holds at least 16 bits. */ void agx_48_to_48(unsigned short *my_restrict dest, const unsigned short *my_restrict src, size_t length, float red_gamma, float green_gamma, float blue_gamma) { float_double a; const float_double inv_65535=(float_double)(1/65535.); for (;length;length--,src+=3,dest+=3) { a=src[0]; a*=inv_65535; a=fd_pow(a,red_gamma); dest[0]=(unsigned short)((a*65535)+(float_double)0.5); cmd_limit_16(dest[0]); a=src[1]; a*=inv_65535; a=fd_pow(a,green_gamma); dest[1]=(unsigned short)((a*65535)+(float_double)0.5); cmd_limit_16(dest[1]); a=src[2]; a*=inv_65535; a=fd_pow(a,blue_gamma); dest[2]=(unsigned short)((a*65535)+(float_double)0.5); cmd_limit_16(dest[2]); } } /* src is a block of three-bytes. All bytes are gamma corrected. length is * number of triples. output is input powered to the given gamma, passed into * dest. src and dest may be identical and it will work. * We assume unsigned short holds at least 16 bits. */ void agx_48_to_48_table(unsigned short *my_restrict dest, const unsigned short *my_restrict src, size_t length, unsigned short *my_restrict table) { for (;length;length--,src+=3,dest+=3) { dest[0]=table[*src]; dest[1]=table[src[1]+65536]; dest[2]=table[src[2]+131072]; } } /* src is a block of three-bytes. All bytes are gamma corrected. length is * number of triples. output is input powered to the given gamma, passed into * dest. src and dest may be identical and it will work. * We assume unsigned short holds at least 16 bits. */ void agx_24_to_48(unsigned short *my_restrict dest, const unsigned char *my_restrict src, size_t length, float red_gamma, float green_gamma, float blue_gamma) { float_double a; const float_double inv_255=(float_double)(1/255.); for (;length;length--,src+=3,dest+=3) { a=*src; a*=inv_255; a=fd_pow(a,red_gamma); dest[0]=(unsigned short)((a*65535)+(float_double)0.5); cmd_limit_16(dest[0]); a=src[1]; a*=inv_255; a=fd_pow(a,green_gamma); dest[1]=(unsigned short)((a*65535)+(float_double)0.5); cmd_limit_16(dest[1]); a=src[2]; a*=inv_255; a=fd_pow(a,blue_gamma); dest[2]=(unsigned short)((a*65535)+(float_double)0.5); cmd_limit_16(dest[2]); } } /* Allocates new gamma_table and fills it with mapping 8 bits -> * power to user_gamma/cimg->*_gamma -> 16 bits * We assume unsigned short holds at least 16 bits. */ void make_gamma_table(struct cached_image *cimg) { float_double rg=(float_double)((float_double)(user_gamma tcc_hack) / cimg->red_gamma); float_double gg=(float_double)((float_double)(user_gamma tcc_hack) / cimg->green_gamma); float_double bg=(float_double)((float_double)(user_gamma tcc_hack) / cimg->blue_gamma); int a; unsigned short *ptr_16; unsigned short last_val; const float_double inv_255=(float_double)(1/255.); const float_double inv_65535=(float_double)(1/65535.); if (cimg->buffer_bytes_per_pixel<=4) { /* 8-bit */ ptr_16=mem_alloc(768*sizeof(*(cimg->gamma_table))); cimg->gamma_table=ptr_16; for (a=0;a<256;a++,ptr_16++) { last_val = (unsigned short)(65535*fd_pow((float_double)a*inv_255,rg)+(float_double)0.5); cmd_limit_16(last_val); *ptr_16 = last_val; } for (a=0;a<256;a++,ptr_16++) { last_val = (unsigned short)(65535*fd_pow((float_double)a*inv_255,gg)+(float_double)0.5); cmd_limit_16(last_val); *ptr_16 = last_val; } for (a=0;a<256;a++,ptr_16++) { last_val = (unsigned short)(65535*fd_pow((float_double)a*inv_255,bg)+(float_double)0.5); cmd_limit_16(last_val); *ptr_16 = last_val; } }else{ int x_slow_fpu; if (gamma_bits != 2) x_slow_fpu = !gamma_bits; else x_slow_fpu = slow_fpu == 1; last_val = 0; /* against warning */ /* 16-bit */ ptr_16=mem_alloc(196608*sizeof(*(cimg->gamma_table))); cimg->gamma_table=ptr_16; for (a=0;a<0x10000;a++,ptr_16++) { if (!x_slow_fpu || !(a & 0xff)) { last_val = (unsigned short)(65535*fd_pow((float_double)a*inv_65535,rg)+(float_double)0.5); cmd_limit_16(last_val); } *ptr_16 = last_val; } for (a=0;a<0x10000;a++,ptr_16++) { if (!x_slow_fpu || !(a & 0xff)) { last_val = (unsigned short)(65535*fd_pow((float_double)a*inv_65535,gg)+(float_double)0.5); cmd_limit_16(last_val); } *ptr_16 = last_val; } for (a=0;a<0x10000;a++,ptr_16++) { if (!x_slow_fpu || !(a & 0xff)) { last_val = (unsigned short)(65535*fd_pow((float_double)a*inv_65535,bg)+(float_double)0.5); cmd_limit_16(last_val); } *ptr_16 = last_val; } } } /* We assume unsigned short holds at least 16 bits. */ void agx_24_to_48_table(unsigned short *my_restrict dest, const unsigned char *my_restrict src, size_t length, unsigned short *my_restrict table) { for (;length;length--,src+=3,dest+=3) { dest[0]=table[src[0]]; dest[1]=table[src[1]+256]; dest[2]=table[src[2]+512]; } } #if 0 /* Input is 0-255 (8-bit). Output is 0-255 (8-bit)*/ unsigned char ags_8_to_8(unsigned char input, float gamma) { const float_double inv_255=1/255.; return 255*fd_pow((float_double)input*inv_255,gamma)+(float_double)0.5; } #endif /* Input is 0-255 (8-bit). Output is 0-65535 (16-bit)*/ /* We assume unsigned short holds at least 16 bits. */ unsigned short ags_8_to_16(unsigned char input, float gamma) { float_double a=input; unsigned short retval; const float_double inv_255=(float_double)(1/255.); a*=inv_255; a=fd_pow(a,gamma); a*=65535; retval = (unsigned short)(a+(float_double)0.5); cmd_limit_16(retval); return retval; } /* Input is 0-65535 (16-bit). Output is 0-255 (8-bit)*/ /* We assume unsigned short holds at least 16 bits. */ unsigned char ags_16_to_8(unsigned short input, float gamma) { const float_double inv_65535=(float_double)(1/65535.); return (unsigned char)(fd_pow((float_double)input*inv_65535,gamma)*255+(float_double)0.5); } /* Input is 0-65535 (16-bit). Output is 0-255 (8-bit)*/ unsigned short ags_16_to_16(unsigned short input, float gamma) { unsigned short retval; const float_double inv_65535=(float_double)(1/65535.); retval = (unsigned short)(65535*fd_pow((float_double)input*inv_65535,gamma)+(float_double)0.5); cmd_limit_16(retval); return retval; } #define FONT_NORMAL 1 #define FONT_BOLD 2 #define FONT_MONOSPACED 3 /* Returns a pointer to a structure describing the letter found or NULL * if the letter is not found. Tries all possibilities in the style table * before returning NULL. */ static struct letter *find_stored_letter(struct style *style, int letter_number) { int tries[3]; int try; if (style->flags & FF_MONOSPACED) { tries[0] = FONT_MONOSPACED; if (style->flags & FF_BOLD) { tries[1] = FONT_BOLD; tries[2] = FONT_NORMAL; } else { tries[1] = FONT_NORMAL; tries[2] = FONT_BOLD; } } else { if (style->flags & FF_BOLD) { tries[0] = FONT_BOLD; tries[1] = FONT_NORMAL; tries[2] = FONT_MONOSPACED; } else { tries[0] = FONT_NORMAL; tries[1] = FONT_MONOSPACED; tries[2] = FONT_BOLD; } } for (try = 0; try < 3; try++) { struct font *font = &font_table[tries[try]]; int start = font->begin; int result; #define EQ(v, key) (letter_data[start + v].code == key) #define AB(v, key) (letter_data[start + v].code > key) BIN_SEARCH(font->length, EQ, AB, letter_number, result); if (result >= 0) return letter_data + start + result; } return letter_data + font_table[0].begin; } struct read_work { const unsigned char *pointer; int length; }; static void read_stored_data(png_structp png_ptr, png_bytep data, png_size_t length) { struct read_work *work; length=(png_uint_32)length; work=png_get_io_ptr(png_ptr); if (length>(png_uint_32)work->length) png_error(png_ptr,"Ran out of input data"); memcpy(data,work->pointer,length); work->length-=(int)length; work->pointer+=length; } static void my_png_warning(png_structp a, png_const_charp b) { } static jmp_buf png_error_jump; static int png_error_jump_valid = 0; static void my_png_error(png_structp a, png_const_charp error_string) { if (png_error_jump_valid) longjmp(png_error_jump, 1); error("Error when loading compiled-in font: %s.", error_string); } /* Loads width and height of the PNG (nothing is scaled). Style table is * already incremented. */ static void load_metric(struct style *style, int char_number, int *x, int *y) { struct letter *l; #ifdef HAVE_FREETYPE if (style->ft_face) { *y = style->height; if (!freetype_load_metric_cached(style, char_number, x, *y)) return; } #endif l = find_stored_letter(style, char_number); if (!l) { *x = 0; *y = 0; } else { *x = l->xsize; *y = l->ysize; } return; } #ifdef PNG_USER_MEM_SUPPORTED void *my_png_alloc(png_structp png_ptr, png_size_t size) { void *ptr = mem_alloc_mayfail(size); return ptr; } void my_png_free(png_structp png_ptr, void *ptr) { if (ptr) mem_free(ptr); } #endif static void dip_set_gamma(png_structp png_ptr, png_infop info_ptr) { png_error_jump_valid = 1; if (!setjmp(png_error_jump)) { double gamma; if (png_get_gAMA(png_ptr, info_ptr, &gamma)) png_set_gamma(png_ptr, 1.0, gamma); else png_set_gamma(png_ptr, 1.0, sRGB_gamma); } png_error_jump_valid = 0; } /* The data tha fall out of this function express this: 0 is paper. 255 is ink. 34 * is 34/255ink+(255-34)paper. No gamma is involved in this formula, as you can see. * The multiplications and additions take place in photon space. */ static void load_char(unsigned char **dest, int *x, int *y, const unsigned char *png_data, int png_length) { png_structp png_ptr; png_infop info_ptr; int y1,number_of_passes; unsigned char **ptrs; struct read_work work; work.pointer = png_data; work.length = png_length; retry1: #ifdef PNG_USER_MEM_SUPPORTED png_ptr = png_create_read_struct_2(PNG_LIBPNG_VER_STRING, NULL, my_png_error, my_png_warning, NULL, my_png_alloc, my_png_free); #else png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, my_png_error, my_png_warning); #endif if (!png_ptr) { if (out_of_memory(0, NULL, 0)) goto retry1; fatal_exit("png_create_read_struct failed"); } retry2: info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { if (out_of_memory(0, NULL, 0)) goto retry2; fatal_exit("png_create_info_struct failed"); } png_set_read_fn(png_ptr, &work, (png_rw_ptr)read_stored_data); png_read_info(png_ptr, info_ptr); *x = (int)png_get_image_width(png_ptr, info_ptr); *y = (int)png_get_image_height(png_ptr, info_ptr); dip_set_gamma(png_ptr, info_ptr); { int bit_depth; int color_type; color_type = png_get_color_type(png_ptr, info_ptr); bit_depth = png_get_bit_depth(png_ptr, info_ptr); if (color_type == PNG_COLOR_TYPE_GRAY) { if (bit_depth < 8) { png_set_expand(png_ptr); } if (bit_depth == 16) { png_set_strip_16(png_ptr); } } if (color_type == PNG_COLOR_TYPE_PALETTE) { png_set_expand(png_ptr); #ifdef HAVE_PNG_SET_RGB_TO_GRAY png_set_rgb_to_gray(png_ptr, 1, -1, -1); #else goto end; #endif } if (color_type & PNG_COLOR_MASK_ALPHA) { png_set_strip_alpha(png_ptr); } if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) { #ifdef HAVE_PNG_SET_RGB_TO_GRAY png_set_rgb_to_gray(png_ptr, 1, -1, -1); #else goto end; #endif } } /* If the depth is different from 8 bits/gray, make the libpng expand * it to 8 bit gray. */ number_of_passes = png_set_interlace_handling(png_ptr); png_read_update_info(png_ptr, info_ptr); if (*x && (unsigned)*x * (unsigned)*y / (unsigned)*x != (unsigned)*y) overalloc(); if ((unsigned)*x * (unsigned)*y > MAXINT) overalloc(); *dest = mem_alloc(*x * *y); if ((unsigned)*y > MAXINT / sizeof(*ptrs)) overalloc(); ptrs = mem_alloc(*y * sizeof(*ptrs)); for (y1 = 0; y1 < *y; y1++) ptrs[y1] = *dest + *x * y1; for (;number_of_passes; number_of_passes--) { png_read_rows(png_ptr, ptrs, NULL, *y); } png_read_end(png_ptr, NULL); png_destroy_read_struct(&png_ptr, &info_ptr, NULL); mem_free(ptrs); return; #ifndef HAVE_PNG_SET_RGB_TO_GRAY end: if (*x && (unsigned)*x * (unsigned)*y / (unsigned)*x != (unsigned)*y) overalloc(); if ((unsigned)*x * (unsigned)*y > MAXINT) overalloc(); *dest=mem_calloc(*x * *y)); return; #endif } /* Like load_char, but we dictate the y. */ static void load_scaled_char(unsigned char **dest, int *x, int y, const unsigned char *png_data, int png_length, struct style *style) { unsigned char *interm; unsigned char *interm2; unsigned char *i2ptr, *dptr; int ix, iy, y0, x0, c; float conv0, conv1, sharpness,contrast; load_char(&interm, &ix, &iy, png_data, png_length); if (style->mono_space >= 0) *x = compute_width(style->mono_space, style->mono_height, y); else *x = compute_width(ix, iy, y); if (display_optimize) *x *= 3; scale_gray(interm, ix, iy, dest, *x, y); if (y > 32 || y <= 0) return; /* No convolution */ ix = *x + 2; /* There is one-pixel border around */ iy = y + 2; if (ix && (unsigned)ix * (unsigned)iy / (unsigned)ix != (unsigned)iy) overalloc(); if ((unsigned)ix * (unsigned)iy > MAXINT) overalloc(); interm2 = mem_alloc(ix * iy); i2ptr = interm2 + ix + 1; dptr = *dest; memset(interm2, 0, ix); memset(interm2 + (iy - 1) * ix, 0, ix); for (y0 = y; y0; y0--) { i2ptr[-1] = 0; memcpy(i2ptr, dptr, *x); i2ptr[*x] = 0; i2ptr += ix; dptr += *x; } i2ptr = interm2 + ix + 1; dptr = *dest; /* Determine the sharpness and contrast */ sharpness = fancy_constants[2 * y - 2]; contrast = fancy_constants[2 * y - 1]; /* Compute the matrix constants from contrast and sharpness */ conv0 = (1 + sharpness) * contrast; conv1 = (float)(-sharpness * (float)0.25 * contrast); for (y0 = y; y0; y0--) { for (x0 = *x; x0; x0--) { /* Convolution */ c=(int)((*i2ptr * conv0) + i2ptr[-ix] * conv1 + i2ptr[-1] * conv1 + i2ptr[1] * conv1 + i2ptr[ix] * conv1 + (float)0.5); if (c & ~255) c = c < 0 ? 0 : 255; *dptr = (unsigned char)c; dptr++; i2ptr++; } i2ptr += 2; } mem_free(interm2); } static struct font_cache_entry *locked_color_entry = NULL; /* Adds required entry into font_cache and returns pointer to the entry. */ ATTR_NOINLINE static struct font_cache_entry *supply_color_cache_entry(struct style *style, struct letter *letter, int char_number) { int found_x, found_y; unsigned char *found_data; struct font_cache_entry *neww; unsigned short *primary_data; unsigned short red, green, blue; unsigned bytes_consumed; found_y = style->height; #ifdef HAVE_FREETYPE if (!letter) { if (freetype_type_character(style, char_number, &found_data, &found_x, found_y)) return NULL; } else #endif { load_scaled_char(&found_data, &found_x, found_y, letter->begin, letter->length, style); } neww = mem_alloc(sizeof(struct font_cache_entry)); locked_color_entry = neww; neww->bitmap.x = found_x; neww->bitmap.y = found_y; neww->r0 = style->r0; neww->g0 = style->g0; neww->b0 = style->b0; neww->r1 = style->r1; neww->g1 = style->g1; neww->b1 = style->b1; neww->flags = style->flags; neww->char_number = char_number; neww->mono_space = style->mono_space; neww->mono_height = style->mono_height; #ifdef HAVE_FREETYPE neww->font = freetype_get_allocated_font_name(style); #endif if (neww->bitmap.x && (unsigned)neww->bitmap.x * (unsigned)neww->bitmap.y / (unsigned)neww->bitmap.x != (unsigned)neww->bitmap.y) overalloc(); if ((unsigned)neww->bitmap.x * (unsigned)neww->bitmap.y > MAXINT / 3 / sizeof(*primary_data)) overalloc(); primary_data = mem_alloc(3 * neww->bitmap.x * neww->bitmap.y * sizeof(*primary_data)); /* We assume the gamma of HTML styles is in sRGB space */ round_color_sRGB_to_48(&red, &green, &blue, (style->r0 << 16) | (style->g0 << 8) | style->b0); mix_two_colors(primary_data, found_data, found_x * found_y, red, green, blue, ags_8_to_16(style->r1, (float)((float)(user_gamma tcc_hack) / (float)sRGB_gamma)), ags_8_to_16(style->g1, (float)((float)(user_gamma tcc_hack) / (float)sRGB_gamma)), ags_8_to_16(style->b1, (float)((float)(user_gamma tcc_hack) / (float)sRGB_gamma)) ); if (display_optimize) { /* A correction for LCD */ neww->bitmap.x /= 3; decimate_3(&primary_data, neww->bitmap.x, neww->bitmap.y); } /* We have a buffer with photons */ if (drv->get_empty_bitmap(&neww->bitmap)) goto skip_dither; if (dither_letters) dither(primary_data, &neww->bitmap); else (*round_fn)(primary_data, &neww->bitmap); skip_dither: mem_free(primary_data); drv->register_bitmap(&neww->bitmap); mem_free(found_data); bytes_consumed = neww->bitmap.x * neww->bitmap.y * (drv->depth&7); /* Number of bytes per pixel in passed bitmaps */ bytes_consumed += (int)sizeof(*neww); bytes_consumed += (int)sizeof(struct lru_entry); #ifdef HAVE_FREETYPE if (!letter) { lru_insert(&font_cache, neww, &freetype_cache[char_number & (sizeof_freetype_cache - 1)], bytes_consumed); } else #endif { lru_insert(&font_cache, neww, &letter->color_list, bytes_consumed); } return neww; } static int destroy_font_cache_bottom(void) { struct font_cache_entry *bottom; bottom = lru_get_bottom(&font_cache); if (!bottom) return 0; if (bottom == locked_color_entry) return 0; #ifdef HAVE_FREETYPE if (bottom->font) mem_free(bottom->font); #endif drv->unregister_bitmap(&bottom->bitmap); mem_free(bottom); lru_destroy_bottom(&font_cache); return 1; } /* Prunes the cache to comply with maximum size */ static int prune_font_cache(void) { int r = 0; while (font_cache.bytes > (unsigned)font_cache_size) { if (destroy_font_cache_bottom()) { r = 1; } else { break; } } return r; } /* Prints a letter to the specified position and * returns the width of the printed letter */ static inline int print_letter(struct graphics_device *device, int x, int y, struct style *style, int char_number) { int xw; struct font_cache_entry *found; struct font_cache_entry templat; struct letter *letter; templat.r0 = style->r0; templat.r1 = style->r1; templat.g0 = style->g0; templat.g1 = style->g1; templat.b0 = style->b0; templat.b1 = style->b1; templat.flags = style->flags; templat.char_number = char_number; templat.mono_space = style->mono_space; templat.mono_height = style->mono_height; templat.bitmap.y = style->height; #ifdef HAVE_FREETYPE templat.font = freetype_get_allocated_font_name(style); #endif #ifdef HAVE_FREETYPE if (style->ft_face) { found = lru_lookup(&font_cache, &templat, &freetype_cache[char_number & (sizeof_freetype_cache - 1)]); letter = NULL; } else bypass_freetype: #endif { /* Find a suitable letter */ letter = find_stored_letter(style, char_number); #ifdef DEBUG if (!letter) internal_error("print_letter could not find a letter - even not the blotch!"); #endif /* #ifdef DEBUG */ found = lru_lookup(&font_cache, &templat, &letter->color_list); } if (!found) { found = supply_color_cache_entry(style, letter, char_number); #ifdef HAVE_FREETYPE if (!found) goto bypass_freetype; #endif } else { locked_color_entry = found; } #ifdef HAVE_FREETYPE if (templat.font) mem_free(templat.font); #endif #if 0 drv->fill_area(device, x, y, x + found->bitmap.x, y + found->bitmap.y, drv->get_color(0xff0000)); if (drv->flush) drv->flush(device); portable_sleep(1); #endif drv->draw_bitmap(device, &found->bitmap, x, y); xw = found->bitmap.x; if (locked_color_entry != found) internal_error("bad letter lock"); locked_color_entry = NULL; prune_font_cache(); return xw; } /* Must return values that are: * >=0 * <=height * at least 1 apart * Otherwise g_print_text will print nonsense (but won't segfault) */ static void get_line_pos(int height, int *top, int *bottom, unsigned style) { int thickness, baseline; thickness=(height+15)/16; if (style == FF_STRIKE) baseline=height/2 - height/12; else baseline=height/7; if (baseline<=0) baseline=1; if (thickness>baseline) thickness=baseline; *top=height-baseline; *bottom=*top+thickness; } /* *width will be advanced by the width of the text */ void g_print_text(struct graphics_device *device, int x, int y, struct style *style, unsigned char *text, int *width) { int top_line, bottom_line, original_width, my_width; unsigned char original_flags, line_style; struct rect saved_clip; if (y + style->height <= device->clip.y1 || y >= device->clip.y2) goto o; if (style->flags & FF_UNDERLINE || style->flags & FF_STRIKE) { /* Underline or strike */ if (!width) { width = &my_width; *width = 0; } original_flags = style->flags; original_width = *width; line_style = style->flags & FF_UNDERLINE ? FF_UNDERLINE : FF_STRIKE; style->flags &= ~line_style; get_line_pos(style->height, &top_line, &bottom_line, line_style); restrict_clip_area(device, &saved_clip, 0, 0, device->size.x2, y + top_line); g_print_text(device, x, y, style, text, width); set_clip_area(device, &saved_clip); if (bottom_line - top_line == 1) { /* Line */ drv->draw_hline(device, x, y + top_line, safe_add(x, *width) - original_width, style->line_color); } else { /* Area */ drv->fill_area(device, x, y + top_line, safe_add(x, *width) - original_width, y + bottom_line, style->line_color); } if (bottom_line < style->height) { /* Do the bottom half only if the line is above * the bottom of the letters. */ *width = original_width; restrict_clip_area(device, &saved_clip, 0, y + bottom_line, device->size.x2, device->size.y2); g_print_text(device, x, y, style, text, width); set_clip_area(device, &saved_clip); } style->flags = original_flags; return; } while (*text) { int p; int u; GET_UTF_8(text, u); /* 00-09, 0b-1f, 80, 81, 84, 86-9f ignorovat * 0a = LF * 82 = ' ' * 83 = nobrk * 85 = radkovy zlom * a0 = NBSP * ad = soft hyphen */ #if 0 if ( (u>=0x00&&u<=0x09)|| (u>=0x0b&&u<=0x1f)|| u==0x80|| u==0x82|| u==0x84|| (u>=0x86&&u<=0x9f) )continue; if (u==0x82)u=' '; #endif /* stare Mikulasovo patchovani, musim to opravit -- Brain */ if (!u || u == 0xad) continue; if (u == 0x01 || u == 0xa0) u = ' '; p = print_letter(device, x, y, style, u); x += p; if (width) { *width = safe_add(*width, p); continue; } if (x >= device->clip.x2) return; } return; o: if (width) { int qw = g_text_width(style, text); *width = safe_add(*width, qw); } } /* 0=equality 1=inequality */ static int compare_font_entries(void *entry, void *templat) { struct font_cache_entry *e1 = entry; struct font_cache_entry *e2 = templat; if (e1->r0 != e2->r0) return 1; if (e1->g0 != e2->g0) return 1; if (e1->b0 != e2->b0) return 1; if (e1->r1 != e2->r1) return 1; if (e1->g1 != e2->g1) return 1; if (e1->b1 != e2->b1) return 1; if (e1->flags != e2->flags) return 1; if (e1->char_number != e2->char_number) return 1; if (e1->mono_space != e2->mono_space) return 1; if (e1->mono_space >= 0) { if (e1->mono_height != e2->mono_height) return 1; } if (e1->bitmap.y != e2->bitmap.y) return 1; #ifdef HAVE_FREETYPE if (!e1->font != !e2->font) return 1; if (e1->font && strcmp(cast_const_char e1->font, cast_const_char e2->font)) return 1; #endif return 0; } /* If the cache already exists, it is destroyed and reallocated. If you call it with the same * size argument, only a cache flush will yield. */ static void init_font_cache(void) { lru_init(&font_cache, &compare_font_entries); } /* Ensures there are no lru_entry objects allocated - destroys them. * Also destroys the bitmaps asociated with them. Does not destruct the font_cache per se. */ static void destroy_font_cache(void) { while (destroy_font_cache_bottom()) ; if (lru_get_bottom(&font_cache)) internal_error("destroy_font_cache: cache not freed due to locks"); } /* Returns 0 in case the char is not found. */ static inline int g_get_width(struct style *style, unsigned charcode) { int x, y, width; if (!charcode || charcode == 0xad) return 0; if (charcode == 0x01 || charcode == 0xa0) charcode = ' '; if (style->mono_space >= 0) { x = style->mono_space; y = style->mono_height; } else load_metric(style, charcode, &x, &y); if (!(x && y)) width = 0; else width = compute_width(x, y, style->height); return width; } int g_text_width(struct style *style, unsigned char *text) { int w = 0; while (*text) { int u; int qw; GET_UTF_8(text, u); qw = g_get_width(style, u); w = safe_add(w, qw); } return w; } int g_char_width(struct style *style, unsigned charcode) { return g_get_width(style, charcode); } int g_wrap_text(struct wrap_struct *w) { unsigned char *init_text = w->text; while (*w->text) { int u; int s; unsigned char *l_text = w->text; if (*l_text == ' ') w->last_wrap = l_text, w->last_wrap_obj = w->obj; GET_UTF_8(w->text, u); if (!u) continue; s = g_get_width(w->style, u); w->pos = safe_add(w->pos, s); if (w->pos <= w->width) { c: if (u != 0xad || *w->text == ' ' || w->force_break) continue; s = g_char_width(w->style, '-'); if (safe_add(w->pos, s) <= w->width || (!w->last_wrap && !w->last_wrap_obj)) { w->last_wrap = l_text; w->last_wrap_obj = w->obj; continue; } } if (w->force_break && !w->last_wrap && l_text != init_text) { w->last_wrap = l_text; w->last_wrap_obj = w->obj; } if (!w->last_wrap && !w->last_wrap_obj) goto c; return 0; } return 1; } void update_aspect(void) { aspect = (int)(65536 * bfu_aspect + 0.5); destroy_font_cache(); } void flush_bitmaps(int flush_font, int flush_images, int redraw_all) { if (flush_font) destroy_font_cache(); if (flush_images) gamma_stamp++; if (redraw_all) cls_redraw_all_terminals(); } my_uintptr_t fontcache_info(int type) { switch (type) { case CI_BYTES: return font_cache.bytes; case CI_FILES: return font_cache.items; default: internal_error("fontcache_info: query %d", type); return 0; } } static int shrink_font_cache(int u) { int freed_something = 0; int has_something; if (u == SH_CHECK_QUOTA) { freed_something = prune_font_cache(); } if (u == SH_FREE_ALL) { while (destroy_font_cache_bottom()) freed_something = 1; } if (u == SH_FREE_SOMETHING) { freed_something = destroy_font_cache_bottom(); } has_something = !!lru_get_bottom(&font_cache); return (freed_something ? ST_SOMETHING_FREED : 0) | (has_something ? 0 : ST_CACHE_EMPTY); } void init_dip(void) { init_font_cache(); update_aspect(); register_cache_upcall(shrink_font_cache, MF_GPI, cast_uchar "fontcache"); } struct style *g_invert_style(struct style *old) { struct style *st; st = mem_alloc(sizeof(struct style)); st->refcount = 1; st->r0 = old->r1; st->g0 = old->g1; st->b0 = old->b1; st->r1 = old->r0; st->g1 = old->g0; st->b1 = old->b0; st->height = old->height; st->flags = old->flags; #ifdef HAVE_FREETYPE st->ft_face = old->ft_face; #endif if (st->flags & FF_UNDERLINE || st->flags & FF_STRIKE) { /* We have to get a foreground color for underlining and * striking */ st->line_color = dip_get_color_sRGB((st->r1 << 16) | (st->g1 << 8) | (st->b1)); } st->mono_space = old->mono_space; st->mono_height = old->mono_height; return st; } int hack_rgb(int rgb) { if (((drv->depth >> 3) & 0x1f) <= 4) { int r,g,b; r=(rgb>>16)&255; g=(rgb>>8)&255; b=rgb&255; if (r == g && g == b) { if (r < 128) return 0; else return 0xffffff; } } return rgb; } /* Never returns NULL. */ struct style *g_get_style_font(int fg, int bg, int size, int fflags, unsigned char *font) { struct style *st; bg = hack_rgb(bg); st = mem_alloc(sizeof(struct style)); st->refcount = 1; st->r0 = bg >> 16; st->g0 = (bg >> 8) & 255; st->b0 = bg & 255; st->r1 = fg >> 16; st->g1 = (fg >> 8) & 255; st->b1 = fg & 255; if (size <= 0) size = 1; st->height = size; st->flags = (unsigned char)fflags; #ifdef HAVE_FREETYPE if (!font) st->ft_face = freetype_flags_to_face(fflags); else st->ft_face = freetype_get_font(font); #endif if (fflags & FF_UNDERLINE || fflags & FF_STRIKE) { /* We have to get a foreground color for underlining and * striking */ st->line_color = dip_get_color_sRGB(fg); } if (fflags & FF_MONOSPACED) load_metric(st, ' ', &st->mono_space, &st->mono_height); else st->mono_space = -1; return st; } struct style *g_get_style(int fg, int bg, int size, int fflags) { return g_get_style_font(fg, bg, size, fflags, NULL); } struct style *g_clone_style(struct style *st) { st->refcount++; return st; } void g_free_style(struct style *st) { if (--st->refcount) return; #ifdef HAVE_FREETYPE if (st->ft_face) freetype_free_font(st->ft_face); #endif mem_free(st); } tcount gamma_stamp = 1; /* stamp counter for gamma changes */ long gamma_cache_color_1; int gamma_cache_rgb_1; long gamma_cache_color_2; int gamma_cache_rgb_2; /* IEC 61966-2-1 * Input gamma: sRGB space (directly from HTML, i. e. unrounded) * Output: color index for graphics driver that is closest to the * given sRGB value. * We assume unsigned short holds at least 16 bits. */ long real_dip_get_color_sRGB(int rgb) { unsigned short r, g, b; int hacked_rgb, new_rgb; hacked_rgb = hack_rgb(rgb); round_color_sRGB_to_48(&r, &g, &b, hacked_rgb); r = ags_16_to_8(r, (float)(1 / (float)(display_red_gamma tcc_hack))); g = ags_16_to_8(g, (float)(1 / (float)(display_green_gamma tcc_hack))); b = ags_16_to_8(b, (float)(1 / (float)(display_blue_gamma tcc_hack))); new_rgb = b | (g << 8) | (r << 16); gamma_cache_rgb_2 = gamma_cache_rgb_1; gamma_cache_color_2 = gamma_cache_color_1; gamma_cache_rgb_1 = rgb; /* The get_color takes values with gamma of display_*_gamma */ return gamma_cache_color_1 = drv->get_color(new_rgb); } #include "links_ic.inc" void get_links_icon(unsigned char **data, int *width, int *height, ssize_t *skip, int pad) { struct bitmap b; unsigned short *tmp1; float g = (float)((float)(user_gamma tcc_hack) / (float)sRGB_gamma); b.x = 48; b.y = 48; *width = b.x; *height = b.y; b.skip = b.x * (drv->depth & 7); while (b.skip % pad) b.skip++; *skip = b.skip; b.data = *data = mem_alloc(b.skip * b.y); tmp1 = mem_alloc(6 * b.y * b.x); agx_24_to_48(tmp1, links_icon, b.x * b.y, g, g, g); (*round_fn)(tmp1, &b); mem_free(tmp1); } static inline void qb_palette(unsigned r_max, unsigned g_max, unsigned b_max, unsigned r, unsigned g, unsigned b, unsigned scale, unsigned rgb[3]) { rgb[0] = (r * scale + r_max / 2) / r_max; rgb[1] = (g * scale + g_max / 2) / g_max; rgb[2] = (b * scale + b_max / 2) / b_max; } void q_palette(unsigned size, unsigned color, unsigned scale, unsigned rgb[3]) { if (color >= size) { rgb[0] = rgb[1] = rgb[2] = 0; return; } switch (size) { case 8: qb_palette(1, 1, 1, color >> 2, (color >> 1) & 1, color & 1, scale, rgb); break; case 16: qb_palette(1, 3, 1, color >> 3, (color >> 1) & 3, color & 1, scale, rgb); break; case 216: qb_palette(5, 5, 5, color / 36, color / 6 % 6, color % 6, scale, rgb); break; case 256: qb_palette(7, 7, 3, color >> 5, (color >> 2) & 7, color & 3, scale, rgb); break; case 32768: qb_palette(31, 31, 31, color >> 10, (color >> 5) & 31, color & 31, scale, rgb); break; case 65536: qb_palette(31, 63, 31, color >> 11, (color >> 5) & 63, color & 31, scale, rgb); break; case 16777216: qb_palette(255, 255, 255, color >> 16, (color >> 8) & 255, color & 255, scale, rgb); break; default: internal_error("q_palette: invalid size %u", size); } } double rgb_distance(int r1, int g1, int b1, int r2, int g2, int b2) { double diff_r, diff_g, diff_b; diff_r = (r1 - r2) * .30; diff_r *= diff_r; diff_g = (g1 - g2) * .59; diff_g *= diff_g; diff_b = (b1 - b2) * .11; diff_b *= diff_b; return diff_r + diff_g + diff_b; } #endif /* G */