diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm
index 3466ec36e95..a9a04a4f621 100644
--- a/code/__HELPERS/icons.dm
+++ b/code/__HELPERS/icons.dm
@@ -699,7 +699,7 @@ proc // Creates a single icon from a given /atom or /image. Only the first argu
while(TRUE)
if(curIndex<=process.len)
current = process[curIndex]
- if(!current)
+ if(!current)
curIndex++ //Skip this bad layer item
continue
currentLayer = current:layer
@@ -857,3 +857,26 @@ proc/sort_atoms_by_layer(var/list/atoms)
result.Swap(i, gap + i)
swapped = 1
return result
+
+//Interface for using DrawBox() to draw 1 pixel on a coordinate.
+//Returns the same icon specifed in the argument, but with the pixel drawn
+/proc/DrawPixel(var/icon/I,var/colour,var/drawX,var/drawY)
+ if(!I)
+ return 0
+ var/Iwidth = I.Width()
+ var/Iheight = I.Height()
+ if(drawX > Iwidth || drawX <= 0)
+ return 0
+ if(drawY > Iheight || drawY <= 0)
+ return 0
+ I.DrawBox(colour,drawX, drawY)
+ return I
+
+//Interface for easy drawing of one pixel on an atom.
+/atom/proc/DrawPixelOn(var/colour, var/drawX, var/drawY)
+ var/icon/I = new(icon)
+ var/icon/J = DrawPixel(I, colour, drawX, drawY)
+ if(J) //Only set the icon if it succeeded, the icon without the pixel is 1000x better than a black square.
+ icon = J
+ return J
+ return 0
\ No newline at end of file
diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm
index 30ac106c1b3..3f7190e90e2 100644
--- a/code/game/objects/items/stacks/sheets/sheet_types.dm
+++ b/code/game/objects/items/stacks/sheets/sheet_types.dm
@@ -155,6 +155,7 @@ var/global/list/datum/stack_recipe/wood_recipes = list ( \
new/datum/stack_recipe("wooden door", /obj/structure/mineral_door/wood, 10, time = 20, one_per_turf = 1, on_floor = 1), \
new/datum/stack_recipe("coffin", /obj/structure/closet/coffin, 5, time = 15, one_per_turf = 1, on_floor = 1), \
new/datum/stack_recipe("apiary", /obj/item/apiary, 10, time = 25, one_per_turf = 0, on_floor = 0), \
+ new/datum/stack_recipe("easel", /obj/structure/easel, 3, one_per_turf = 1, on_floor = 1), \
)
/obj/item/stack/sheet/wood
diff --git a/code/game/objects/structures/artstuff.dm b/code/game/objects/structures/artstuff.dm
new file mode 100644
index 00000000000..64f09d7e803
--- /dev/null
+++ b/code/game/objects/structures/artstuff.dm
@@ -0,0 +1,151 @@
+/////////////////
+// NEW DEFINES //
+/////////////////
+
+/client
+ var/viewingCanvas = 0 //If this is 1, as soon as client /TRIES/ to move the view resets.
+
+///////////
+// EASEL //
+///////////
+
+/obj/structure/easel
+ name = "easel"
+ desc = "only for the finest of art!"
+ icon = 'icons/obj/artstuff.dmi'
+ icon_state = "easel"
+ density = 1
+ var/obj/item/weapon/canvas/painting = null
+
+
+//Adding canvases
+/obj/structure/easel/attackby(var/obj/item/I, var/mob/user, params)
+ if(istype(I, /obj/item/weapon/canvas))
+ var/obj/item/weapon/canvas/C = I
+ user.unEquip(C)
+ painting = C
+ C.loc = get_turf(src)
+ C.layer = layer+0.1
+ user.visible_message("[user] puts \the [C] on \the [src].","You place \the [C] on \the [src].")
+ return
+
+ ..()
+
+
+//Stick to the easel like glue
+/obj/structure/easel/Move()
+ var/turf/T = get_turf(src)
+ ..()
+ if(painting && painting.loc == T) //Only move if it's near us.
+ painting.loc = get_turf(src)
+ else
+ painting = null
+
+
+//////////////
+// CANVASES //
+//////////////
+
+#define AMT_OF_CANVASES 4 //Keep this up to date or shit will break.
+
+//To safe memory on making /icons we cache the blanks..
+var/global/list/globalBlankCanvases[AMT_OF_CANVASES]
+
+/obj/item/weapon/canvas
+ name = "11px by 11px canvas"
+ desc = "Draw out your soul on this canvas! Only crayons can draw on it. Examine it to focus on the canvas."
+ icon = 'icons/obj/artstuff.dmi'
+ icon_state = "11x11"
+ var/whichGlobalBackup = 1 //List index
+
+/obj/item/weapon/canvas/nineteenXnineteen
+ name = "19px by 19px canvas"
+ icon_state = "19x19"
+ whichGlobalBackup = 2
+
+/obj/item/weapon/canvas/twentythreeXnineteen
+ name = "23px by 19px canvas"
+ icon_state = "23x19"
+ whichGlobalBackup = 3
+
+/obj/item/weapon/canvas/twentythreeXtwentythree
+ name = "23px by 23px canvas"
+ icon_state = "23x23"
+ whichGlobalBackup = 4
+
+
+//Find the right size blank canvas
+/obj/item/weapon/canvas/proc/getGlobalBackup()
+ . = null
+ if(globalBlankCanvases[whichGlobalBackup])
+ . = globalBlankCanvases[whichGlobalBackup]
+ else
+ var/icon/I = icon(initial(icon),initial(icon_state))
+ globalBlankCanvases[whichGlobalBackup] = I
+ . = I
+
+
+
+//One pixel increments
+/obj/item/weapon/canvas/attackby(var/obj/item/I, var/mob/user, params)
+ //Click info
+ var/list/click_params = params2list(params)
+ var/pixX = text2num(click_params["icon-x"])
+ var/pixY = text2num(click_params["icon-y"])
+
+ //Should always be true, otherwise you didn't click the object, but let's check because SS13~
+ if(!click_params || !click_params["icon-x"] || !click_params["icon-y"])
+ return
+
+ var/icon/masterpiece = icon(icon,icon_state)
+ //Cleaning one pixel with a soap or rag
+ if(istype(I, /obj/item/weapon/soap) || istype(I, /obj/item/weapon/reagent_containers/glass/rag))
+ //Pixel info created only when needed
+ var/thePix = masterpiece.GetPixel(pixX,pixY)
+ var/icon/Ico = getGlobalBackup()
+ if(!Ico)
+ qdel(masterpiece)
+ return
+
+ var/theOriginalPix = Ico.GetPixel(pixX,pixY)
+ if(thePix != theOriginalPix) //colour changed
+ DrawPixelOn(theOriginalPix,pixX,pixY)
+ qdel(masterpiece)
+ return
+
+ //Drawing one pixel with a crayon
+ if(istype(I, /obj/item/toy/crayon))
+ var/obj/item/toy/crayon/C = I
+ if(masterpiece.GetPixel(pixX, pixY)) // if the located pixel isn't blank (null))
+ DrawPixelOn(C.colour, pixX, pixY)
+ return
+
+ ..()
+
+//Clean the whole canvas
+/obj/item/weapon/canvas/attack_self(var/mob/user)
+ if(!user)
+ return
+ var/icon/blank = getGlobalBackup()
+ if(blank)
+ //it's basically a giant etch-a-sketch
+ icon = blank
+ user.visible_message("[user] cleans the canvas.","You clean the canvas.")
+
+//Examine to enlarge
+/obj/item/weapon/canvas/examine(var/mob/user = usr)
+ ..()
+ if(in_range(user, src) && get_turf(src) && user.client && ishuman(user)) //Let only humans be the robust zoominators. I'm too spooked other mobs trying to use it may get broken huds.
+ if(src.loc == user || get_turf(src) == get_turf(user))
+ user << "[src] has to be on the ground to focus on it!"
+ return
+ user << "You focus on \the [src]."
+ user.client.screen = list() //This is because screen objects go way past the view bounds we set, therefore not allowing stretch to fit to zoom in properly.
+ user.client.reset_stretch = winget(user.client, "mapwindow.map", "icon-size") //Remember previous icon-size
+ user.client.view = 3 //Decrease view
+ winset(user.client, "mapwindow.map", "icon-size=0") //Enable stretch-to-fit
+ user.client.viewingCanvas = 1 //Reset everything we just changed as soon as client tries to move
+ else
+ user << "It is too far away."
+
+#undef AMT_OF_CANVASES
\ No newline at end of file
diff --git a/code/modules/client/client defines.dm b/code/modules/client/client defines.dm
index 0dfa00ca8cf..cbfa50f0759 100644
--- a/code/modules/client/client defines.dm
+++ b/code/modules/client/client defines.dm
@@ -70,4 +70,6 @@
"AZERTY" = list(
"on" = "AZERTYon",
"off" = "AZERTYoff")
- )
\ No newline at end of file
+ )
+
+ var/reset_stretch = 0 //Used by things that fiddle with client's stretch-to-fit.
\ No newline at end of file
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index 5afda13a9be..0407a0edc07 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -176,6 +176,17 @@
/client/Move(n, direct)
+ if(viewingCanvas)
+ view = world.view //Reset the view
+ winset(src, "mapwindow.map", "icon-size=[src.reset_stretch]")
+ viewingCanvas = 0
+ mob.reset_view()
+ mob.button_pressed_F12()
+ if(!mob.hud_used.hud_shown)
+ mob.button_pressed_F12()
+ mob.update_hud()
+ mob.update_action_buttons()
+
if(mob.control_object) Move_object(direct)
if(world.time < move_delay) return
diff --git a/code/modules/research/designs/autolathe_designs.dm b/code/modules/research/designs/autolathe_designs.dm
index 970f0b139f9..d58d23f09cf 100644
--- a/code/modules/research/designs/autolathe_designs.dm
+++ b/code/modules/research/designs/autolathe_designs.dm
@@ -226,6 +226,38 @@
build_path = /obj/item/device/assembly/voice
category = list("initial", "Miscellaneous")
+/datum/design/canvas
+ name = "11px by 11px Canvas"
+ id = "canvas"
+ build_type = AUTOLATHE
+ materials = list("$metal" = 50)
+ build_path = /obj/item/weapon/canvas
+ category = list("initial", "Miscellaneous")
+
+/datum/design/canvas/nineteenXnineteen
+ name = "19px by 19px Canvas"
+ id = "canvas19x19"
+ build_type = AUTOLATHE
+ materials = list("$metal" = 50)
+ build_path = /obj/item/weapon/canvas/nineteenXnineteen
+ category = list("initial", "Miscellaneous")
+
+/datum/design/canvas/twentythreeXnineteen
+ name = "23px by 19px Canvas"
+ id = "canvas23x19"
+ build_type = AUTOLATHE
+ materials = list("$metal" = 70)
+ build_path = /obj/item/weapon/canvas/twentythreeXnineteen
+ category = list("initial", "Miscellaneous")
+
+/datum/design/canvas/twentythreeXtwentythree
+ name = "23px by 23px Canvas"
+ id = "canvas23x23"
+ build_type = AUTOLATHE
+ materials = list("$metal" = 100)
+ build_path = /obj/item/weapon/canvas/twentythreeXtwentythree
+ category = list("initial", "Miscellaneous")
+
/datum/design/camera_assembly
name = "Camera Assembly"
id = "camera_assembly"
diff --git a/icons/obj/artstuff.dmi b/icons/obj/artstuff.dmi
new file mode 100644
index 00000000000..a11d1a81d0e
Binary files /dev/null and b/icons/obj/artstuff.dmi differ
diff --git a/paradise.dme b/paradise.dme
index a4711b96195..9741089c862 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -762,6 +762,7 @@
#include "code\game\objects\items\weapons\tanks\tanks.dm"
#include "code\game\objects\items\weapons\tanks\watertank.dm"
#include "code\game\objects\random\random.dm"
+#include "code\game\objects\structures\artstuff.dm"
#include "code\game\objects\structures\barsign.dm"
#include "code\game\objects\structures\bedsheet_bin.dm"
#include "code\game\objects\structures\coathanger.dm"