Browse Source

alpha blending for x11

David Rose 16 years ago
parent
commit
fd62333192

+ 16 - 5
direct/src/plugin/p3dSplashWindow.cxx

@@ -462,11 +462,7 @@ set_mouse_data(int mouse_x, int mouse_y, bool mouse_down) {
     }
   }
 
-  if (_bstate != bstate) {
-    _bstate = bstate;
-    // If we've changed button states, we need to refresh the window.
-    refresh();
-  }
+  set_bstate(bstate);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -484,6 +480,21 @@ button_click_detected() {
   _inst->splash_button_clicked();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: P3DSplashWindow::set_bstate
+//       Access: Protected, Virtual
+//  Description: Changes the button state as the mouse interacts with
+//               it.
+////////////////////////////////////////////////////////////////////
+void P3DSplashWindow::
+set_bstate(ButtonState bstate) {
+  if (_bstate != bstate) {
+    _bstate = bstate;
+    // If we've changed button states, we need to refresh the window.
+    refresh();
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DSplashWindow::refresh
 //       Access: Protected, Virtual

+ 9 - 8
direct/src/plugin/p3dSplashWindow.h

@@ -80,7 +80,16 @@ protected:
   void set_button_range(const ImageData &image);
   void set_mouse_data(int mouse_x, int mouse_y, bool mouse_down);
 
+  // The current visual state of the button.
+  enum ButtonState {
+    BS_hidden,
+    BS_ready,
+    BS_rollover,
+    BS_click,
+  };
+
   virtual void button_click_detected();
+  virtual void set_bstate(ButtonState bstate);
   virtual void refresh();
 
 protected:
@@ -99,14 +108,6 @@ protected:
   int _mouse_x, _mouse_y;
   bool _mouse_down;
   bool _button_depressed;
-
-  // The current visual state of the button.
-  enum ButtonState {
-    BS_hidden,
-    BS_ready,
-    BS_rollover,
-    BS_click,
-  };
   ButtonState _bstate;
 };
 

+ 0 - 5
direct/src/plugin/p3dX11SplashWindow.I

@@ -21,10 +21,6 @@
 inline P3DX11SplashWindow::X11ImageData::
 X11ImageData() {
   _filename_changed = false;
-  _image = NULL;
-  _resized_image = NULL;
-  _resized_width = 0;
-  _resized_height = 0;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -34,6 +30,5 @@ X11ImageData() {
 ////////////////////////////////////////////////////////////////////
 inline P3DX11SplashWindow::X11ImageData::
 ~X11ImageData() {
-  dump_image();
 }
 

+ 295 - 148
direct/src/plugin/p3dX11SplashWindow.cxx

@@ -44,6 +44,8 @@ P3DX11SplashWindow(P3DInstance *inst) :
   INIT_THREAD(_read_thread);
 
   // Init for subprocess
+  _composite_image = NULL;
+  _needs_new_composite = false;
   _display = None;
   _window = None;
   _screen = 0;
@@ -182,14 +184,28 @@ void P3DX11SplashWindow::
 button_click_detected() {
   // This method is called in the child process, and must relay
   // the information to the parent process.
-  cerr << "click detected\n";
-
   TiXmlDocument doc;
   TiXmlElement *xcommand = new TiXmlElement("click");
   doc.LinkEndChild(xcommand);
   write_xml(_pipe_write, &doc, nout);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: P3DX11SplashWindow::set_bstate
+//       Access: Protected, Virtual
+//  Description: Changes the button state as the mouse interacts with
+//               it.
+////////////////////////////////////////////////////////////////////
+void P3DX11SplashWindow::
+set_bstate(ButtonState bstate) {
+  if (_bstate != bstate) {
+    // When the button state changes, we need to remake the composite
+    // image.
+    _needs_new_composite = true;
+    P3DSplashWindow::set_bstate(bstate);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DX11SplashWindow::start_subprocess
 //       Access: Private
@@ -473,13 +489,12 @@ subprocess_run() {
             event.xconfigure.height != _win_height) {
           _win_width = event.xconfigure.width;
           _win_height = event.xconfigure.height;
+
+          set_button_range(_button_ready_image);
           
-          // If the window changes size, we need to recompute all of the
-          // resized images.
-          _background_image.dump_resized_image();
-          _button_ready_image.dump_resized_image();
-          _button_rollover_image.dump_resized_image();
-          _button_click_image.dump_resized_image();
+          // If the window changes size, we need to recompute the
+          // composed image.
+          _needs_new_composite = true;
         }
         needs_redraw = true;
         break;
@@ -498,10 +513,15 @@ subprocess_run() {
       }
     }
 
-    update_image(_background_image, needs_redraw);
-    update_image(_button_ready_image, needs_redraw);
-    update_image(_button_rollover_image, needs_redraw);
-    update_image(_button_click_image, needs_redraw);
+    update_image(_background_image);
+    update_image(_button_ready_image);
+    update_image(_button_rollover_image);
+    update_image(_button_click_image);
+
+    if (_needs_new_composite) {
+      needs_redraw = true;
+      compose_image();
+    }
 
     if (_bstate != prev_bstate) {
       needs_redraw = true;
@@ -689,78 +709,14 @@ receive_command() {
 ////////////////////////////////////////////////////////////////////
 void P3DX11SplashWindow::
 redraw() {
-  XClearWindow(_display, _window);
-
-  paint_image(_background_image);
-
-  switch (_bstate) {
-  case BS_hidden:
-    break;
-  case BS_ready:
-    paint_image(_button_ready_image);
-    break;
-  case BS_rollover:
-    if (!paint_image(_button_rollover_image)) {
-      paint_image(_button_ready_image);
-    }
-    break;
-  case BS_click:
-    if (!paint_image(_button_click_image)) {
-      paint_image(_button_ready_image);
-    }
-    break;
-  }
-}
+  if (_composite_image == NULL) {
+    XClearWindow(_display, _window);
 
-////////////////////////////////////////////////////////////////////
-//     Function: P3DX11SplashWindow::paint_image
-//       Access: Private
-//  Description: Draws the indicated image, centered within the
-//               window.  Returns true on success, false if the image
-//               is not defined.
-////////////////////////////////////////////////////////////////////
-bool P3DX11SplashWindow::
-paint_image(X11ImageData &image) {
-  if (image._image == NULL) {
-    return false;
-  }
-
-  // We have an image. Let's see how we can output it.
-  if (image._width <= _win_width && image._height <= _win_height) {
-    // It fits within the window - just draw it.
-    XPutImage(_display, _window, _graphics_context, image._image, 0, 0, 
-              (_win_width - image._width) / 2, (_win_height - image._height) / 2,
-              image._width, image._height);
-  } else if (image._resized_image != NULL) {
-    // We have a resized image already, draw that one.
-    XPutImage(_display, _window, _graphics_context, image._resized_image, 0, 0, 
-              (_win_width - image._resized_width) / 2, (_win_height - image._resized_height) / 2,
-              image._resized_width, image._resized_height);
   } else {
-    // Yuck, the bad case - we need to scale it down.
-    double scale = min((double) _win_width  / (double) image._width,
-                       (double) _win_height / (double) image._height);
-    image._resized_width = (int)(image._width * scale);
-    image._resized_height = (int)(image._height * scale);
-    char *new_data = (char*) malloc(4 * _win_width * _win_height);
-    image._resized_image = XCreateImage(_display, CopyFromParent, DefaultDepth(_display, _screen), 
-                                  ZPixmap, 0, (char *) new_data, _win_width, _win_height, 32, 0);
-    double x_ratio = ((double) image._width) / ((double) image._resized_width);
-    double y_ratio = ((double) image._height) / ((double) image._resized_height);
-    for (int x = 0; x < _win_width; ++x) {
-      for (int y = 0; y < _win_height; ++y) {
-        XPutPixel(image._resized_image, x, y, 
-                  XGetPixel(image._image,
-                            (int)clamp(x * x_ratio, 0, image._width),
-                            (int)clamp(y * y_ratio, 0, image._height)));
-      }
-    }
-    XPutImage(_display, _window, _graphics_context, image._resized_image, 0, 0,
-              (_win_width - image._resized_width) / 2, (_win_height - image._resized_height) / 2,
-              image._resized_width, image._resized_height);
+    XPutImage(_display, _window, _graphics_context, _composite_image, 0, 0, 
+              (_win_width - _composite_width) / 2, (_win_height - _composite_height) / 2,
+              _composite_width, _composite_height);
   }
-
-  return true;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -876,10 +832,10 @@ setup_gc() {
 ////////////////////////////////////////////////////////////////////
 void P3DX11SplashWindow::
 close_window() {
-  _background_image.dump_image();
-  _button_ready_image.dump_image();
-  _button_rollover_image.dump_image();
-  _button_click_image.dump_image();
+  if (_composite_image != NULL) {
+    XDestroyImage(_composite_image);
+    _composite_image = NULL;
+  }
   
   if (_bar_context != None) {
     if (_bar_context != _graphics_context) {
@@ -918,97 +874,288 @@ close_window() {
 //               If the image is changed, sets needs_redraw to true.
 ////////////////////////////////////////////////////////////////////
 void P3DX11SplashWindow::
-update_image(X11ImageData &image, bool &needs_redraw) {
+update_image(X11ImageData &image) {
   if (!image._filename_changed) {
     // No changes.
     return;
   }
   image._filename_changed = false;
-  needs_redraw = true;
 
-  // Clear the old image.
-  image.dump_image();
+  // We'll need to rebuild the composite image.
+  _needs_new_composite = true;
 
   // Go read the image.
   string data;
-  if (!read_image_data(image, data, image._filename)) {
+  if (!read_image_data(image, image._data, image._filename)) {
     return;
   }
+}
 
+////////////////////////////////////////////////////////////////////
+//     Function: P3DX11SplashWindow::compose_image
+//       Access: Private
+//  Description: Constructs the XImage to display onscreen.  It's a
+//               composition of the background image and/or one of the
+//               button images, scaled to fit the window.
+////////////////////////////////////////////////////////////////////
+void P3DX11SplashWindow::
+compose_image() {
+  if (_composite_image != NULL) {
+    XDestroyImage(_composite_image);
+    _composite_image = NULL;
+  }
+  _needs_new_composite = false;
+
+  vector<unsigned char> image1;
+  int image1_width = 0, image1_height = 0;
+  scale_image(image1, image1_width, image1_height, _background_image);
+
+  vector<unsigned char> image2;
+  int image2_width = 0, image2_height = 0;
+
+  switch (_bstate) {
+  case BS_hidden:
+    break;
+  case BS_ready:
+    scale_image(image2, image2_width, image2_height, _button_ready_image);
+    break;
+  case BS_rollover:
+    if (!scale_image(image2, image2_width, image2_height, _button_rollover_image)) {
+      scale_image(image2, image2_width, image2_height, _button_ready_image);
+    }
+    break;
+  case BS_click:
+    if (!scale_image(image2, image2_width, image2_height, _button_click_image)) {
+      scale_image(image2, image2_width, image2_height, _button_ready_image);
+    }
+    break;
+  }
+
+  if (image1.empty() && image2.empty()) {
+    // We have no image.  Never mind.
+    return;
+  }
+
+  if (image2.empty()) {
+    // We have no button image; image1 will serve as the result.
+
+  } else {
+    // We do have a button image.  Compose the button image on top of
+    // the background image (or on top of a white image if we have no
+    // background).
+
+    // We compose them here on the client, because X11 doesn't
+    // natively provide an alpha-blending mechanism (at least, not
+    // without the XRender extension).
+    vector<unsigned char> image0;
+    int image0_width, image0_height;
+    compose_two_images(image0, image0_width, image0_height,
+                       image1, image1_width, image1_height,
+                       image2, image2_width, image2_height);
+    image1.swap(image0);
+    image1_width = image0_width;
+    image1_height = image0_height;
+  }
+
+  // Now construct an XImage from the result.
   Visual *dvisual = DefaultVisual(_display, _screen);
   double r_ratio = dvisual->red_mask / 255.0;
   double g_ratio = dvisual->green_mask / 255.0;
   double b_ratio = dvisual->blue_mask / 255.0;
-  uint32_t *new_data = (uint32_t*)malloc(4 * image._width * image._height);
-
-  if (image._num_channels == 4) {
-    int j = 0;
-    for (int i = 0; i < 4 * image._width * image._height; i += 4) {
-      unsigned int r, g, b;
-      r = (unsigned int)(data[i+0] * r_ratio);
-      g = (unsigned int)(data[i+1] * g_ratio);
-      b = (unsigned int)(data[i+2] * b_ratio);
-      new_data[j++] = (r & dvisual->red_mask) |
-                      (g & dvisual->green_mask) |
-                      (b & dvisual->blue_mask);
-    }
-  } else if (image._num_channels == 3) {
-    int j = 0;
-    for (int i = 0; i < 3 * image._width * image._height; i += 3) {
-      unsigned int r, g, b;
-      r = (unsigned int)(data[i+0] * r_ratio);
-      g = (unsigned int)(data[i+1] * g_ratio);
-      b = (unsigned int)(data[i+2] * b_ratio);
-      new_data[j++] = (r & dvisual->red_mask) |
-                      (g & dvisual->green_mask) |
-                      (b & dvisual->blue_mask);
-    }
-  } else if (image._num_channels == 1) {
-    // A grayscale image.  Replicate out the channels.
-    for (int i = 0; i < image._width * image._height; ++i) {
-      unsigned int r, g, b;
-      r = (unsigned int)(data[i] * r_ratio);
-      g = (unsigned int)(data[i] * g_ratio);
-      b = (unsigned int)(data[i] * b_ratio);
-      new_data[i] = (r & dvisual->red_mask) |
-                    (g & dvisual->green_mask) |
-                    (b & dvisual->blue_mask);
-    }
+  int data_length = 4 * image1_width * image1_height;
+  assert(data_length == image1.size());
+  uint32_t *new_data = (uint32_t*)malloc(data_length);
+
+  int j = 0;
+  for (int i = 0; i < data_length; i += 4) {
+    unsigned int r, g, b;
+    r = (unsigned int)(image1[i+0] * r_ratio);
+    g = (unsigned int)(image1[i+1] * g_ratio);
+    b = (unsigned int)(image1[i+2] * b_ratio);
+    new_data[j++] = ((r & dvisual->red_mask) |
+                     (g & dvisual->green_mask) |
+                     (b & dvisual->blue_mask));
   }
 
   // Now load the image.
-  image._image = XCreateImage(_display, CopyFromParent, DefaultDepth(_display, _screen), 
-                              ZPixmap, 0, (char *)new_data, image._width, image._height, 32, 0);
+  _composite_image = XCreateImage(_display, CopyFromParent, DefaultDepth(_display, _screen), 
+                                  ZPixmap, 0, (char *)new_data, image1_width, image1_height, 32, 0);
+  _composite_width = image1_width;
+  _composite_height = image1_height;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: P3DX11SplashWindow::X11ImageData::dump_image
-//       Access: Public
-//  Description: Frees the previous image data.
+//     Function: P3DX11SplashWindow::scale_image
+//       Access: Private
+//  Description: Scales the image into the window size, and expands it
+//               to four channels.  Returns true if the image is
+//               valid, false if it is empty.
 ////////////////////////////////////////////////////////////////////
-void P3DX11SplashWindow::X11ImageData::
-dump_image() {
-  if (_image != NULL) {
-    XDestroyImage(_image);
-    _image = NULL;
+bool P3DX11SplashWindow::
+scale_image(vector<unsigned char> &image0, int &image0_width, int &image0_height,
+            X11ImageData &image) {
+  if (image._data.empty()) {
+    return false;
   }
-  if (_resized_image != NULL) {
-    XDestroyImage(_resized_image);
-    _resized_image = NULL;
+
+  const unsigned char *orig_data = (const unsigned char *)image._data.data();
+  int row_stride = image._width * image._num_channels;
+  int data_length = image._height * row_stride;
+  assert(data_length == image._data.size());
+
+  if (image._width <= _win_width && image._height <= _win_height) {
+    // It fits within the window - just keep it.
+    image0_width = image._width;
+    image0_height = image._height;
+    int new_row_stride = image0_width * 4;
+    int new_data_length = image0_height * new_row_stride;
+
+    image0.clear();
+    image0.reserve(new_data_length);
+    image0.insert(image0.begin(), new_data_length, 0);
+    unsigned char *new_data = &image0[0];
+
+    if (image._num_channels == 4) {
+      // Easy case.  Already four channels.
+      assert(data_length == new_data_length && row_stride == new_row_stride);
+      memcpy(new_data, orig_data, data_length);
+
+    } else if (image._num_channels == 3) {
+      // Expand three channels to four.
+      for (int yi = 0; yi < image._height; ++yi) {
+        const unsigned char *sp = orig_data + yi * row_stride;
+        unsigned char *dp = new_data + yi * new_row_stride;
+        for (int xi = 0; xi < image._width; ++xi) {
+          dp[0] = sp[0];
+          dp[1] = sp[1];
+          dp[2] = sp[2];
+          dp[3] = 0xff;
+          sp += 3;
+          dp += 4;
+        }
+      }
+    } else if (image._num_channels == 1) {
+      // A grayscale image.  Replicate out the channels.
+      for (int yi = 0; yi < image._height; ++yi) {
+        const unsigned char *sp = orig_data + yi * row_stride;
+        unsigned char *dp = new_data + yi * new_row_stride;
+        for (int xi = 0; xi < image._width; ++xi) {
+          dp[0] = sp[0];
+          dp[1] = sp[0];
+          dp[2] = sp[0];
+          dp[3] = 0xff;
+          sp += 1;
+          dp += 4;
+        }
+      }
+    }
+
+  } else {
+    // Yuck, the bad case - we need to scale it down.
+    double scale = min((double)_win_width  / (double)image._width,
+                       (double)_win_height / (double)image._height);
+    image0_width = (int)(image._width * scale);
+    image0_height = (int)(image._height * scale);
+    int new_row_stride = image0_width * 4;
+    int new_data_length = image0_height * new_row_stride;
+
+    image0.clear();
+    image0.reserve(new_data_length);
+    image0.insert(image0.begin(), new_data_length, 0);
+    unsigned char *new_data = &image0[0];
+
+    for (int yi = 0; yi < image0_height; ++yi) {
+      int orig_yi = (yi * image._height) / image0_height;
+      const unsigned char *sp = orig_data + orig_yi * row_stride;
+      unsigned char *dp = new_data + yi * new_row_stride;
+      for (int xi = 0; xi < image0_width; ++xi) {
+        int orig_xi = (xi * image._width) / image0_width;
+        const unsigned char *spx = sp + orig_xi * image._num_channels;
+        if (image._num_channels == 4) {
+          dp[0] = spx[0];
+          dp[1] = spx[1];
+          dp[2] = spx[2];
+          dp[3] = spx[3];
+        } else if (image._num_channels == 3) {
+          dp[0] = spx[0];
+          dp[1] = spx[1];
+          dp[2] = spx[2];
+          dp[3] = 0xff;
+        } else if (image._num_channels == 1) {
+          dp[0] = spx[0];
+          dp[1] = spx[0];
+          dp[2] = spx[0];
+          dp[3] = 0xff;
+        }
+        dp += 4;
+      }
+    }
   }
+
+  return true;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: P3DX11SplashWindow::X11ImageData::dump_resized_image
-//       Access: Public
-//  Description: Frees the previous resized image data only, retaining
-//               the original image data.
-////////////////////////////////////////////////////////////////////
-void P3DX11SplashWindow::X11ImageData::
-dump_resized_image() {
-  if (_resized_image != NULL) {
-    XDestroyImage(_resized_image);
-    _resized_image = NULL;
+//     Function: P3DX11SplashWindow::compose_two_images
+//       Access: Private
+//  Description: Constructs into image0 the alpha-composite of image1
+//               beneath image2.
+////////////////////////////////////////////////////////////////////
+void P3DX11SplashWindow::
+compose_two_images(vector<unsigned char> &image0, int &image0_width, int &image0_height,
+                   const vector<unsigned char> &image1, int image1_width, int image1_height,
+                   const vector<unsigned char> &image2, int image2_width, int image2_height) {
+  // First, the resulting image size is the larger of the two.
+  image0_width = max(image1_width, image2_width);
+  image0_height = max(image1_height, image2_height);
+
+  int new_row_stride = image0_width * 4;
+  int new_data_length = image0_height * new_row_stride;
+
+  // Now copy in the first image.  If the first image exactly fills
+  // the output image, this is easy.
+  if (image1_width == image0_width && image1_height == image0_height) {
+    image0 = image1;
+
+  } else {
+    // If the first image doesn't fill it, it's only a little bit more
+    // work.  Start by finding the top-left pixel.
+    int xo = (image0_width - image1_width) / 2;
+    int yo = (image0_height - image1_height) / 2;
+
+    // Initialize the image to all white pixels.
+    image0.clear();
+    image0.reserve(new_data_length);
+    image0.insert(image0.begin(), new_data_length, 0xff);
+
+    int image0_row_stride = image0_width * 4;
+    int image1_row_stride = image1_width * 4;
+    for (int yi = 0; yi < image1_height; ++yi) {
+      const unsigned char *sp = &image1[0] + yi * image1_row_stride;
+      unsigned char *dp = &image0[0] + (yi + yo) * image0_row_stride;
+      memcpy(dp + xo * 4, sp, image1_row_stride);
+    }
+  }
+
+  // Now blend in the second image.  Find the top-left pixel.
+  int xo = (image0_width - image2_width) / 2;
+  int yo = (image0_height - image2_height) / 2;
+
+  int image0_row_stride = image0_width * 4;
+  int image2_row_stride = image2_width * 4;
+
+  for (int yi = 0; yi < image2_height; ++yi) {
+    const unsigned char *sp = &image2[0] + yi * image2_row_stride;
+    unsigned char *dp = &image0[0] + (yi + yo) * image0_row_stride;
+    dp += xo * 4;
+    for (int xi = 0; xi < image2_width; ++xi) {
+      double alpha = (double)sp[3] / 255.0;
+      dp[0] = (unsigned char)(dp[0] + alpha * (sp[0] - dp[0]));
+      dp[1] = (unsigned char)(dp[1] + alpha * (sp[1] - dp[1]));
+      dp[2] = (unsigned char)(dp[2] + alpha * (sp[2] - dp[2]));
+      dp += 4;
+      sp += 4;
+    }
   }
 }
 

+ 17 - 8
direct/src/plugin/p3dX11SplashWindow.h

@@ -25,6 +25,8 @@
 #include <X11/Xlib.h>
 #include <X11/Xutil.h>
 
+#include <vector>
+
 ////////////////////////////////////////////////////////////////////
 //       Class : P3DX11SplashWindow
 // Description : This is the Windows implementation of the
@@ -45,6 +47,7 @@ public:
 
 protected:
   virtual void button_click_detected();
+  virtual void set_bstate(ButtonState bstate);
 
 private:
   void start_subprocess();
@@ -79,27 +82,29 @@ private:
   void receive_command();
 
   void redraw();
-  bool paint_image(X11ImageData &image);
   void make_window();
   void setup_gc();
-  void update_image(X11ImageData &image, bool &needs_redraw); 
   void close_window();
 
+  void update_image(X11ImageData &image); 
+  void compose_image();
+  bool scale_image(vector<unsigned char> &image0, int &image0_width, int &image0_height,
+                   X11ImageData &image);
+
+  void compose_two_images(vector<unsigned char> &image0, int &image0_width, int &image0_height,
+                          const vector<unsigned char> &image1, int image1_width, int image1_height,
+                          const vector<unsigned char> &image2, int image2_width, int image2_height);
+
 private:
   // Data members that are stored in the subprocess.
   class X11ImageData : public ImageData {
   public:
     inline X11ImageData();
     inline ~X11ImageData();
-    void dump_image();
-    void dump_resized_image();
 
     string _filename;
     bool _filename_changed;
-    XImage *_image;
-
-    XImage *_resized_image;
-    int _resized_width, _resized_height;
+    string _data;
   };
 
   X11ImageData _background_image;
@@ -107,6 +112,10 @@ private:
   X11ImageData _button_rollover_image;
   X11ImageData _button_click_image;
 
+  XImage *_composite_image;
+  int _composite_width, _composite_height;
+  bool _needs_new_composite;
+
   bool _subprocess_continue;
   
   bool _own_display;