Live Texture Painting Script

Forum for OctaneRender Lua scripting examples, discussion and support.
User avatar
stratified
OctaneRender Team
Posts: 945
Joined: Wed Aug 15, 2012 6:32 am
Location: Auckland, New Zealand

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 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:
texture painting
texture painting
my skills
my 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
Last edited by stratified on Tue Jan 07, 2014 3:21 am, edited 3 times in total.
User avatar
grimm
Licensed Customer
Posts: 1332
Joined: Wed Jan 27, 2010 8:11 pm
Location: Spokane, Washington, USA

I'm thinking QA production tool? Looks like fun. :)

Jason
Linux Mint 21.3 x64 | Nvidia GTX 980 4GB (displays) RTX 2070 8GB| Intel I7 5820K 3.8 Ghz | 32Gb Memory | Nvidia Driver 535.171
Tugpsx
Licensed Customer
Posts: 1150
Joined: Thu Feb 04, 2010 8:04 pm
Location: Chicago, IL
Contact:

Guys I think you found King Solomon's mine when you added Lua scripting to Octane, can't wait to see the goodies that get created with this and maybe some interesting add-ons.
Thanks again.
Win 11 64GB | NVIDIA RTX3060 12GB
User avatar
stratified
OctaneRender Team
Posts: 945
Joined: Wed Aug 15, 2012 6:32 am
Location: Auckland, New Zealand

Yeah, we hope to see some killer apps in the future ;)

cheers,
Thomas
User avatar
pixelrush
Licensed Customer
Posts: 1618
Joined: Mon Jan 11, 2010 7:11 pm
Location: Nelson, New Zealand

Very nifty. Can you make size pressure sensitive for a Wacom tablet? A simple circular brush with opacity is ok. :) 3d markups would be cool.
Yeah, I know what Uncle Roeland is going to say... :roll:
i7-3820 @4.3Ghz | 24gb | Win7pro-64
GTS 250 display + 2 x GTX 780 cuda| driver 331.65
Octane v1.55
UnCommonGrafx
Licensed Customer
Posts: 199
Joined: Wed Mar 13, 2013 9:14 pm

That's awfully cool. Made me pay attention to what the future holds for octane.
Perhaps this is a benefit to being 'late to the party' of renderers: out of the box can actually occur now that the box has been clearly defined.
i7-4770K, 32gb ram, windows 8.1, GTX Titan and gt 620 for display
User avatar
stratified
OctaneRender Team
Posts: 945
Joined: Wed Aug 15, 2012 6:32 am
Location: Auckland, New Zealand

pixelrush wrote:Very nifty. Can you make size pressure sensitive for a Wacom tablet? A simple circular brush with opacity is ok. :) 3d markups would be cool.
Yeah, I know what Uncle Roeland is going to say... :roll:
pressure sensitive is not going to work right now. There's not enough info in the mouse event to do that. You could modify the brush size with the scroll wheel (or use that to zoom on the texture).

Or you can do something more funky like creating some pictures of shapes (triangle, circle, square, ...). Display those in a bitmap on the component and then when clicked change the shape of the brush.

cheers,
Thomas
User avatar
pixelrush
Licensed Customer
Posts: 1618
Joined: Mon Jan 11, 2010 7:11 pm
Location: Nelson, New Zealand

Well my tablet has a touch strip I can use for scroll purposes so we aren't defeated yet ;)
Please do that. Brush size <-> scroll wheel.
Funk mode brush swapping sounds way cool. 8-)
i7-3820 @4.3Ghz | 24gb | Win7pro-64
GTS 250 display + 2 x GTX 780 cuda| driver 331.65
Octane v1.55
Tugpsx
Licensed Customer
Posts: 1150
Joined: Thu Feb 04, 2010 8:04 pm
Location: Chicago, IL
Contact:

Interesting, I wonder if this procedure could be applied to a painted path scenario (although the math behind it may get interesting) since moving the brush in 3 space and tracking the paths in an array and using those as waypoint rails for a camera system is turning Octane into way more than we would bargain for.

Just more crazy ideas.
Win 11 64GB | NVIDIA RTX3060 12GB
User avatar
stratified
OctaneRender Team
Posts: 945
Joined: Wed Aug 15, 2012 6:32 am
Location: Auckland, New Zealand

Yep, that's possible. It would require a lot of work, all you can do in a bitmap is load pictures, capture mouse events or manipulate pixels. So it's powerful but really low level. It's like a step back to 80's game programming (not that I would now) ...

You could start with drawing in a camera path in 2D. This means that the camera would move in the same plane.

If somebody's keen, I think it's interesting to create a 2D draw library on top of this to draw primitives like circles, arcs, rectangles, ... We might add them in the future but we don't have the bandwidth right now.

cheers,
Thomas
Post Reply

Return to “Lua Scripting”