mirror of
https://github.com/phil-opp/blog_os.git
synced 2025-12-16 22:37:49 +00:00
Polish section about drawing blue pixels and squares
This commit is contained in:
@@ -144,94 +144,146 @@ We start by creating a new `framebuffer` [module]:
|
|||||||
mod framebuffer;
|
mod framebuffer;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
In the new module, we create basic structs for representing pixel positions and colors:
|
||||||
|
|
||||||
```rust ,hl_lines=3-16
|
```rust ,hl_lines=3-16
|
||||||
// in new kernel/src/framebuffer.rs file
|
// in new kernel/src/framebuffer.rs file
|
||||||
|
|
||||||
|
use bootloader_api::info::FrameBuffer;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct Position {
|
pub struct Position {
|
||||||
pub x: usize,
|
pub x: usize,
|
||||||
pub y: usize,
|
pub y: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct Color {
|
pub struct Color {
|
||||||
pub red: u8,
|
pub red: u8,
|
||||||
pub green: u8,
|
pub green: u8,
|
||||||
pub blue: u8,
|
pub blue: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_pixel_in(framebuffer: &mut FrameBuffer, position: Position, color: Color) {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
TODO explain
|
By marking the sturcts and their fields as `pub`, we make them accessible from the parent `kernel` module.
|
||||||
|
We use the `#[derive]` attribute to implement the [`Debug`], [`Clone`], [`Copy`], [`PartialEq`], and [`Eq`] traits of Rust's core library.
|
||||||
|
These traits allow us to duplicate, compare, and print the structs.
|
||||||
|
|
||||||
```rust ,hl_lines=4-12 14-34
|
[`Debug`]: https://doc.rust-lang.org/stable/core/fmt/trait.Debug.html
|
||||||
|
[`Clone`]: https://doc.rust-lang.org/stable/core/clone/trait.Clone.html
|
||||||
|
[`Copy`]: https://doc.rust-lang.org/stable/core/marker/trait.Copy.html
|
||||||
|
[`PartialEq`]: https://doc.rust-lang.org/stable/core/cmp/trait.PartialEq.html
|
||||||
|
[`Eq`]: https://doc.rust-lang.org/stable/core/cmp/trait.Eq.html
|
||||||
|
|
||||||
|
Next, we create a function for setting a specific pixel in the framebuffer to a given color:
|
||||||
|
|
||||||
|
```rust ,hl_lines=3 5-39
|
||||||
// in new kernel/src/framebuffer.rs file
|
// in new kernel/src/framebuffer.rs file
|
||||||
|
|
||||||
|
use bootloader_api::info::PixelFormat;
|
||||||
|
|
||||||
pub fn set_pixel_in(framebuffer: &mut FrameBuffer, position: Position, color: Color) {
|
pub fn set_pixel_in(framebuffer: &mut FrameBuffer, position: Position, color: Color) {
|
||||||
|
let info = framebuffer.info();
|
||||||
|
|
||||||
// calculate offset to first byte of pixel
|
// calculate offset to first byte of pixel
|
||||||
let byte_offset = {
|
let byte_offset = {
|
||||||
// use stride to calculate pixel offset of target line
|
// use stride to calculate pixel offset of target line
|
||||||
let line_offset = position.y * framebuffer.info.stride;
|
let line_offset = position.y * info.stride;
|
||||||
// add x position to get the absolute pixel offset in buffer
|
// add x position to get the absolute pixel offset in buffer
|
||||||
let pixel_offset = line_offset + position.x;
|
let pixel_offset = line_offset + position.x;
|
||||||
// convert to byte offset
|
// convert to byte offset
|
||||||
pixel_offset * framebuffer.bytes_per_pixel
|
pixel_offset * info.bytes_per_pixel
|
||||||
};
|
};
|
||||||
|
|
||||||
/// set pixel based on color format
|
// set pixel based on color format
|
||||||
match framebuffer.info.pixel_format {
|
let pixel_buffer = &mut framebuffer.buffer_mut()[byte_offset..];
|
||||||
|
match info.pixel_format {
|
||||||
PixelFormat::Rgb => {
|
PixelFormat::Rgb => {
|
||||||
let bytes = &mut framebuffer.buffer_mut()[byte_offset..][..3];
|
pixel_buffer[0] = color.red;
|
||||||
bytes[0] = color.red;
|
pixel_buffer[1] = color.green;
|
||||||
bytes[1] = color.green;
|
pixel_buffer[2] = color.blue;
|
||||||
bytes[2] = color.blue;
|
|
||||||
}
|
}
|
||||||
PixelFormat::Bgr => {
|
PixelFormat::Bgr => {
|
||||||
let bytes = &mut framebuffer.buffer_mut()[byte_offset..][..3];
|
pixel_buffer[0] = color.blue;
|
||||||
bytes[0] = color.blue;
|
pixel_buffer[1] = color.green;
|
||||||
bytes[1] = color.green;
|
pixel_buffer[2] = color.red;
|
||||||
bytes[2] = color.red;
|
|
||||||
}
|
}
|
||||||
PixelFormat::U8 => {
|
PixelFormat::U8 => {
|
||||||
// use a simple average-based grayscale transform
|
// use a simple average-based grayscale transform
|
||||||
let gray = color.red / 3 + color.green / 3 + color.blue / 3;
|
let gray = color.red / 3 + color.green / 3 + color.blue / 3;
|
||||||
framebuffer.buffer_mut()[byte_offset] = gray;
|
pixel_buffer[0] = gray;
|
||||||
}
|
}
|
||||||
other => panic!("unknown pixel format {other:?}"),
|
other => panic!("unknown pixel format {other:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
TODO explain
|
The first step is to calculate the byte offset within the framebuffer slice at which the pixel starts.
|
||||||
|
For this, we first calculate the pixel offset of the line by multiplying the `y` position with the stride of the framebuffer, i.e. its line width plus the line padding.
|
||||||
|
We then add the `x` position to get the absolute index of the pixel.
|
||||||
|
As the framebuffer slice is a byte slice, we need to transform the pixel index to a byte offset by multiplying it with the number of `bytes_per_pixel`.
|
||||||
|
|
||||||
Let's try our new function:
|
[`FrameBuffer::buffer_mut`]: https://docs.rs/bootloader_api/0.11.5/bootloader_api/info/struct.FrameBuffer.html#method.buffer_mut
|
||||||
|
|
||||||
```rust ,hl_lines=5-7
|
The second step is to set the pixel to the desired color.
|
||||||
|
We first use the [`FrameBuffer::buffer_mut`] method to get access to the actual bytes of the framebuffer in form of a slice.
|
||||||
|
Then, we use the slicing operator `[byte_offset..]` to get a sub-slice starting at the `byte_offset` of the target pixel.
|
||||||
|
As the write operation depends on the pixel format, we use a [`match`] statement:
|
||||||
|
|
||||||
|
[`match`]: https://doc.rust-lang.org/stable/std/keyword.match.html
|
||||||
|
|
||||||
|
- For `Rgb` framebuffers, we write three bytes; first red, then green, then blue.
|
||||||
|
- For `Bgr` framebuffers, we also write three bytes, but blue first and red last.
|
||||||
|
- For `U8` framebuffers, we first convert the color to grayscale by taking the average of the three color channels.
|
||||||
|
Note that there are multiple [different ways to convert colors to grayscale], so you can also use different factors here.
|
||||||
|
- For all other framebuffer formats, we [panic] for now.
|
||||||
|
|
||||||
|
[different ways to convert colors to grayscale]: https://www.baeldung.com/cs/convert-rgb-to-grayscale#bd-convert-rgb-to-grayscale
|
||||||
|
[panic]: https://doc.rust-lang.org/stable/core/macro.panic.html
|
||||||
|
|
||||||
|
Let's try to use our new function to write a blue pixel in our `kernel_main` function:
|
||||||
|
|
||||||
|
```rust ,hl_lines=5-11
|
||||||
// in kernel/src/main.rs
|
// in kernel/src/main.rs
|
||||||
|
|
||||||
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
|
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
|
||||||
if let Some(framebuffer) = boot_info.framebuffer.as_mut() {
|
if let Some(framebuffer) = boot_info.framebuffer.as_mut() {
|
||||||
let position = framebuffer::Position { x: 200, y: 100 };
|
let position = framebuffer::Position { x: 20, y: 100 };
|
||||||
let color = framebuffer::Color { red: 0, green: 0, blue: 255 };
|
let color = framebuffer::Color {
|
||||||
set_pixel_in(framebuffer, position, color);
|
red: 0,
|
||||||
|
green: 0,
|
||||||
|
blue: 255,
|
||||||
|
};
|
||||||
|
framebuffer::set_pixel_in(framebuffer, position, color);
|
||||||
}
|
}
|
||||||
loop {}
|
loop {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Of course a single pixel is difficult to see, so let's set a square of 10 pixels:
|
When we run our code in QEMU using `cargo run --bin qemu-bios` (or `--bin qemu-uefi`) and look _very closely_, we can see the blue pixel.
|
||||||
|
It's really difficult to see, so I marked with an arrow below:
|
||||||
|
|
||||||
```rust ,hl_lines=6-11
|

|
||||||
|
|
||||||
|
As this single pixel is too difficult to see, let's draw a filled square of 100x100 pixels instead:
|
||||||
|
|
||||||
|
```rust ,hl_lines=10-18
|
||||||
// in kernel/src/main.rs
|
// in kernel/src/main.rs
|
||||||
|
|
||||||
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
|
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
|
||||||
if let Some(framebuffer) = boot_info.framebuffer.as_mut() {
|
if let Some(framebuffer) = boot_info.framebuffer.as_mut() {
|
||||||
let color = framebuffer::Color { red: 0, green: 0, blue: 255 };
|
let color = framebuffer::Color {
|
||||||
for x in 0..10 {
|
red: 0,
|
||||||
for y in 0..10 {
|
green: 0,
|
||||||
let position = framebuffer::Position { x: 200 + x, y: 100 + y};
|
blue: 255,
|
||||||
set_pixel_in(framebuffer, position, color);
|
};
|
||||||
|
for x in 0..100 {
|
||||||
|
for y in 0..100 {
|
||||||
|
let position = framebuffer::Position {
|
||||||
|
x: 20 + x,
|
||||||
|
y: 100 + y,
|
||||||
|
};
|
||||||
|
framebuffer::set_pixel_in(framebuffer, position, color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,11 +291,23 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Now we modifications more easily: TODO image
|
Now we clearly see that our code works as intended:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Feel free to experiment with different positions and colors if you like.
|
||||||
|
You can also try to draw a circle instead of a square, or a line with a certain thickness.
|
||||||
|
|
||||||
|
As you can probably imagine, it would be a lot of work to draw more complex shapes this way.
|
||||||
|
One example for such complex shapes is _text_, i.e. the rendering of letters and punctuation.
|
||||||
|
Fortunately, there is the nice `no_std`-compatible [`embedded-graphics`] crate, which provides draw functions for text, various shapes, and image data.
|
||||||
|
|
||||||
|
[`embedded-graphics`]: https://docs.rs/embedded-graphics/latest/embedded_graphics/index.html
|
||||||
|
|
||||||
## The `embedded-graphics` crate
|
## The `embedded-graphics` crate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Implementing `DrawTarget`
|
### Implementing `DrawTarget`
|
||||||
|
|
||||||
```rust ,hl_lines=3
|
```rust ,hl_lines=3
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 107 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 100 KiB |
Reference in New Issue
Block a user