From fd62333192fb7961870a744e8bf0e70c97c99b61 Mon Sep 17 00:00:00 2001 From: David Rose Date: Wed, 9 Sep 2009 16:44:57 +0000 Subject: [PATCH] alpha blending for x11 --- direct/src/plugin/p3dSplashWindow.cxx | 21 +- direct/src/plugin/p3dSplashWindow.h | 17 +- direct/src/plugin/p3dX11SplashWindow.I | 5 - direct/src/plugin/p3dX11SplashWindow.cxx | 439 +++++++++++++++-------- direct/src/plugin/p3dX11SplashWindow.h | 25 +- 5 files changed, 335 insertions(+), 172 deletions(-) diff --git a/direct/src/plugin/p3dSplashWindow.cxx b/direct/src/plugin/p3dSplashWindow.cxx index 7d3a694802..327c0fc71b 100755 --- a/direct/src/plugin/p3dSplashWindow.cxx +++ b/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 diff --git a/direct/src/plugin/p3dSplashWindow.h b/direct/src/plugin/p3dSplashWindow.h index 0fc909e4af..6c13c5dbdf 100755 --- a/direct/src/plugin/p3dSplashWindow.h +++ b/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; }; diff --git a/direct/src/plugin/p3dX11SplashWindow.I b/direct/src/plugin/p3dX11SplashWindow.I index 12375c0a96..cf1c0f7c5a 100644 --- a/direct/src/plugin/p3dX11SplashWindow.I +++ b/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(); } diff --git a/direct/src/plugin/p3dX11SplashWindow.cxx b/direct/src/plugin/p3dX11SplashWindow.cxx index 57e960c813..a1b8469695 100755 --- a/direct/src/plugin/p3dX11SplashWindow.cxx +++ b/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); + if (_composite_image == NULL) { + 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; - } -} - -//////////////////////////////////////////////////////////////////// -// 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 image1; + int image1_width = 0, image1_height = 0; + scale_image(image1, image1_width, image1_height, _background_image); + + vector 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 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); + int data_length = 4 * image1_width * image1_height; + assert(data_length == image1.size()); + uint32_t *new_data = (uint32_t*)malloc(data_length); - 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 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 &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. +// Function: P3DX11SplashWindow::compose_two_images +// Access: Private +// Description: Constructs into image0 the alpha-composite of image1 +// beneath image2. //////////////////////////////////////////////////////////////////// -void P3DX11SplashWindow::X11ImageData:: -dump_resized_image() { - if (_resized_image != NULL) { - XDestroyImage(_resized_image); - _resized_image = NULL; +void P3DX11SplashWindow:: +compose_two_images(vector &image0, int &image0_width, int &image0_height, + const vector &image1, int image1_width, int image1_height, + const vector &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; + } } } diff --git a/direct/src/plugin/p3dX11SplashWindow.h b/direct/src/plugin/p3dX11SplashWindow.h index f237909786..bdb3383033 100755 --- a/direct/src/plugin/p3dX11SplashWindow.h +++ b/direct/src/plugin/p3dX11SplashWindow.h @@ -25,6 +25,8 @@ #include #include +#include + //////////////////////////////////////////////////////////////////// // 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 &image0, int &image0_width, int &image0_height, + X11ImageData &image); + + void compose_two_images(vector &image0, int &image0_width, int &image0_height, + const vector &image1, int image1_width, int image1_height, + const vector &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;