1 /* grab-ximage.c --- grab the screen to an XImage for use with OpenGL.
2 * xscreensaver, Copyright (c) 2001-2008 Jamie Zawinski <jwz@jwz.org>
4 * Permission to use, copy, modify, distribute, and sell this software and its
5 * documentation for any purpose is hereby granted without fee, provided that
6 * the above copyright notice appear in all copies and that both that
7 * copyright notice and this permission notice appear in supporting
8 * documentation. No representations are made about the suitability of this
9 * software for any purpose. It is provided "as is" without express or
24 # include <OpenGL/glu.h>
27 # include <X11/Xlib.h>
28 # include <X11/Xutil.h>
29 # include <GL/gl.h> /* only for GLfloat */
30 # include <GL/glu.h> /* for gluBuild2DMipmaps */
31 # include <GL/glx.h> /* for glXMakeCurrent() */
36 #endif /* HAVE_JWZGLES */
38 #include "grab-ximage.h"
39 #include "grabscreen.h"
42 /* If REFORMAT_IMAGE_DATA is defined, then we convert Pixmaps to textures
45 - get Pixmap as an XImage in whatever form the server hands us;
46 - convert that XImage to 32-bit RGBA in client-local endianness;
47 - make the texture using RGBA, UNSIGNED_BYTE.
49 If undefined, we do this:
51 - get Pixmap as an XImage in whatever form the server hands us;
52 - figure out what OpenGL texture packing parameters correspond to
53 the image data that the server sent us and use that, e.g.,
54 BGRA, INT_8_8_8_8_REV.
56 You might expect the second method to be faster, since we're not making
57 a second copy of the data and iterating each pixel before we hand it
58 to GL. But, you'd be wrong. The first method is almost 6x faster.
59 I guess GL is reformatting it *again*, and doing it very inefficiently!
61 #define REFORMAT_IMAGE_DATA
64 #ifdef HAVE_XSHM_EXTENSION
65 # include "resources.h"
67 #endif /* HAVE_XSHM_EXTENSION */
69 extern char *progname;
76 # include <X11/Xutil.h>
80 #define MAX(a,b) ((a)>(b)?(a):(b))
83 #define countof(x) (sizeof((x))/sizeof((*x)))
86 static int debug_p = 0;
91 union { int i; char c[sizeof(int)]; } u;
97 #ifdef REFORMAT_IMAGE_DATA
99 /* Given a bitmask, returns the position and width of the field.
102 decode_mask (unsigned long mask, unsigned long *pos_ret,
103 unsigned long *size_ret)
106 for (i = 0; i < 32; i++)
107 if (mask & (1L << i))
111 for (; i < 32; i++, j++)
112 if (! (mask & (1L << i)))
120 /* Given a value and a field-width, expands the field to fill out 8 bits.
123 spread_bits (unsigned char value, unsigned char width)
127 case 8: return value;
128 case 7: return (value << 1) | (value >> 6);
129 case 6: return (value << 2) | (value >> 4);
130 case 5: return (value << 3) | (value >> 2);
131 case 4: return (value << 4) | (value);
132 case 3: return (value << 5) | (value << 2) | (value >> 2);
133 case 2: return (value << 6) | (value << 4) | (value);
134 default: abort(); break;
140 convert_ximage_to_rgba32 (Screen *screen, XImage *image)
142 Display *dpy = DisplayOfScreen (screen);
143 Visual *visual = DefaultVisualOfScreen (screen);
146 unsigned long crpos=0, cgpos=0, cbpos=0, capos=0; /* bitfield positions */
147 unsigned long srpos=0, sgpos=0, sbpos=0;
148 unsigned long srmsk=0, sgmsk=0, sbmsk=0;
149 unsigned long srsiz=0, sgsiz=0, sbsiz=0;
151 unsigned char spread_map[3][256];
153 /* Note: height+2 in "to" to work around an array bounds overrun
154 in gluBuild2DMipmaps / gluScaleImage.
156 XImage *from = image;
157 XImage *to = XCreateImage (dpy, visual, 32, /* depth */
158 ZPixmap, 0, 0, from->width, from->height + 2,
161 to->data = (char *) calloc (to->height, to->bytes_per_line);
163 /* Set the bit order in the XImage structure to whatever the
164 local host's native bit order is.
166 to->bitmap_bit_order =
168 (bigendian() ? MSBFirst : LSBFirst);
170 if (visual_class (screen, visual) == PseudoColor ||
171 visual_class (screen, visual) == GrayScale)
173 Colormap cmap = DefaultColormapOfScreen (screen);
174 int ncolors = visual_cells (screen, visual);
176 colors = (XColor *) calloc (sizeof (*colors), ncolors+1);
177 for (i = 0; i < ncolors; i++)
179 XQueryColors (dpy, cmap, colors, ncolors);
182 if (colors == 0) /* truecolor */
184 srmsk = to->red_mask;
185 sgmsk = to->green_mask;
186 sbmsk = to->blue_mask;
188 decode_mask (srmsk, &srpos, &srsiz);
189 decode_mask (sgmsk, &sgpos, &sgsiz);
190 decode_mask (sbmsk, &sbpos, &sbsiz);
193 /* Pack things in "RGBA" order in client endianness. */
195 crpos = 24, cgpos = 16, cbpos = 8, capos = 0;
197 crpos = 0, cgpos = 8, cbpos = 16, capos = 24;
199 if (colors == 0) /* truecolor */
202 for (i = 0; i < 256; i++)
204 spread_map[0][i] = spread_bits (i, srsiz);
205 spread_map[1][i] = spread_bits (i, sgsiz);
206 spread_map[2][i] = spread_bits (i, sbsiz);
210 /* trying to track down an intermittent crash in ximage_putpixel_32 */
211 if (to->width < from->width) abort();
212 if (to->height < from->height) abort();
214 for (y = 0; y < from->height; y++)
215 for (x = 0; x < from->width; x++)
217 unsigned long sp = XGetPixel (from, x, y);
218 unsigned char sr, sg, sb;
223 sr = colors[sp].red & 0xFF;
224 sg = colors[sp].green & 0xFF;
225 sb = colors[sp].blue & 0xFF;
229 sr = (sp & srmsk) >> srpos;
230 sg = (sp & sgmsk) >> sgpos;
231 sb = (sp & sbmsk) >> sbpos;
233 sr = spread_map[0][sr];
234 sg = spread_map[1][sg];
235 sb = spread_map[2][sb];
238 cp = ((sr << crpos) |
243 XPutPixel (to, x, y, cp);
246 if (colors) free (colors);
251 #endif /* REFORMAT_IMAGE_DATA */
253 /* Shrinks the XImage by a factor of two.
254 We use this when mipmapping fails on large textures.
257 halve_image (XImage *ximage, XRectangle *geom)
259 int w2 = ximage->width/2;
260 int h2 = ximage->height/2;
264 if (w2 <= 32 || h2 <= 32) /* let's not go crazy here, man. */
268 fprintf (stderr, "%s: shrinking image %dx%d -> %dx%d\n",
269 progname, ximage->width, ximage->height, w2, h2);
271 ximage2 = (XImage *) calloc (1, sizeof (*ximage2));
274 ximage2->height = h2;
275 ximage2->bytes_per_line = 0;
277 XInitImage (ximage2);
279 ximage2->data = (char *) calloc (h2, ximage2->bytes_per_line);
282 fprintf (stderr, "%s: out of memory (scaling %dx%d image to %dx%d)\n",
283 progname, ximage->width, ximage->height, w2, h2);
287 for (y = 0; y < h2; y++)
288 for (x = 0; x < w2; x++)
289 XPutPixel (ximage2, x, y, XGetPixel (ximage, x*2, y*2));
306 #ifdef REFORMAT_IMAGE_DATA
308 /* Pulls the Pixmap bits from the server and returns an XImage
309 in some format acceptable to OpenGL.
312 pixmap_to_gl_ximage (Screen *screen, Window window, Pixmap pixmap)
314 Display *dpy = DisplayOfScreen (screen);
315 unsigned int width, height, depth;
317 # ifdef HAVE_XSHM_EXTENSION
318 Bool use_shm = get_boolean_resource (dpy, "useSHM", "Boolean");
319 XShmSegmentInfo shm_info;
320 # endif /* HAVE_XSHM_EXTENSION */
322 XImage *server_ximage = 0;
323 XImage *client_ximage = 0;
329 XGetGeometry (dpy, pixmap, &root, &x, &y, &width, &height, &bw, &depth);
332 if (width < 5 || height < 5) /* something's gone wrong somewhere... */
335 /* Convert the server-side Pixmap to a client-side GL-ordered XImage.
337 # ifdef HAVE_XSHM_EXTENSION
340 Visual *visual = DefaultVisualOfScreen (screen);
341 server_ximage = create_xshm_image (dpy, visual, depth,
342 ZPixmap, 0, &shm_info,
345 XShmGetImage (dpy, pixmap, server_ximage, 0, 0, ~0L);
349 # endif /* HAVE_XSHM_EXTENSION */
352 server_ximage = XGetImage (dpy, pixmap, 0, 0, width, height, ~0L, ZPixmap);
354 client_ximage = convert_ximage_to_rgba32 (screen, server_ximage);
356 # ifdef HAVE_XSHM_EXTENSION
358 destroy_xshm_image (dpy, server_ximage, &shm_info);
360 # endif /* HAVE_XSHM_EXTENSION */
361 XDestroyImage (server_ximage);
363 return client_ximage;
367 # else /* ! REFORMAT_IMAGE_DATA */
370 unsigned int depth, red_mask, green_mask, blue_mask; /* when this... */
371 GLint type, format; /* ...use this. */
374 /* Abbreviate these so that the table entries all fit on one line...
376 #define BYTE GL_UNSIGNED_BYTE
377 #define BYTE_2_3_3_REV GL_UNSIGNED_BYTE_2_3_3_REV
378 #define BYTE_3_3_2 GL_UNSIGNED_BYTE_3_3_2
379 #define INT_10_10_10_2 GL_UNSIGNED_INT_10_10_10_2
380 #define INT_2_10_10_10_REV GL_UNSIGNED_INT_2_10_10_10_REV
381 #define INT_8_8_8_8 GL_UNSIGNED_INT_8_8_8_8
382 #define INT_8_8_8_8_REV GL_UNSIGNED_INT_8_8_8_8_REV
383 #define SHORT_1_5_5_5_REV GL_UNSIGNED_SHORT_1_5_5_5_REV
384 #define SHORT_4_4_4_4 GL_UNSIGNED_SHORT_4_4_4_4
385 #define SHORT_4_4_4_4_REV GL_UNSIGNED_SHORT_4_4_4_4_REV
386 #define SHORT_5_5_5_1 GL_UNSIGNED_SHORT_5_5_5_1
387 #define SHORT_5_6_5 GL_UNSIGNED_SHORT_5_6_5
388 #define SHORT_5_6_5_REV GL_UNSIGNED_SHORT_5_6_5_REV
390 static const conversion_table ctable[] = {
391 { 8, 0x000000E0, 0x0000001C, 0x00000003, BYTE_3_3_2, GL_RGB },
392 { 8, 0x00000007, 0x00000038, 0x000000C0, BYTE_2_3_3_REV, GL_RGB },
393 { 16, 0x0000F800, 0x000007E0, 0x0000001F, SHORT_5_6_5, GL_RGB },
394 { 16, 0x0000001F, 0x000007E0, 0x0000F800, SHORT_5_6_5_REV, GL_RGB },
395 { 16, 0x0000F000, 0x00000F00, 0x000000F0, SHORT_4_4_4_4, GL_RGBA },
396 { 16, 0x000000F0, 0x00000F00, 0x0000F000, SHORT_4_4_4_4, GL_BGRA },
397 { 16, 0x0000000F, 0x000000F0, 0x00000F00, SHORT_4_4_4_4, GL_ABGR_EXT },
398 { 16, 0x0000000F, 0x000000F0, 0x00000F00, SHORT_4_4_4_4_REV, GL_RGBA },
399 { 16, 0x00000F00, 0x000000F0, 0x0000000F, SHORT_4_4_4_4_REV, GL_BGRA },
400 { 16, 0x0000F800, 0x000007C0, 0x0000003E, SHORT_5_5_5_1, GL_RGBA },
401 { 16, 0x0000003E, 0x000007C0, 0x0000F800, SHORT_5_5_5_1, GL_BGRA },
402 { 16, 0x00000001, 0x0000003E, 0x000007C0, SHORT_5_5_5_1, GL_ABGR_EXT },
403 { 16, 0x0000001F, 0x000003E0, 0x00007C00, SHORT_1_5_5_5_REV, GL_RGBA },
404 { 16, 0x00007C00, 0x000003E0, 0x0000001F, SHORT_1_5_5_5_REV, GL_BGRA },
405 { 32, 0xFF000000, 0x00FF0000, 0x0000FF00, INT_8_8_8_8, GL_RGBA },
406 { 32, 0x0000FF00, 0x00FF0000, 0xFF000000, INT_8_8_8_8, GL_BGRA },
407 { 32, 0x000000FF, 0x0000FF00, 0x00FF0000, INT_8_8_8_8, GL_ABGR_EXT },
408 { 32, 0x000000FF, 0x0000FF00, 0x00FF0000, INT_8_8_8_8_REV, GL_RGBA },
409 { 32, 0x00FF0000, 0x0000FF00, 0x000000FF, INT_8_8_8_8_REV, GL_BGRA },
410 { 32, 0xFFC00000, 0x003FF000, 0x00000FFC, INT_10_10_10_2, GL_RGBA },
411 { 32, 0x00000FFC, 0x003FF000, 0xFFC00000, INT_10_10_10_2, GL_BGRA },
412 { 32, 0x00000003, 0x00000FFC, 0x003FF000, INT_10_10_10_2, GL_ABGR_EXT },
413 { 32, 0x000003FF, 0x000FFC00, 0x3FF00000, INT_2_10_10_10_REV, GL_RGBA },
414 { 32, 0x3FF00000, 0x000FFC00, 0x000003FF, INT_2_10_10_10_REV, GL_BGRA },
415 { 24, 0x000000FF, 0x0000FF00, 0x00FF0000, BYTE, GL_RGB },
416 { 24, 0x00FF0000, 0x0000FF00, 0x000000FF, BYTE, GL_BGR },
420 /* Given an XImage, returns the GL settings to use its data as a texture.
423 gl_settings_for_ximage (XImage *image,
424 GLint *type_ret, GLint *format_ret, GLint *swap_ret)
427 for (i = 0; i < countof(ctable); ++i)
429 if (image->bits_per_pixel == ctable[i].depth &&
430 image->red_mask == ctable[i].red_mask &&
431 image->green_mask == ctable[i].green_mask &&
432 image->blue_mask == ctable[i].blue_mask)
434 *type_ret = ctable[i].type;
435 *format_ret = ctable[i].format;
437 if (image->bits_per_pixel == 24)
439 /* don't know how to test this... */
440 *type_ret = (ctable[i].type == GL_RGB) ? GL_BGR : GL_RGB;
445 *swap_ret = !!(image->byte_order == MSBFirst) ^ !!bigendian();
450 fprintf (stderr, "%s: using %s %s %d for %d %08lX %08lX %08lX\n",
452 (*format_ret == GL_RGB ? "RGB" :
453 *format_ret == GL_BGR ? "BGR" :
454 *format_ret == GL_RGBA ? "RGBA" :
455 *format_ret == GL_BGRA ? "BGRA" :
456 *format_ret == GL_ABGR_EXT ? "ABGR_EXT" :
458 (*type_ret == BYTE ? "BYTE" :
459 *type_ret == BYTE_3_3_2 ? "BYTE_3_3_2" :
460 *type_ret == BYTE_2_3_3_REV ? "BYTE_2_3_3_REV" :
461 *type_ret == INT_8_8_8_8 ? "INT_8_8_8_8" :
462 *type_ret == INT_8_8_8_8_REV ? "INT_8_8_8_8_REV" :
463 *type_ret == INT_10_10_10_2 ? "INT_10_10_10_2" :
464 *type_ret == INT_2_10_10_10_REV ? "INT_2_10_10_10_REV":
465 *type_ret == SHORT_4_4_4_4 ? "SHORT_4_4_4_4" :
466 *type_ret == SHORT_4_4_4_4_REV ? "SHORT_4_4_4_4_REV" :
467 *type_ret == SHORT_5_5_5_1 ? "SHORT_5_5_5_1" :
468 *type_ret == SHORT_1_5_5_5_REV ? "SHORT_1_5_5_5_REV" :
469 *type_ret == SHORT_5_6_5 ? "SHORT_5_6_5" :
470 *type_ret == SHORT_5_6_5_REV ? "SHORT_5_6_5_REV" :
473 image->bits_per_pixel,
474 image->red_mask, image->green_mask, image->blue_mask);
481 /* Unknown RGB fields? */
485 #endif /* ! REFORMAT_IMAGE_DATA */
488 GLXContext glx_context;
490 int pix_width, pix_height, pix_depth;
495 /* Used in async mode
497 void (*callback) (const char *filename, XRectangle *geometry,
498 int iw, int ih, int tw, int th,
504 char **filename_return;
505 XRectangle *geometry_return;
506 int *image_width_return;
507 int *image_height_return;
508 int *texture_width_return;
509 int *texture_height_return;
514 /* Returns the current time in seconds as a double.
520 # ifdef GETTIMEOFDAY_TWO_ARGS
522 gettimeofday(&now, &tzp);
527 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
531 /* return the next larger power of 2. */
536 while (i < value) i <<= 1;
541 /* Loads the given XImage into GL's texture memory.
542 The image may be of any size.
543 If mipmap_p is true, then make mipmaps instead of just a single texture.
544 Writes to stderr and returns False on error.
547 ximage_to_texture (XImage *ximage,
548 GLint type, GLint format,
551 XRectangle *geometry,
554 int max_reduction = 7;
557 int orig_width = ximage->width;
558 int orig_height = ximage->height;
566 /* gluBuild2DMipmaps doesn't require textures to be a power of 2. */
567 tex_width = ximage->width;
568 tex_height = ximage->height;
571 fprintf (stderr, "%s: mipmap %d x %d\n",
572 progname, ximage->width, ximage->height);
574 gluBuild2DMipmaps (GL_TEXTURE_2D, 3, ximage->width, ximage->height,
575 format, type, ximage->data);
580 /* glTexImage2D() requires the texture sizes to be powers of 2.
581 So first, create a texture of that size (but don't write any
584 tex_width = to_pow2 (ximage->width);
585 tex_height = to_pow2 (ximage->height);
588 fprintf (stderr, "%s: texture %d x %d (%d x %d)\n",
589 progname, ximage->width, ximage->height,
590 tex_width, tex_height);
592 glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, tex_width, tex_height, 0,
596 /* Now load our non-power-of-2 image data into the existing texture. */
599 glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0,
600 ximage->width, ximage->height,
601 GL_RGBA, GL_UNSIGNED_BYTE, ximage->data);
609 const char *s = (char *) gluErrorString (err);
613 sprintf (buf, "unknown error %d", (int) err);
617 while (glGetError() != GL_NO_ERROR)
618 ; /* clear any lingering errors */
620 if (++err_count > max_reduction)
624 "%s: %dx%d texture failed, even after reducing to %dx%d:\n"
625 "%s: The error was: \"%s\".\n"
626 "%s: probably this means "
627 "\"your video card is worthless and weak\"?\n\n",
628 progname, orig_width, orig_height,
629 ximage->width, ximage->height,
637 fprintf (stderr, "%s: mipmap error (%dx%d): %s\n",
638 progname, ximage->width, ximage->height, s);
639 halve_image (ximage, geometry);
644 if (width_return) *width_return = tex_width;
645 if (height_return) *height_return = tex_height;
650 static void load_texture_async_cb (Screen *screen,
651 Window window, Drawable drawable,
652 const char *name, XRectangle *geometry,
656 /* Grabs an image of the desktop (or another random image file) and
657 loads the image into GL's texture memory.
658 When the callback is called, the image data will have been loaded
659 into texture number `texid' (via glBindTexture.)
661 If an error occurred, width/height will be 0.
664 load_texture_async (Screen *screen, Window window,
665 GLXContext glx_context,
666 int desired_width, int desired_height,
669 void (*callback) (const char *filename,
670 XRectangle *geometry,
678 Display *dpy = DisplayOfScreen (screen);
679 XWindowAttributes xgwa;
680 img_closure *data = (img_closure *) calloc (1, sizeof(*data));
683 data->load_time = double_time();
686 data->mipmap_p = mipmap_p;
687 data->glx_context = glx_context;
688 data->callback = callback;
689 data->closure = closure;
691 XGetWindowAttributes (dpy, window, &xgwa);
692 data->pix_width = xgwa.width;
693 data->pix_height = xgwa.height;
694 data->pix_depth = xgwa.depth;
696 if (desired_width && desired_width < xgwa.width)
697 data->pix_width = desired_width;
698 if (desired_height && desired_height < xgwa.height)
699 data->pix_height = desired_height;
701 data->pixmap = XCreatePixmap (dpy, window, data->pix_width, data->pix_height,
703 load_image_async (screen, window, data->pixmap,
704 load_texture_async_cb, data);
708 /* Once we have an XImage, this loads it into GL.
709 This is used in both synchronous and asynchronous mode.
712 load_texture_async_cb (Screen *screen, Window window, Drawable drawable,
713 const char *name, XRectangle *geometry, void *closure)
715 Display *dpy = DisplayOfScreen (screen);
719 int iw=0, ih=0, tw=0, th=0;
720 double cvt_time=0, tex_time=0, done_time=0;
721 img_closure *data = (img_closure *) closure;
722 /* copy closure data to stack and free the original before running cb */
723 img_closure dd = *data;
724 memset (data, 0, sizeof (*data));
729 glXMakeCurrent (dpy, window, dd.glx_context);
731 if (geometry->width <= 0 || geometry->height <= 0)
733 /* This can happen if an old version of xscreensaver-getimage
737 geometry->width = dd.pix_width;
738 geometry->height = dd.pix_height;
741 if (geometry->width <= 0 || geometry->height <= 0)
745 cvt_time = double_time();
747 # ifdef REFORMAT_IMAGE_DATA
748 ximage = pixmap_to_gl_ximage (screen, window, dd.pixmap);
750 type = GL_UNSIGNED_BYTE;
752 #else /* ! REFORMAT_IMAGE_DATA */
754 Visual *visual = DefaultVisualOfScreen (screen);
757 ximage = XCreateImage (dpy, visual, dd.pix_depth, ZPixmap, 0, 0,
758 dd.pix_width, dd.pix_height, 32, 0);
760 /* Note: height+2 in "to" to be to work around an array bounds overrun
761 in gluBuild2DMipmaps / gluScaleImage. */
762 ximage->data = (char *) calloc (ximage->height+2, ximage->bytes_per_line);
765 !XGetSubImage (dpy, dd.pixmap, 0, 0, ximage->width, ximage->height,
766 ~0L, ximage->format, ximage, 0, 0))
768 XDestroyImage (ximage);
772 gl_settings_for_ximage (ximage, &type, &format, &swap);
773 glPixelStorei (GL_UNPACK_SWAP_BYTES, !swap);
775 #endif /* REFORMAT_IMAGE_DATA */
777 XFreePixmap (dpy, dd.pixmap);
781 tex_time = double_time();
790 glBindTexture (GL_TEXTURE_2D, dd.texid);
792 glPixelStorei (GL_UNPACK_ALIGNMENT, ximage->bitmap_pad / 8);
793 ok = ximage_to_texture (ximage, type, format, &tw, &th, geometry,
797 iw = ximage->width; /* in case the image was shrunk */
802 if (ximage) XDestroyImage (ximage);
805 iw = ih = tw = th = 0;
808 done_time = double_time();
812 /* prints: A + B + C = D
813 A = file I/O time (happens in background)
814 B = time to pull bits from server (this process)
815 C = time to convert bits to GL textures (this process)
816 D = total elapsed time from "want image" to "see image"
818 B+C is responsible for any frame-rate glitches.
820 "%s: loading elapsed: %.2f + %.2f + %.2f = %.2f sec\n",
822 cvt_time - dd.load_time,
824 done_time - tex_time,
825 done_time - dd.load_time);
828 /* asynchronous mode */
829 dd.callback (name, geometry, iw, ih, tw, th, dd.closure);
832 /* synchronous mode */
833 if (dd.filename_return) *dd.filename_return = (char *) name;
834 if (dd.geometry_return) *dd.geometry_return = *geometry;
835 if (dd.image_width_return) *dd.image_width_return = iw;
836 if (dd.image_height_return) *dd.image_height_return = ih;
837 if (dd.texture_width_return) *dd.texture_width_return = tw;
838 if (dd.texture_height_return) *dd.texture_height_return = th;