Live Texture Painting Script
Posted: Mon Jan 06, 2014 11:14 pm
Hi all,
In release candidate 1.25, we replaced the IMAGE_LABEL by the BITMAP component. The BITMAP component is like a paint canvas that can react to mouse events so you can write some pretty cool scripts with it. The bitmap is low-level (pixel level) so it may be a bit cumbersome. But because it's so low level it would allow you to do some pretty powerful stuff (e.g. creating your own components, draw paths, display the scene, ...).
In this example we do live texture painting. Just start rendering something, select a texture node and start the script. Now you should by able to paint some stuff on your selected texture. You can also use it to paint a texture environment. This is a proof-of-concept, not a production stable script!
First, the selected texture's buffer is loaded in the bitmap. Then we show the bitmap in a window and start listening for mouse events. When the mouse is dragged on the bitmap, we draw a red square in the bitmap and update the texture's buffer with the red square. Important here is the call to
Here's the code:
Here's a result of my artistic skills:
Some ideas for improvement:
cheers,
Thomas
In release candidate 1.25, we replaced the IMAGE_LABEL by the BITMAP component. The BITMAP component is like a paint canvas that can react to mouse events so you can write some pretty cool scripts with it. The bitmap is low-level (pixel level) so it may be a bit cumbersome. But because it's so low level it would allow you to do some pretty powerful stuff (e.g. creating your own components, draw paths, display the scene, ...).
In this example we do live texture painting. Just start rendering something, select a texture node and start the script. Now you should by able to paint some stuff on your selected texture. You can also use it to paint a texture environment. This is a proof-of-concept, not a production stable script!
First, the selected texture's buffer is loaded in the bitmap. Then we show the bitmap in a window and start listening for mouse events. When the mouse is dragged on the bitmap, we draw a red square in the bitmap and update the texture's buffer with the red square. Important here is the call to
octane.changemanager.update()
, each time you want to see your changes reflected in the current render, you should call this function. (The idea is that you make all your changes and then call this function once to optimize things).Here's the code:
- Code: Select all
-- Live texture painting script.
-- Proof of concept, don't use this to launch nuclear missiles!
--
-- @version Live Texture Painting
-- @author Thomas Loockx
-- @version 0.2
-- Retrieves the colour of a texel.
local function getTexPixel(tex, x, y)
local ix = 4 * ((x-1) + tex.size[1] * (y-1)) + 1
return { tex.texels[ix], tex.texels[ix+1], tex.texels[ix+2], tex.texels[ix+3] }
end
-- Sets a pixel in our texel data structure.
local function setTexPixel(tex, x, y, cl)
-- bounds check
if x > tex.size[1] or x < 1 then return end
if y > tex.size[1] or y < 1 then return end
local ix = 4 * ((x-1) + tex.size[1] * (y-1)) + 1
-- bounds check on the index
if (ix > #tex.texels) then return end
tex.texels[ix] = cl[1]
tex.texels[ix+1] = cl[2]
tex.texels[ix+2] = cl[3]
tex.texels[ix+3] = cl[4]
end
-- get all the info about the selected texture
local TEX = {}
TEX.node = octane.project.getSelection()[1]
assert(TEX.node and TEX.node:getProperties().type == octane.NT_TEX_IMAGE, "please select an image texture node")
TEX.size = TEX.node:getAttribute(octane.A_SIZE)
TEX.texels = TEX.node:getAttribute(octane.A_BUFFER)
print(string.format("texture %d x %d, buffer len %d", TEX.size[1], TEX.size[2], #TEX.texels))
-- clear the filename attributes. This tricks Octane into thinking that the texture
-- came directly from the image buffer.
TEX.node:clearAttribute(octane.A_FILENAME, false)
TEX.node:clearAttribute(octane.A_PACKAGE, false)
function mouseCallback(bitmap, event, x, y, dx, dy)
if event == octane.gui.eventType.MOUSE_DOWN or event == octane.gui.eventType.MOUSE_DRAG then
print(x, y)
-- paint brush settings
local thickness = 5
local colour = { 255, 0, 0, 255 }
for i=x,x+thickness do
for j=y,y+thickness do
bitmap:setPixel(i, j, colour)
setTexPixel(TEX, i, TEX.size[2] - j - 1, colour)
end
end
-- update the texture node
TEX.node:setAttribute(octane.A_BUFFER, TEX.texels)
-- update the render engine
octane.changemanager.update()
end
end
-- create a plain vanilla bitmap same size as the texture
local bitmap = octane.gui.create
{
type = octane.gui.componentType.BITMAP,
width = TEX.size[1],
height = TEX.size[2],
opacity = 0.5,
backgroundColour = { 255, 255, 255, 0 },
mouseCallback = mouseCallback,
}
-- fill the bitmap with the texture colours
for x=1,TEX.size[1] do
for y=1,TEX.size[2] do
-- we need to flip the texture to display it properly
bitmap:setPixel(x, TEX.size[2]-y, getTexPixel(TEX, x, y))
end
end
local group = octane.gui.create
{
type = octane.gui.componentType.GROUP,
rows = 1,
cols = 1,
children = { bitmap },
border = false,
inset = { 5 },
padding = { 5 },
}
local window = octane.gui.create
{
type = octane.gui.componentType.WINDOW,
children = { group },
width = group:getProperties().width,
height = group:getProperties().height,
text = "Texture Painter",
}
window:showWindow()
Here's a result of my artistic skills:
Some ideas for improvement:
- Modify the brush colour, shape and size (1.25 features a colour swatch as well to pick a colour).
- Allow saving of the texture
- Don't screw up the original texture
- Make it work for HDRI images.
- It's slow for larger textures.
- ...
cheers,
Thomas