xref: /plan9/sys/src/cmd/gs/src/siscale.c (revision 593dc095aefb2a85c828727bbfa9da139a49bdf4)
1 /* Copyright (C) 1995, 2000 Aladdin Enterprises.  All rights reserved.
2 
3   This software is provided AS-IS with no warranty, either express or
4   implied.
5 
6   This software is distributed under license and may not be copied,
7   modified or distributed except as expressly authorized under the terms
8   of the license contained in the file LICENSE in this distribution.
9 
10   For more information about licensing, please refer to
11   http://www.ghostscript.com/licensing/. For information on
12   commercial licensing, go to http://www.artifex.com/licensing/ or
13   contact Artifex Software, Inc., 101 Lucas Valley Road #110,
14   San Rafael, CA  94903, U.S.A., +1(415)492-9861.
15 */
16 
17 /* $Id: siscale.c,v 1.7 2002/06/16 03:58:15 lpd Exp $ */
18 /* Image scaling filters */
19 #include "math_.h"
20 #include "memory_.h"
21 #include "stdio_.h"
22 #include <assert.h>
23 #include "gconfigv.h"
24 #include "gdebug.h"
25 #include "strimpl.h"
26 #include "siscale.h"
27 
28 /*
29  *    Image scaling code is based on public domain code from
30  *      Graphics Gems III (pp. 414-424), Academic Press, 1992.
31  */
32 
33 /* ---------------- ImageScaleEncode/Decode ---------------- */
34 
35 /* Define whether to accumulate pixels in fixed or floating point. */
36 #if USE_FPU <= 0
37 
38 	/* Accumulate pixels in fixed point. */
39 
40 typedef int PixelWeight;
41 
42 #  if arch_ints_are_short
43 typedef long AccumTmp;
44 #  else
45 typedef int AccumTmp;
46 #  endif
47 
48 /*
49  *    The optimal scaling for fixed point arithmetic is a function of the
50  *    size of AccumTmp type, the size if the input pixel, the size of the
51  *    intermediate pixel (PixelTmp) and the size of the output pixel.  This
52  *    is set by these definitions and the fraction_bits variables in the
53  *    functions.
54  */
55 #define num_weight_bits\
56   ((sizeof(AccumTmp) - maxSizeofPixel) * 8 - (LOG2_MAX_ISCALE_SUPPORT + 1))
57 #define numScaleBits  ((maxSizeofPixel - sizeof(PixelTmp)) * 8 )
58 #define fixedScaleFactor  ((int) (1 << numScaleBits))
59 #define scale_PixelWeight(factor) ((int)((factor) * (1 << num_weight_bits)))
60 #define unscale_AccumTmp(atemp, fraction_bits) arith_rshift(atemp, fraction_bits)
61 #define NEED_FRACTION_BITS
62 
63 #else /* USE_FPU > 0 */
64 
65 	/* Accumulate pixels in floating point. */
66 
67 typedef float PixelWeight;
68 typedef double AccumTmp;
69 
70 #define num_weight_bits 0		/* Not used for floating point */
71 #define fixedScaleFactor 1		/* Straight scaling for floating point */
72 #define scale_PixelWeight(factor) (factor)
73 #define unscale_AccumTmp(atemp, fraction_bits) ((int)(atemp))
74 /*#undef NEED_FRACTION_BITS*/
75 
76 #endif /* USE_FPU */
77 
78 /* Temporary intermediate values */
79 typedef byte PixelTmp;
80 typedef int PixelTmp2;		/* extra width for clamping sum */
81 
82 #define minPixelTmp 0
83 #define maxPixelTmp 255
84 #define unitPixelTmp 255
85 
86 /* Max of all pixel sizes */
87 #define maxSizeofPixel 2
88 
89 /* Auxiliary structures. */
90 
91 typedef struct {
92     PixelWeight weight;		/* float or scaled fraction */
93 } CONTRIB;
94 
95 typedef struct {
96     int index;			/* index of first element in list of */
97     /* contributors */
98     int n;			/* number of contributors */
99     /* (not multiplied by stride) */
100     int first_pixel;		/* offset of first value in source data */
101 } CLIST;
102 
103 /* ImageScaleEncode / ImageScaleDecode */
104 typedef struct stream_IScale_state_s {
105     /* The client sets the params values before initialization. */
106     stream_image_scale_state_common;  /* = state_common + params */
107     /* The init procedure sets the following. */
108     int sizeofPixelIn;		/* bytes per input value, 1 or 2 */
109     int sizeofPixelOut;		/* bytes per output value, 1 or 2 */
110     double xscale, yscale;
111     void /*PixelIn */ *src;
112     void /*PixelOut */ *dst;
113     PixelTmp *tmp;
114     CLIST *contrib;
115     CONTRIB *items;
116     /* The following are updated dynamically. */
117     int src_y;
118     uint src_offset, src_size;
119     int dst_y;
120     uint dst_offset, dst_size;
121     CLIST dst_next_list;	/* for next output value */
122     int dst_last_index;		/* highest index used in list */
123     CONTRIB dst_items[MAX_ISCALE_SUPPORT];	/* ditto */
124 } stream_IScale_state;
125 
126 gs_private_st_ptrs5(st_IScale_state, stream_IScale_state,
127     "ImageScaleEncode/Decode state",
128     iscale_state_enum_ptrs, iscale_state_reloc_ptrs,
129     dst, src, tmp, contrib, items);
130 
131 /* ------ Digital filter definition ------ */
132 
133 /* Mitchell filter definition */
134 #define Mitchell_support 2.0
135 #define B (1.0 / 3.0)
136 #define C (1.0 / 3.0)
137 private double
Mitchell_filter(double t)138 Mitchell_filter(double t)
139 {
140     double t2 = t * t;
141 
142     if (t < 0)
143 	t = -t;
144 
145     if (t < 1)
146 	return
147 	    ((12 - 9 * B - 6 * C) * (t * t2) +
148 	     (-18 + 12 * B + 6 * C) * t2 +
149 	     (6 - 2 * B)) / 6;
150     else if (t < 2)
151 	return
152 	    ((-1 * B - 6 * C) * (t * t2) +
153 	     (6 * B + 30 * C) * t2 +
154 	     (-12 * B - 48 * C) * t +
155 	     (8 * B + 24 * C)) / 6;
156     else
157 	return 0;
158 }
159 
160 #define filter_support Mitchell_support
161 #define filter_proc Mitchell_filter
162 #define fproc(t) filter_proc(t)
163 #define fWidthIn filter_support
164 
165 /*
166  * The environment provides the following definitions:
167  *      typedef PixelTmp, PixelTmp2
168  *      double fproc(double t)
169  *      double fWidthIn
170  *      PixelTmp {min,max,unit}PixelTmp
171  */
172 #define CLAMP(v, mn, mx)\
173   (v < mn ? mn : v > mx ? mx : v)
174 
175 /* ------ Auxiliary procedures ------ */
176 
177 /* Define the minimum scale. */
178 #define min_scale ((fWidthIn * 2) / (MAX_ISCALE_SUPPORT - 1.01))
179 
180 /* Calculate the support for a given scale. */
181 /* The value is always in the range 1 .. MAX_ISCALE_SUPPORT. */
182 private int
contrib_pixels(double scale)183 contrib_pixels(double scale)
184 {
185     return (int)(fWidthIn / (scale >= 1.0 ? 1.0 : max(scale, min_scale))
186 		 * 2 + 1);
187 }
188 
189 /* Pre-calculate filter contributions for a row or a column. */
190 /* Return the highest input pixel index used. */
191 private int
calculate_contrib(CLIST * contrib,CONTRIB * items,double scale,int input_index,int size,int limit,int modulus,int stride,double rescale_factor)192 calculate_contrib(
193 	/* Return weight list parameters in contrib[0 .. size-1]. */
194 		     CLIST * contrib,
195 	/* Store weights in items[0 .. contrib_pixels(scale)*size-1]. */
196 	/* (Less space than this may actually be needed.) */
197 		     CONTRIB * items,
198 	/* The output image is scaled by 'scale' relative to the input. */
199 		     double scale,
200 	/* Start generating weights for input pixel 'input_index'. */
201 		     int input_index,
202 	/* Generate 'size' weight lists. */
203 		     int size,
204 	/* Limit pixel indices to 'limit', for clamping at the edges */
205 	/* of the image. */
206 		     int limit,
207 	/* Wrap pixel indices modulo 'modulus'. */
208 		     int modulus,
209 	/* Successive pixel values are 'stride' distance apart -- */
210 	/* normally, the number of color components. */
211 		     int stride,
212 	/* The unit of output is 'rescale_factor' times the unit of input. */
213 		     double rescale_factor
214 )
215 {
216     double scaled_factor = scale_PixelWeight(rescale_factor);
217     double WidthIn, fscale;
218     bool squeeze;
219     int npixels;
220     int i, j;
221     int last_index = -1;
222 
223     if (scale < 1.0) {
224 	double clamped_scale = max(scale, min_scale);
225 
226 	WidthIn = fWidthIn / clamped_scale;
227 	fscale = 1.0 / clamped_scale;
228 	squeeze = true;
229     } else {
230 	WidthIn = fWidthIn;
231 	fscale = 1.0;
232 	squeeze = false;
233     }
234     npixels = (int)(WidthIn * 2 + 1);
235 
236     for (i = 0; i < size; ++i) {
237 	double center = (input_index + i) / scale;
238 	int left = (int)ceil(center - WidthIn);
239 	int right = (int)floor(center + WidthIn);
240 
241 	/*
242 	 * In pathological cases, the limit may be much less
243 	 * than the support.  We do need to deal with this.
244 	 */
245 #define clamp_pixel(j)\
246   (j < 0 ? (-j >= limit ? limit - 1 : -j) :\
247    j >= limit ? (j >> 1 >= limit ? 0 : (limit - j) + limit - 1) :\
248    j)
249 	int lmin =
250 	(left < 0 ? 0 : left);
251 	int lmax =
252 	(left < 0 ? (-left >= limit ? limit - 1 : -left) : left);
253 	int rmin =
254 	(right >= limit ?
255 	 (right >> 1 >= limit ? 0 : (limit - right) + limit - 1) :
256 	 right);
257 	int rmax =
258 	(right >= limit ? limit - 1 : right);
259 	int first_pixel = min(lmin, rmin);
260 	int last_pixel = max(lmax, rmax);
261 	CONTRIB *p;
262 
263 	if (last_pixel > last_index)
264 	    last_index = last_pixel;
265 	contrib[i].first_pixel = (first_pixel % modulus) * stride;
266 	contrib[i].n = last_pixel - first_pixel + 1;
267 	contrib[i].index = i * npixels;
268 	p = items + contrib[i].index;
269 	for (j = 0; j < npixels; ++j)
270 	    p[j].weight = 0;
271 	if (squeeze) {
272 	    for (j = left; j <= right; ++j) {
273 		double weight =
274 		fproc((center - j) / fscale) / fscale;
275 		int n = clamp_pixel(j);
276 		int k = n - first_pixel;
277 
278 		p[k].weight +=
279 		    (PixelWeight) (weight * scaled_factor);
280 	    }
281 	} else {
282 	    for (j = left; j <= right; ++j) {
283 		double weight = fproc(center - j);
284 		int n = clamp_pixel(j);
285 		int k = n - first_pixel;
286 
287 		p[k].weight +=
288 		    (PixelWeight) (weight * scaled_factor);
289 	    }
290 	}
291     }
292     return last_index;
293 }
294 
295 
296 /* Apply filter to zoom horizontally from src to tmp. */
297 private void
zoom_x(PixelTmp * tmp,const void * src,int sizeofPixelIn,int tmp_width,int WidthIn,int Colors,const CLIST * contrib,const CONTRIB * items)298 zoom_x(PixelTmp * tmp, const void /*PixelIn */ *src, int sizeofPixelIn,
299        int tmp_width, int WidthIn, int Colors, const CLIST * contrib,
300        const CONTRIB * items)
301 {
302     int c, i;
303 #ifdef NEED_FRACTION_BITS
304     const int fraction_bits =
305 	(sizeofPixelIn - sizeof(PixelTmp)) * 8 + num_weight_bits;
306 #endif
307 
308     for (c = 0; c < Colors; ++c) {
309 	PixelTmp *tp = tmp + c;
310 	const CLIST *clp = contrib;
311 
312 	if_debug1('W', "[W]zoom_x color %d:", c);
313 
314 #define zoom_x_loop(PixelIn, PixelIn2)\
315 		const PixelIn *raster = (const PixelIn *)src + c;\
316 		for ( i = 0; i < tmp_width; tp += Colors, ++clp, ++i )\
317 		  {	AccumTmp weight = 0;\
318 			{ int j = clp->n;\
319 			  const PixelIn *pp = raster + clp->first_pixel;\
320 			  const CONTRIB *cp = items + clp->index;\
321 			  switch ( Colors )\
322 			  {\
323 			  case 1:\
324 			    for ( ; j > 0; pp += 1, ++cp, --j )\
325 			      weight += *pp * cp->weight;\
326 			    break;\
327 			  case 3:\
328 			    for ( ; j > 0; pp += 3, ++cp, --j )\
329 			      weight += *pp * cp->weight;\
330 			    break;\
331 			  default:\
332 			    for ( ; j > 0; pp += Colors, ++cp, --j )\
333 			      weight += *pp * cp->weight;\
334 			  }\
335 			}\
336 			{ PixelIn2 pixel = unscale_AccumTmp(weight, fraction_bits);\
337 			  if_debug1('W', " %ld", (long)pixel);\
338 			  *tp =\
339 			    (PixelTmp)CLAMP(pixel, minPixelTmp, maxPixelTmp);\
340 			}\
341 		  }
342 
343 	if (sizeofPixelIn == 1) {
344 	    zoom_x_loop(byte, int)
345 	} else {		/* sizeofPixelIn == 2 */
346 #if arch_ints_are_short
347 	    zoom_x_loop(bits16, long)
348 #else
349 	    zoom_x_loop(bits16, int)
350 #endif
351 	}
352 	if_debug0('W', "\n");
353     }
354 }
355 
356 
357 /*
358  * Apply filter to zoom vertically from tmp to dst.
359  * This is simpler because we can treat all columns identically
360  * without regard to the number of samples per pixel.
361  */
362 private void
zoom_y(void * dst,int sizeofPixelOut,uint MaxValueOut,const PixelTmp * tmp,int WidthOut,int tmp_width,int Colors,const CLIST * contrib,const CONTRIB * items)363 zoom_y(void /*PixelOut */ *dst, int sizeofPixelOut, uint MaxValueOut,
364        const PixelTmp * tmp, int WidthOut, int tmp_width,
365        int Colors, const CLIST * contrib, const CONTRIB * items)
366 {
367     int kn = WidthOut * Colors;
368     int cn = contrib->n;
369     int first_pixel = contrib->first_pixel;
370     const CONTRIB *cbp = items + contrib->index;
371     int kc;
372     PixelTmp2 max_weight = MaxValueOut;
373 #ifdef NEED_FRACTION_BITS
374     const int fraction_bits =
375 	(sizeof(PixelTmp) - sizeofPixelOut) * 8 + num_weight_bits;
376 #endif
377 
378     if_debug0('W', "[W]zoom_y: ");
379 
380 #define zoom_y_loop(PixelOut)\
381 	for ( kc = 0; kc < kn; ++kc ) {\
382 		AccumTmp weight = 0;\
383 		{ const PixelTmp *pp = &tmp[kc + first_pixel];\
384 		  int j = cn;\
385 		  const CONTRIB *cp = cbp;\
386 		  for ( ; j > 0; pp += kn, ++cp, --j )\
387 		    weight += *pp * cp->weight;\
388 		}\
389 		{ PixelTmp2 pixel = unscale_AccumTmp(weight, fraction_bits);\
390 		  if_debug1('W', " %d", pixel);\
391 		  ((PixelOut *)dst)[kc] =\
392 		    (PixelOut)CLAMP(pixel, 0, max_weight);\
393 		}\
394 	}
395 
396     if (sizeofPixelOut == 1) {
397 	zoom_y_loop(byte)
398     } else {			/* sizeofPixelOut == 2 */
399 	zoom_y_loop(bits16)
400     }
401     if_debug0('W', "\n");
402 }
403 
404 /* ------ Stream implementation ------ */
405 
406 #define tmp_width params.WidthOut
407 #define tmp_height params.HeightIn
408 
409 /* Forward references */
410 private void s_IScale_release(stream_state * st);
411 
412 /* Calculate the weights for an output row. */
413 private void
calculate_dst_contrib(stream_IScale_state * ss,int y)414 calculate_dst_contrib(stream_IScale_state * ss, int y)
415 {
416     uint row_size = ss->params.WidthOut * ss->params.Colors;
417     int last_index =
418     calculate_contrib(&ss->dst_next_list, ss->dst_items, ss->yscale,
419 		      y, 1, ss->params.HeightIn, MAX_ISCALE_SUPPORT, row_size,
420 		      (double)ss->params.MaxValueOut / (fixedScaleFactor * unitPixelTmp) );
421     int first_index_mod = ss->dst_next_list.first_pixel / row_size;
422 
423     ss->dst_last_index = last_index;
424     last_index %= MAX_ISCALE_SUPPORT;
425     if (last_index < first_index_mod) {		/* Shuffle the indices to account for wraparound. */
426 	CONTRIB shuffle[MAX_ISCALE_SUPPORT];
427 	int i;
428 
429 	for (i = 0; i < MAX_ISCALE_SUPPORT; ++i)
430 	    shuffle[i].weight =
431 		(i <= last_index ?
432 		 ss->dst_items[i + MAX_ISCALE_SUPPORT - first_index_mod].weight :
433 		 i >= first_index_mod ?
434 		 ss->dst_items[i - first_index_mod].weight :
435 		 0);
436 	memcpy(ss->dst_items, shuffle, MAX_ISCALE_SUPPORT * sizeof(CONTRIB));
437 	ss->dst_next_list.n = MAX_ISCALE_SUPPORT;
438 	ss->dst_next_list.first_pixel = 0;
439     }
440 #ifdef DEBUG
441     if (gs_debug_c('w')) {
442 	dprintf1("[w]calc dest contrib for y = %d\n", y);
443     }
444 #endif
445 }
446 
447 /* Set default parameter values (actually, just clear pointers). */
448 private void
s_IScale_set_defaults(stream_state * st)449 s_IScale_set_defaults(stream_state * st)
450 {
451     stream_IScale_state *const ss = (stream_IScale_state *) st;
452 
453     ss->src = 0;
454     ss->dst = 0;
455     ss->tmp = 0;
456     ss->contrib = 0;
457     ss->items = 0;
458 }
459 
460 /* Initialize the filter. */
461 private int
s_IScale_init(stream_state * st)462 s_IScale_init(stream_state * st)
463 {
464     stream_IScale_state *const ss = (stream_IScale_state *) st;
465     gs_memory_t *mem = ss->memory;
466 
467     ss->sizeofPixelIn = ss->params.BitsPerComponentIn / 8;
468     ss->sizeofPixelOut = ss->params.BitsPerComponentOut / 8;
469     ss->xscale = (double)ss->params.WidthOut / (double)ss->params.WidthIn;
470     ss->yscale = (double)ss->params.HeightOut / (double)ss->params.HeightIn;
471 
472     ss->src_y = 0;
473     ss->src_size = ss->params.WidthIn * ss->sizeofPixelIn * ss->params.Colors;
474     ss->src_offset = 0;
475     ss->dst_y = 0;
476     ss->dst_size = ss->params.WidthOut * ss->sizeofPixelOut * ss->params.Colors;
477     ss->dst_offset = 0;
478 
479     /* create intermediate image to hold horizontal zoom */
480     ss->tmp = (PixelTmp *) gs_alloc_byte_array(mem,
481 					   min(ss->tmp_height, MAX_ISCALE_SUPPORT),
482 			      ss->tmp_width * ss->params.Colors * sizeof(PixelTmp),
483 					       "image_scale tmp");
484     ss->contrib = (CLIST *) gs_alloc_byte_array(mem,
485 					   max(ss->params.WidthOut, ss->params.HeightOut),
486 				      sizeof(CLIST), "image_scale contrib");
487     ss->items = (CONTRIB *) gs_alloc_byte_array(mem,
488 				  contrib_pixels(ss->xscale) * ss->params.WidthOut,
489 				 sizeof(CONTRIB), "image_scale contrib[*]");
490     /* Allocate buffers for 1 row of source and destination. */
491     ss->dst = gs_alloc_byte_array(mem, ss->params.WidthOut * ss->params.Colors,
492 				  ss->sizeofPixelOut, "image_scale dst");
493     ss->src = gs_alloc_byte_array(mem, ss->params.WidthIn * ss->params.Colors,
494 				  ss->sizeofPixelIn, "image_scale src");
495     if (ss->tmp == 0 || ss->contrib == 0 || ss->items == 0 ||
496 	ss->dst == 0 || ss->src == 0
497 	) {
498 	s_IScale_release(st);
499 	return ERRC;
500 /****** WRONG ******/
501     }
502     /* Pre-calculate filter contributions for a row. */
503     calculate_contrib(ss->contrib, ss->items, ss->xscale,
504 		      0, ss->params.WidthOut, ss->params.WidthIn, ss->params.WidthIn,
505 		      ss->params.Colors, (double)unitPixelTmp * fixedScaleFactor / ss->params.MaxValueIn);
506 
507     /* Prepare the weights for the first output row. */
508     calculate_dst_contrib(ss, 0);
509 
510     return 0;
511 
512 }
513 
514 /* Process a buffer.  Note that this handles Encode and Decode identically. */
515 private int
s_IScale_process(stream_state * st,stream_cursor_read * pr,stream_cursor_write * pw,bool last)516 s_IScale_process(stream_state * st, stream_cursor_read * pr,
517 		 stream_cursor_write * pw, bool last)
518 {
519     stream_IScale_state *const ss = (stream_IScale_state *) st;
520 
521     /* Check whether we need to deliver any output. */
522 
523   top:while (ss->src_y > ss->dst_last_index) {	/* We have enough horizontally scaled temporary rows */
524 	/* to generate a vertically scaled output row. */
525 	uint wleft = pw->limit - pw->ptr;
526 
527 	if (ss->dst_y == ss->params.HeightOut)
528 	    return EOFC;
529 	if (wleft == 0)
530 	    return 1;
531 	if (ss->dst_offset == 0) {
532 	    byte *row;
533 
534 	    if (wleft >= ss->dst_size) {	/* We can scale the row directly into the output. */
535 		row = pw->ptr + 1;
536 		pw->ptr += ss->dst_size;
537 	    } else {		/* We'll have to buffer the row. */
538 		row = ss->dst;
539 	    }
540 	    /* Apply filter to zoom vertically from tmp to dst. */
541 	    zoom_y(row, ss->sizeofPixelOut, ss->params.MaxValueOut, ss->tmp,
542 		   ss->params.WidthOut, ss->tmp_width, ss->params.Colors,
543 		   &ss->dst_next_list, ss->dst_items);
544 	    /* Idiotic C coercion rules allow T* and void* to be */
545 	    /* inter-assigned freely, but not compared! */
546 	    if ((void *)row != ss->dst)		/* no buffering */
547 		goto adv;
548 	} {			/* We're delivering a buffered output row. */
549 	    uint wcount = ss->dst_size - ss->dst_offset;
550 	    uint ncopy = min(wleft, wcount);
551 
552 	    memcpy(pw->ptr + 1, (byte *) ss->dst + ss->dst_offset, ncopy);
553 	    pw->ptr += ncopy;
554 	    ss->dst_offset += ncopy;
555 	    if (ncopy != wcount)
556 		return 1;
557 	    ss->dst_offset = 0;
558 	}
559 	/* Advance to the next output row. */
560       adv:++(ss->dst_y);
561 	if (ss->dst_y != ss->params.HeightOut)
562 	    calculate_dst_contrib(ss, ss->dst_y);
563     }
564 
565     /* Read input data and scale horizontally into tmp. */
566 
567     {
568 	uint rleft = pr->limit - pr->ptr;
569 	uint rcount = ss->src_size - ss->src_offset;
570 
571 	if (rleft == 0)
572 	    return 0;		/* need more input */
573 #ifdef DEBUG
574 	assert(ss->src_y < ss->params.HeightIn);
575 #endif
576 	if (rleft >= rcount) {	/* We're going to fill up a row. */
577 	    const byte *row;
578 
579 	    if (ss->src_offset == 0) {	/* We have a complete row.  Read the data */
580 		/* directly from the input. */
581 		row = pr->ptr + 1;
582 	    } else {		/* We're buffering a row in src. */
583 		row = ss->src;
584 		memcpy((byte *) ss->src + ss->src_offset, pr->ptr + 1,
585 		       rcount);
586 		ss->src_offset = 0;
587 	    }
588 	    /* Apply filter to zoom horizontally from src to tmp. */
589 	    if_debug2('w', "[w]zoom_x y = %d to tmp row %d\n",
590 		      ss->src_y, (ss->src_y % MAX_ISCALE_SUPPORT));
591 	    zoom_x(ss->tmp + (ss->src_y % MAX_ISCALE_SUPPORT) *
592 		   ss->tmp_width * ss->params.Colors, row,
593 		   ss->sizeofPixelIn, ss->tmp_width, ss->params.WidthIn,
594 		   ss->params.Colors, ss->contrib, ss->items);
595 	    pr->ptr += rcount;
596 	    ++(ss->src_y);
597 	    goto top;
598 	} else {		/* We don't have a complete row.  Copy data to src buffer. */
599 	    memcpy((byte *) ss->src + ss->src_offset, pr->ptr + 1, rleft);
600 	    ss->src_offset += rleft;
601 	    pr->ptr += rleft;
602 	    return 0;
603 	}
604     }
605 }
606 
607 /* Release the filter's storage. */
608 private void
s_IScale_release(stream_state * st)609 s_IScale_release(stream_state * st)
610 {
611     stream_IScale_state *const ss = (stream_IScale_state *) st;
612     gs_memory_t *mem = ss->memory;
613 
614     gs_free_object(mem, (void *)ss->src, "image_scale src");	/* no longer const */
615     ss->src = 0;
616     gs_free_object(mem, ss->dst, "image_scale dst");
617     ss->dst = 0;
618     gs_free_object(mem, ss->items, "image_scale contrib[*]");
619     ss->items = 0;
620     gs_free_object(mem, ss->contrib, "image_scale contrib");
621     ss->contrib = 0;
622     gs_free_object(mem, ss->tmp, "image_scale tmp");
623     ss->tmp = 0;
624 }
625 
626 /* Stream template */
627 const stream_template s_IScale_template = {
628     &st_IScale_state, s_IScale_init, s_IScale_process, 1, 1,
629     s_IScale_release, s_IScale_set_defaults
630 };
631