I need help with my Projects and understanding canvases

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
User avatar
nice
Party member
Posts: 191
Joined: Sun Sep 15, 2013 12:17 am
Location: Sweden

I need help with my Projects and understanding canvases

Post by nice »

Hello boys and girls!

I don't really know if this question is appropriate for Support and Development but I will ask it anyways and I apologize for the inconvenience.

I'm currently working on a project that I call "Helium", it's a MS paint clone project and it's a part of a bigger project for me to understand Löve2D better.

Here's the code:

Code: Select all

function love.load()

   squares = {}
   Lines = {}
   currentLine = {0, 0, 0, 0}
   lineSize = 16
   mouseIsDown = false

-- Colors
   colors = 
   --[[White]]--
   {{255, 255, 255},

      --[[Blue]]--
      {50, 130, 185}, 

      --[[Brown]]--
      {143, 114, 30}, 

      --[[Green]]--
      {50, 158, 30}, 

      --[[Orange]]--
      {199, 129, 68},

      --[[Pink]]--
      {226, 131, 239},

      --[[Purple]]--
      {143, 83, 185}, 

      --[[Red]]--
      {171, 91, 75}, 

      --[[Turquoise]]--
      {50, 137, 151}, 

      --[[Black]]--
      {0, 0, 0}}
   curColor = 1 

-- User Interface
   ui = love.graphics.newImage("heliumUI.png")

   saveIcon = {x = 675, y = 500}
   
   saveIcon.image = love.graphics.newImage("SaveIcon.png")
   saveIcon.namingBarColor = {100, 100, 100}
   saveIcon.imageName = "Image_Name"
   love.filesystem.setIdentity("helium")

-- White Background
   love.graphics.setBackgroundColor(255, 255, 255)
   
   function OpenFolder()
      love.system.openURL("file://"..love.filesystem.getSaveDirectory())
   end
   
   love.keyboard.setKeyRepeat(true)
end

function love.update( dt )
-- The Brush
   local x, y = love.mouse.getPosition()

   if love.mouse.isDown( "l" ) and not love.keyboard.isDown("lshift") then
      local newPosition = {}
      newPosition.x = x - 8
      newPosition.y = y - 8
      newPosition.color = colors[curColor]
      table.insert( squares, newPosition )
   end

   if mouseIsDown then
      currentLine.x2 = x
      currentLine.y2 = y
   end

end


-- Draws All The Graphics --
function love.draw()
   local x, y = love.mouse.getPosition()

   love.graphics.setLineWidth(1) 
   for key, square in ipairs( squares ) do
      love.graphics.setColor(square.color)
      love.graphics.rectangle( "fill", square.x, square.y, 16, 16 ) 
   end

   -- Draws Current Colour
   love.graphics.setColor(colors[curColor])
   love.graphics.rectangle("fill", 47, 515, 100, 50)

   -- Draws A 16x16 Square That Follows The Mouse
   if not saveIcon.naming then
      love.graphics.rectangle("line", x - 8, y - 8, 16, 16)
   end

   love.graphics.setLineWidth(lineSize)
   for i = 1, #Lines do
      love.graphics.setColor(Lines[i].color)
      love.graphics.line(Lines[i].x1, Lines[i].y1, Lines[i].x2, Lines[i].y2)
   end

   love.graphics.setColor(colors[curColor])
   love.graphics.line(currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2)

   -- User Interface
   love.graphics.setColor(255, 255, 255)
   love.graphics.draw(ui, 0, 480)
   
   love.graphics.draw(saveIcon.image, saveIcon.x, saveIcon.y)

   if saveIcon.naming then
      love.graphics.setColor(saveIcon.namingBarColor)
      love.graphics.rectangle("fill", 650, 575, 120, 15)
      
      love.graphics.setColor(0, 0, 0)
      love.graphics.print(saveIcon.imageName, 650, 575)
   end
end

function love.mousepressed(x, y, button)

   -- Drop Tool -- I'll leave this here in case you ever need it.
--   if button == "l" and love.keyboard.isDown("lshift") then
--      local screenshot = love.graphics.newScreenshot()
--      local r, g, b = screenshot: getPixel(x - 1, y - 1)
--      colors[curColor] = {r, g, b}
--   end

   if button == "r" then
      mouseIsDown = true
      currentLine = {x1 = x, y1 = y, x2 = x, y2 = y}
   elseif button == "l" then
      if x >= saveIcon.x and x <= saveIcon.x + saveIcon.image:getWidth() and
         y >= saveIcon.y and y <= saveIcon.y + saveIcon.image:getHeight() then
         
         saveIcon.naming = true
      end
   end

end

function love.mousereleased(x, y, button)
   if button == "r" and mouseIsDown then
      mouseIsDown = false
      currentLine.color = colors[curColor]
      table.insert(Lines, currentLine)
      currentLine = {0, 0, 0, 0}
   end
end

function love.keypressed(key)
-- Select Your Color By Pressing The Number Keys (1 to 0)
   if key == "1" then
      curColor = 1 -- White
   elseif key == "2" then
      curColor = 2 -- Blue
   elseif key == "3" then
      curColor = 3 -- Brown
   elseif key == "4" then
      curColor = 4 -- Green
   elseif key == "5" then
      curColor = 5 -- Orange
   elseif key == "6" then
      curColor = 6 -- Pink
   elseif key == "7" then
      curColor = 7 -- Purple
   elseif key == "8" then
      curColor = 8 -- Red
   elseif key == "9" then
      curColor = 9 -- Turquoise
   elseif key == "0" then
      curColor = 10 -- Black
   elseif key == "f" then
      squares = {}
      Lines = {}
      love.graphics.setBackgroundColor(colors[curColor])
   end
   
   if saveIcon.naming then
      if key == "return" then
         saveIcon.naming = false
         
         love.mouse.setVisible(false)
         local screenShot = love.graphics.newScreenshot()
         local image = love.image.newImageData(800, 480)
         local tabler = {}
         
         for y = 0, 479 do
            for x = 0, 799 do
               local r, g, b, a = screenShot:getPixel(x, y)
               
               image:setPixel(x, y, r, g, b, a)
            end
         end
         
         image:encode(saveIcon.imageName..".png")
         
         love.mouse.setVisible(true)
         
         OpenFolder()
      elseif key == "backspace" then
         saveIcon.imageName = saveIcon.imageName:sub(1, -2)
      end
   end
end

function love.textinput(t)
   if saveIcon.naming then
      saveIcon.imageName = saveIcon.imageName .. t
   end
end
I got some help with the project along the way and I asked another Löve user called Jasoco said that I should use canvases for my project.
Here's what Jasoco said about it:
A paint program would really benefit from using canvases. Actually they'd pretty much be required. The way you have it now works fine at first, but eventually is going to grind to a halt because you're looping through each square every frame. Plus a canvas could be saved just as easy as a screenshot.
So I decided to do another side project

Code: Select all

function love.load()

canvas = love.graphics.newCanvas(800, 480)
-- Images --
-- Floppy Disc Save Icon
saveIcon = love.graphics.newImage("saveIcon.png")

-- Blue User Interface Bar
ui = love.graphics.newImage("blueUI.png")
end

function love.update()
end
	
function love.draw()
love.graphics.setBackgroundColor(255, 255, 255)

-- Square With Random Color
love.graphics.setColor( math.random(0, 255), math.random(0, 255), math.random(0, 255) )
love.graphics.rectangle("fill", 350, 250, 120, 120)

-- Part Of The User Interface --
-- User Interface
love.graphics.setColor(255, 255, 255)
love.graphics.draw(ui, 0, 480)

-- Save Icon
love.graphics.draw(saveIcon, 675, 500)

end
to understand how canvases works and here's were it gets complicated:
I don't really know how I should implement the canvas function as I don't know how it works, I have checked the wiki and I don't get any wiser.

This is what I want for both projects and for myself:

Myself:
- I want to understand how canvases work
- I want to understand how you can implement saving a image that you have made in Helium
- Is it possible to "draw" within the canvas function?

Helium:
- I want a proper saving function for the project by using the canvas function.
- Again is it possible to draw within the canvas function?

Canvas side project
This project is where I have a square flashing in different colours and I have a UI with a save icon where the idea is that I click on the icon and it screenshots the colour and saves it.

- Here I want to see how canvases works and to gain more knowledge about it.

Once again I'm sorry for the inconvenience and I thank you for taking your time reading this.
Also here's the thread where I ask about canvases:
http://love2d.org/forums/viewtopic.php?f=3&t=78950
Attachments
A UI that Helium uses
A UI that Helium uses
heliumUI.png (2.9 KiB) Viewed 4255 times
A save icon that both Helium and canvas side project uses
A save icon that both Helium and canvas side project uses
SaveIcon.png (665 Bytes) Viewed 4255 times
a Blue UI for the canvas test project
a Blue UI for the canvas test project
blueUI.png (3.18 KiB) Viewed 4255 times
:awesome: Have a good day! :ultraglee:
Lzard
Prole
Posts: 1
Joined: Mon Oct 27, 2014 9:00 am

Re: I need help with my Projects and understanding canvases

Post by Lzard »

I'm not really experienced with Love2D yet, but I can try to answer. :p

A canvas is a virtual environment of pixels, on which you would draw your things. This canvas contains each pixel and its color. For example, Love2D's window use a special canvas that is always drawn. If you want your own canvas to be drawn, you must specifically draw them within Love2D's window canvas.

To create a canvas, use love.graphics.newCanvas(), it will return an empty canvas.
Then, use love.graphics.setCanvas with your newly created canvas as parameter. Once this function is called, every call to draw will draw on the newly created canvas. Once you're done drawing onto this canvas, use setCanvas again to change canvas, or setCanvas without any parameter to resume to Love2D's window canvas.
You can then use the draw function to draw your canvas where you wish to draw it.

Using this, you can save precious time by storing the canvases rather than redrawing the whole thing at each loop.

Hope it helps :)
User avatar
nice
Party member
Posts: 191
Joined: Sun Sep 15, 2013 12:17 am
Location: Sweden

Re: I need help with my Projects and understanding canvases

Post by nice »

Lzard wrote:Hope it helps :)
Thanks!
I think I'm starting to have a better idea how canvases works in Löve2D.
Also Welcome to Löve :D
:awesome: Have a good day! :ultraglee:
User avatar
ArchAngel075
Party member
Posts: 319
Joined: Mon Jun 24, 2013 5:16 am

Re: I need help with my Projects and understanding canvases

Post by ArchAngel075 »

Welcome!


Canvases are amazing, and became important in a project i am working on for modding in render functions.

Don't forget you can also use http://www.love2d.org/wiki/Canvas:renderTo
Here you can skip the need for setCanvas and simply push a function onto the canvas and draw it later,

Say the user paints a small image, you can then push all the draw calls of the pixels/boxes etc by passing their draws (love.graphics.rectangle etc) onto the canvas one time..., don't forget to still use love.graphics.draw to actually draw the canvas onto the screen.

And it is possible to get a "screenshot" or image to save of the canvas using http://www.love2d.org/wiki/Canvas:getImageData
Encode a new image using that.

Goodluck in your endeavors!
User avatar
nice
Party member
Posts: 191
Joined: Sun Sep 15, 2013 12:17 am
Location: Sweden

Re: I need help with my Projects and understanding canvases

Post by nice »

ArchAngel075 wrote:Welcome!


Canvases are amazing, and became important in a project i am working on for modding in render functions.

Don't forget you can also use http://www.love2d.org/wiki/Canvas:renderTo
Here you can skip the need for setCanvas and simply push a function onto the canvas and draw it later,

Say the user paints a small image, you can then push all the draw calls of the pixels/boxes etc by passing their draws (love.graphics.rectangle etc) onto the canvas one time..., don't forget to still use love.graphics.draw to actually draw the canvas onto the screen.

And it is possible to get a "screenshot" or image to save of the canvas using http://www.love2d.org/wiki/Canvas:getImageData
Encode a new image using that.

Goodluck in your endeavors!
Thanks for your encouragement!
A question though, do you need to put the code within canvas:renderTo to make it work?
like what I do in here:

Code: Select all

function love.load()

   squares = {}
   Lines = {}
   currentLine = {0, 0, 0, 0}
   lineSize = 16
   mouseIsDown = false

-- Colors
   colors = 
   --[[White]]--
   {{255, 255, 255},

      --[[Blue]]--
      {50, 130, 185}, 

      --[[Brown]]--
      {143, 114, 30}, 

      --[[Green]]--
      {50, 158, 30}, 

      --[[Orange]]--
      {199, 129, 68},

      --[[Pink]]--
      {226, 131, 239},

      --[[Purple]]--
      {143, 83, 185}, 

      --[[Red]]--
      {171, 91, 75}, 

      --[[Turquoise]]--
      {50, 137, 151}, 

      --[[Black]]--
      {0, 0, 0}}
   curColor = 1 

-- User Interface
   ui = love.graphics.newImage("heliumUI.png")

   saveIcon = {x = 675, y = 500}
   
   saveIcon.image = love.graphics.newImage("SaveIcon.png")
   saveIcon.namingBarColor = {100, 100, 100}
   saveIcon.imageName = "Image_Name"
   love.filesystem.setIdentity("helium")

-- White Background
   love.graphics.setBackgroundColor(255, 255, 255)
   
   function OpenFolder()
      love.system.openURL("file://"..love.filesystem.getSaveDirectory())
   end
   
   love.keyboard.setKeyRepeat(true)
end

function love.update( dt )
-- The Brush
   local x, y = love.mouse.getPosition()

   if love.mouse.isDown( "l" ) and not love.keyboard.isDown("lshift") then
      local newPosition = {}
      newPosition.x = x - 8
      newPosition.y = y - 8
      newPosition.color = colors[curColor]
      table.insert( squares, newPosition )
   end

   if mouseIsDown then
      currentLine.x2 = x
      currentLine.y2 = y
   end

end


-- Draws All The Graphics --
function love.draw()
   local x, y = love.mouse.getPosition()

   love.graphics.setLineWidth(1) 
   for key, square in ipairs( squares ) do
      love.graphics.setColor(square.color)
      love.graphics.rectangle( "fill", square.x, square.y, 16, 16 ) 
   end

   -- Draws Current Colour
   love.graphics.setColor(colors[curColor])
   love.graphics.rectangle("fill", 47, 515, 100, 50)

   -- Draws A 16x16 Square That Follows The Mouse
   if not saveIcon.naming then
      love.graphics.rectangle("line", x - 8, y - 8, 16, 16)
   end

   love.graphics.setLineWidth(lineSize)
   for i = 1, #Lines do
      love.graphics.setColor(Lines[i].color)
      love.graphics.line(Lines[i].x1, Lines[i].y1, Lines[i].x2, Lines[i].y2)
   end

   love.graphics.setColor(colors[curColor])
   love.graphics.line(currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2)

   -- User Interface
   love.graphics.setColor(255, 255, 255)
   love.graphics.draw(ui, 0, 480)
   
   love.graphics.draw(saveIcon.image, saveIcon.x, saveIcon.y)

   if saveIcon.naming then
      love.graphics.setColor(saveIcon.namingBarColor)
      love.graphics.rectangle("fill", 650, 575, 120, 15)
      
      love.graphics.setColor(0, 0, 0)
      love.graphics.print(saveIcon.imageName, 650, 575)
   end
end

function love.mousepressed(x, y, button)

   -- Drop Tool -- I'll leave this here in case you ever need it.
--   if button == "l" and love.keyboard.isDown("lshift") then
--      local screenshot = love.graphics.newScreenshot()
--      local r, g, b = screenshot: getPixel(x - 1, y - 1)
--      colors[curColor] = {r, g, b}
--   end

   if button == "r" then
      mouseIsDown = true
      currentLine = {x1 = x, y1 = y, x2 = x, y2 = y}
   elseif button == "l" then
      if x >= saveIcon.x and x <= saveIcon.x + saveIcon.image:getWidth() and
         y >= saveIcon.y and y <= saveIcon.y + saveIcon.image:getHeight() then
         
         saveIcon.naming = true
      end
   end

end

function love.mousereleased(x, y, button)
   if button == "r" and mouseIsDown then
      mouseIsDown = false
      currentLine.color = colors[curColor]
      table.insert(Lines, currentLine)
      currentLine = {0, 0, 0, 0}
   end
end

function love.keypressed(key)
-- Select Your Color By Pressing The Number Keys (1 to 0)
   if key == "1" then
      curColor = 1 -- White
   elseif key == "2" then
      curColor = 2 -- Blue
   elseif key == "3" then
      curColor = 3 -- Brown
   elseif key == "4" then
      curColor = 4 -- Green
   elseif key == "5" then
      curColor = 5 -- Orange
   elseif key == "6" then
      curColor = 6 -- Pink
   elseif key == "7" then
      curColor = 7 -- Purple
   elseif key == "8" then
      curColor = 8 -- Red
   elseif key == "9" then
      curColor = 9 -- Turquoise
   elseif key == "0" then
      curColor = 10 -- Black
   elseif key == "f" then
      squares = {}
      Lines = {}
      love.graphics.setBackgroundColor(colors[curColor])
   end
   
   if saveIcon.naming then
      if key == "return" then
         saveIcon.naming = false
         
         love.mouse.setVisible(false)
         local screenShot = love.graphics.newScreenshot()
         local image = love.image.newImageData(800, 480)
         local tabler = {}
         
         for y = 0, 479 do
            for x = 0, 799 do
               local r, g, b, a = screenShot:getPixel(x, y)
               
               image:setPixel(x, y, r, g, b, a)
            end
         end
         
         image:encode(saveIcon.imageName..".png")
         
         love.mouse.setVisible(true)
         
         OpenFolder()
      elseif key == "backspace" then
         saveIcon.imageName = saveIcon.imageName:sub(1, -2)
      end
   end
end

function love.textinput(t)
   if saveIcon.naming then
      saveIcon.imageName = saveIcon.imageName .. t
   end
end
I tried the renderTo function before with my canvas testing project but it said it had a nil value, does renderTo need a number?
I still feel that I don't really have a good understanding yet, I would guess if I could get a code example that's similar to what I'm trying to do would make it clear.
:awesome: Have a good day! :ultraglee:
User avatar
ArchAngel075
Party member
Posts: 319
Joined: Mon Jun 24, 2013 5:16 am

Re: I need help with my Projects and understanding canvases

Post by ArchAngel075 »

RenderTo is passed a function, do not call the function in it
ie dont do
canvas:renderTo( drawStuff( ) )

instead pass the function name :
canvas:renderTo( drawStuff )

Perhaps you were calling the function, which returns nil, and the renderTo then attempts to operate on that nil value, I know i had the same messup when i first touched it.


You can simply make a function that does the drawing as if you were to use it with love.draw except here you can keep the function around and push it onto a canvas using render to, this way you dont need to have a function that specifically sets canvas, draws stuff/runs the function and then unsets it and then draw the canvas. No now all you need to do is where you want to give the canvas the update use renderTo one time and then draw that canvas later.

Also keep in mind, unlike love.draw frames, canvases dont clear themselves after every update, instead if you want to have a canvas cleared use canvas:clear(). Although the fun in not clearing the canvas and seeing a image "stretch" and "blurr" across the screen is entertaining to watch haha.

Goodluck again!

PS, i could share a "scene" system i use for my project for buffering layers of canvases on a series of scenes but it loses some benefits i think. Plus it was meant as a state-system for love.draw states of sorts.
Post Reply

Who is online

Users browsing this forum: No registered users and 78 guests