diff --git a/blog/content/edition-3/posts/03-screen-output/index.md b/blog/content/edition-3/posts/03-screen-output/index.md
index 83a9ed08..127134f8 100644
--- a/blog/content/edition-3/posts/03-screen-output/index.md
+++ b/blog/content/edition-3/posts/03-screen-output/index.md
@@ -41,7 +41,7 @@ Using the [`BootInfo`] provided by the bootloader, we were able to access a spec
We wrote some example code to display a gray background:
[previous post]: @/edition-3/posts/02-booting/index.md
-[`BootInfo`]: https://docs.rs/bootloader_api/latest/bootloader_api/info/struct.BootInfo.html
+[`BootInfo`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/struct.BootInfo.html
```rust
// in kernel/src/main.rs
@@ -65,22 +65,229 @@ The pixels are laid out line by line, typically starting at the top.
For example, the pixels of an image with width 10 and height 3 would be typically stored in this order:
-
+
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
So top left pixel is stored at offset 0 in the bitmap array.
-The pixel on its right is at offset `pixel_size`.
-The first pixel of the next line starts at offset `line_length * pixel_size`.
+The pixel on its right is at pixel offset 1.
+The first pixel of the next line starts at pixel offset `line_length`, which is 10 in this case.
+The last line starts at pixel offset 20, which is `line_length * 2`.
### Padding
-Depending on the hardware and GPU firmware, it is often more efficient
+Depending on the hardware and GPU firmware, it is often more efficient to make lines start at well-aligned offsets.
+Because of this, there is often some additional padding at the end of each line.
+So the actual memory layout of the 10x3 example image might look like this, with the padding marked as yellow:
+
+
+ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
+ | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
+ | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
+
+
+So now the second line starts at pixel offset 12.
+The two pixels at the end of each line are considered as padding and ignored.
+So if we want to set the first pixel of the second line, we need to be aware of the additional padding and set the pixel at offset 12 instead of offset 10.
+
+The line length plus the padding bytes is typically called the _stride_ or _pitch_ of the buffer.
+In the example above, the stride is 12 and the line length is 10.
+
+Since the amount of padding depends on the hardware, the stride is only known at runtime.
+The `bootloader` crate queries the framebuffer parameters from the UEFI or BIOS firmware and reports them as part of the `BootInfo`.
+It provides the stride of the framebuffer, among other parameters, in form of a [`FrameBufferInfo`] struct that can be created using the [`FrameBuffer::info`] method.
+
+[`FrameBufferInfo`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/struct.FrameBufferInfo.html
+[`FrameBuffer::info`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/struct.FrameBuffer.html#method.info
### Color formats
+The [`FrameBufferInfo`] also specifies the [`PixelFormat`] of the framebuffer, which also depends on the underlying hardware.
+Using this information, we can set pixels to different colors.
+For example, the [`PixelFormat::Rgb`] variant specifies that each pixel is represented in the [RGB color space], which stores the red, green, and blue parts of the pixel as separate bytes.
+In this model, the color red would be represented as the three bytes `[255, 0, 0]`, or `0xff0000` in [hexadecimal representation].
+The color yellow is represented the addition of red and green, which results in `[255, 255, 0]` (or `0xffff00` in hexadecimal representation).
+
+[`PixelFormat`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/enum.PixelFormat.html
+[`PixelFormat::Rgb`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/enum.PixelFormat.html#variant.Rgb
+[RGB color space]: https://en.wikipedia.org/wiki/RGB_color_spaces
+[hexadecimal representation]: https://en.wikipedia.org/wiki/RGB_color_model#Numeric_representations
+
+While the `Rgb` format is most common, there are also framebuffers that use a different color format.
+For example, the [`PixelFormat::Bgr`] stores the three colors in inverted order, i.e. blue first and red last.
+There are also buffers that don't support colors at all and can represent only grayscale pixels.
+The `bootloader_api` crate reports such buffers as [`PixelFormat::U8`].
+
+[`PixelFormat::Bgr`]: https://docs.rs/bootloader_api/0.11.5/bootloader_api/info/enum.PixelFormat.html#variant.Bgr
+[`PixelFormat::U8`]: https://docs.rs/bootloader_api/0.11.5/bootloader_api/info/enum.PixelFormat.html#variant.U8
+
+Note that there might be some additional padding at the pixel-level as well.
+For example, an `Rgb` pixel might be stored as 4 bytes instead of 3 to ensure 32-bit alignment.
+The number of bytes per pixel is reported by the bootloader in the [`FrameBufferInfo::bytes_per_pixel`] field.
+
+[`FrameBufferInfo::bytes_per_pixel`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/struct.FrameBufferInfo.html#structfield.bytes_per_pixel
+
+## Setting specific Pixels
+
+Based on this above details, we can now create a function to set a specific pixel to a certain color.
+We start by creating a new `framebuffer` [module]:
+
+[module]: https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html
+
+```rust ,hl_lines=3-5
+// in kernel/src/main.rs
+
+// declare a submodule -> the compiler will automatically look
+// for a file named `framebuffer.rs` or `framebuffer/mod.rs`
+mod framebuffer;
+```
+
+```rust ,hl_lines=3-16
+// in new kernel/src/framebuffer.rs file
+
+pub struct Position {
+ pub x: usize,
+ pub y: usize,
+}
+
+pub struct Color {
+ pub red: u8,
+ pub green: u8,
+ pub blue: u8,
+}
+
+pub fn set_pixel_in(framebuffer: &mut FrameBuffer, position: Position, color: Color) {
+ todo!()
+}
+```
+
+TODO explain
+
+```rust ,hl_lines=4-12 14-34
+// in new kernel/src/framebuffer.rs file
+
+pub fn set_pixel_in(framebuffer: &mut FrameBuffer, position: Position, color: Color) {
+ // calculate offset to first byte of pixel
+ let byte_offset = {
+ // use stride to calculate pixel offset of target line
+ let line_offset = position.y * framebuffer.info.stride;
+ // add x position to get the absolute pixel offset in buffer
+ let pixel_offset = line_offset + position.x;
+ // convert to byte offset
+ pixel_offset * framebuffer.bytes_per_pixel
+ };
+
+ /// set pixel based on color format
+ match framebuffer.info.pixel_format {
+ PixelFormat::Rgb => {
+ let bytes = &mut framebuffer.buffer_mut()[byte_offset..][..3];
+ bytes[0] = color.red;
+ bytes[1] = color.green;
+ bytes[2] = color.blue;
+ }
+ PixelFormat::Bgr => {
+ let bytes = &mut framebuffer.buffer_mut()[byte_offset..][..3];
+ bytes[0] = color.blue;
+ bytes[1] = color.green;
+ bytes[2] = color.red;
+ }
+ PixelFormat::U8 => {
+ // use a simple average-based grayscale transform
+ let gray = color.red / 3 + color.green / 3 + color.blue / 3;
+ framebuffer.buffer_mut()[byte_offset] = gray;
+ }
+ other => panic!("unknown pixel format {other:?}"),
+ }
+}
+```
+
+TODO explain
+
+Let's try our new function:
+
+```rust ,hl_lines=5-7
+// in kernel/src/main.rs
+
+fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
+ if let Some(framebuffer) = boot_info.framebuffer.as_mut() {
+ let position = framebuffer::Position { x: 200, y: 100 };
+ let color = framebuffer::Color { red: 0, green: 0, blue: 255 };
+ set_pixel_in(framebuffer, position, color);
+ }
+ loop {}
+}
+```
+
+Of course a single pixel is difficult to see, so let's set a square of 10 pixels:
+
+```rust ,hl_lines=6-11
+// in kernel/src/main.rs
+
+fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
+ if let Some(framebuffer) = boot_info.framebuffer.as_mut() {
+ let color = framebuffer::Color { red: 0, green: 0, blue: 255 };
+ for x in 0..10 {
+ for y in 0..10 {
+ let position = framebuffer::Position { x: 200 + x, y: 100 + y};
+ set_pixel_in(framebuffer, position, color);
+ }
+ }
+ }
+ loop {}
+}
+```
+
+Now we modifications more easily: TODO image
+
+## The `embedded-graphics` crate
+
+
+### Implementing `DrawTarget`
+
+```rust ,hl_lines=3
+// in kernel/src/framebuffer.rs
+
+pub struct Display {
+ framebuffer: Framebuffer,
+}
+
+impl Display {
+ pub fn new(framebuffer: Framebuffer) -> Display {
+ Self { framebuffer }
+ }
+
+ fn draw_pixel(&mut self, pixel: Pixel) {
+ // ignore any pixels that are out of bounds.
+ let (width, height) = {
+ let info = self.framebuffer.info();
+ (info.width, info.height)
+ }
+ if let Ok((x @ 0..width, y @ 0..height)) = coordinates.try_into() {
+ let color = Color { red: color.r(), green: color.g(), blue: color.b()};
+ set_pixel_in(&mut self.framebuffer, Position { x, y }, color);
+ }
+ }
+}
+
+impl embedded_graphics::draw_target::DrawTarget for Display {
+ type Color = embedded_graphics::pixelcolor::Rgb888;
+
+ /// Drawing operations can never fail.
+ type Error = core::convert::Infallible;
+
+ fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error>
+ where
+ I: IntoIterator- >,
+ {
+ for Pixel(coordinates, color) in pixels.into_iter() {
+ self.draw_pixel(pixel);
+ }
+ Ok(())
+ }
+}
+```
---