Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Choose drawing size after placing contents? #260

Closed
evanfields opened this issue Feb 2, 2023 · 6 comments
Closed

Choose drawing size after placing contents? #260

evanfields opened this issue Feb 2, 2023 · 6 comments
Labels
stale stale/inactive/superseded

Comments

@evanfields
Copy link

evanfields commented Feb 2, 2023

Toy example: let's say I wanted to draw a bunch of circles in a row, but I don't know a priori how many.

@drawsvg begin
    background("antiquewhite")
    n_circles = rand(1:20)
    for _ in 1:n_circles
        circle(O, :10, :stroke)
        translate(30, 0)
    end
end 200 50

image

If n_circles is too big, some of the circles won't fit in the drawing. Is there a way to draw things on some abstract canvas and then choose the drawing size?

Of course in this example n_circles could be precomputed outside the @drawsvg macro and from there a width and height computed. But in practice it may not be so easy to know the width and height needed before hand, for example if objects are placed using Luxor's drawing-specific geometric tools like relative translations.

Also, even in this toy example, precomputing width and height offloads to the user some manual geometric calculation (how far does the right side of the drawing need to be? Well, the first circle at centered-origin is centered at x = width/2 from the left side ... then each successive circle ... plus the stroke width ... plus some buffer ...).

So in summary, I'm hoping for a Luxor-idiomatic way to do two related but separate things:

  1. Find the bounding box of all objects drawn
  2. [more importantly] resize the drawing surface before rendering/serialization to cover a specified bounding box, whether that box comes from a magic function or is simply manually calculated by the user

Thanks in advance for any help!

@cormullion
Copy link
Member

Hi! I don't think there's anything simple that will do this.

Interactive drawing applications probably store all graphics 'in the abstract' and then render them on any size drawing at scale/position, etc. And image editing applications can probably copy a buffer quickly onto another surface of a different size. With Cairo/Luxor, it's more like when you start to draw on a sheet of paper or whiteboard and then realize too late that you started out in the wrong place... :)

The nearest you're going to get at present is by using a recording surface and snapshots. I don't know anything about these (the feature was coded by @hustf :)), but the idea is that you specify a bounding box when you output the file. Of course, you still have to be able to keep track of and calculate the bounding box somehow (since graphics are inaccessible once they've been drawn).

Drawing(NaN, NaN, :rec) # no bounds 
background("antiquewhite")
n_circles = 5
margin = 10
@layer begin # non-permanent transformations 
    for _ in 1:n_circles
        randomhue()
        circle(O, 10, :fill)
        translate(30, 0)
    end
end
snapshot(fname="/tmp/temp.svg", 
    cb=BoundingBox(Point(-margin, -100), Point(150, 100)))
finish()

@cormullion
Copy link
Member

cormullion commented Feb 2, 2023

Talking of recording surfaces, there appears to be a Cairo function called cairo_recording_surface_ink_extents() (https://cairographics.org/manual/cairo-Recording-Surfaces.html#cairo-recording-surface-ink-extents). This suggests that it could calculate the bounding box required to enclose the recording surface. However, my abilities in calling Cairo functions - and my understanding of the recording surface feature - are limited, and my attempt below doesn't work.

function recording_surface_ink_extents()
    x0 = Cdouble[0]
    y0 = Cdouble[0]
    w = Cdouble[0]
    h = Cdouble[0]
    d = Luxor._get_current_drawing_save()
    if d.surfacetype === :rec
        ccall((:cairo_recording_surface_ink_extents, Luxor.Cairo.libcairo),
            Ptr{Nothing},
            (Ptr{Nothing}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}),
            d.surface.ptr, x0, y0, w, h)
        return BoundingBox(Point(x0[1], y0[1]), Point(x0[1] + w[1], y0[1] + h[1]))
    else
        throw(error("not a recording surface"))
    end
end

See JuliaGraphics/Cairo.jl#356 for upstream solution.

@evanfields
Copy link
Author

Hi! I don't think there's anything simple that will do this.

Thanks for the speedy response! I think the recording surface + snapshot looks like a great solution, and luckily in my case manually tracking the bounding box isn't too hard. From the docs on snapshot referencing drawing it seems like the snapshots can also be in memory with fname = :svg? Seems excellent.

@cormullion
Copy link
Member

If you encounter problems, it night be worth running on the current master. There’s been some recent work on this which hasn’t yet been released. (Here’s a place to start: #150 )

@hustf
Copy link
Contributor

hustf commented Feb 2, 2023

Yes, this complicates things fast, which is why it's not in Luxor currently. Also, a surprising number of Luxor functions uses the limits defined in Drawing. In #258, I linked to a proposal, which makes alternative functions for limitless drawings.

@stale
Copy link

stale bot commented Feb 11, 2023

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale stale/inactive/superseded label Feb 11, 2023
@stale stale bot closed this as completed Feb 19, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stale stale/inactive/superseded
Projects
None yet
Development

No branches or pull requests

3 participants