├── image.png ├── Transparent.mm ├── LICENSE.txt ├── README.md └── Transparent.cpp /image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/texus/TransparentWindows/HEAD/image.png -------------------------------------------------------------------------------- /Transparent.mm: -------------------------------------------------------------------------------- 1 | #include 2 | #import 3 | 4 | @implementation NSOpenGLView (Opaque) 5 | -(BOOL)isOpaque 6 | { 7 | return NO; 8 | } 9 | @end 10 | 11 | bool setShape(sf::WindowHandle handle, const sf::Image& image) 12 | { 13 | NSWindow* wnd = (NSWindow*)handle; 14 | 15 | GLint opaque = 0; 16 | [[[wnd contentView] openGLContext] setValues:&opaque forParameter:NSOpenGLCPSurfaceOpacity]; 17 | [wnd setBackgroundColor:[NSColor clearColor]]; 18 | [wnd setOpaque:NO]; 19 | 20 | return true; 21 | } 22 | 23 | bool setTransparency(sf::WindowHandle handle, unsigned char alpha) 24 | { 25 | NSWindow* wnd = (NSWindow*)handle; 26 | 27 | CGFloat opacity = alpha / 255.0f; 28 | [wnd setAlphaValue:opacity]; 29 | [wnd setOpaque:NO]; 30 | 31 | return true; 32 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Bruno Van de Velde 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Transparent Windows 2 | =================== 3 | 4 | This repository contains an example of how to create non-rectangular semi-transparent windows on multiple operating systems with SFML (which is only used to load the image and display the result). 5 | It loads an image with transparent parts, uses this image to set the shape of the window and draws the image on the window. The code also contains an opacity variable to set the opacity of the window. 6 | 7 | The code works for Windows, Linux and Mac OS X. 8 | 9 | Feel free to send pull requests to improve the code. 10 | 11 | 12 | ### Compiling 13 | 14 | Linux: 15 | 16 | g++ Transparent.cpp -lsfml-graphics -lsfml-window -lsfml-system -lX11 -lXext 17 | 18 | 19 | Mac OS X: 20 | 21 | If you installed SFML manually (Frameworks): 22 | 23 | clang++ Transparent.cpp Transparent.mm -framework sfml-graphics -framework sfml-window -framework sfml-system -framework Cocoa 24 | 25 | If you installed SFML via Homebrew: 26 | 27 | clang++ Transparent.cpp Transparent.mm \ 28 | -I$(brew --prefix sfml@2)/include \ 29 | -L$(brew --prefix sfml@2)/lib \ 30 | -lsfml-graphics -lsfml-window -lsfml-system \ 31 | -framework Cocoa \ 32 | -std=c++17 33 | 34 | 35 | Windows: 36 | 37 | No special libraries are needed for the transparency. You should just compile Transparent.cpp and link to sfml the way you do it for your own programs. 38 | 39 | 40 | ### Translucent windows 41 | 42 | This code in this project works by using a fully opaque image and then applying an alpha value over all the visible pixels, it does not support per-pixel alpha. If you want to use a transparent image and set the alpha values of the window like in the image then check the following resources: 43 | 44 | - Windows: I wrote an [implementation for Windows](https://gist.github.com/texus/31676aba4ca774b1298e1e15133b8141) using the UpdateLayeredWindow function. 45 | 46 | - Linux: I found [this gist](https://gist.github.com/je-so/903479) which can be changed to use per-pixel alpha. Adapting it to use SFML only worked when creating the sf::RenderWindow from the created Xlib Window, but not the other way around. If you manage to get it working on linux with a window created by SFML then please let me know. 47 | 48 | - macOS: The code in this repository already supports translucent windows for macOS! You can just replace the image with a transparent one and it will work. 49 | -------------------------------------------------------------------------------- /Transparent.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #if defined (SFML_SYSTEM_WINDOWS) 4 | #include 5 | 6 | bool setShape(HWND hWnd, const sf::Image& image) 7 | { 8 | const sf::Uint8* pixelData = image.getPixelsPtr(); 9 | 10 | // Create a region with the size of the entire window 11 | HRGN hRegion = CreateRectRgn(0, 0, image.getSize().x, image.getSize().y); 12 | 13 | // Loop over the pixels in the image and for each pixel where the alpha component equals 0, 14 | // we will remove that pixel from the region. 15 | // As an optimization, we will combine adjacent transparent pixels on the same row and 16 | // remove them together, instead of using "CreateRectRgn(x, y, x+1, y+1)" to define 17 | // a region for each transparent pixel individually. 18 | bool transparentPixelFound = false; 19 | unsigned int rectLeft = 0; 20 | for (unsigned int y = 0; y < image.getSize().y; y++) 21 | { 22 | for (unsigned int x = 0; x < image.getSize().x; x++) 23 | { 24 | const bool isTransparentPixel = (pixelData[y * image.getSize().x * 4 + x * 4 + 3] == 0); 25 | if (isTransparentPixel && !transparentPixelFound) 26 | { 27 | transparentPixelFound = true; 28 | rectLeft = x; 29 | } 30 | else if (!isTransparentPixel && transparentPixelFound) 31 | { 32 | HRGN hRegionPixel = CreateRectRgn(rectLeft, y, x, y+1); 33 | CombineRgn(hRegion, hRegion, hRegionPixel, RGN_XOR); 34 | DeleteObject(hRegionPixel); 35 | transparentPixelFound = false; 36 | } 37 | } 38 | 39 | if (transparentPixelFound) 40 | { 41 | HRGN hRegionPixel = CreateRectRgn(rectLeft, y, image.getSize().x, y+1); 42 | CombineRgn(hRegion, hRegion, hRegionPixel, RGN_XOR); 43 | DeleteObject(hRegionPixel); 44 | transparentPixelFound = false; 45 | } 46 | } 47 | 48 | SetWindowRgn(hWnd, hRegion, true); 49 | DeleteObject(hRegion); 50 | return true; 51 | } 52 | 53 | bool setTransparency(HWND hWnd, unsigned char alpha) 54 | { 55 | SetWindowLong(hWnd, GWL_EXSTYLE, GetWindowLong(hWnd, GWL_EXSTYLE) | WS_EX_LAYERED); 56 | SetLayeredWindowAttributes(hWnd, 0, alpha, LWA_ALPHA); 57 | return true; 58 | } 59 | 60 | #elif defined (SFML_SYSTEM_LINUX) 61 | #include 62 | #include 63 | 64 | bool setShape(Window wnd, const sf::Image& image) 65 | { 66 | Display* display = XOpenDisplay(NULL); 67 | 68 | // Setting the window shape requires the XShape extension 69 | int event_base; 70 | int error_base; 71 | if (!XShapeQueryExtension(display, &event_base, &error_base)) 72 | { 73 | XCloseDisplay(display); 74 | return false; 75 | } 76 | 77 | const sf::Uint8* pixelData = image.getPixelsPtr(); 78 | 79 | // Create a black and white pixmap that has the size of the window 80 | Pixmap pixmap = XCreatePixmap(display, wnd, image.getSize().x, image.getSize().y, 1); 81 | GC gc = XCreateGC(display, pixmap, 0, NULL); 82 | 83 | // Make the entire pixmap white 84 | XSetForeground(display, gc, 1); 85 | XFillRectangle(display, pixmap, gc, 0, 0, image.getSize().x, image.getSize().y); 86 | 87 | // Loop over the pixels in the image and change the color of the pixmap to black 88 | // for each pixel where the alpha component equals 0. 89 | // As an optimization, we will combine adjacent transparent pixels on the same row and 90 | // draw them together, instead of calling "XFillRectangle(display, pixmap, gc, x, y, 1, 1)" 91 | // for each transparent pixel individually. 92 | XSetForeground(display, gc, 0); 93 | bool transparentPixelFound = false; 94 | unsigned int rectLeft = 0; 95 | for (unsigned int y = 0; y < image.getSize().y; y++) 96 | { 97 | for (unsigned int x = 0; x < image.getSize().x; x++) 98 | { 99 | const bool isTransparentPixel = (pixelData[y * image.getSize().x * 4 + x * 4 + 3] == 0); 100 | if (isTransparentPixel && !transparentPixelFound) 101 | { 102 | transparentPixelFound = true; 103 | rectLeft = x; 104 | } 105 | else if (!isTransparentPixel && transparentPixelFound) 106 | { 107 | XFillRectangle(display, pixmap, gc, rectLeft, y, x - rectLeft, 1); 108 | transparentPixelFound = false; 109 | } 110 | } 111 | 112 | if (transparentPixelFound) 113 | { 114 | XFillRectangle(display, pixmap, gc, rectLeft, y, image.getSize().x - rectLeft, 1); 115 | transparentPixelFound = false; 116 | } 117 | } 118 | 119 | // Use the black and white pixmap to define the shape of the window. All pixels that are 120 | // white will be kept, while all black pixels will be clipped from the window. 121 | XShapeCombineMask(display, wnd, ShapeBounding, 0, 0, pixmap, ShapeSet); 122 | 123 | // Free resources 124 | XFreeGC(display, gc); 125 | XFreePixmap(display, pixmap); 126 | XFlush(display); 127 | XCloseDisplay(display); 128 | return true; 129 | } 130 | 131 | bool setTransparency(Window wnd, unsigned char alpha) 132 | { 133 | Display* display = XOpenDisplay(NULL); 134 | unsigned long opacity = (0xffffffff / 0xff) * alpha; 135 | Atom property = XInternAtom(display, "_NET_WM_WINDOW_OPACITY", false); 136 | if (property != None) 137 | { 138 | XChangeProperty(display, wnd, property, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)&opacity, 1); 139 | XFlush(display); 140 | XCloseDisplay(display); 141 | return true; 142 | } 143 | else 144 | { 145 | XCloseDisplay(display); 146 | return false; 147 | } 148 | } 149 | 150 | #undef None // None conflicts with SFML 151 | #elif defined (SFML_SYSTEM_MACOS) 152 | bool setShape(sf::WindowHandle handle, const sf::Image& image); 153 | bool setTransparency(sf::WindowHandle handle, unsigned char alpha); 154 | #else 155 | bool setShape(sf::WindowHandle handle, const sf::Image& image) 156 | { 157 | return false; 158 | } 159 | 160 | bool setTransparency(sf::WindowHandle handle, unsigned char alpha) 161 | { 162 | return false; 163 | } 164 | #endif 165 | 166 | int main() 167 | { 168 | // Change this to the wanted transparency 169 | const unsigned char opacity = 185; 170 | 171 | // Load an image with transparent parts that will define the shape of the window 172 | sf::Image backgroundImage; 173 | backgroundImage.loadFromFile("image.png"); 174 | 175 | // Create the window and center it on the screen 176 | sf::RenderWindow window(sf::VideoMode(backgroundImage.getSize().x, backgroundImage.getSize().y, 32), "Transparent Window", sf::Style::None); 177 | window.setPosition(sf::Vector2i((sf::VideoMode::getDesktopMode().width - backgroundImage.getSize().x) / 2, 178 | (sf::VideoMode::getDesktopMode().height - backgroundImage.getSize().y) / 2)); 179 | 180 | // These functions return false on an unsupported OS or when it is not supported on linux (e.g. display doesn't support shape extention) 181 | setShape(window.getSystemHandle(), backgroundImage); 182 | setTransparency(window.getSystemHandle(), opacity); 183 | 184 | // We will also draw the image on the window instead of just showing an empty window with the wanted shape 185 | sf::Texture backgroundTexture; 186 | sf::Sprite backgroundSprite; 187 | backgroundTexture.loadFromImage(backgroundImage); 188 | backgroundSprite.setTexture(backgroundTexture); 189 | 190 | // Main loop to display the image while the window is open (pressing the escape key to close the window) 191 | while (window.isOpen()) 192 | { 193 | sf::Event event; 194 | while (window.pollEvent(event)) 195 | { 196 | if (event.type == sf::Event::Closed || (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape)) 197 | window.close(); 198 | } 199 | 200 | window.clear(sf::Color::Transparent); 201 | window.draw(backgroundSprite); 202 | window.display(); 203 | } 204 | 205 | return 0; 206 | } 207 | --------------------------------------------------------------------------------