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