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

Allow CairoMakie to use GLMakie to rasterize plots #2061

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CairoMakie/src/CairoMakie.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ include("infrastructure.jl")
include("utils.jl")
include("primitives.jl")
include("overrides.jl")
include("glmakie_integration.jl")

function __init__()
activate!()
Expand Down
176 changes: 176 additions & 0 deletions CairoMakie/src/glmakie_integration.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# Upstreamable code

# TODO: make this function more efficient!
function alpha_colorbuffer(Backend, scene::Scene)
img = try
Backend.activate!()
display(scene)
bg = scene.backgroundcolor[]
scene.backgroundcolor[] = RGBAf(0, 0, 0, 1)
b1 = copy(Makie.colorbuffer(scene))
scene.backgroundcolor[] = RGBAf(1, 1, 1, 1)
b2 = Makie.colorbuffer(scene)
scene.backgroundcolor[] = bg
map(infer_alphacolor, b1, b2)
catch e
println("Error: something failed in alpha colorbuffer!")
rethrow(e)
finally
CairoMakie.activate!()
end

return img
end

function infer_alphacolor(rgb1, rgb2)
rgb1 == rgb2 && return RGBAf(rgb1.r, rgb1.g, rgb1.b, 1)
c1 = Float64.((rgb1.r, rgb1.g, rgb1.b))
c2 = Float64.((rgb2.r, rgb2.g, rgb2.b))
alpha = @. 1 - (c1 - c2) * -1 # ( / (0 - 1))
meanalpha = clamp(sum(alpha) / 3, 0, 1)
meanalpha == 0 && return RGBAf(0, 0, 0, 0)
c = @. clamp((c1 / meanalpha), 0, 1)
return RGBAf(c..., meanalpha)
end

function create_render_scene(plot::Combined; scale::Real = 1)
scene = Makie.parent_scene(plot)
w, h = Int.(scene.px_area[].widths)

# We create a dummy scene to render to, which inherits its parent's
# transformation and camera.
# WARNING: lights, SSAO and axis do not update when the original Scene's
# attributes do. This is because they are stored as fields in the Scene
# struct, not as attributes.
render_scene = Makie.Scene(
camera = Makie.camera(scene),
lights = scene.lights,
ssao = scene.ssao,
show_axis = false,
backgroundcolor = :transparent
)

# continually keep the pixel area updated
on(pixelarea(scene); update = true) do px_area
Makie.resize!(render_scene, (px_area.widths .* scale)...)
end

# link the transofrmation attributes
Makie.Observables.connect!(render_scene.transformation.transform_func, scene.transformation.transform_func)
Makie.Observables.connect!(render_scene.transformation.rotation , scene.transformation.rotation)
Makie.Observables.connect!(render_scene.transformation.scale , scene.transformation.scale)
Makie.Observables.connect!(render_scene.transformation.translation , scene.transformation.translation)
Makie.Observables.connect!(render_scene.transformation.model , scene.transformation.model)

# push only the relevant plot to the scene
push!(render_scene, plot)

return render_scene

end

"""
plot2img(backend::Makie.AbstractBackend, plot::Combined; scale::Real = 1, use_backgroundcolor = false)
"""
function plot2img(Backend, plot::Combined; scale::Real = 1, use_backgroundcolor = false)
parent = Makie.parent_scene(plot)
# obtain or create the render scene
render_scene = if haskey(parent.theme, :_render_scenes) && haskey(parent.theme._render_scenes.val, plot)
@show keys(parent.theme._render_scenes[])
parent.theme._render_scenes[][plot]
else # we have to create a render scene
println("Rerendering")
scene = create_render_scene(plot; scale = scale)

# set up cache
rs_dict = get!(parent.theme.attributes, :_render_scenes, Dict{Union{Makie.AbstractPlot, Makie.Combined}, Makie.Scene}())
rs_dict[][plot] = scene

scene
end

img = if use_backgroundcolor
Makie.colorbuffer(render_scene)
else # render with transparency, using the alpha-colorbuffer hack
alpha_colorbuffer(Backend, render_scene)
end

return img
end

# Utility function to remove rendercaches
function purge_render_cache!(sc::Scene)
haskey(scene.attributes, :_render_scenes) && delete!(scene.attributes, :_render_scenes)
purge_render_cache!.(scene.children)
end
purge_render_cache!(fig::Figure) = purge_render_cache!(fig.scene)

# Rendering pipeline

# This goes as follows:
# The first time a plot is encountered which has to be rasterized,
# we create the rasterization scene, and connect it to the original Scene's
# attributes.
# Then, we retrieve this scene from `plot._render_scene`, an attribute of the plot
# which we set in the previous step to contain the scene.
# This retrieval

function draw_plot_as_image_with_backend(Backend, scene::Scene, screen::CairoScreen, plot; scale = 1)

# special case if CairoMakie is the backend, since
# we don't want an infinite loop.
if Backend == @__MODULE__
draw_plot_as_image(scene, screen, plot, scale)
return
end

w, h = Int.(scene.px_area[].widths)

img = plot2img(Backend, plot; scale = scale, use_backgroundcolor = false)
Makie.save("hi.png", img) # cache for debugging

surf = Cairo.CairoARGBSurface(to_uint32_color.(img))

Cairo.rectangle(screen.context, 0, 0, w, h)
Cairo.save(screen.context)
Cairo.scale(screen.context, w / surf.width, h / surf.height)
Cairo.set_source_surface(screen.context, surf, 0, 0)
p = Cairo.get_source(screen.context)
Cairo.pattern_set_extend(p, Cairo.EXTEND_PAD) # avoid blurry edges
Cairo.pattern_set_filter(p, Cairo.FILTER_NEAREST)
Cairo.fill(screen.context)
Cairo.restore(screen.context)

return

end

function draw_scene_as_image(Backend, scene::Scene, screen::CairoScreen; scale = 1)
w, h = Int.(scene.px_area[].widths)

render_scene = create_render_scene(scene.plots[begin]; scale = scale)
if length(scene.plots) > 1
push!.(Ref(render_scene), scene.plots[(begin+1):end])
end

img = if RGBAf(Makie.to_color(scene.backgroundcolor[])) == RGBAf(0,0,0,0)
alpha_colorbuffer(Backend, scene)
else
Makie.colorbuffer(scene)
end

Makie.save("hi.png", img) # cache for debugging

surf = Cairo.CairoARGBSurface(to_uint32_color.(img))

Cairo.rectangle(screen.context, 0, 0, w, h)
Cairo.save(screen.context)
Cairo.scale(screen.context, w / surf.width, h / surf.height)
Cairo.set_source_surface(screen.context, surf, 0, 0)
p = Cairo.get_source(screen.context)
Cairo.pattern_set_extend(p, Cairo.EXTEND_PAD) # avoid blurry edges
Cairo.pattern_set_filter(p, Cairo.FILTER_NEAREST)
Cairo.fill(screen.context)
Cairo.restore(screen.context)

end
17 changes: 14 additions & 3 deletions CairoMakie/src/infrastructure.jl
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,20 @@ function cairo_draw(screen::Screen, scene::Scene)
# rasterize it when plotting to vector backends, by using the `rasterize`
# keyword argument. This can be set to a Bool or an Int which describes
# the density of rasterization (in terms of a direct scaling factor.)
if to_value(get(p, :rasterize, false)) != false && should_rasterize
draw_plot_as_image(pparent, screen, p, p[:rasterize][])
else # draw vector
# This can also be set to a `Module` (i.e., )
if to_value(get(p, :rasterize, false)) != false
if p.rasterize[] isa Union{Bool, Int} && should_rasterize
draw_plot_as_image(pparent, screen, p, p[:rasterize][])
elseif p.rasterize[] isa Union{<: Module, Tuple{<: Module, Int}}
backend = p.rasterize[] isa Module ? p.rasterize[] : p.rasterize[][1]
scale = p.rasterize[] isa Module ? 1 : p.rasterize[][2]
draw_plot_as_image_with_backend(backend, pparent, screen, p; scale = scale)
else # rasterization option was not recognized, or should_rasterize
# was false and backend was not selected.
draw_plot(pparent, screen, p)
end
else # draw vector, only if a parent plot has not been rasterized

draw_plot(pparent, screen, p)
end
Cairo.restore(screen.context)
Expand Down
5 changes: 5 additions & 0 deletions GLMakie/src/GLAbstraction/GLUniforms.jl
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@ gl_convert(s::Vector{Matrix{T}}) where {T<:Colorant} = Texture(s)
gl_convert(s::Nothing) = s


# special overloads for rasterization
gl_convert(s::Module) = nothing
gl_convert(s::Tuple{Module, Any}) = nothing


isa_gl_struct(x::AbstractArray) = false
isa_gl_struct(x::NATIVE_TYPES) = false
isa_gl_struct(x::Colorant) = false
Expand Down