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