Files
2026-08-20 20:28:40 +01:00

715 lines
21 KiB
C

/*
* render2d
*
* Threshold or Error diffusion screen pixel processing object.
* (Simplified from DPS code)
*
* Author: Graeme W. Gill
* Date: 8/9/2005
* Version: 1.00
*
* Copyright 2005, 2012 Graeme W. Gill
* All rights reserved.
* This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
* see the License.txt file for licencing details.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <fcntl.h>
#include <string.h>
#include <math.h>
#include "aconfig.h"
#include "numlib.h"
//#include "icc.h"
#include "sort.h"
//#include "xcolorants.h"
#include "thscreen.h"
/* Configuration: */
#undef DEBUG
#undef CHECK_EXPECTED_ED_LEVELS /* Output expected quantized levels for checkking */
/* ----------------------------------------------------------- */
#ifdef DEBUG
# define DBG(text) printf text ; fflush(stdout);
#else
# define DBG(text)
#endif
/* ----------------------------------------------------------- */
/* Setup a set of screens */
/* Screen data is used that best matches the requested parameters. */
#include "screens.h" /* Pre-generated screen patterns */
/* Threshold screen lines of multiplane pixels */
void screen_thscreens(
thscreens *t, /* Screening object pointer */
int width, int height, /* Width and height to screen in pixels */
int xoff, int yoff, /* Offset into screening pattern */
unsigned char *out, /* Output pixel buffer */
unsigned long opitch, /* Increment between output lines in components */
unsigned char *in, /* Input pixel buffer */
unsigned long ipitch /* Increment between input lines in components */
) {
int i;
for (i = 0; i < t->np; i++)
t->sc[i]->screen(t->sc[i], width, height, xoff, yoff,
out + i, t->np, opitch,
in + 2 * i, t->np, ipitch);
}
/* Error diffusion screen lines of multiplane pixels */
void screen_edscreens(
thscreens *t, /* Screening object pointer */
int width, int height, /* Width and height to screen in pixels */
int xoff, int yoff, /* Offset into screening pattern, [xoff + width < mxwidth] */
unsigned char *out, /* Output pixel buffer */
unsigned long opitch, /* Increment between output lines in components */
unsigned char *_in, /* Input pixel buffer */
unsigned long ipitch /* Increment between input lines in components */
) {
unsigned short *in = (unsigned short *)_in; /* Pointer to input pixel sized values */
unsigned short *ein = in + height * ipitch; /* Vertical end pixel marker */
unsigned short *ein1; /* Horizontal end pixel markers */
int xo, yo; /* Threshold screen offset */
int x, j;
/* Limit width to mxwidth */
if ((xoff + width) > t->mxwidth) {
width = t->mxwidth - xoff;
if (width < 0)
return;
}
/* If not sequential, clear error buffer */
if (yoff != (t->lastyoff+1)) {
for (x = -1; x <= t->mxwidth; x++) {
for (j = 0; j < t->np; j++)
t->ebuf[j][x] = 0.0;
}
}
/* Clear "next to right" error */
for (j = 0; j < t->np; j++) {
t->ebuf[j][-2] = 0.0;
}
t->lastyoff = yoff;
/* For each line: */
for (; in < ein; in += ipitch, ein1 += ipitch, out += opitch, yoff++) {
unsigned short *ip; /* Horizontal input pointer */
unsigned char *op; /* Horizontal output pointer */
int xinc, pinc;
/* Do in serpentine order */
if (yoff & 1) {
xinc = -1;
x = xoff + width-1; /* x is index into error buffer */
pinc = -t->np;
ein1 = in + pinc;
ip = in + t->np * (width-1);
op = out + t->np * (width-1);
} else {
xinc = 1;
x = xoff;
pinc = t->np;
ein1 = in + t->np * width;
ip = in;
op = out;
}
/* For each pixel */
for (; ip != ein1; ip += pinc, op += pinc, x += xinc) {
double ov[THMXCH2D], tv[THMXCH2D], ev[THMXCH2D];
/* Limit error propogation if asked */
if (t->mxerr != 0.0) {
for (j = 0; j < t->np; j++) {
if (t->ebuf[j][x] < -t->mxerr)
t->ebuf[j][x] = -t->mxerr;
else if (t->ebuf[j][x] > t->mxerr)
t->ebuf[j][x] = t->mxerr;
}
}
/* For each plane */
for (j = 0; j < t->np; j++) {
tv[j] = t->luts[j][ip[j]] / 65535.0; /* 0.0 - 1.0 value */
/* Value + accumulated error */
ov[j] = tv[j] = tv[j] + t->ebuf[j][x];
/* Limit */
if (ov[j] > 1.0)
ov[j] = 1.0;
else if (ov[j] < 0.0)
ov[j] = 0.0;
/* Output encode */
op[j] = t->oevalues[(int)(ov[j] * (t->oelev-1.0) + 0.5)];
}
#ifdef CHECK_EXPECTED_ED_LEVELS
#pragma message("######### render/thscreen.c CHECK_EXPECTED_ED_LEVELS defined ! ##")
// Put expected values in output to check levels
t->quant(t->qcntx, ev, ov);
for (j = 0; j < t->np; j++)
op[j] = t->oevalues[(int)(ev[j] * (t->oelev-1.0) + 0.5)];
#endif
/* Quantize to values that it actually will be */
if (t->quant != NULL)
t->quant(t->qcntx, ov, ov);
else {
for (j = 0; j < t->np; j++)
ov[j] = floor(ov[j] * (t->oelev-1) + 0.5)/(t->oelev-1.0);
}
/* Compute the error to the target */
for (j = 0; j < t->np; j++) {
/* Error to target */
ev[j] = tv[j] - ov[j];
}
/* Distribute the error */
for (j = 0; j < t->np; j++) {
#ifdef NEVER
/* Classic error diffusion */
t->ebuf[j][x-xinc] += 0.1875 * ev[j]; /* Lower left */
t->ebuf[j][x] = t->ebuf[j][-2] + 0.3125 * ev[j]; /* Lower */
t->ebuf[j][-2] = 0.0625 * ev[j]; /* Lower right */
t->ebuf[j][x+xinc] += 0.4375 * ev[j]; /* Right */
#else
/* Using random placement error distribution */
double rav;
int ii;
t->so->next(t->so, &rav); /* For some order */
rav *= 4.0;
rav += d_rand(0.0, 2.5); /* For some randomness */
ii = (int)(rav);
if (ii > 3)
ii -= 4;
t->ebuf[j][x] = t->ebuf[j][-2];
t->ebuf[j][-2] = 0.0;
switch (ii) {
case 0:
t->ebuf[j][x-xinc] += ev[j]; /* Lower left */
break;
case 1:
t->ebuf[j][x] += ev[j]; /* Lower */
break;
case 2:
t->ebuf[j][-2] += ev[j]; /* Lower right */
break;
case 3:
t->ebuf[j][x+xinc] += ev[j]; /* Right */
break;
}
#endif
}
}
}
}
/* Delete a thscreens */
void del_thscreens(thscreens *t) {
int i;
if (t->sc != NULL) {
for (i = 0; i < t->np; i++) {
if (t->sc[i] != NULL)
t->sc[i]->del(t->sc[i]);
}
free(t->sc);
}
if (t->ebuf != NULL) {
free_fmatrix(t->ebuf, 0, t->np-1, -2, t->mxwidth);
}
if (t->luts != NULL) {
free_imatrix(t->luts, 0, t->np-1, 0, 65535);
}
if (t->so != NULL)
t->so->del(t->so);
free(t);
}
/* Create a new thscreens object matching the parameters */
thscreens *new_thscreens(
int exact, /* Return only exact matches */
int nplanes, /* Number of planes to screen */
double asp, /* Target aspect ratio (== dpiX/dpiY) */
int size, /* Target screen size */
sc_iencoding ie, /* Input encoding - must be scie_16 */
int oebpc, /* Output encoding bits per component - must be 8 */
int oelev, /* Output encoding levels. Must be <= 2 ^ oebpc */
int *oevalues, /* Optional output encoding values for each level */
/* Must be oelev entries. Default is 0 .. oelev-1 */
sc_oorder oo, /* Output bit ordering */
double overlap, /* Overlap between levels, 0 - 1.0 */
int mxwidth, /* max width in pixels of raster to be screened */
void **cntx, /* List of contexts for lookup table callback */
double (**lutfunc)(void *cntx, double in), /* List of callback functions, NULL if none */
int edif, /* nz if using error diffusion */
void (*quant)(void *qcntx, double *out, double *in), /* optional quantization func. for edif */
void *qcntx,
double mxerr /* If error diffusion anf != 0, max error to propogate */
) {
thscreens *t;
int i, bi = -1;
double bamatch; /* Best aspect match */
int bsize = 100000; /* Best size match */
int swap = 0; /* width and height will need swapping */
DBG(("thscreens: new called with:\n"));
DBG((" nplanes = 0x%x\n",nplanes));
DBG((" asp = %f\n",asp));
DBG((" ie = %d\n",ie));
DBG((" oebpc = %d\n",oebpc));
DBG((" oelev = %d\n",oelev));
DBG((" oo = %d\n",oo));
DBG((" overlap = %f\n",overlap));
if (asp < 1.0) { /* All screens[] have asp >= 1.0 */
asp = 1.0/asp;
swap = 1;
DBG(("thscreens: aspect swap needed\n"));
}
if ((t = (thscreens *)calloc(1, sizeof(thscreens))) == NULL) {
DBG(("thscreens: malloc of thscreens failed\n"));
return NULL;
}
t->np = nplanes; /* Number of planes */
t->edif = edif; /* Error diffusion */
t->quant = quant; /* Optional quantization function */
t->qcntx = qcntx;
t->mxwidth = mxwidth;
t->mxerr = mxerr;
t->lastyoff = -1;
/* Allocate and initialise a next line error buffer. */
/* we allow 2 extra locations for pixels to the left and right of the current one: */
/* [-1] for the one to the below left when we are at x = 0, */
/* [-2] for the one below right, before we use [x] */
if (t->edif)
t->ebuf = fmatrixz(0, t->np-1, -2, t->mxwidth);
t->oebpc = oebpc;
t->oelev = oelev;
if (oevalues != NULL) {
for (i = 0; i < t->oelev; i++) {
if (oevalues[i] >= (1 << t->oebpc)) {
DBG(("new_thscreens() oevalues[%d] value %d can't fit in %d bits\n",i,oevalues[i],t->oebpc));
free(t);
return NULL;
}
t->oevalues[i] = oevalues[i];
}
} else {
for (i = 0; i < t->oelev; i++)
t->oevalues[i] = i;
}
DBG(("thscreens no planes = %d\n",t->np));
t->del = del_thscreens;
DBG(("thscreens: searching amongst %d screens, exact = %d\n",NO_SCREENS,exact));
DBG(("thscreens: looking for non-exact match\n"));
/* Synthesise a set of screens from what's there */
/* (Don't bother with matching the colorspace) */
for (i = 0;i < NO_SCREENS; i++) {
double thamatch; /* This aspect match */
int thsize; /* This size match */
thamatch = asp/screens[i].asp;
if (thamatch < 1.0)
thamatch = 1.0/thamatch;
if (bi < 0 || (thamatch < bamatch)) { /* No best or new best */
bamatch = thamatch;
bi = i;
DBG(("thscreens: new best with aspmatch %f\n",bamatch));
continue; /* On to next */
}
if (thamatch > bamatch) /* Worse aspect match */
continue;
/* Same on aspect ratio. Check size */
thsize = size - screens[i].size;
if (thsize < 0)
thsize = -thsize;
if (thsize < bsize) { /* New better size match */
bsize = thsize;
bi = i;
DBG(("thscreens: new best with size %d\n",bsize));
}
}
if (bi < 0) /* Strange */
return NULL;
if (t->edif) {
int j;
int npix;
t->screen = screen_edscreens;
t->luts = imatrix(0, t->np-1, 0, 65535);
/* Create a suitable LUT from the given function */
/* Input is either 8 or 16 bits, output is always 16 bits */
for (j = 0; j < t->np; j++) {
for (i = 0; i < 65536; i++) {
if (lutfunc != NULL && lutfunc[j] != NULL) {
double v = i/65535.0;
v = lutfunc[j](cntx[j], v);
t->luts[j][i] = (int)(v * 65535.0 + 0.5);
} else
t->luts[j][i] = i;
}
}
if ((t->so = new_sobol(1)) == NULL) {
DBG(("thscreens: new_sobol() failed\n"));
return NULL;
}
} else {
t->screen = screen_thscreens;
if ((t->sc = malloc(sizeof(thscreen *) * t->np)) == NULL) {
free(t);
DBG(("thscreens: malloc of thscreens->sc[] failed\n"));
return NULL;
}
/* Create each screening object from one defined screen. */
/* Use the 0'th plane screen */
/* Stagger the screens with a round of 9 offset */
for (i = 0; i < t->np; i++) {
int xoff = ((i % 3) * screens[bi].width)/3;
int yoff = (((i/3) % 3) * screens[bi].height)/3;
void *cx = NULL;
double (*lf)(void *cntx, double in) = 0;
if (cntx != NULL)
cx = cntx[i];
if (lutfunc != NULL)
lf = lutfunc[i];
DBG(("thscreens: creating plane %d/%d thscreen, offset %d %d\n",i,t->np,xoff,yoff));
if ((t->sc[i] = new_thscreen(screens[bi].width, screens[bi].height, xoff, yoff,
screens[bi].asp, swap, screens[bi].list[0],
ie, oebpc, oelev, oevalues, oo, overlap,
cx, lf)) == NULL) {
for (--i; i >= 0; i--)
t->sc[i]->del(t->sc[i]);
free(t->sc);
free(t);
DBG(("thscreens: new_thscreen() failed\n"));
return NULL;
}
}
}
DBG(("thscreens: returning nonexact match\n"));
return t;
}
/* ----------------------------------------------------------- */
/* The kernel stocastic screening routine */
void thscreen16_8(
struct _thscreen *t, /* Screening object pointer */
int width, int height, /* Width and height to screen in pixels */
int xoff, int yoff, /* Offset into screening pattern (must be +ve) */
unsigned char *out, /* Output pixel buffer */
unsigned long opinc, /* Increment between output pixels in components */
unsigned long opitch, /* Increment between output lines in components */
unsigned char *_in, /* Input pixel buffer */
unsigned long ipinc, /* Increment between input pixels in components */
unsigned long ipitch /* Increment between input lines in components */
) {
unsigned short *in = (unsigned short *)_in; /* Pointer to input pixel sized values */
int *lut = t->lut; /* Copy of 8 or 16 -> 16 bit lookup table */
unsigned short *ein = in + height * ipitch; /* Vertical end pixel marker */
unsigned short *ein1; /* Horizontal end pixel markers */
unsigned char **oth, **eth; /* Current lines start, origin and end in screening table. */
int thtsize; /* Overall size of threshold table */
unsigned char **eeth; /* Very end of threshold table */
{
unsigned char **sth; /* Start point of line intable */
sth = t->thp + (yoff % t->sheight) * t->twidth;
oth = sth + (xoff % t->swidth); /* Orgin of pattern to start from */
eth = sth + t->swidth; /* Ending point to wrap back */
thtsize = t->twidth * t->theight;
eeth = t->thp + thtsize; /* very end of table */
}
ein1 = in + ipinc * width;
/* For each line: */
for (; in < ein; in += ipitch, ein1 += ipitch, out += opitch) {
unsigned char **th = oth; /* Threshold table origin */
unsigned short *ip = in; /* Horizontal input pointer */
unsigned char *op = out; /* Horizontal output pointer */
/* Do pixels one output byte at a time */
for (; ip < ein1; ip += ipinc, op += opinc) {
int tt = lut[*ip];
*op = (unsigned char)th[0][tt];
if (++th >= eth)
th -= t->swidth;
}
/* Advance screen table pointers with vertical wrap */
oth += t->twidth;
eth += t->twidth;
if (eth > eeth) {
oth -= thtsize;
eth -= thtsize;
}
}
}
/* ----------------------------------------------------------- */
/* We're done with the screening object */
static void th_del(
thscreen *t
) {
if (t->lut != NULL)
free(t->lut);
if (t->thp != NULL)
free(t->thp);
free(t);
}
/* Create a new thscreen object */
/* Return NULL on error */
thscreen *new_thscreen(
int width, /* width in pixels of screen */
int height, /* Height in pixels of screen */
int xoff, int yoff, /* Pattern offsets into width & height */
double asp, /* Aspect ratio of screen (== dpiX/dpiY) */
int swap, /* Swap X & Y to invert aspect ratio & swap width/height */
ccoord *thli, /* Pointer to list of threshold coordinates */
sc_iencoding ie, /* Input encoding - must be scie_16 */
int oebpc, /* Output encoding bits per component - must be 8 */
int oelev, /* Output encoding levels. Must be <= 2 ^ oebpc */
int *oevalues, /* Optional output encoding values for each level */
/* Must be oelev entries. Default is 0 .. oelev-1 */
sc_oorder oo, /* Output bit ordering */
double olap, /* Overlap between levels, 0 - 1.0 */
void *cntx, /* Context for LUT table callback */
double (*lutfunc)(void *cntx, double in) /* Callback function, NULL if none */
) {
thscreen *t; /* Object being created */
int npix; /* Total pixels in screen */
double mrang; /* threshold modulation range */
double **fthr; /* Floating point threshold array */
int i, j;
DBG(("new_thscreen() called, oebpc = %d\n",oebpc));
DBG(("new_thscreen() called, oelev = %d\n",oelev));
/* Sanity check overlap */
if (olap < 0.0)
olap = 0.0;
else if (olap > 1.0)
olap = 1.0;
/* Sanity check parameters */
if (ie != scie_16) {
DBG(("new_thscreen() ie %d != scie_16\n",ie));
return NULL;
}
if (oebpc != 8) {
DBG(("new_thscreen() oebpc %d != 8\n",oebpc));
return NULL;
}
if (oelev < 2 || oelev > (1 << oebpc)) {
DBG(("new_thscreen() oelev %d > 2^%d = %d\n",oelev,1 << oebpc,oebpc));
return NULL;
}
if ((t = (thscreen *)calloc(1, sizeof(thscreen))) == NULL) {
DBG(("new_thscreen() calloc failed\n"));
return NULL;
}
/* Instantiation parameters */
t->ie = ie;
t->oebpc = oebpc;
t->oelev = oelev;
if (oevalues != NULL) {
for (i = 0; i < t->oelev; i++) {
if (oevalues[i] >= (1 << t->oebpc)) {
DBG(("new_thscreen() oevalues[%d] value %d can't fit in %d bits\n",i,oevalues[i],t->oebpc));
free(t);
return NULL;
}
t->oevalues[i] = oevalues[i];
}
} else {
for (i = 0; i < t->oelev; i++)
t->oevalues[i] = i;
}
t->oo = oo;
t->overlap = olap;
/* Create a suitable LUT from the given function */
/* Input is either 8 or 16 bits, output is always 16 bits */
DBG(("new_thscreen() about to create LUT\n"));
if ((t->lut = (int *)malloc(sizeof(int) * 65536)) == NULL) {
free(t);
DBG(("new_thscreen() malloc of 16 bit LUT failed\n"));
return NULL;
}
for (i = 0; i < 65536; i++) {
if (lutfunc != NULL) {
double v = i/65535.0;
v = lutfunc(cntx, v);
t->lut[i] = (int)(v * 65535.0 + 0.5);
} else
t->lut[i] = i;
}
/* Screen definition parameters */
if (swap) {
t->asp = 1.0/asp;
t->swidth = height;
t->sheight = width;
} else {
t->asp = asp;
t->swidth = width;
t->sheight = height;
}
DBG(("new_thscreen() target width %d, height %d, asp %f\n",t->swidth,t->sheight,t->asp));
DBG(("new_thscreen() given width %d, height %d, asp %f\n",width,height,asp));
npix = t->swidth * t->sheight; /* Total pixels */
/* Allow for read of a words worth of pixels from within screen: */
DBG(("new_thscreen() tot pix %d, lev %d, bpp %d\n",npix,t->oelev,t->oebpc));
t->twidth = t->swidth + (8/t->oebpc) -1;
t->theight = t->sheight;
DBG(("new_thscreen() th table size = %d x %d\n",t->twidth,t->theight));
DBG(("new_thscreen() about to turn screen list into float threshold matrix\n"));
/* Convert the list of screen cells into a floating point threshold array */
fthr = dmatrix(0, t->sheight-1, 0, t->swidth-1); /* Temporary matrix */
if (swap) {
double tt = xoff; /* Swap offsets to align with orientation */
xoff = yoff;
yoff = tt;
for (i = 0; i < npix; i++)
fthr[thli[i].x][thli[i].y] = (double)(i/(npix - 1.0));
} else {
for (i = 0; i < npix; i++)
fthr[thli[i].y][thli[i].x] = (double)(i/(npix - 1.0));
}
/* The range that the screen has to modulate */
/* over to cross all the thresholds evenly. */
mrang = 65535.0/(t->oelev - 1.0);
DBG(("new_thscreen() raw modulation rande = %f\n",mrang));
/* Modify the modulation range to accommodate any level overlap */
if (olap > 0.0 && t->oelev > 2) {
mrang = ((t->oelev - 2.0) * olap * mrang + 65535.0)/(t->oelev - 1.0);
DBG(("new_thscreen() modulation adjusted for overlap = %f\n",mrang));
}
/* Init the threshold table. It holds the quantized, encoded output */
/* values, allowing an input value offset by the screen to be */
/* thresholded directly into the output value. We allow a guard band at */
/* each end for the effects of the screen modulating the input value. */
DBG(("new_thscreen() about to init threshold table\n"));
t->tht = &t->_tht[32768]; /* base allows for -ve & +ve range */
for (i = -32768; i < (2 * 65536) + 32768; i++) {
if (i < mrang) { /* Lower guard band */
t->tht[i] = t->oevalues[0];
} else if (i >= 65535) { /* Upper guard band */
t->tht[i] = t->oevalues[t->oelev-1];
} else { /* Middle range */
t->tht[i] = t->oevalues[1 + (int)((t->oelev - 2.0) * (i - mrang)/(65535.0 - mrang))];
}
}
/* Allocate the 2D table of pointers into the */
/* threshold table that encodes the screen offset. */
if ((t->thp = (unsigned char **)malloc(sizeof(unsigned char *)
* t->twidth * t->theight)) == NULL) {
free_dmatrix(fthr, 0, t->sheight-1, 0, t->swidth-1);
free(t->lut);
free(t);
DBG(("new_thscreen() malloc of threshold pointer matrix failed\n"));
return NULL;
}
/* Setup the threshold pointer array to point into the apropriate */
/* point into the threshold array itself. This implicitly adds */
/* the screen pattern offset value to the input before thresholding it. */
/* The input screen offsets are applied at this point too. */
DBG(("new_thscreen() about to init threshold pointer table\n"));
for (i = 0; i < t->twidth; i++) {
for (j = 0; j < t->theight; j++) {
double sov = fthr[(j+yoff) % t->sheight][(i+xoff) % t->swidth];
int tho = (int)((mrang - 1.0) * (1.0 - sov) + 0.5);
t->thp[j * t->twidth + i] = &t->tht[tho];
}
}
free_dmatrix(fthr, 0, t->sheight-1, 0, t->swidth-1);
DBG(("new_thscreen() about to setup method pointers\n"));
/* Methods */
t->screen = thscreen16_8;
t->del = th_del;
DBG(("new_thscreen() done\n"));
return t;
}