1 /* Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999 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: gsbitops.c,v 1.8 2002/10/07 08:28:56 ghostgum Exp $ */
18 /* Bitmap filling, copying, and transforming operations */
19 #include "stdio_.h"
20 #include "memory_.h"
21 #include "gdebug.h"
22 #include "gserror.h"
23 #include "gserrors.h"
24 #include "gstypes.h"
25 #include "gsbittab.h"
26 #include "gxbitops.h"
27 #include "gxcindex.h"
28
29 /* ---------------- Bit-oriented operations ---------------- */
30
31 /* Define masks for little-endian operation. */
32 /* masks[i] has the first i bits off and the rest on. */
33 #if !arch_is_big_endian
34 const bits16 mono_copy_masks[17] = {
35 0xffff, 0xff7f, 0xff3f, 0xff1f,
36 0xff0f, 0xff07, 0xff03, 0xff01,
37 0xff00, 0x7f00, 0x3f00, 0x1f00,
38 0x0f00, 0x0700, 0x0300, 0x0100,
39 0x0000
40 };
41 const bits32 mono_fill_masks[33] = {
42 #define mask(n)\
43 ((~0xff | (0xff >> (n & 7))) << (n & -8))
44 mask( 0),mask( 1),mask( 2),mask( 3),mask( 4),mask( 5),mask( 6),mask( 7),
45 mask( 8),mask( 9),mask(10),mask(11),mask(12),mask(13),mask(14),mask(15),
46 mask(16),mask(17),mask(18),mask(19),mask(20),mask(21),mask(22),mask(23),
47 mask(24),mask(25),mask(26),mask(27),mask(28),mask(29),mask(30),mask(31),
48 0
49 #undef mask
50 };
51 #endif
52
53 /* Fill a rectangle of bits with an 8x1 pattern. */
54 /* The pattern argument must consist of the pattern in every byte, */
55 /* e.g., if the desired pattern is 0xaa, the pattern argument must */
56 /* have the value 0xaaaa (if ints are short) or 0xaaaaaaaa. */
57 #undef chunk
58 #define chunk mono_fill_chunk
59 #undef mono_masks
60 #define mono_masks mono_fill_masks
61 void
bits_fill_rectangle(byte * dest,int dest_bit,uint draster,mono_fill_chunk pattern,int width_bits,int height)62 bits_fill_rectangle(byte * dest, int dest_bit, uint draster,
63 mono_fill_chunk pattern, int width_bits, int height)
64 {
65 uint bit;
66 chunk right_mask;
67 int line_count = height;
68 chunk *ptr;
69 int last_bit;
70
71 #define FOR_EACH_LINE(stat)\
72 do { stat } while ( inc_ptr(ptr, draster), --line_count )
73
74 dest += (dest_bit >> 3) & -chunk_align_bytes;
75 ptr = (chunk *) dest;
76 bit = dest_bit & chunk_align_bit_mask;
77 last_bit = width_bits + bit - (chunk_bits + 1);
78
79 if (last_bit < 0) { /* <=1 chunk */
80 set_mono_thin_mask(right_mask, width_bits, bit);
81 if (pattern == 0)
82 FOR_EACH_LINE(*ptr &= ~right_mask;);
83 else if (pattern == (mono_fill_chunk)(-1))
84 FOR_EACH_LINE(*ptr |= right_mask;);
85 else
86 FOR_EACH_LINE(
87 *ptr = (*ptr & ~right_mask) | (pattern & right_mask); );
88 } else {
89 chunk mask;
90 int last = last_bit >> chunk_log2_bits;
91
92 set_mono_left_mask(mask, bit);
93 set_mono_right_mask(right_mask, (last_bit & chunk_bit_mask) + 1);
94 switch (last) {
95 case 0: /* 2 chunks */
96 if (pattern == 0)
97 FOR_EACH_LINE(*ptr &= ~mask; ptr[1] &= ~right_mask;);
98 else if (pattern == (mono_fill_chunk)(-1))
99 FOR_EACH_LINE(*ptr |= mask; ptr[1] |= right_mask;);
100 else
101 FOR_EACH_LINE(
102 *ptr = (*ptr & ~mask) | (pattern & mask);
103 ptr[1] = (ptr[1] & ~right_mask) | (pattern & right_mask); );
104 break;
105 case 1: /* 3 chunks */
106 if (pattern == 0)
107 FOR_EACH_LINE( *ptr &= ~mask;
108 ptr[1] = 0;
109 ptr[2] &= ~right_mask; );
110 else if (pattern == (mono_fill_chunk)(-1))
111 FOR_EACH_LINE( *ptr |= mask;
112 ptr[1] = ~(chunk) 0;
113 ptr[2] |= right_mask; );
114 else
115 FOR_EACH_LINE( *ptr = (*ptr & ~mask) | (pattern & mask);
116 ptr[1] = pattern;
117 ptr[2] = (ptr[2] & ~right_mask) | (pattern & right_mask); );
118 break;
119 default:{ /* >3 chunks */
120 uint byte_count = (last_bit >> 3) & -chunk_bytes;
121
122 if (pattern == 0)
123 FOR_EACH_LINE( *ptr &= ~mask;
124 memset(ptr + 1, 0, byte_count);
125 ptr[last + 1] &= ~right_mask; );
126 else if (pattern == (mono_fill_chunk)(-1))
127 FOR_EACH_LINE( *ptr |= mask;
128 memset(ptr + 1, 0xff, byte_count);
129 ptr[last + 1] |= right_mask; );
130 else
131 FOR_EACH_LINE(
132 *ptr = (*ptr & ~mask) | (pattern & mask);
133 memset(ptr + 1, (byte) pattern, byte_count);
134 ptr[last + 1] = (ptr[last + 1] & ~right_mask) |
135 (pattern & right_mask); );
136 }
137 }
138 }
139 #undef FOR_EACH_LINE
140 }
141
142 /*
143 * Similar to bits_fill_rectangle, but with an additional source mask.
144 * The src_mask variable is 1 for those bits of the original that are
145 * to be retained. The mask argument must consist of the requisite value
146 * in every byte, in the same manner as the pattern.
147 */
148 void
bits_fill_rectangle_masked(byte * dest,int dest_bit,uint draster,mono_fill_chunk pattern,mono_fill_chunk src_mask,int width_bits,int height)149 bits_fill_rectangle_masked(byte * dest, int dest_bit, uint draster,
150 mono_fill_chunk pattern, mono_fill_chunk src_mask,
151 int width_bits, int height)
152 {
153 uint bit;
154 chunk right_mask;
155 int line_count = height;
156 chunk *ptr;
157 int last_bit;
158
159 #define FOR_EACH_LINE(stat)\
160 do { stat } while ( inc_ptr(ptr, draster), --line_count )
161
162 dest += (dest_bit >> 3) & -chunk_align_bytes;
163 ptr = (chunk *) dest;
164 bit = dest_bit & chunk_align_bit_mask;
165 last_bit = width_bits + bit - (chunk_bits + 1);
166
167 if (last_bit < 0) { /* <=1 chunk */
168 set_mono_thin_mask(right_mask, width_bits, bit);
169 right_mask &= ~src_mask;
170 if (pattern == 0)
171 FOR_EACH_LINE(*ptr &= ~right_mask;);
172 else if (pattern == (mono_fill_chunk)(-1))
173 FOR_EACH_LINE(*ptr |= right_mask;);
174 else
175 FOR_EACH_LINE(
176 *ptr = (*ptr & ~right_mask) | (pattern & right_mask); );
177 } else {
178 chunk mask;
179 int last = last_bit >> chunk_log2_bits;
180
181 set_mono_left_mask(mask, bit);
182 set_mono_right_mask(right_mask, (last_bit & chunk_bit_mask) + 1);
183 mask &= ~src_mask;
184 right_mask &= ~src_mask;
185 switch (last) {
186 case 0: /* 2 chunks */
187 if (pattern == 0)
188 FOR_EACH_LINE(*ptr &= ~mask; ptr[1] &= ~right_mask;);
189 else if (pattern == (mono_fill_chunk)(-1))
190 FOR_EACH_LINE(*ptr |= mask; ptr[1] |= right_mask;);
191 else
192 FOR_EACH_LINE(
193 *ptr = (*ptr & ~mask) | (pattern & mask);
194 ptr[1] = (ptr[1] & ~right_mask) | (pattern & right_mask); );
195 break;
196 case 1: /* 3 chunks */
197 if (pattern == 0)
198 FOR_EACH_LINE( *ptr &= ~mask;
199 ptr[1] &= src_mask;
200 ptr[2] &= ~right_mask; );
201 else if (pattern == (mono_fill_chunk)(-1))
202 FOR_EACH_LINE( *ptr |= mask;
203 ptr[1] |= ~src_mask;
204 ptr[2] |= right_mask; );
205 else
206 FOR_EACH_LINE( *ptr = (*ptr & ~mask) | (pattern & mask);
207 ptr[1] =(ptr[1] & src_mask) | pattern;
208 ptr[2] = (ptr[2] & ~right_mask) | (pattern & right_mask); );
209 break;
210 default:{ /* >3 chunks */
211 int i;
212
213 if (pattern == 0)
214 FOR_EACH_LINE( *ptr++ &= ~mask;
215 for (i = 0; i < last; i++)
216 *ptr++ &= src_mask;
217 *ptr &= ~right_mask; );
218 else if (pattern == (mono_fill_chunk)(-1))
219 FOR_EACH_LINE( *ptr++ |= mask;
220 for (i = 0; i < last; i++)
221 *ptr++ |= ~src_mask;
222 *ptr |= right_mask; );
223 else
224 FOR_EACH_LINE(
225 /* note: we know (pattern & ~src_mask) == pattern */
226 *ptr = (*ptr & ~mask) | (pattern & mask);
227 ++ptr;
228 for (i = 0; i < last; i++, ptr++)
229 *ptr = (*ptr & src_mask) | pattern;
230 *ptr = (*ptr & ~right_mask) | (pattern & right_mask); );
231 }
232 }
233 }
234 #undef FOR_EACH_LINE
235 }
236
237 /* Replicate a bitmap horizontally in place. */
238 void
bits_replicate_horizontally(byte * data,uint width,uint height,uint raster,uint replicated_width,uint replicated_raster)239 bits_replicate_horizontally(byte * data, uint width, uint height,
240 uint raster, uint replicated_width, uint replicated_raster)
241 {
242 /* The current algorithm is extremely inefficient! */
243 const byte *orig_row = data + (height - 1) * raster;
244 byte *tile_row = data + (height - 1) * replicated_raster;
245 uint y;
246
247 if (!(width & 7)) {
248 uint src_bytes = width >> 3;
249 uint dest_bytes = replicated_width >> 3;
250
251 for (y = height; y-- > 0;
252 orig_row -= raster, tile_row -= replicated_raster
253 ) {
254 uint move = src_bytes;
255 const byte *from = orig_row;
256 byte *to = tile_row + dest_bytes - src_bytes;
257
258 memmove(to, from, move);
259 while (to - tile_row >= move) {
260 from = to;
261 to -= move;
262 memmove(to, from, move);
263 move <<= 1;
264 }
265 if (to != tile_row)
266 memmove(tile_row, to, to - tile_row);
267 }
268 } else {
269 /*
270 * This algorithm is inefficient, but probably not worth improving.
271 */
272 uint bit_count = width & (uint)(-(int)width); /* lowest bit: 1, 2, or 4 */
273 uint left_mask = (0xff00 >> bit_count) & 0xff;
274
275 for (y = height; y-- > 0;
276 orig_row -= raster, tile_row -= replicated_raster
277 ) {
278 uint sx;
279
280 for (sx = width; sx > 0;) {
281 uint bits, dx;
282
283 sx -= bit_count;
284 bits = (orig_row[sx >> 3] << (sx & 7)) & left_mask;
285 for (dx = sx + replicated_width; dx >= width;) {
286 byte *dp;
287 int dbit;
288
289 dx -= width;
290 dbit = dx & 7;
291 dp = tile_row + (dx >> 3);
292 *dp = (*dp & ~(left_mask >> dbit)) | (bits >> dbit);
293 }
294 }
295 }
296 }
297 }
298
299 /* Replicate a bitmap vertically in place. */
300 void
bits_replicate_vertically(byte * data,uint height,uint raster,uint replicated_height)301 bits_replicate_vertically(byte * data, uint height, uint raster,
302 uint replicated_height)
303 {
304 byte *dest = data;
305 uint h = replicated_height;
306 uint size = raster * height;
307
308 while (h > height) {
309 memcpy(dest + size, dest, size);
310 dest += size;
311 h -= height;
312 }
313 }
314
315 /* Find the bounding box of a bitmap. */
316 /* Assume bits beyond the width are zero. */
317 void
bits_bounding_box(const byte * data,uint height,uint raster,gs_int_rect * pbox)318 bits_bounding_box(const byte * data, uint height, uint raster,
319 gs_int_rect * pbox)
320 {
321 register const ulong *lp;
322 static const byte first_1[16] = {
323 4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0
324 };
325 static const byte last_1[16] = {
326 0, 4, 3, 4, 2, 4, 3, 4, 1, 4, 3, 4, 2, 4, 3, 4
327 };
328
329 /* Count trailing blank rows. */
330 /* Since the raster is a multiple of sizeof(long), */
331 /* we don't need to scan by bytes, only by longs. */
332
333 lp = (const ulong *)(data + raster * height);
334 while ((const byte *)lp > data && !lp[-1])
335 --lp;
336 if ((const byte *)lp == data) {
337 pbox->p.x = pbox->q.x = pbox->p.y = pbox->q.y = 0;
338 return;
339 }
340 pbox->q.y = height = ((const byte *)lp - data + raster - 1) / raster;
341
342 /* Count leading blank rows. */
343
344 lp = (const ulong *)data;
345 while (!*lp)
346 ++lp;
347 {
348 uint n = ((const byte *)lp - data) / raster;
349
350 pbox->p.y = n;
351 if (n)
352 height -= n, data += n * raster;
353 }
354
355 /* Find the left and right edges. */
356 /* We know that the first and last rows are non-blank. */
357
358 {
359 uint raster_longs = raster >> arch_log2_sizeof_long;
360 uint left = raster_longs - 1, right = 0;
361 ulong llong = 0, rlong = 0;
362 const byte *q;
363 uint h, n;
364
365 for (q = data, h = height; h-- > 0; q += raster) { /* Work from the left edge by longs. */
366 for (lp = (const ulong *)q, n = 0;
367 n < left && !*lp; lp++, n++
368 );
369 if (n < left)
370 left = n, llong = *lp;
371 else
372 llong |= *lp;
373 /* Work from the right edge by longs. */
374 for (lp = (const ulong *)(q + raster - sizeof(long)),
375 n = raster_longs - 1;
376
377 n > right && !*lp; lp--, n--
378 );
379 if (n > right)
380 right = n, rlong = *lp;
381 else
382 rlong |= *lp;
383 }
384
385 /* Do binary subdivision on edge longs. We assume that */
386 /* sizeof(long) = 4 or 8. */
387 #if arch_sizeof_long > 8
388 Error_longs_are_too_large();
389 #endif
390
391 #if arch_is_big_endian
392 # define last_bits(n) ((1L << (n)) - 1)
393 # define shift_out_last(x,n) ((x) >>= (n))
394 # define right_justify_last(x,n) DO_NOTHING
395 #else
396 # define last_bits(n) (-1L << ((arch_sizeof_long * 8) - (n)))
397 # define shift_out_last(x,n) ((x) <<= (n))
398 # define right_justify_last(x,n) (x) >>= ((arch_sizeof_long * 8) - (n))
399 #endif
400
401 left <<= arch_log2_sizeof_long + 3;
402 #if arch_sizeof_long == 8
403 if (llong & ~last_bits(32))
404 shift_out_last(llong, 32);
405 else
406 left += 32;
407 #endif
408 if (llong & ~last_bits(16))
409 shift_out_last(llong, 16);
410 else
411 left += 16;
412 if (llong & ~last_bits(8))
413 shift_out_last(llong, 8);
414 else
415 left += 8;
416 right_justify_last(llong, 8);
417 if (llong & 0xf0)
418 left += first_1[(byte) llong >> 4];
419 else
420 left += first_1[(byte) llong] + 4;
421
422 right <<= arch_log2_sizeof_long + 3;
423 #if arch_sizeof_long == 8
424 if (!(rlong & last_bits(32)))
425 shift_out_last(rlong, 32);
426 else
427 right += 32;
428 #endif
429 if (!(rlong & last_bits(16)))
430 shift_out_last(rlong, 16);
431 else
432 right += 16;
433 if (!(rlong & last_bits(8)))
434 shift_out_last(rlong, 8);
435 else
436 right += 8;
437 right_justify_last(rlong, 8);
438 if (!(rlong & 0xf))
439 right += last_1[(byte) rlong >> 4];
440 else
441 right += last_1[(uint) rlong & 0xf] + 4;
442
443 pbox->p.x = left;
444 pbox->q.x = right;
445 }
446 }
447
448 /* Extract a plane from a pixmap. */
449 int
bits_extract_plane(const bits_plane_t * dest,const bits_plane_t * source,int shift,int width,int height)450 bits_extract_plane(const bits_plane_t *dest /*write*/,
451 const bits_plane_t *source /*read*/, int shift, int width, int height)
452 {
453 int source_depth = source->depth;
454 int source_bit = source->x * source_depth;
455 const byte *source_row = source->data.read + (source_bit >> 3);
456 int dest_depth = dest->depth;
457 uint plane_mask = (1 << dest_depth) - 1;
458 int dest_bit = dest->x * dest_depth;
459 byte *dest_row = dest->data.write + (dest_bit >> 3);
460 enum {
461 EXTRACT_SLOW = 0,
462 EXTRACT_4_TO_1,
463 EXTRACT_32_TO_8
464 } loop_case = EXTRACT_SLOW;
465 int y;
466
467 source_bit &= 7;
468 dest_bit &= 7;
469 /* Check for the fast CMYK cases. */
470 if (!(source_bit | dest_bit)) {
471 switch (source_depth) {
472 case 4:
473 loop_case =
474 (dest_depth == 1 && !(source->raster & 3) &&
475 !(source->x & 1) ? EXTRACT_4_TO_1 :
476 EXTRACT_SLOW);
477 break;
478 case 32:
479 if (dest_depth == 8 && !(shift & 7)) {
480 loop_case = EXTRACT_32_TO_8;
481 source_row += 3 - (shift >> 3);
482 }
483 break;
484 }
485 }
486 for (y = 0; y < height;
487 ++y, source_row += source->raster, dest_row += dest->raster
488 ) {
489 int x;
490
491 switch (loop_case) {
492 case EXTRACT_4_TO_1: {
493 const byte *source = source_row;
494 byte *dest = dest_row;
495
496 /* Do groups of 8 pixels. */
497 for (x = width; x >= 8; source += 4, x -= 8) {
498 bits32 sword =
499 (*(const bits32 *)source >> shift) & 0x11111111;
500
501 *dest++ =
502 byte_acegbdfh_to_abcdefgh[(
503 #if arch_is_big_endian
504 (sword >> 21) | (sword >> 14) | (sword >> 7) | sword
505 #else
506 (sword << 3) | (sword >> 6) | (sword >> 15) | (sword >> 24)
507 #endif
508 ) & 0xff];
509 }
510 if (x) {
511 /* Do the final 1-7 pixels. */
512 uint test = 0x10 << shift, store = 0x80;
513
514 do {
515 *dest = (*source & test ? *dest | store : *dest & ~store);
516 if (test >= 0x10)
517 test >>= 4;
518 else
519 test <<= 4, ++source;
520 store >>= 1;
521 } while (--x > 0);
522 }
523 break;
524 }
525 case EXTRACT_32_TO_8: {
526 const byte *source = source_row;
527 byte *dest = dest_row;
528
529 for (x = width; x > 0; source += 4, --x)
530 *dest++ = *source;
531 break;
532 }
533 default: {
534 sample_load_declare_setup(sptr, sbit, source_row, source_bit,
535 source_depth);
536 sample_store_declare_setup(dptr, dbit, dbbyte, dest_row, dest_bit,
537 dest_depth);
538
539 sample_store_preload(dbbyte, dptr, dbit, dest_depth);
540 for (x = width; x > 0; --x) {
541 gx_color_index color;
542 uint pixel;
543
544 sample_load_next_any(color, sptr, sbit, source_depth);
545 pixel = (color >> shift) & plane_mask;
546 sample_store_next8(pixel, dptr, dbit, dest_depth, dbbyte);
547 }
548 sample_store_flush(dptr, dbit, dest_depth, dbbyte);
549 }
550 }
551 }
552 return 0;
553 }
554
555 /* Expand a plane into a pixmap. */
556 int
bits_expand_plane(const bits_plane_t * dest,const bits_plane_t * source,int shift,int width,int height)557 bits_expand_plane(const bits_plane_t *dest /*write*/,
558 const bits_plane_t *source /*read*/, int shift, int width, int height)
559 {
560 /*
561 * Eventually we will optimize this just like bits_extract_plane.
562 */
563 int source_depth = source->depth;
564 int source_bit = source->x * source_depth;
565 const byte *source_row = source->data.read + (source_bit >> 3);
566 int dest_depth = dest->depth;
567 int dest_bit = dest->x * dest_depth;
568 byte *dest_row = dest->data.write + (dest_bit >> 3);
569 enum {
570 EXPAND_SLOW = 0,
571 EXPAND_1_TO_4,
572 EXPAND_8_TO_32
573 } loop_case = EXPAND_SLOW;
574 int y;
575
576 source_bit &= 7;
577 /* Check for the fast CMYK cases. */
578 if (!(source_bit || (dest_bit & 31) || (dest->raster & 3))) {
579 switch (dest_depth) {
580 case 4:
581 if (source_depth == 1)
582 loop_case = EXPAND_1_TO_4;
583 break;
584 case 32:
585 if (source_depth == 8 && !(shift & 7))
586 loop_case = EXPAND_8_TO_32;
587 break;
588 }
589 }
590 dest_bit &= 7;
591 switch (loop_case) {
592
593 case EXPAND_8_TO_32: {
594 #if arch_is_big_endian
595 # define word_shift (shift)
596 #else
597 int word_shift = 24 - shift;
598 #endif
599 for (y = 0; y < height;
600 ++y, source_row += source->raster, dest_row += dest->raster
601 ) {
602 int x;
603 const byte *source = source_row;
604 bits32 *dest = (bits32 *)dest_row;
605
606 for (x = width; x > 0; --x)
607 *dest++ = (bits32)(*source++) << word_shift;
608 }
609 #undef word_shift
610 }
611 break;
612
613 case EXPAND_1_TO_4:
614 default:
615 for (y = 0; y < height;
616 ++y, source_row += source->raster, dest_row += dest->raster
617 ) {
618 int x;
619 sample_load_declare_setup(sptr, sbit, source_row, source_bit,
620 source_depth);
621 sample_store_declare_setup(dptr, dbit, dbbyte, dest_row, dest_bit,
622 dest_depth);
623
624 sample_store_preload(dbbyte, dptr, dbit, dest_depth);
625 for (x = width; x > 0; --x) {
626 uint color;
627 gx_color_index pixel;
628
629 sample_load_next8(color, sptr, sbit, source_depth);
630 pixel = color << shift;
631 sample_store_next_any(pixel, dptr, dbit, dest_depth, dbbyte);
632 }
633 sample_store_flush(dptr, dbit, dest_depth, dbbyte);
634 }
635 break;
636
637 }
638 return 0;
639 }
640
641 /* ---------------- Byte-oriented operations ---------------- */
642
643 /* Fill a rectangle of bytes. */
644 void
bytes_fill_rectangle(byte * dest,uint raster,byte value,int width_bytes,int height)645 bytes_fill_rectangle(byte * dest, uint raster,
646 byte value, int width_bytes, int height)
647 {
648 while (height-- > 0) {
649 memset(dest, value, width_bytes);
650 dest += raster;
651 }
652 }
653
654 /* Copy a rectangle of bytes. */
655 void
bytes_copy_rectangle(byte * dest,uint dest_raster,const byte * src,uint src_raster,int width_bytes,int height)656 bytes_copy_rectangle(byte * dest, uint dest_raster,
657 const byte * src, uint src_raster, int width_bytes, int height)
658 {
659 while (height-- > 0) {
660 memcpy(dest, src, width_bytes);
661 src += src_raster;
662 dest += dest_raster;
663 }
664 }
665