JuliaGtk / GtkUtilities.jl

Interactive utilities for the Gtk toolkit (Julia)
Other
5 stars 8 forks source link

Issues with basic GtkUtilities workflow #17

Closed tomerarnon closed 7 years ago

tomerarnon commented 7 years ago

Hi, At the moment I am using Gtk to build a gui, and would like to add GtkUtilities for its added interactivity functions. I'm running into issues whenever I try to incorporate anything from GtkUtilities, however.

A very simple draw function (like below) works fine.

using  Gtk.ShortNames, Images, GtkUtilities
filepath = "image.tiff"
win = Window("image")
c = Canvas()
push!(win, c)
A = Images.load(filepath)

@guarded draw(c) do widget
    copy!(widget, A)
end

showall(win)

Adding, for example, guidata[c, :xview] into the draw function in any form, crashes julia. E.g.:

@guarded draw(c) do widget
    xview, yview = guidata[widget, :xview], guidata[widget, :yview]
    println(xview, yview)

    copy!(widget, A)
end

breaks the code. Since guidata is critical for panzoom and other operations, I'm not able to use GtkUtilities the way I would like/it was meant to be used. Is there something I'm obviously doing wrong here? I've tried following the julia_gui example as well as @timholy's youtube tutorial from a couple of years back about how to use panzoom, and haven't been able to improve my results.

timholy commented 7 years ago

Did you initialize panzoom? (See the readme.)

tomerarnon commented 7 years ago

Edit: it definitely is in the readme. Another edit: using getgc(c) like in the example yields the error

UndefVarError: getgc not defined in include_string(::String, ::String) at loading.jl:441 in include_string(::String, ::String, ::Int64) at eval.jl:30 in include_string(::Module, ::String, ::String, ::Int64, ::Vararg{Int64,N}) at eval.jl:34 in (::Atom.##53#56{String,Int64,String})() at eval.jl:50 in withpath(::Atom.##53#56{String,Int64,String}, ::String) at utils.jl:30 in withpath(::Function, ::String) at eval.jl:38 in macro expansion at eval.jl:49 [inlined] in (::Atom.##52#55{Dict{String,Any}})() at task.jl:60

and guidata[c, :xview] on it's own (outside of draw method) yields a similar error.

Original message: I didn't notice that in the README (just skimmed through again to make sure; I don't think it's in there) but I remember seeing it in your video. Doing so prevents the crash but also doesn't load copy the image into the canvas (black canvas appears).

For reference, this is what I am now doing:

A = Images.load(filepath)

panzoom(c)
@guarded draw(c) do widget
    xview = guidata[widget, :xview]
    println(xview)
    copy!(widget, A)
end
timholy commented 7 years ago

Did you showall(win)? That might solve the getgc crashes.

tomerarnon commented 7 years ago

Yes, at the end.

timholy commented 7 years ago

Try using Graphics.

timholy commented 7 years ago

Once we get you up and running, would you mind submitting a pull request to improve the README? It's easier for you to know what's confusing than it is for me.

tomerarnon commented 7 years ago

Using Graphics doesn't seem to correct the issue. Introducing getgc(c) produces the following error message:

ERROR: LoadError: UndefRefError: access to undefined reference
 in getgc(::Gtk.GtkCanvas) at C:\Users\Tomer\.julia\v0.5\Gtk\src\cairo.jl:114
 in include_from_node1(::String) at .\loading.jl:488 

I've also tried updating the relevant packages in case that was the issue (it wasn't).

A window still appears after the error message, but it is empty. No complete crashes though.

I'd be happy to submit a pull request once this is sorted out.

timholy commented 7 years ago

That error means you're calling getgc before the canvas is fully initialized. Safest if you put it inside the draw function so you know it won't be called before the canvas is ready.

Maybe something like this?

using  Gtk.ShortNames, Images, GtkUtilities, TestImages, Graphics
win = Window("image")
c = Canvas()
push!(win, c)
A = testimage("lighthouse")

zr = map(x->(first(x),last(x)), indices(A))
panzoom(c, zr[2], zr[1])
panzoom_mouse(c)

@guarded draw(c) do widget
    set_coords(getgc(c), BoundingBox(0, width(c), 0, height(c)), BoundingBox(zr[2][1], zr[2][2], zr[1][1], zr[1][2]))
    xview, yview = guidata[c, :xview], guidata[c, :yview]
    xv = round(Int, xview.min):round(Int, xview.max)
    yv = round(Int, yview.min):round(Int, yview.max)
    copy!(widget, view(A, yv, xv))
end

showall(win)

See also the demos folder.

tomerarnon commented 7 years ago

Copy-pasting your code, it worked perfectly. Thanks!

Still not sure exactly what's going on, but I can probably replicate it for my application. I'll go through line by line with the documentation to figure out exactly what (voodoo) you're doing to get this to work, and once I figure it out, I'll consider how to amend the readme to be easily accessible for simple image manipulation like this.

timholy commented 7 years ago

Some of the mystery might diminish if you check out how Gtk defines draw: https://github.com/JuliaGraphics/Gtk.jl/blob/00b7178d81e534e869f9169cf8e550f188a84b1b/src/cairo.jl#L74-L87. You can see how the getgc calls are "protected" inside the draw function.

tomerarnon commented 7 years ago

Do you mean that getgc is protected by being run only if theif widget.is_realized && widget.is_sized requirement is met?

I think the greatest source of confusion for me was/is coming from not completely understanding what set_coords() did in this context, and why/how it was necessary. While I'm still not entirely sure, after going back to read its documentation at Cairo.jl, my guess is that running it is required in order to use the ::xview (etc.) functionality of guidata. Is that correct?

I've commented and formatted the code you sent me to be (in my opinion) a bit more newbie friendly. Assuming everything is accurate and adequately explained, it could be a useful inclusion along with the julia_gui demo, since it deals directly with images.

# example code for image manipulation using GtkUtilties

using  Gtk.ShortNames, Images, GtkUtilities, Graphics, TestImages

A = testimage("lighthouse")

win = Window("image")                                                 # Create Window
c = Canvas()                                                          # Create Canvas
push!(win, c)                                                         # Place Canvas in Window

yx_ranges = map(x->(first(x),last(x)), indices(A))                    # returns: yx_ranges = ((min-Y, max-Y), (min-X, max-X)). Note that y is first, since it refers to the rows of A
panzoom(c, yx_ranges[2], yx_ranges[1])                                # Initialize panzoom *before* draw method, with arguments for x-y ranges.
panzoom_mouse(c)                                                      # Initialize mouse functions for panning/zooming

@guarded draw(c) do widget
    set_coords( getgc(widget),                                        # running set_coords is required to "set up" guidata[]. See Graphics.jl for more info on set_coords
                BoundingBox(0,width(widget),                          # BoundingBox describing total viewable area in canvas
                            0,height(widget)),                        #
                BoundingBox(yx_ranges[2][1], yx_ranges[2][2],         # BoundingBox describing total image size
                            yx_ranges[1][1], yx_ranges[1][2]) )       #

    xview, yview = guidata[widget, :xview], guidata[widget, :yview]   # see readme for info on guidata[]
    xv = round(Int, xview.min):round(Int, xview.max)                  #
    yv = round(Int, yview.min):round(Int, yview.max)                  #
    region_of_interest = view(A, yv, xv)                              # select subset of A according to the indices yv and xv
    copy!(widget, region_of_interest)                                 # copy the region of interest into c (widget)
end

showall(win)
timholy commented 7 years ago

I think the greatest source of confusion for me was/is coming from not completely understanding what set_coords() did in this context, and why/how it was necessary. While I'm still not entirely sure, after going back to read its documentation at Cairo.jl, my guess is that running it is required in order to use the ::xview (etc.) functionality of guidata. Is that correct?

It means that two-level zooming works properly: once you've zoomed in, the canvas coordinates become equal to the zoom region, so if you draw another rubber band then that becomes an adjustment relative to the first rubber band, not relative to the original full-image coordinates.

tomerarnon commented 7 years ago

Aha! That makes a lot of sense.

Such double zooming is giving me a problem though. Rather than zooming in a second time, mouse-selection will bring me to somewhere else. It appears that the region it's bringing me to is as though the selection is relative to the unzoomed image. This is equally true using the ctrl-scroll method as well.

I've looked again at the julia_gui example, and this problem only occurs in these image-loading scenarios. Any thoughts as to why?

timholy commented 7 years ago

I like it! Would be great to have this. Do you want to contribute it directly, or should I?

timholy commented 7 years ago

Ah, browser refresh delay hit me here. I'm not immediately sure why double-zooming isn't working. There's a chance I'm getting confused between device coordinates and user coordinates somewhere.

If you're adventurous, a whole new approach is brewing in https://github.com/JuliaGizmos/GtkReactive.jl/pull/2.

tomerarnon commented 7 years ago

To be honest, I've been using git for about two weeks and still don't know exactly what I'm doing, so you might just have to do it yourself for that reason.

Maybe set_coords should take xview,yvew information instead of xvewlimits, yviewlimits? I'll give that a shot in a moment.

tomerarnon commented 7 years ago

Just did... something? If it's what I intended then it's a pull request for a new demo file.

timholy commented 7 years ago

git is definitely a barrier. Nice job figuring it out!