From 9b71307d499924834d029adc91baf9689bfa49fd Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Tue, 19 Feb 2019 01:42:53 +0100 Subject: [PATCH] WindowServer: Support windows with alpha channels. And per-WSWindow opacity. This patch also adds a Format concept to GraphicsBitmap. For now there are only two formats: RGB32 and RGBA32. Windows with alpha channel have their backing stores created in the RGBA32 format. Use this to make Terminal windows semi-transparent for that comfy rice look. There is one problem here, in that window compositing overdraw incurs multiple passes of blending of the same pixels. This leads to a mismatch in opacity which is obviously not good. I will work on this in a later patch. The alpha blending is currently straight C++. It should be relatively easy to optimize this using SSE instructions. For now I'm just happy with the cute effect. :^) --- Applications/About/main.cpp | 2 +- Applications/FileManager/DirectoryView.cpp | 8 ++-- Applications/Launcher/main.cpp | 2 +- Applications/Terminal/Terminal.cpp | 16 ++++--- Applications/Terminal/Terminal.h | 3 ++ Applications/Terminal/main.cpp | 1 + LibGUI/GButton.cpp | 2 +- LibGUI/GLabel.cpp | 2 +- LibGUI/GWidget.h | 4 +- LibGUI/GWindow.cpp | 21 +++++++++ LibGUI/GWindow.h | 7 ++- SharedGraphics/Color.cpp | 2 +- SharedGraphics/Color.h | 38 ++++++++++++---- SharedGraphics/GraphicsBitmap.cpp | 25 +++++----- SharedGraphics/GraphicsBitmap.h | 19 +++++--- SharedGraphics/Painter.cpp | 53 ++++++++++++++++++++-- SharedGraphics/Painter.h | 3 +- WindowServer/WSAPITypes.h | 4 ++ WindowServer/WSClientConnection.cpp | 21 ++++++++- WindowServer/WSClientConnection.h | 3 +- WindowServer/WSMessage.h | 29 +++++++++++- WindowServer/WSMessageLoop.cpp | 2 +- WindowServer/WSWindow.cpp | 7 +-- WindowServer/WSWindow.h | 10 +++- WindowServer/WSWindowManager.cpp | 31 ++++++++----- 25 files changed, 244 insertions(+), 71 deletions(-) diff --git a/Applications/About/main.cpp b/Applications/About/main.cpp index bd405bd719..64dfdd7766 100644 --- a/Applications/About/main.cpp +++ b/Applications/About/main.cpp @@ -17,7 +17,7 @@ int main(int argc, char** argv) window->set_main_widget(widget); auto* icon_label = new GLabel(widget); - icon_label->set_icon(GraphicsBitmap::load_from_file("/res/icons/Serenity.rgb", { 32, 32 })); + icon_label->set_icon(GraphicsBitmap::load_from_file(GraphicsBitmap::Format::RGBA32, "/res/icons/Serenity.rgb", { 32, 32 })); icon_label->set_relative_rect( widget->rect().center().x() - 16, 10, diff --git a/Applications/FileManager/DirectoryView.cpp b/Applications/FileManager/DirectoryView.cpp index a50fb4b41b..cb0ca5e201 100644 --- a/Applications/FileManager/DirectoryView.cpp +++ b/Applications/FileManager/DirectoryView.cpp @@ -12,9 +12,9 @@ DirectoryView::DirectoryView(GWidget* parent) : GWidget(parent) { - m_directory_icon = GraphicsBitmap::load_from_file("/res/icons/folder16.rgb", { 16, 16 }); - m_file_icon = GraphicsBitmap::load_from_file("/res/icons/file16.rgb", { 16, 16 }); - m_symlink_icon = GraphicsBitmap::load_from_file("/res/icons/link16.rgb", { 16, 16 }); + m_directory_icon = GraphicsBitmap::load_from_file(GraphicsBitmap::Format::RGBA32, "/res/icons/folder16.rgb", { 16, 16 }); + m_file_icon = GraphicsBitmap::load_from_file(GraphicsBitmap::Format::RGBA32, "/res/icons/file16.rgb", { 16, 16 }); + m_symlink_icon = GraphicsBitmap::load_from_file(GraphicsBitmap::Format::RGBA32, "/res/icons/link16.rgb", { 16, 16 }); m_scrollbar = new GScrollBar(Orientation::Vertical, this); m_scrollbar->set_step(4); @@ -142,7 +142,7 @@ void DirectoryView::paint_event(GPaintEvent&) Rect name_rect(icon_rect.right() + horizontal_padding, y, 100, item_height()); Rect size_rect(name_rect.right() + horizontal_padding, y, 64, item_height()); painter.fill_rect(row_rect(painted_item_index), i % 2 ? Color(210, 210, 210) : Color::White); - painter.blit_with_alpha(icon_rect.location(), icon_for(entry), { 0, 0, icon_size, icon_size }); + painter.blit(icon_rect.location(), icon_for(entry), { 0, 0, icon_size, icon_size }); painter.draw_text(name_rect, entry.name, TextAlignment::CenterLeft, Color::Black); if (should_show_size_for(entry)) painter.draw_text(size_rect, pretty_byte_size(entry.size), TextAlignment::CenterRight, Color::Black); diff --git a/Applications/Launcher/main.cpp b/Applications/Launcher/main.cpp index 9d765cb484..2b9f0ab898 100644 --- a/Applications/Launcher/main.cpp +++ b/Applications/Launcher/main.cpp @@ -38,7 +38,7 @@ public: : GButton(parent) , m_executable_path(exec_path) { - set_icon(GraphicsBitmap::load_from_file(icon_path, { 32, 32 })); + set_icon(GraphicsBitmap::load_from_file(GraphicsBitmap::Format::RGBA32, icon_path, { 32, 32 })); resize(50, 50); on_click = [this] (GButton&) { pid_t child_pid = fork(); diff --git a/Applications/Terminal/Terminal.cpp b/Applications/Terminal/Terminal.cpp index 70721af153..e9f63cf551 100644 --- a/Applications/Terminal/Terminal.cpp +++ b/Applications/Terminal/Terminal.cpp @@ -143,7 +143,7 @@ unsigned parse_uint(const String& str, bool& ok) static inline Color lookup_color(unsigned color) { - return xterm_colors[color]; + return Color::from_rgb(xterm_colors[color]); } void Terminal::escape$m(const Vector& params) @@ -668,10 +668,14 @@ void Terminal::keydown_event(GKeyEvent& event) } void Terminal::paint_event(GPaintEvent&) -{ - Rect rect { 0, 0, m_pixel_width, m_pixel_height }; +{ Painter painter(*this); + if (m_needs_background_fill) { + m_needs_background_fill = false; + painter.fill_rect(rect(), Color(Color::Black).with_alpha(255 * m_opacity)); + } + if (m_rows_to_scroll_backing_store && m_rows_to_scroll_backing_store < m_rows) { int first_scanline = m_inset; int second_scanline = m_inset + (m_rows_to_scroll_backing_store * m_line_height); @@ -695,7 +699,7 @@ void Terminal::paint_event(GPaintEvent&) line.dirty = false; bool has_only_one_background_color = line.has_only_one_background_color(); if (has_only_one_background_color) { - painter.fill_rect(row_rect(row), lookup_color(line.attributes[0].background_color)); + painter.fill_rect(row_rect(row), lookup_color(line.attributes[0].background_color).with_alpha(255 * m_opacity)); } for (word column = 0; column < m_columns; ++column) { bool should_reverse_fill_for_cursor = m_in_active_window && row == m_cursor_row && column == m_cursor_column; @@ -704,7 +708,7 @@ void Terminal::paint_event(GPaintEvent&) auto character_rect = glyph_rect(row, column); if (!has_only_one_background_color || should_reverse_fill_for_cursor) { auto cell_rect = character_rect.inflated(0, m_line_spacing); - painter.fill_rect(cell_rect, lookup_color(should_reverse_fill_for_cursor ? attribute.foreground_color : attribute.background_color)); + painter.fill_rect(cell_rect, lookup_color(should_reverse_fill_for_cursor ? attribute.foreground_color : attribute.background_color).with_alpha(255 * m_opacity)); } if (ch == ' ') continue; @@ -718,7 +722,7 @@ void Terminal::paint_event(GPaintEvent&) } if (m_belling) - painter.draw_rect(rect, Color::Red); + painter.draw_rect(rect(), Color::Red); } void Terminal::set_window_title(String&& title) diff --git a/Applications/Terminal/Terminal.h b/Applications/Terminal/Terminal.h index 298596a479..a51c7b3ad9 100644 --- a/Applications/Terminal/Terminal.h +++ b/Applications/Terminal/Terminal.h @@ -144,4 +144,7 @@ private: RetainPtr m_font; GNotifier m_notifier; + + float m_opacity { 0.8f }; + bool m_needs_background_fill { true }; }; diff --git a/Applications/Terminal/main.cpp b/Applications/Terminal/main.cpp index f1dc759dc8..e27d2c0f35 100644 --- a/Applications/Terminal/main.cpp +++ b/Applications/Terminal/main.cpp @@ -89,6 +89,7 @@ int main(int argc, char** argv) window->set_should_exit_app_on_close(true); Terminal terminal(ptm_fd); + window->set_has_alpha_channel(true); window->set_main_widget(&terminal); window->move_to(300, 300); window->show(); diff --git a/LibGUI/GButton.cpp b/LibGUI/GButton.cpp index 8d117ea178..9062baf181 100644 --- a/LibGUI/GButton.cpp +++ b/LibGUI/GButton.cpp @@ -36,7 +36,7 @@ void GButton::paint_event(GPaintEvent&) icon_location.move_by(1, 1); } if (m_icon) { - painter.blit_with_alpha(icon_location, *m_icon, m_icon->rect()); + painter.blit(icon_location, *m_icon, m_icon->rect()); painter.draw_text(content_rect, caption(), TextAlignment::Center, Color::Black); } else { painter.draw_text(content_rect, caption(), TextAlignment::Center, Color::Black); diff --git a/LibGUI/GLabel.cpp b/LibGUI/GLabel.cpp index ef9813a07a..140d1282f3 100644 --- a/LibGUI/GLabel.cpp +++ b/LibGUI/GLabel.cpp @@ -31,7 +31,7 @@ void GLabel::paint_event(GPaintEvent&) painter.fill_rect({ 0, 0, width(), height() }, background_color()); if (m_icon) { auto icon_location = rect().center().translated(-(m_icon->width() / 2), -(m_icon->height() / 2)); - painter.blit_with_alpha(icon_location, *m_icon, m_icon->rect()); + painter.blit(icon_location, *m_icon, m_icon->rect()); } if (!text().is_empty()) painter.draw_text({ 0, 0, width(), height() }, text(), m_text_alignment, foreground_color()); diff --git a/LibGUI/GWidget.h b/LibGUI/GWidget.h index 5b5cb957d5..f3211e6c6b 100644 --- a/LibGUI/GWidget.h +++ b/LibGUI/GWidget.h @@ -128,8 +128,8 @@ private: OwnPtr m_layout; Rect m_relative_rect; - Color m_background_color { 0xffffff }; - Color m_foreground_color { 0x000000 }; + Color m_background_color; + Color m_foreground_color; RetainPtr m_font; SizePolicy m_horizontal_size_policy { SizePolicy::Fill }; diff --git a/LibGUI/GWindow.cpp b/LibGUI/GWindow.cpp index 49771bc8d9..4ae92562d6 100644 --- a/LibGUI/GWindow.cpp +++ b/LibGUI/GWindow.cpp @@ -59,6 +59,8 @@ void GWindow::show() request.type = WSAPI_ClientMessage::Type::CreateWindow; request.window_id = m_window_id; request.window.rect = m_rect_when_windowless; + request.window.has_alpha_channel = m_has_alpha_channel; + request.window.opacity = m_opacity_when_windowless; ASSERT(m_title_when_windowless.length() < sizeof(request.text)); strcpy(request.text, m_title_when_windowless.characters()); request.text_length = m_title_when_windowless.length(); @@ -280,3 +282,22 @@ void GWindow::set_global_cursor_tracking_widget(GWidget* widget) // Maybe there could be a response that includes the current cursor location as of enabling. GEventLoop::main().post_message_to_server(request); } + +void GWindow::set_has_alpha_channel(bool value) +{ + ASSERT(!m_window_id); + m_has_alpha_channel = value; +} + +void GWindow::set_opacity(float opacity) +{ + m_opacity_when_windowless = opacity; + if (!m_window_id) + return; + WSAPI_ClientMessage request; + request.type = WSAPI_ClientMessage::Type::SetWindowOpacity; + request.window_id = m_window_id; + request.window.opacity = opacity; + m_opacity_when_windowless = opacity; + GEventLoop::main().post_message_to_server(request); +} diff --git a/LibGUI/GWindow.h b/LibGUI/GWindow.h index f959226596..2cb99ecef7 100644 --- a/LibGUI/GWindow.h +++ b/LibGUI/GWindow.h @@ -15,6 +15,9 @@ public: static GWindow* from_window_id(int); + void set_has_alpha_channel(bool); + void set_opacity(float); + int window_id() const { return m_window_id; } String title() const; @@ -66,13 +69,15 @@ private: RetainPtr m_backing; int m_window_id { 0 }; - bool m_is_active { false }; + float m_opacity_when_windowless { 1.0f }; GWidget* m_main_widget { nullptr }; GWidget* m_focused_widget { nullptr }; WeakPtr m_global_cursor_tracking_widget; Rect m_rect_when_windowless; String m_title_when_windowless; Vector m_pending_paint_event_rects; + bool m_is_active { false }; bool m_should_exit_app_on_close { false }; + bool m_has_alpha_channel { false }; }; diff --git a/SharedGraphics/Color.cpp b/SharedGraphics/Color.cpp index 6d34582ebc..aac4ad2c76 100644 --- a/SharedGraphics/Color.cpp +++ b/SharedGraphics/Color.cpp @@ -23,5 +23,5 @@ Color::Color(NamedColor named) default: ASSERT_NOT_REACHED(); break; } - m_value = (rgb.r << 16) | (rgb.g << 8) | rgb.b; + m_value = 0xff000000 | (rgb.r << 16) | (rgb.g << 8) | rgb.b; } diff --git a/SharedGraphics/Color.h b/SharedGraphics/Color.h index 934e6bdd13..eb9ed1ee88 100644 --- a/SharedGraphics/Color.h +++ b/SharedGraphics/Color.h @@ -26,26 +26,48 @@ public: Color() { } Color(NamedColor); - Color(byte r, byte g, byte b) : m_value((r << 16) | (g << 8) | b) { } - Color(RGBA32 rgba) : m_value(rgba) { } + Color(byte r, byte g, byte b) : m_value(0xff000000 | (r << 16) | (g << 8) | b) { } + Color(byte r, byte g, byte b, byte a) : m_value((a << 24) | (r << 16) | (g << 8) | b) { } + + static Color from_rgb(unsigned rgb) { return Color(rgb | 0xff000000); } + static Color from_rgba(unsigned rgba) { return Color(rgba); } byte red() const { return (m_value >> 16) & 0xff; } byte green() const { return (m_value >> 8) & 0xff; } byte blue() const { return m_value & 0xff; } byte alpha() const { return (m_value >> 24) & 0xff; } - Color blend(Color source) const + void set_alpha(byte value) { - RGBA32 redblue1 = ((0x100u - source.alpha()) * (m_value & 0xff00ff)) >> 8; - RGBA32 redblue2 = (source.alpha() * (source.m_value & 0xff00ff)) >> 8; - RGBA32 green1 = ((0x100u - source.alpha()) * (m_value & 0x00ff00)) >> 8; - RGBA32 green2 = (source.alpha() * (source.m_value & 0x00ff00)) >> 8; - return Color(((redblue1 | redblue2) & 0xff00ff) + ((green1 | green2) & 0x00ff00)); + m_value &= 0x00ffffff; + m_value |= value << 24; } + Color with_alpha(byte alpha) + { + return Color((m_value & 0x00ffffff) | alpha << 24); + } + + Color blend(Color source) const + { + if (!alpha() || source.alpha() == 255) + return source; + + if (!source.alpha()) + return *this; + + int d = 255 * (alpha() + source.alpha()) - alpha() * source.alpha(); + byte r = (red() * alpha() * (255 - source.alpha()) + 255 * source.alpha() * source.red()) / d; + byte g = (green() * alpha() * (255 - source.alpha()) + 255 * source.alpha() * source.green()) / d; + byte b = (blue() * alpha() * (255 - source.alpha()) + 255 * source.alpha() * source.blue()) / d; + byte a = d / 255; + return Color(r, g, b, a); + } RGBA32 value() const { return m_value; } private: + explicit Color(RGBA32 rgba) : m_value(rgba) { } + RGBA32 m_value { 0 }; }; diff --git a/SharedGraphics/GraphicsBitmap.cpp b/SharedGraphics/GraphicsBitmap.cpp index e689550897..40f25ee697 100644 --- a/SharedGraphics/GraphicsBitmap.cpp +++ b/SharedGraphics/GraphicsBitmap.cpp @@ -5,14 +5,15 @@ #include #include -RetainPtr GraphicsBitmap::create(const Size& size) +RetainPtr GraphicsBitmap::create(Format format, const Size& size) { - return adopt(*new GraphicsBitmap(size)); + return adopt(*new GraphicsBitmap(format, size)); } -GraphicsBitmap::GraphicsBitmap(const Size& size) +GraphicsBitmap::GraphicsBitmap(Format format, const Size& size) : m_size(size) , m_pitch(size.width() * sizeof(RGBA32)) + , m_format(format) { size_t size_in_bytes = size.area() * sizeof(RGBA32); m_data = (RGBA32*)mmap(nullptr, size_in_bytes, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); @@ -21,12 +22,12 @@ GraphicsBitmap::GraphicsBitmap(const Size& size) m_mmaped = true; } -RetainPtr GraphicsBitmap::create_wrapper(const Size& size, RGBA32* data) +RetainPtr GraphicsBitmap::create_wrapper(Format format, const Size& size, RGBA32* data) { - return adopt(*new GraphicsBitmap(size, data)); + return adopt(*new GraphicsBitmap(format, size, data)); } -RetainPtr GraphicsBitmap::load_from_file(const String& path, const Size& size) +RetainPtr GraphicsBitmap::load_from_file(Format format, const String& path, const Size& size) { int fd = open(path.characters(), O_RDONLY, 0644); if (fd < 0) { @@ -44,19 +45,20 @@ RetainPtr GraphicsBitmap::load_from_file(const String& path, con int rc = close(fd); ASSERT(rc == 0); - auto bitmap = create_wrapper(size, mapped_data); + auto bitmap = create_wrapper(format, size, mapped_data); bitmap->m_mmaped = true; return bitmap; } -GraphicsBitmap::GraphicsBitmap(const Size& size, RGBA32* data) +GraphicsBitmap::GraphicsBitmap(Format format, const Size& size, RGBA32* data) : m_size(size) , m_data(data) , m_pitch(size.width() * sizeof(RGBA32)) + , m_format(format) { } -RetainPtr GraphicsBitmap::create_with_shared_buffer(int shared_buffer_id, const Size& size, RGBA32* data) +RetainPtr GraphicsBitmap::create_with_shared_buffer(Format format, int shared_buffer_id, const Size& size, RGBA32* data) { if (!data) { void* shared_buffer = get_shared_buffer(shared_buffer_id); @@ -64,13 +66,14 @@ RetainPtr GraphicsBitmap::create_with_shared_buffer(int shared_b return nullptr; data = (RGBA32*)shared_buffer; } - return adopt(*new GraphicsBitmap(shared_buffer_id, size, data)); + return adopt(*new GraphicsBitmap(format, shared_buffer_id, size, data)); } -GraphicsBitmap::GraphicsBitmap(int shared_buffer_id, const Size& size, RGBA32* data) +GraphicsBitmap::GraphicsBitmap(Format format, int shared_buffer_id, const Size& size, RGBA32* data) : m_size(size) , m_data(data) , m_pitch(size.width() * sizeof(RGBA32)) + , m_format(format) , m_shared_buffer_id(shared_buffer_id) { } diff --git a/SharedGraphics/GraphicsBitmap.h b/SharedGraphics/GraphicsBitmap.h index 1a29dbe965..7d8f2ec65f 100644 --- a/SharedGraphics/GraphicsBitmap.h +++ b/SharedGraphics/GraphicsBitmap.h @@ -9,10 +9,12 @@ class GraphicsBitmap : public Retainable { public: - static RetainPtr create(const Size&); - static RetainPtr create_wrapper(const Size&, RGBA32*); - static RetainPtr load_from_file(const String& path, const Size&); - static RetainPtr create_with_shared_buffer(int shared_buffer_id, const Size&, RGBA32* buffer = nullptr); + enum class Format { Invalid, RGB32, RGBA32 }; + + static RetainPtr create(Format, const Size&); + static RetainPtr create_wrapper(Format, const Size&, RGBA32*); + static RetainPtr load_from_file(Format, const String& path, const Size&); + static RetainPtr create_with_shared_buffer(Format, int shared_buffer_id, const Size&, RGBA32* buffer = nullptr); ~GraphicsBitmap(); RGBA32* scanline(int y); @@ -25,14 +27,17 @@ public: size_t pitch() const { return m_pitch; } int shared_buffer_id() const { return m_shared_buffer_id; } + bool has_alpha_channel() const { return m_format == Format::RGBA32; } + private: - GraphicsBitmap(const Size&); - GraphicsBitmap(const Size&, RGBA32*); - GraphicsBitmap(int shared_buffer_id, const Size&, RGBA32*); + GraphicsBitmap(Format, const Size&); + GraphicsBitmap(Format, const Size&, RGBA32*); + GraphicsBitmap(Format, int shared_buffer_id, const Size&, RGBA32*); Size m_size; RGBA32* m_data { nullptr }; size_t m_pitch { 0 }; + Format m_format { Format::Invalid }; bool m_mmaped { false }; int m_shared_buffer_id { -1 }; }; diff --git a/SharedGraphics/Painter.cpp b/SharedGraphics/Painter.cpp index e8866b2f51..b9f55a2af3 100644 --- a/SharedGraphics/Painter.cpp +++ b/SharedGraphics/Painter.cpp @@ -32,7 +32,10 @@ Painter::Painter(GWidget& widget) request.window_id = widget.window()->window_id(); auto response = GEventLoop::main().sync_request(request, WSAPI_ServerMessage::DidGetWindowBackingStore); - m_target = GraphicsBitmap::create_with_shared_buffer(response.backing.shared_buffer_id, response.backing.size); + m_target = GraphicsBitmap::create_with_shared_buffer( + response.backing.has_alpha_channel ? GraphicsBitmap::Format::RGBA32 : GraphicsBitmap::Format::RGB32, + response.backing.shared_buffer_id, + response.backing.size); ASSERT(m_target); m_window = widget.window(); m_translation.move_by(widget.window_relative_rect().location()); @@ -67,7 +70,7 @@ void Painter::fill_rect_with_draw_op(const Rect& a_rect, Color color) for (int i = rect.height() - 1; i >= 0; --i) { for (int j = 0; j < rect.width(); ++j) - set_pixel_with_draw_op(dst[j], color.value()); + set_pixel_with_draw_op(dst[j], color); dst += dst_skip; } } @@ -230,8 +233,17 @@ void Painter::draw_bitmap(const Point& p, const GlyphBitmap& bitmap, Color color } } -void Painter::blit_with_alpha(const Point& position, const GraphicsBitmap& source, const Rect& src_rect) +void Painter::blit_with_opacity(const Point& position, const GraphicsBitmap& source, const Rect& src_rect, float opacity) { + ASSERT(!m_target->has_alpha_channel()); + + if (!opacity) + return; + if (opacity >= 1.0f) + return blit(position, source, src_rect); + + byte alpha = 255 * opacity; + Rect dst_rect(position, src_rect.size()); dst_rect.move_by(m_translation); auto clipped_rect = Rect::intersection(dst_rect, m_clip_rect); @@ -248,13 +260,42 @@ void Painter::blit_with_alpha(const Point& position, const GraphicsBitmap& sourc for (int row = first_row; row <= last_row; ++row) { for (int x = 0; x <= (last_column - first_column); ++x) { - byte alpha = Color(src[x]).alpha(); + Color src_color_with_alpha = Color::from_rgb(src[x]); + src_color_with_alpha.set_alpha(alpha); + Color dst_color = Color::from_rgb(dst[x]); + dst[x] = dst_color.blend(src_color_with_alpha).value(); + } + dst += dst_skip; + src += src_skip; + } +} + +void Painter::blit_with_alpha(const Point& position, const GraphicsBitmap& source, const Rect& src_rect) +{ + ASSERT(source.has_alpha_channel()); + Rect dst_rect(position, src_rect.size()); + dst_rect.move_by(m_translation); + auto clipped_rect = Rect::intersection(dst_rect, m_clip_rect); + if (clipped_rect.is_empty()) + return; + const int first_row = clipped_rect.top() - dst_rect.top(); + const int last_row = clipped_rect.bottom() - dst_rect.top(); + const int first_column = clipped_rect.left() - dst_rect.left(); + const int last_column = clipped_rect.right() - dst_rect.left(); + RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x(); + const RGBA32* src = source.scanline(src_rect.top() + first_row) + src_rect.left() + first_column; + const size_t dst_skip = m_target->width(); + const unsigned src_skip = source.width(); + + for (int row = first_row; row <= last_row; ++row) { + for (int x = 0; x <= (last_column - first_column); ++x) { + byte alpha = Color::from_rgba(src[x]).alpha(); if (alpha == 0xff) dst[x] = src[x]; else if (!alpha) continue; else - dst[x] = Color(dst[x]).blend(src[x]).value(); + dst[x] = Color::from_rgba(dst[x]).blend(Color::from_rgba(src[x])).value(); } dst += dst_skip; src += src_skip; @@ -263,6 +304,8 @@ void Painter::blit_with_alpha(const Point& position, const GraphicsBitmap& sourc void Painter::blit(const Point& position, const GraphicsBitmap& source, const Rect& src_rect) { + if (source.has_alpha_channel()) + return blit_with_alpha(position, source, src_rect); Rect dst_rect(position, src_rect.size()); dst_rect.move_by(m_translation); auto clipped_rect = Rect::intersection(dst_rect, m_clip_rect); diff --git a/SharedGraphics/Painter.h b/SharedGraphics/Painter.h index 9bffea12f5..a6482e89f2 100644 --- a/SharedGraphics/Painter.h +++ b/SharedGraphics/Painter.h @@ -34,7 +34,7 @@ public: void draw_line(const Point&, const Point&, Color); void draw_focus_rect(const Rect&); void blit(const Point&, const GraphicsBitmap&, const Rect& src_rect); - void blit_with_alpha(const Point&, const GraphicsBitmap&, const Rect& src_rect); + void blit_with_opacity(const Point&, const GraphicsBitmap&, const Rect& src_rect, float opacity); void draw_text(const Rect&, const String&, TextAlignment = TextAlignment::TopLeft, Color = Color()); void draw_glyph(const Point&, char, Color); @@ -58,6 +58,7 @@ public: private: void set_pixel_with_draw_op(dword& pixel, const Color&); void fill_rect_with_draw_op(const Rect&, Color); + void blit_with_alpha(const Point&, const GraphicsBitmap&, const Rect& src_rect); const Font* m_font; Point m_translation; diff --git a/WindowServer/WSAPITypes.h b/WindowServer/WSAPITypes.h index 8f9257c715..3ed1b55077 100644 --- a/WindowServer/WSAPITypes.h +++ b/WindowServer/WSAPITypes.h @@ -117,6 +117,7 @@ struct WSAPI_ServerMessage { size_t bpp; size_t pitch; int shared_buffer_id; + bool has_alpha_channel; } backing; }; }; @@ -142,6 +143,7 @@ struct WSAPI_ClientMessage { DidFinishPainting, GetWindowBackingStore, SetGlobalCursorTracking, + SetWindowOpacity, }; Type type { Invalid }; int window_id { -1 }; @@ -157,6 +159,8 @@ struct WSAPI_ClientMessage { } menu; struct { WSAPI_Rect rect; + bool has_alpha_channel; + float opacity; } window; }; }; diff --git a/WindowServer/WSClientConnection.cpp b/WindowServer/WSClientConnection.cpp index 841d58a7c9..ad11034333 100644 --- a/WindowServer/WSClientConnection.cpp +++ b/WindowServer/WSClientConnection.cpp @@ -79,14 +79,14 @@ void WSClientConnection::post_message(const WSAPI_ServerMessage& message) ASSERT(nwritten == sizeof(message)); } -RetainPtr WSClientConnection::create_shared_bitmap(const Size& size) +RetainPtr WSClientConnection::create_shared_bitmap(GraphicsBitmap::Format format, const Size& size) { RGBA32* buffer; int shared_buffer_id = create_shared_buffer(m_pid, size.area() * sizeof(RGBA32), (void**)&buffer); ASSERT(shared_buffer_id >= 0); ASSERT(buffer); ASSERT(buffer != (void*)-1); - return GraphicsBitmap::create_with_shared_buffer(shared_buffer_id, size, buffer); + return GraphicsBitmap::create_with_shared_buffer(format, shared_buffer_id, size, buffer); } void WSClientConnection::on_message(WSMessage& message) @@ -236,6 +236,18 @@ void WSClientConnection::handle_request(WSAPIAddMenuSeparatorRequest& request) post_message(response); } +void WSClientConnection::handle_request(WSAPISetWindowOpacityRequest& request) +{ + int window_id = request.window_id(); + auto it = m_windows.find(window_id); + if (it == m_windows.end()) { + post_error("Bad window ID"); + return; + } + auto& window = *(*it).value; + window.set_opacity(request.opacity()); +} + void WSClientConnection::handle_request(WSAPISetWindowTitleRequest& request) { int window_id = request.window_id(); @@ -298,8 +310,10 @@ void WSClientConnection::handle_request(WSAPICreateWindowRequest& request) { int window_id = m_next_window_id++; auto window = make(*this, window_id); + window->set_has_alpha_channel(request.has_alpha_channel()); window->set_title(request.title()); window->set_rect(request.rect()); + window->set_opacity(request.opacity()); m_windows.set(window_id, move(window)); WSAPI_ServerMessage response; response.type = WSAPI_ServerMessage::Type::DidCreateWindow; @@ -364,6 +378,7 @@ void WSClientConnection::handle_request(WSAPIGetWindowBackingStoreRequest& reque response.backing.bpp = sizeof(RGBA32); response.backing.pitch = backing_store->pitch(); response.backing.size = backing_store->size(); + response.backing.has_alpha_channel = backing_store->has_alpha_channel(); response.backing.shared_buffer_id = backing_store->shared_buffer_id(); post_message(response); } @@ -419,6 +434,8 @@ void WSClientConnection::on_request(WSAPIClientRequest& request) return handle_request(static_cast(request)); case WSMessage::APISetGlobalCursorTrackingRequest: return handle_request(static_cast(request)); + case WSMessage::APISetWindowOpacityRequest: + return handle_request(static_cast(request)); default: break; } diff --git a/WindowServer/WSClientConnection.h b/WindowServer/WSClientConnection.h index f2467c14fd..fdcc27adab 100644 --- a/WindowServer/WSClientConnection.h +++ b/WindowServer/WSClientConnection.h @@ -22,7 +22,7 @@ public: static void for_each_client(Function); void post_message(const WSAPI_ServerMessage&); - RetainPtr create_shared_bitmap(const Size&); + RetainPtr create_shared_bitmap(GraphicsBitmap::Format, const Size&); int client_id() const { return m_client_id; } WSMenuBar* app_menubar() { return m_app_menubar.ptr(); } @@ -52,6 +52,7 @@ private: void handle_request(WSAPIDidFinishPaintingNotification&); void handle_request(WSAPIGetWindowBackingStoreRequest&); void handle_request(WSAPISetGlobalCursorTrackingRequest&); + void handle_request(WSAPISetWindowOpacityRequest&); void post_error(const String&); diff --git a/WindowServer/WSMessage.h b/WindowServer/WSMessage.h index fdbd67dbe3..0656bc4bd5 100644 --- a/WindowServer/WSMessage.h +++ b/WindowServer/WSMessage.h @@ -39,6 +39,7 @@ public: APIDidFinishPaintingNotification, APIGetWindowBackingStoreRequest, APISetGlobalCursorTrackingRequest, + APISetWindowOpacityRequest, __End_API_Client_Requests, }; @@ -253,6 +254,26 @@ private: int m_window_id { 0 }; }; +class WSAPISetWindowOpacityRequest final : public WSAPIClientRequest { +public: + explicit WSAPISetWindowOpacityRequest(int client_id, int window_id, float opacity) + : WSAPIClientRequest(WSMessage::APISetWindowOpacityRequest, client_id) + , m_client_id(client_id) + , m_window_id(window_id) + , m_opacity(opacity) + { + } + + int client_id() const { return m_client_id; } + int window_id() const { return m_window_id; } + float opacity() const { return m_opacity; } + +private: + int m_client_id { 0 }; + int m_window_id { 0 }; + float m_opacity { 0 }; +}; + class WSAPISetWindowRectRequest final : public WSAPIClientRequest { public: explicit WSAPISetWindowRectRequest(int client_id, int window_id, const Rect& rect) @@ -292,19 +313,25 @@ private: class WSAPICreateWindowRequest : public WSAPIClientRequest { public: - WSAPICreateWindowRequest(int client_id, const Rect& rect, const String& title) + WSAPICreateWindowRequest(int client_id, const Rect& rect, const String& title, bool has_alpha_channel, float opacity) : WSAPIClientRequest(WSMessage::APICreateWindowRequest, client_id) , m_rect(rect) , m_title(title) + , m_opacity(opacity) + , m_has_alpha_channel(has_alpha_channel) { } Rect rect() const { return m_rect; } String title() const { return m_title; } + bool has_alpha_channel() const { return m_has_alpha_channel; } + float opacity() const { return m_opacity; } private: Rect m_rect; String m_title; + float m_opacity { 0 }; + bool m_has_alpha_channel { false }; }; class WSAPIDestroyWindowRequest : public WSAPIClientRequest { diff --git a/WindowServer/WSMessageLoop.cpp b/WindowServer/WSMessageLoop.cpp index 6cf92b8374..199222a4dd 100644 --- a/WindowServer/WSMessageLoop.cpp +++ b/WindowServer/WSMessageLoop.cpp @@ -302,7 +302,7 @@ void WSMessageLoop::on_receive_from_client(int client_id, const WSAPI_ClientMess break; case WSAPI_ClientMessage::Type::CreateWindow: ASSERT(message.text_length < sizeof(message.text)); - post_message(client, make(client_id, message.window.rect, String(message.text, message.text_length))); + post_message(client, make(client_id, message.window.rect, String(message.text, message.text_length), message.window.has_alpha_channel, message.window.opacity)); break; case WSAPI_ClientMessage::Type::DestroyWindow: post_message(client, make(client_id, message.window_id)); diff --git a/WindowServer/WSWindow.cpp b/WindowServer/WSWindow.cpp index 8e440e1ec8..61e9073123 100644 --- a/WindowServer/WSWindow.cpp +++ b/WindowServer/WSWindow.cpp @@ -44,9 +44,10 @@ void WSWindow::set_rect(const Rect& rect) m_rect = rect; if (!m_backing || old_rect.size() != rect.size()) { if (m_menu) - m_backing = GraphicsBitmap::create(m_rect.size()); - else if (m_client) - m_backing = m_client->create_shared_bitmap(m_rect.size()); + m_backing = GraphicsBitmap::create(GraphicsBitmap::Format::RGB32, m_rect.size()); + else if (m_client) { + m_backing = m_client->create_shared_bitmap(m_has_alpha_channel ? GraphicsBitmap::Format::RGBA32 : GraphicsBitmap::Format::RGB32, m_rect.size()); + } } WSWindowManager::the().notify_rect_changed(*this, old_rect, rect); diff --git a/WindowServer/WSWindow.h b/WindowServer/WSWindow.h index 050ec7f63f..3cb02b5a35 100644 --- a/WindowServer/WSWindow.h +++ b/WindowServer/WSWindow.h @@ -25,6 +25,9 @@ public: String title() const { return m_title; } void set_title(String&&); + float opacity() const { return m_opacity; } + void set_opacity(float opacity) { m_opacity = opacity; } + int x() const { return m_rect.x(); } int y() const { return m_rect.y(); } int width() const { return m_rect.width(); } @@ -59,6 +62,9 @@ public: void set_global_cursor_tracking_enabled(bool); bool global_cursor_tracking() const { return m_global_cursor_tracking_enabled; } + bool has_alpha_channel() const { return m_has_alpha_channel; } + void set_has_alpha_channel(bool value) { m_has_alpha_channel = value; } + // For InlineLinkedList. // FIXME: Maybe make a ListHashSet and then WSWindowManager can just use that. WSWindow* m_next { nullptr }; @@ -72,9 +78,9 @@ private: bool m_is_being_dragged { false }; bool m_global_cursor_tracking_enabled { false }; bool m_visible { true }; - + bool m_has_alpha_channel { false }; WSMenu* m_menu { nullptr }; - RetainPtr m_backing; int m_window_id { -1 }; + float m_opacity { 1 }; }; diff --git a/WindowServer/WSWindowManager.cpp b/WindowServer/WSWindowManager.cpp index 1772a68346..02154287ef 100644 --- a/WindowServer/WSWindowManager.cpp +++ b/WindowServer/WSWindowManager.cpp @@ -144,8 +144,8 @@ WSWindowManager::WSWindowManager() (void)m_flush_count; #endif auto size = m_screen_rect.size(); - m_front_bitmap = GraphicsBitmap::create_wrapper(size, m_screen.scanline(0)); - m_back_bitmap = GraphicsBitmap::create_wrapper(size, m_screen.scanline(size.height())); + m_front_bitmap = GraphicsBitmap::create_wrapper(GraphicsBitmap::Format::RGB32, size, m_screen.scanline(0)); + m_back_bitmap = GraphicsBitmap::create_wrapper(GraphicsBitmap::Format::RGB32, size, m_screen.scanline(size.height())); m_front_painter = make(*m_front_bitmap); m_back_painter = make(*m_back_bitmap); @@ -170,17 +170,17 @@ WSWindowManager::WSWindowManager() m_cursor_bitmap_outer = CharacterBitmap::create_from_ascii(cursor_bitmap_outer_ascii, 12, 17); m_wallpaper_path = "/res/wallpapers/cool.rgb"; - m_wallpaper = GraphicsBitmap::load_from_file(m_wallpaper_path, { 1024, 768 }); + m_wallpaper = GraphicsBitmap::load_from_file(GraphicsBitmap::Format::RGBA32, m_wallpaper_path, { 1024, 768 }); #ifdef KERNEL ProcFS::the().add_sys_bool("wm_flash_flush", m_flash_flush); ProcFS::the().add_sys_string("wm_wallpaper", m_wallpaper_path, [this] { - m_wallpaper = GraphicsBitmap::load_from_file(m_wallpaper_path, m_screen_rect.size()); + m_wallpaper = GraphicsBitmap::load_from_file(GraphicsBitmap::Format::RGBA32, m_wallpaper_path, m_screen_rect.size()); invalidate(m_screen_rect); }); #endif - m_menu_selection_color = Color(0x84351a); + m_menu_selection_color = Color::from_rgb(0x84351a); { byte system_menu_name[] = { 0xf8, 0 }; @@ -242,8 +242,8 @@ void WSWindowManager::set_resolution(int width, int height) return; m_screen.set_resolution(width, height); m_screen_rect = m_screen.rect(); - m_front_bitmap = GraphicsBitmap::create_wrapper({ width, height }, m_screen.scanline(0)); - m_back_bitmap = GraphicsBitmap::create_wrapper({ width, height }, m_screen.scanline(height)); + m_front_bitmap = GraphicsBitmap::create_wrapper(GraphicsBitmap::Format::RGB32, { width, height }, m_screen.scanline(0)); + m_back_bitmap = GraphicsBitmap::create_wrapper(GraphicsBitmap::Format::RGB32, { width, height }, m_screen.scanline(height)); m_front_painter = make(*m_front_bitmap); m_back_painter = make(*m_back_bitmap); m_buffers_are_flipped = false; @@ -620,10 +620,17 @@ void WSWindowManager::compose() dbgprintf("[WM] compose #%u (%u rects)\n", ++m_compose_count, dirty_rects.size()); #endif - auto any_window_contains_rect = [this] (const Rect& r) { + auto any_opaque_window_contains_rect = [this] (const Rect& r) { for (auto* window = m_windows_in_order.head(); window; window = window->next()) { if (!window->is_visible()) continue; + if (window->opacity() < 1.0f) + continue; + if (window->has_alpha_channel()) { + // FIXME: Just because the window has an alpha channel doesn't mean it's not opaque. + // Maybe there's some way we could know this? + continue; + } if (outer_window_rect(window->rect()).contains(r)) return true; } @@ -640,9 +647,8 @@ void WSWindowManager::compose() }; for (auto& dirty_rect : dirty_rects) { - if (any_window_contains_rect(dirty_rect)) { + if (any_opaque_window_contains_rect(dirty_rect)) continue; - } if (!m_wallpaper) m_back_painter->fill_rect(dirty_rect, m_background_color); else @@ -665,7 +671,10 @@ void WSWindowManager::compose() dirty_rect_in_window_coordinates.set_y(dirty_rect_in_window_coordinates.y() - window.y()); auto dst = window.position(); dst.move_by(dirty_rect_in_window_coordinates.location()); - m_back_painter->blit(dst, *backing, dirty_rect_in_window_coordinates); + if (window.opacity() == 1.0f) + m_back_painter->blit(dst, *backing, dirty_rect_in_window_coordinates); + else + m_back_painter->blit_with_opacity(dst, *backing, dirty_rect_in_window_coordinates, window.opacity()); m_back_painter->clear_clip_rect(); } m_back_painter->clear_clip_rect();