diff --git a/CHANGELOG.md b/CHANGELOG.md
index cc69b4e5733251e22e9d201891d29bbc9bf0f56b..0bb0627ccfb530d355b92a2a55717515bbfef075 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Added
 
 * Support for serial data transfer - [#19](https://gitlab.stage.hive.pt/joamag/boytacean/-/issues/19)
+* Support for printing of images using Printer emulation - [#19](https://gitlab.stage.hive.pt/joamag/boytacean/-/issues/19)
 
 ### Changed
 
diff --git a/README.md b/README.md
index 5506448669d561bbf8d6f9487df007f96683e5ef..e0b5316e569c16368e3752f378ab355ee2143cdc 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,8 @@ A Game Boy emulator that is written in Rust 🦀.
 * Simple navigable source-code
 * Web and SDL front-ends
 * Audio, with a pretty accurate APU
+* Serial Data Transfer (Link Cable) support
+* Game Boy Printer emulation
 * Support for multiple MBCs: MBC1, MBC2, MBC3, and MBC5
 * Variable CPU clock speed
 * Accurate PPU - passes [dmg-acid2](https://github.com/mattcurrie/dmg-acid2) tests
diff --git a/frontends/sdl/Cargo.toml b/frontends/sdl/Cargo.toml
index 70fabd1dbd1e4ac50407cfc1a8e4ea616568adc4..5a68376361c2cd4fe2ec12e9279e6ab2f44b97de 100644
--- a/frontends/sdl/Cargo.toml
+++ b/frontends/sdl/Cargo.toml
@@ -13,6 +13,12 @@ debug = ["boytacean/debug"]
 [dependencies.boytacean]
 path = "../.."
 
+[dependencies.image]
+version = "0.24"
+
+[dependencies.chrono]
+version = "0.4"
+
 [dependencies.sdl2]
 version = "0.35"
 features = ["ttf", "image", "gfx", "mixer", "static-link", "use-vcpkg"]
diff --git a/frontends/sdl/src/main.rs b/frontends/sdl/src/main.rs
index e9ae117a50e53277ea1c43758de58ad804fc48cd..f6d1fc838e0a703f84ea7d6f80e4d2f0b564db8c 100644
--- a/frontends/sdl/src/main.rs
+++ b/frontends/sdl/src/main.rs
@@ -6,13 +6,16 @@ pub mod graphics;
 
 use audio::Audio;
 use boytacean::{
+    devices::printer::PrinterDevice,
     gb::{AudioProvider, GameBoy},
     pad::PadKey,
     ppu::{PaletteInfo, PpuMode, DISPLAY_HEIGHT, DISPLAY_WIDTH},
 };
+use chrono::Utc;
 use graphics::{surface_from_bytes, Graphics};
+use image::ColorType;
 use sdl2::{event::Event, keyboard::Keycode, pixels::PixelFormatEnum, Sdl};
-use std::{cmp::max, time::SystemTime};
+use std::{cmp::max, path::Path, time::SystemTime};
 
 /// The scale at which the screen is going to be drawn
 /// meaning the ratio between Game Boy resolution and
@@ -444,7 +447,19 @@ fn main() {
     // creates a new Game Boy instance and loads both the boot ROM
     // and the initial game ROM to "start the engine"
     let mut game_boy = GameBoy::new();
-    game_boy.attach_printer_serial();
+    let mut printer = Box::<PrinterDevice>::default();
+    printer.set_callback(|image_buffer| {
+        let file_name = format!("printer-{}.png", Utc::now().format("%Y%m%d-%H%M%S"));
+        image::save_buffer(
+            Path::new(&file_name),
+            image_buffer,
+            160,
+            (image_buffer.len() / 4 / 160) as u32,
+            ColorType::Rgba8,
+        )
+        .unwrap();
+    });
+    game_boy.attach_serial(printer);
     game_boy.load_boot_default();
 
     // creates a new generic emulator structure then starts
diff --git a/src/devices/printer.rs b/src/devices/printer.rs
index c262fc43efef5ac510fc3c7e6ea8c3b465ad6478..6dde7f6edbbe3b1bfbc275b40dbeb3b5503bf3f6 100644
--- a/src/devices/printer.rs
+++ b/src/devices/printer.rs
@@ -1,6 +1,15 @@
-use std::fmt::{self, Display, Formatter};
+use std::{
+    fmt::{self, Display, Formatter}
+};
 
-use crate::{serial::SerialDevice, warnln};
+use crate::{ppu::PaletteAlpha, serial::SerialDevice, warnln};
+
+const PRINTER_PALETTE: PaletteAlpha = [
+    [0xff, 0xff, 0xff, 0xff],
+    [0xaa, 0xaa, 0xaa, 0xff],
+    [0x55, 0x55, 0x55, 0xff],
+    [0x00, 0x00, 0x00, 0xff],
+];
 
 #[derive(Clone, Copy, PartialEq, Eq)]
 enum PrinterState {
@@ -63,7 +72,7 @@ impl Display for PrinterState {
 #[derive(Clone, Copy, PartialEq, Eq)]
 enum PrinterCommand {
     Init = 0x01,
-    Start = 0x02,
+    Print = 0x02,
     Data = 0x04,
     Status = 0x0f,
     Other = 0xff,
@@ -73,7 +82,7 @@ impl PrinterCommand {
     pub fn description(&self) -> &'static str {
         match self {
             PrinterCommand::Init => "Init",
-            PrinterCommand::Start => "Start",
+            PrinterCommand::Print => "Print",
             PrinterCommand::Data => "Data",
             PrinterCommand::Status => "Status",
             PrinterCommand::Other => "Other",
@@ -83,7 +92,7 @@ impl PrinterCommand {
     fn from_u8(value: u8) -> Self {
         match value {
             0x01 => PrinterCommand::Init,
-            0x02 => PrinterCommand::Start,
+            0x02 => PrinterCommand::Print,
             0x04 => PrinterCommand::Data,
             0x0f => PrinterCommand::Status,
             _ => PrinterCommand::Other,
@@ -107,6 +116,10 @@ pub struct PrinterDevice {
     status: u8,
     byte_out: u8,
     data: [u8; 0x280],
+    image: [u8; 160 * 200],
+    image_offset: u16,
+    image_buffer: Vec<u8>,
+    callback: fn(image_buffer: &Vec<u8>)
 }
 
 impl PrinterDevice {
@@ -121,6 +134,10 @@ impl PrinterDevice {
             status: 0x0,
             byte_out: 0x0,
             data: [0x00; 0x280],
+            image: [0x00; 160 * 200],
+            image_offset: 0,
+            image_buffer: Vec::new(),
+            callback: |_| {}
         }
     }
 
@@ -133,7 +150,27 @@ impl PrinterDevice {
         self.checksum = 0x0;
         self.status = 0x0;
         self.byte_out = 0x0;
-        self.data = [0x00; 0x280]
+        self.data = [0x00; 0x280];
+        self.image = [0x00; 160 * 200];
+        self.image_offset = 0;
+        
+        self.clear_image_buffer()
+    }
+
+    pub fn set_callback(&mut self, callback: fn(image_buffer: &Vec<u8>)) {
+        self.callback = callback;
+    }
+
+    pub fn image_buffer(&self) -> &Vec<u8> {
+        &self.image_buffer
+    }
+
+    pub fn image_buffer_mut(&mut self) -> &mut Vec<u8> {
+        &mut self.image_buffer
+    }
+
+    pub fn clear_image_buffer(&mut self) {
+        self.image_buffer.clear();
     }
 
     fn run_command(&mut self, command: PrinterCommand) {
@@ -141,14 +178,30 @@ impl PrinterDevice {
             PrinterCommand::Init => {
                 self.status = 0x00;
                 self.byte_out = self.status;
+                self.image_offset = 0;
+                self.image_buffer.clear();
             }
-            PrinterCommand::Start => {
+            PrinterCommand::Print => {
+                let palette_index = self.data[2];
+
+                for index in 0..self.image_offset {
+                    let value = self.image[index as usize];
+                    let pixel_offset = (palette_index >> (value << 1)) & 0x03;
+                    let pixel = PRINTER_PALETTE[pixel_offset as usize];
+                    self.image_buffer.push(pixel[0]);
+                    self.image_buffer.push(pixel[1]);
+                    self.image_buffer.push(pixel[2]);
+                    self.image_buffer.push(pixel[3]);
+                }
+
+                (self.callback)(&self.image_buffer);
+
                 self.byte_out = self.status;
                 self.status = 0x06;
             }
             PrinterCommand::Data => {
                 if self.command_length == 0x280 {
-                    println!("Printer: Going to copy the image for printing");
+                    self.flush_image();
                 }
                 // in case the command is of size 0 we assume this is
                 // an EOF and we ignore this data operation
@@ -166,7 +219,8 @@ impl PrinterDevice {
                 self.byte_out = self.status;
 
                 // in case the current status is printing let's
-                // mark it as done
+                // mark it as done, resetting the status back to
+                // the original value
                 if self.status == 0x06 {
                     // @TODO: check if this value should be 0x04 instead
                     // this seems to be a bug with the print demo
@@ -178,6 +232,34 @@ impl PrinterDevice {
             }
         }
     }
+
+    fn flush_image(&mut self) {
+        // sets the initial value of the index that will point to
+        // the data that is going to be copied to the image buffer
+        let mut index = 0;
+
+        // iterates over the two rows that are going to be printed
+        // keep in mind that the printer only allows 2 lines at each
+        // time to be printed
+        for _ in 0..2 {
+            for col in 0..20 {
+                for y in 0..8 {
+                    let mut first = self.data[index];
+                    let mut second = self.data[index + 1];
+                    for x in 0..8 {
+                        let offset = self.image_offset as usize + (col * 8) + (y * 160) + x;
+                        self.image[offset] = (first >> 7) | ((second >> 6) & 0x02);
+
+                        first <<= 1;
+                        second <<= 1;
+                    }
+                    index += 2
+                }
+            }
+
+            self.image_offset += 160 * 8;
+        }
+    }
 }
 
 impl SerialDevice for PrinterDevice {
diff --git a/src/gb.rs b/src/gb.rs
index e107c389a5820a06f68ced464798a07410728a7e..0a7880f7da907de19684f3a02205c5570e6ae245 100644
--- a/src/gb.rs
+++ b/src/gb.rs
@@ -8,7 +8,7 @@ use crate::{
     pad::{Pad, PadKey},
     ppu::{Ppu, PpuMode, Tile, FRAME_BUFFER_SIZE},
     rom::Cartridge,
-    serial::Serial,
+    serial::{Serial, SerialDevice},
     timer::Timer,
     util::read_file,
 };
@@ -358,12 +358,16 @@ impl GameBoy {
         self.apu().set_clock_freq(value);
     }
 
+    pub fn attach_serial(&mut self, device: Box<dyn SerialDevice>) {
+        self.serial().set_device(device);
+    }
+
     pub fn attach_stdout_serial(&mut self) {
-        self.serial().set_device(Box::<StdoutDevice>::default());
+        self.attach_serial(Box::<StdoutDevice>::default());
     }
 
     pub fn attach_printer_serial(&mut self) {
-        self.serial().set_device(Box::<PrinterDevice>::default());
+        self.attach_serial(Box::<PrinterDevice>::default());
     }
 }