summaryrefslogtreecommitdiff
path: root/src/main/dither-ed.c
blob: 3bf31af360f80dd6ebcc07fb6ebfa1f47d522d79 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
/*
 *
 *   Error diffusion and closely related adaptive hybrid dither algorithm
 *
 *   Copyright 1997-2003 Michael Sweet (mike@easysw.com) and
 *	Robert Krawitz (rlk@alum.mit.edu)
 *
 *   This program is free software; you can redistribute it and/or modify it
 *   under the terms of the GNU General Public License as published by the Free
 *   Software Foundation; either version 2 of the License, or (at your option)
 *   any later version.
 *
 *   This program is distributed in the hope that it will be useful, but
 *   WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 *   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 *   for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * Revision History:
 *
 *   See ChangeLog
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <gutenprint/gutenprint.h>
#include "gutenprint-internal.h"
#include <gutenprint/gutenprint-intl-internal.h>
#include <string.h>
#include "dither-impl.h"
#include "dither-inlined-functions.h"

/*
 * Add the error to the input value.  Notice that we micro-optimize this
 * to save a division when appropriate.  This has yielded measurable
 * improvement -- rlk 20030119
 */

#define UPDATE_COLOR(color, dither) (					   \
((dither) >= 0)? (color) + ((dither) >> 3) : (color) - ((-(dither)) >> 3))

/*
 * For Floyd-Steinberg, distribute the error residual.  We spread the
 * error to nearby points, spreading more broadly in lighter regions to
 * achieve more uniform distribution of color.  The actual distribution
 * is a triangular function.
 */

static inline int
update_dither(stpi_dither_t *d, int channel, int width,
	      int direction, int *error0, int *error1)
{
  int r = CHANNEL(d, channel).v;
  int o = CHANNEL(d, channel).o;
  int tmp = r;
  int i, dist, dist1;
  int delta, delta1;
  int offset;
  if (tmp == 0)
    return error0[direction];
  if (tmp > 65535)
    tmp = 65535;
  if (d->spread >= 16 || o >= 2048)
    {
      tmp += tmp;
      tmp += tmp;
      error1[0] += tmp;
      return error0[direction] + tmp;
    }
  else
    {
      int tmpo = o << 5;
      offset = ((65535 - tmpo) >> d->spread) +
	((tmp & d->spread_mask) > (tmpo & d->spread_mask));
    }
  switch (offset)
    {
    case 0:
      tmp += tmp;
      tmp += tmp;
      error1[0] += tmp;
      return error0[direction] + tmp;
    case 1:
      error1[-1] += tmp;
      error1[1] += tmp;
      tmp += tmp;
      error1[0] += tmp;
      tmp += tmp;
      return error0[direction] + tmp;
    default:
      tmp += tmp;
      tmp += tmp;
      dist = tmp / d->offset0_table[offset];
      dist1 = tmp / d->offset1_table[offset];
      delta = dist;
      delta1 = dist1;
      for (i = -offset; i; i++)
	{
	  error1[i] += delta;
	  error1[-i] += delta;
	  error0[i] += delta1;
	  error0[-i] += delta1;
	  delta1 += dist1;
	  delta += dist;
	}
      error1[0] += delta;
      return error0[direction];
    }
}


/*
 * Print a single dot.  This routine has become awfully complicated
 * awfully fast!
 */

static inline int
print_color(const stpi_dither_t *d, stpi_dither_channel_t *dc, int x, int y,
	    unsigned char bit, int length, int dontprint, int stpi_dither_type,
	    const unsigned char *mask)
{
  int base = dc->b;
  int density = dc->o;
  int adjusted = dc->v;
  unsigned randomizer = dc->randomizer;
  stp_dither_matrix_impl_t *pick_matrix = &(dc->pick);
  stp_dither_matrix_impl_t *dither_matrix = &(dc->dithermat);
  unsigned rangepoint = 32768;
  unsigned vmatrix;
  int i;
  int j;
  unsigned char *tptr;
  unsigned bits;
  unsigned v;
  int levels = dc->nlevels - 1;
  int dither_value = adjusted;
  stpi_ink_defn_t *lower;
  stpi_ink_defn_t *upper;

  if (base <= 0 || density <= 0 ||
      (adjusted <= 0 && !(stpi_dither_type & D_ADAPTIVE_BASE)))
    return adjusted;
  if (density > 65535)
    density = 65535;

  /*
   * Look for the appropriate range into which the input value falls.
   * Notice that we use the input, not the error, to decide what dot type
   * to print (if any).  We actually use the "density" input to permit
   * the caller to use something other that simply the input value, if it's
   * desired to use some function of overall density, rather than just
   * this color's input, for this purpose.
   */
  for (i = levels; i >= 0; i--)
    {
      stpi_dither_segment_t *dd = &(dc->ranges[i]);

      if (density <= dd->lower->range)
	continue;

      /*
       * If we're using an adaptive dithering method, decide whether
       * to use the Floyd-Steinberg or the ordered method based on the
       * input value.
       */
      if (stpi_dither_type & D_ADAPTIVE_BASE)
	{
	  stpi_dither_type -= D_ADAPTIVE_BASE;

	  if (i < levels || base <= d->adaptive_limit)
	    {
	      stpi_dither_type = D_ORDERED;
	      dither_value = base;
	    }
	  else if (adjusted <= 0)
	    return adjusted;
	}
      /*
       * Where are we within the range.  If we're going to print at
       * all, this determines the probability of printing the darker
       * vs. the lighter ink.  If the inks are identical (same value
       * and darkness), it doesn't matter.
       *
       * We scale the input linearly against the top and bottom of the
       * range.
       */

      lower = dd->lower;
      upper = dd->upper;

      /*
       * Reduce the randomness as the base value increases, to get
       * smoother output in the midtones.  Idea suggested by
       * Thomas Tonino.
       */
      if (stpi_dither_type & D_ORDERED_BASE)
	{
	  rangepoint = density - dd->lower->range;
	  if (dd->range_span < 65535)
	    rangepoint = rangepoint * 65535 / dd->range_span;
	  vmatrix = 0;
	}
      else
	{

	  /*
	   * Compute the virtual dot size that we're going to print.
	   * This is somewhere between the two candidate dot sizes.
	   * This is scaled between the high and low value.
	   */

	  unsigned virtual_value;
	  rangepoint = density - dd->lower->range;
	  if (dd->range_span < 65535)
	    rangepoint = rangepoint * 65535 / dd->range_span;
	  if (dd->value_span == 0)
	    virtual_value = upper->value;
	  else /* if (dd->range_span == 0) */
	    virtual_value = (upper->value + lower->value) / 2;
	  /*
	    else
	    virtual_value = lower->value + (dd->value_span * rangepoint / 65535);
	  */
	  randomizer = 0;
	  if (randomizer > 0)
	    {
	      if (base > d->d_cutoff)
		randomizer = 0;
	      else if (base > d->d_cutoff / 2)
		randomizer = randomizer * 2 * (d->d_cutoff - base) / d->d_cutoff;
	    }

	  /*
	   * Compute the comparison value to decide whether to print at
	   * all.  If there is no randomness, simply divide the virtual
	   * dotsize by 2 to get standard "pure" Floyd-Steinberg (or "pure"
	   * matrix dithering, which degenerates to a threshold).
	   */
	  if (randomizer == 0)
	    vmatrix = virtual_value / 2;
	  else
	    {
	      /*
	       * First, compute a value between 0 and 65535 that will be
	       * scaled to produce an offset from the desired threshold.
	       */
	      vmatrix = ditherpoint(d, dither_matrix, x);
	      /*
	       * Now, scale the virtual dot size appropriately.  Note that
	       * we'll get something evenly distributed between 0 and
	       * the virtual dot size, centered on the dot size / 2,
	       * which is the normal threshold value.
	       */
	      vmatrix = vmatrix * virtual_value / 65535;
	      if (randomizer != 65535)
		{
		  /*
		   * We want vmatrix to be scaled between 0 and
		   * virtual_value when randomizer is 65535 (fully random).
		   * When it's less, we want it to scale through part of
		   * that range. In all cases, it should center around
		   * virtual_value / 2.
		   *
		   * vbase is the bottom of the scaling range.
		   */
		  unsigned vbase = virtual_value * (65535u - randomizer) /
		    131070u;
		  vmatrix = vmatrix * randomizer / 65535;
		  vmatrix += vbase;
		}
	    } /* randomizer != 0 */
	}
      /*
       * After all that, printing is almost an afterthought.
       * Pick the actual dot size (using a matrix here) and print it.
       */
      if (dither_value > 0 && dither_value >= vmatrix)
	{
	  stpi_ink_defn_t *subc;

	  if (dd->is_same_ink)
	    subc = upper;
	  else
	    {
	      if (rangepoint >= ditherpoint(d, pick_matrix, x))
		subc = upper;
	      else
		subc = lower;
	    }
	  v = subc->value;
	  if (!mask || (*(mask + d->ptr_offset) & bit))
	    {
	      if (dc->ptr)
		{
		  tptr = dc->ptr + d->ptr_offset;

		  /*
		   * Lay down all of the bits in the pixel.
		   */
		  if (dontprint < v)
		    {
		      bits = subc->bits;
		      set_row_ends(dc, x);
		      for (j = 1; j <= bits; j += j, tptr += length)
			{
			  if (j & bits)
			    tptr[0] |= bit;
			}
		    }
		}
	    }
	  if (stpi_dither_type & D_ORDERED_BASE)
	    {
	      double adj = -(int) v;
	      adj /= 2.0;
	      adjusted = adj;
	    }
	  else
	    {
	      double adj = v;
	      adjusted -= adj;
	    }
	}
      return adjusted;
    }
  return adjusted;
}

static int
shared_ed_initializer(stpi_dither_t *d,
		      int row,
		      int duplicate_line,
		      int zero_mask,
		      int length,
		      int direction,
		      int ****error,
		      int **ndither)
{
  int i, j;
  for (i = 0; i < CHANNEL_COUNT(d); i++)
    CHANNEL(d, i).error_rows = 2;
  if (!duplicate_line)
    {
      if ((zero_mask & ((1 << CHANNEL_COUNT(d)) - 1)) !=
	  ((1 << CHANNEL_COUNT(d)) - 1))
	d->last_line_was_empty = 0;
      else
	d->last_line_was_empty++;
    }
  else if (d->last_line_was_empty)
    d->last_line_was_empty++;
  if (d->last_line_was_empty >= 5)
    return 0;
  else if (d->last_line_was_empty == 4)
    {
      for (i = 0; i < CHANNEL_COUNT(d); i++)
	for (j = 0; j < d->error_rows; j++)
	  memset(stpi_dither_get_errline(d, row + j, i), 0,
		 d->dst_width * sizeof(int));
      return 0;
    }
  d->ptr_offset = (direction == 1) ? 0 : length - 1;

  *error = stp_malloc(CHANNEL_COUNT(d) * sizeof(int **));
  *ndither = stp_malloc(CHANNEL_COUNT(d) * sizeof(int));
  for (i = 0; i < CHANNEL_COUNT(d); i++)
    {
      (*error)[i] = stp_malloc(d->error_rows * sizeof(int *));
      for (j = 0; j < d->error_rows; j++)
	{
	  (*error)[i][j] = stpi_dither_get_errline(d, row + j, i);
	  if (j == d->error_rows - 1)
	    memset((*error)[i][j], 0, d->dst_width * sizeof(int));
	  if (direction == -1)
	    (*error)[i][j] += d->dst_width - 1;
	}
      (*ndither)[i] = (*error)[i][0][0];
    }
  return 1;
}

static void
shared_ed_deinitializer(stpi_dither_t *d,
			int ***error,
			int *ndither)
{
  int i;
  for (i = 0; i < CHANNEL_COUNT(d); i++)
    {
      STP_SAFE_FREE(error[i]);
    }
  STP_SAFE_FREE(error);
  STP_SAFE_FREE(ndither);
}

void
stpi_dither_ed(stp_vars_t *v,
	       int row,
	       const unsigned short *raw,
	       int duplicate_line,
	       int zero_mask,
	       const unsigned char *mask)
{
  stpi_dither_t *d = (stpi_dither_t *) stp_get_component_data(v, "Dither");
  int		x,
    		length;
  unsigned char	bit;
  int		i;
  int		*ndither;
  int		***error;

  int		terminate;
  int		direction = row & 1 ? 1 : -1;
  int xerror, xstep, xmod;

  length = (d->dst_width + 7) / 8;
  if (d->stpi_dither_type & D_ADAPTIVE_BASE)
    for (i = 0; i < CHANNEL_COUNT(d); i++)
      if (CHANNEL(d, i).nlevels > 1)
	{
	  stpi_dither_ordered(v, row, raw, duplicate_line, zero_mask, mask);
	  return;
	}
  if (!shared_ed_initializer(d, row, duplicate_line, zero_mask, length,
			     direction, &error, &ndither))
    return;

  x = (direction == 1) ? 0 : d->dst_width - 1;
  bit = 1 << (7 - (x & 7));
  xstep  = CHANNEL_COUNT(d) * (d->src_width / d->dst_width);
  xmod   = d->src_width % d->dst_width;
  xerror = (xmod * x) % d->dst_width;
  terminate = (direction == 1) ? d->dst_width : -1;

  if (direction == -1)
    raw += (CHANNEL_COUNT(d) * (d->src_width - 1));

  for (; x != terminate; x += direction)
    {
      for (i = 0; i < CHANNEL_COUNT(d); i++)
	{
	  if (CHANNEL(d, i).ptr)
	    {
	      CHANNEL(d, i).v = raw[i];
	      CHANNEL(d, i).o = CHANNEL(d, i).v;
	      CHANNEL(d, i).b = CHANNEL(d, i).v;
	      CHANNEL(d, i).v = UPDATE_COLOR(CHANNEL(d, i).v, ndither[i]);
	      CHANNEL(d, i).v = print_color(d, &(CHANNEL(d, i)), x, row, bit,
					    length, 0, d->stpi_dither_type,
					    mask);
	      ndither[i] = update_dither(d, i, d->src_width,
					 direction, error[i][0], error[i][1]);
	    }
	}
      ADVANCE_BIDIRECTIONAL(d, bit, raw, direction, CHANNEL_COUNT(d), xerror,
			    xstep, xmod, error, d->error_rows);
    }
  shared_ed_deinitializer(d, error, ndither);
  if (direction == -1)
    stpi_dither_reverse_row_ends(d);
}