JuliaGraphics / Luxor.jl

Simple drawings using vector graphics; Cairo "for tourists!"
http://juliagraphics.github.io/Luxor.jl/
Other
588 stars 71 forks source link

Drawing method which calls Cairo.CairoImageSurface(img) #232

Closed oheil closed 2 years ago

oheil commented 2 years ago

Playing around with MiniFB and Luxor the currently only available Drawing methods turned out not to be the perfect match for MiniFB because the image buffer is copied a lot. But in Cairo.jl there is also:

CairoImageSurface(img::Matrix{T}) where {T<:Union{RGB24,ARGB32}}

where the image buffer is provided by the user.

So I forked Luxor and added this method to drawings.jl :

mutable struct Drawing
    ....
    function Drawing(img::Matrix{T}; strokescale=false) where {T<:Union{RGB24,ARGB32}}
        w,h = size(img)
        bufdata = UInt8[]
        iobuf = IOBuffer(bufdata, read=true, write=true)
        the_surfacetype = :image
        f = ""
        the_surface = Cairo.CairoImageSurface(img)
        the_cr  = Cairo.CairoContext(the_surface)
        currentdrawing      = new(w, h, f, the_surface, the_cr, the_surfacetype, 0.0, 0.0, 0.0, 1.0, iobuf, bufdata, strokescale)
        if isempty(CURRENTDRAWING)
            push!(CURRENTDRAWING, currentdrawing)
        else
            CURRENTDRAWING[1] = currentdrawing
        end
        return currentdrawing
    end

    function Drawing(w, h, stype::Symbol, f::AbstractString=""; strokescale=false)
        ...
    end
    ...
end

The following example shows an example which wasn't possible as smooth as it is now. First, I wanted to have a MiniFB window which is updated automatically in the background and let's me put Luxor graphic commands at the REPL together with a FPS display as a layer on top without overwriting the image buffer:

using MiniFB, Luxor, Colors

WIDTH=800
HEIGHT=600

mutable struct MfbState
    state::mfb_update_state
    mousePressed::Int   
end
stateArray=[MfbState( MiniFB.STATE_OK, 0 )]

window = mfb_open_ex("MiniFB", WIDTH, HEIGHT, MiniFB.WF_RESIZABLE)

function windowUpdateTask(window,buffer,stateArray,showFPS=false,singleCall=false)
    sb=buffer[1:105,1:55]
    stateArray[1].state=mfb_update(window,buffer)
    updateCount=0
    startTime=floor(Int,time())
    fps="0"
    while stateArray[1].state == MiniFB.STATE_OK && ! singleCall
        if showFPS
            elapsedTime=floor(Int,time())-startTime
            if elapsedTime > 1
                fps=string(round(Int,updateCount/elapsedTime))
                startTime=floor(Int,time())
                updateCount=0
            end
            sb.=buffer[1:105,1:55]
            @layer begin
                (dx,dy)=Point(0.0, 0.0)-getworldposition(Point(0.0, 0.0);centered=false)
                setcolor((1.0, 0, 0, 0.5))
                fontsize(50)
                text(fps, Point(5+dx,5+dy), halign=:left, valign = :top)
            end
        end
        stateArray[1].state=mfb_update(window,buffer)
        if showFPS
            buffer[1:105,1:55].=sb
        end
        sleep(1.0/120.0)
        updateCount+=1
    end
    println("\nWindow closed\n")
end

buffer=zeros(ARGB32, WIDTH, HEIGHT)
d=Drawing(buffer)

@async windowUpdateTask(window,buffer,stateArray,true)

Now doing some graphics on the image buffer rendered directly in the MiniFB window. Pressing "q" + "return" stops the while loop and brings you back to the REPL.

mutable struct Ball
    position::Point
    velocity::Point
end
origin()
function stick(w, h)
    channel = Channel(10)
    @async while true
        kb = readline(stdin)
        if contains(kb, "q")
            put!(channel, 1)
            break
        end
    end
    colors=[rand(1:255),rand(1:255),rand(1:255)]
    newcolors=[rand(1:255),rand(1:255),rand(1:255)]
    c=ARGB(colors[1]/255,colors[2]/255,colors[3]/255,1.0)
    balls=[Ball( rand(BoundingBox(Point(-w/2, -h/2), Point(w/2, h/2))), rand(BoundingBox(Point(-10, -10), Point(10, 10))) ) for _ in 1:2] 
    while true
        background(0,0,0,0.05)
        if colors == newcolors
            newcolors=[rand(1:255),rand(1:255),rand(1:255)]
        end
        for (index,(col,newcol)) in enumerate(zip(colors,newcolors))
            if col != newcol
                col > newcol ? col-=1 : col+=1
                colors[index]=col
            end
        end
        c=ARGB(colors[1]/255,colors[2]/255,colors[3]/255,1.0)
        for ball in balls
            if !(-w/2 < ball.position.x < w/2)
                ball.velocity = Point(-ball.velocity.x,ball.velocity.y)
            end
            if !(-h/2 < ball.position.y < h/2)
                ball.velocity = Point(ball.velocity.x,-ball.velocity.y)
            end
            ball.position = ball.position + ball.velocity
        end
        setcolor(c)
        line(balls[1].position,balls[2].position,:stroke)
        sleep(1.0/120.0)
        if isready(channel)
            break
        end
    end
end
stick(WIDTH, HEIGHT)

What do you think? Do you see what I mean and why I think this is a nice way to expand on Luxor? I can do a PR but for now, the state of my proposal is still a bit rough and needs some discussion.

cormullion commented 2 years ago

It looks great! I don't use this bit of the package at all (except for answering queries 😄) so anything that might add useful features for other people is great. If it doesn't affect current functionality (image buffers, snapshots, etc) then adding it would be straightforward and hopefully not breaking. However, adding tests and documentation is required! 😛

You might like to search through some of the old issues for "buffer" where people have asked about image buffer functionality, thread-safety, etc, just in case there are other considerations you haven't thought of.

oheil commented 2 years ago

Hm, thread-safety is surely nothing I can address on the way, snapshot and preview is something which would be nice to have if MiniFB is not used... I will have a look...

cormullion commented 2 years ago

Closed via #233 - thanks!