From 762dd22e314b20915656b8f10906fa6a71b312d3 Mon Sep 17 00:00:00 2001 From: Benedikt Willi Date: Mon, 12 Jan 2026 17:36:14 +0100 Subject: [PATCH] Implement image loading and rendering support in Bowser browser This commit introduces support for loading and rendering images in the Bowser web browser, enhancing the rendering engine to handle various image sources. Key changes include: - Updated README.md to reflect the new milestone status and added features for image support. - Added `ImageLayout` class to manage image layout and loading. - Implemented synchronous and asynchronous image loading in `src/network/images.py`, including caching mechanisms. - Expanded the DOM parsing capabilities in `DocumentLayout` to handle `` tags and manage their layout directives. - Created a new `DrawImage` command in the rendering pipeline, which handles the drawing of both loaded images and placeholders for unloaded images. - Introduced a task queue for managing asynchronous image loads, ensuring UI remains responsive during image fetching. - Added unit tests for image loading, layout management, and the async task queue to ensure robust functionality and prevent regressions. --- README.md | 85 +++++++- assets/pages/startpage.html | 2 + src/browser/chrome.py | 23 ++- src/layout/document.py | 109 +++++++++- src/layout/embed.py | 213 +++++++++++++++++++- src/network/images.py | 392 ++++++++++++++++++++++++++++++++++++ src/network/tasks.py | 174 ++++++++++++++++ src/render/paint.py | 71 +++++++ src/render/pipeline.py | 63 +++++- tests/test_images.py | 384 +++++++++++++++++++++++++++++++++++ tests/test_tasks.py | 234 +++++++++++++++++++++ 11 files changed, 1726 insertions(+), 24 deletions(-) create mode 100644 src/network/images.py create mode 100644 src/network/tasks.py create mode 100644 tests/test_images.py create mode 100644 tests/test_tasks.py diff --git a/README.md b/README.md index 47b3b6c..f25c31e 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,14 @@ A custom web browser built from scratch following the [browser.engineering](https://browser.engineering/) curriculum. Features a clean architecture with Skia-based rendering, GTK 4/Adwaita UI, and proper separation of concerns. -**Status**: Milestone 2 - Basic HTML rendering with text layout +**Status**: Milestone 3 - Basic HTML rendering with text layout and image support ## Features - **Adwaita Tab Bar** - Modern GNOME-style tab management - **Skia Rendering** - Hardware-accelerated 2D graphics - **Text Layout** - Word wrapping, character-level selection +- **Image Support** - Load and render images from HTTP, data URLs, and local files - **DOM Parsing** - HTML parsing with proper tree structure - **Debug Mode** - Visual layout debugging with FPS counter - **DOM Visualization** - Generate visual graphs of page structure @@ -54,18 +55,21 @@ bowser/ │ ├── layout/ # Layout calculation │ │ ├── document.py # DocumentLayout - full page layout │ │ ├── block.py # BlockLayout, LineLayout - block elements -│ │ └── inline.py # TextLayout, InlineLayout - text runs +│ │ ├── inline.py # TextLayout, InlineLayout - text runs +│ │ └── embed.py # ImageLayout - embedded content │ │ │ ├── render/ # Painting & rendering │ │ ├── pipeline.py # RenderPipeline - coordinates layout/paint │ │ ├── fonts.py # FontCache - Skia font management -│ │ ├── paint.py # DisplayList, DrawText, DrawRect +│ │ ├── paint.py # DisplayList, DrawText, DrawRect, DrawImage │ │ └── composite.py # Layer compositing │ │ │ ├── network/ # Networking │ │ ├── http.py # HTTP client with redirects │ │ ├── url.py # URL parsing and normalization -│ │ └── cookies.py # Cookie management +│ │ ├── cookies.py # Cookie management +│ │ ├── images.py # Image loading and caching +│ │ └── tasks.py # Async task queue for background loading │ │ │ ├── debug/ # Development tools │ │ └── dom_graph.py # DOM tree visualization @@ -91,8 +95,11 @@ bowser/ | `Element`, `Text` | parser | DOM tree nodes | | `DocumentLayout` | layout | Page layout with line positioning | | `LayoutLine`, `LayoutBlock` | layout | Positioned text with bounding boxes | +| `ImageLayout`, `LayoutImage` | layout | Image sizing and positioning | | `RenderPipeline` | render | Coordinates layout → paint | +| `DrawImage` | render | Image rendering command | | `FontCache` | render | Skia font caching | +| `ImageCache` | network | Image loading and caching | | `Chrome` | browser | GTK window, delegates to RenderPipeline | ## Development @@ -141,12 +148,70 @@ Shows: - [x] **M0**: Project scaffold - [x] **M1**: GTK window with Skia rendering - [x] **M2**: HTML parsing and text layout -- [ ] **M3**: CSS parsing and styling -- [ ] **M4**: Clickable links and navigation -- [ ] **M5**: Form input and submission -- [ ] **M6**: JavaScript execution -- [ ] **M7**: Event handling -- [ ] **M8**: Images and iframes +- [x] **M3**: Image loading and rendering +- [ ] **M4**: CSS parsing and styling +- [ ] **M5**: Clickable links and navigation +- [ ] **M6**: Form input and submission +- [ ] **M7**: JavaScript execution +- [ ] **M8**: Event handling + +## Image Support + +Bowser supports loading and rendering images from multiple sources: + +### Supported Sources + +- **HTTP/HTTPS URLs**: `` +- **Data URLs**: `` +- **Local files**: `` + +### Features + +- **Async loading**: Images load in background threads, keeping UI responsive +- **Smart sizing**: Respects width/height attributes, maintains aspect ratios +- **Caching**: Thread-safe global image cache prevents redundant loads +- **Alt text placeholders**: Shows placeholder with alt text when images fail +- **Format support**: PNG, JPEG, GIF, WebP, and more (via Skia) +- **Viewport culling**: Only renders visible images for performance +- **Progressive display**: Page shows immediately, images appear as they load + +### Example + +```html + + + + + + + +A beautiful sunset + + + +``` + +### Architecture + +``` +HTML tag + ↓ +ImageLayout.load(async=True) + ↓ +TaskQueue (background thread pool) + ↓ +load_image() → HTTP/file/data URL + ↓ ↓ +ImageCache GLib.idle_add +(thread-safe) ↓ + on_complete callback + ↓ + ImageLayout.image = loaded + ↓ + RenderPipeline._request_redraw() + ↓ + DrawImage.execute() → Canvas +``` ## References diff --git a/assets/pages/startpage.html b/assets/pages/startpage.html index f53b366..6f13efe 100644 --- a/assets/pages/startpage.html +++ b/assets/pages/startpage.html @@ -98,6 +98,8 @@
  • Incremental feature development
  • + + Bowser Logo