From da4414ce18a86b3de33bca32bd30e4b2a3f317c1 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 21 Jul 2023 15:44:36 +0200 Subject: [PATCH] better colorbuffer, fix record + window hiding when using save (#3078) * fix issues with saving + record * add colorbuffer for axis + subscenes * fixes + tests * add news entry --- GLMakie/src/screen.jl | 9 +++++++-- NEWS.md | 7 ++++--- ReferenceTests/src/tests/short_tests.jl | 25 +++++++++++++++++++++++ src/Makie.jl | 2 +- src/display.jl | 25 ++++++++++++++++++----- src/makielayout/blocks/axis.jl | 27 +++++++++++++++++++++++++ 6 files changed, 84 insertions(+), 11 deletions(-) diff --git a/GLMakie/src/screen.jl b/GLMakie/src/screen.jl index 0bf5028cb50..24432b071c6 100644 --- a/GLMakie/src/screen.jl +++ b/GLMakie/src/screen.jl @@ -342,7 +342,6 @@ function apply_config!(screen::Screen, config::ScreenConfig; start_renderloop::B replace_processor!(config.fxaa ? fxaa_postprocessor : empty_postprocessor, 3) # Set the config screen.config = config - if start_renderloop start_renderloop!(screen) else @@ -688,7 +687,13 @@ function Makie.colorbuffer(screen::Screen, format::Makie.ImageStorageFormat = Ma # GLFW.PollEvents() # keep current buffer size to allows larger-than-window renders render_frame(screen, resize_buffers=false) # let it render - glFinish() # block until opengl is done rendering + if screen.config.visible + GLFW.SwapBuffers(to_native(screen)) + else + # SwapBuffers blocks as well, but if we don't call that + # We need to call glFinish to wait for all OpenGL changes to finish + glFinish() + end if size(ctex) != size(screen.framecache) screen.framecache = Matrix{RGB{N0f8}}(undef, size(ctex)) end diff --git a/NEWS.md b/NEWS.md index ea97f41d58b..dba0dff7c55 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,14 +2,15 @@ ## master +- Exported colorbuffer, and added `colorbuffer(axis::Axis; include_decorations=false, colorbuffer_kws...)`, to get an image of an axis with or without decorations [#3078](https://github.com/MakieOrg/Makie.jl/pull/3078). - Fixed an issue where the `linestyle` of some polys was not applied to the stroke in CairoMakie. [#2604](https://github.com/MakieOrg/Makie.jl/pull/2604) - Add `colorscale = identity` to any plotting function using a colormap. This works with any scaling function like `log10`, `sqrt` etc. Consequently, `scale` for `hexbin` is replaced with `colorscale` [#2900](https://github.com/MakieOrg/Makie.jl/pull/2900). - Add `alpha=1.0` argument to all basic plots, which supports independently adding an alpha component to colormaps and colors. Multiple alphas like in `plot(alpha=0.2, color=RGBAf(1, 0, 0, 0.5))`, will get multiplied [#2900](https://github.com/MakieOrg/Makie.jl/pull/2900). - `hexbin` now supports any per-observation weights which StatsBase respects - `<: StatsBase.AbstractWeights`, `Vector{Real}`, or `nothing` (the default). [#2804](https://github.com/MakieOrg/Makie.jl/pulls/2804) -- Added a new Axis type, `PolarAxis`, which is an axis with a polar projection. Input is in `(r, theta)` coordinates and is transformed to `(x, y)` coordinates using the standard polar-to-cartesian transformation. - Generally, its attributes are very similar to the usual `Axis` attributes, but `x` is replaced by `r` and `y` by `θ`. +- Added a new Axis type, `PolarAxis`, which is an axis with a polar projection. Input is in `(r, theta)` coordinates and is transformed to `(x, y)` coordinates using the standard polar-to-cartesian transformation. + Generally, its attributes are very similar to the usual `Axis` attributes, but `x` is replaced by `r` and `y` by `θ`. It also inherits from the theme of `Axis` in this manner, so should work seamlessly with Makie themes [#2990](https://github.com/MakieOrg/Makie.jl/pull/2990). -- `inherit` now has a new signature `inherit(scene, attrs::NTuple{N, Symbol}, default_value)`, allowing recipe authors to access nested attributes when trying to inherit from the parent Scene. +- `inherit` now has a new signature `inherit(scene, attrs::NTuple{N, Symbol}, default_value)`, allowing recipe authors to access nested attributes when trying to inherit from the parent Scene. For example, one could inherit from `scene.Axis.yticks` by `inherit(scene, (:Axis, :yticks), $default_value)` [#2990](https://github.com/MakieOrg/Makie.jl/pull/2990). - Fixed incorrect rendering of 3D heatmaps [#2959](https://github.com/MakieOrg/Makie.jl/pull/2959) - Deprecated `flatten_plots` in favor of `collect_atomic_plots`. Using the new `collect_atomic_plots` fixed a bug in CairoMakie where the z-level of plots within recipes was not respected. [#2793](https://github.com/MakieOrg/Makie.jl/pull/2793) diff --git a/ReferenceTests/src/tests/short_tests.jl b/ReferenceTests/src/tests/short_tests.jl index 57baa421b1e..c939fc60626 100644 --- a/ReferenceTests/src/tests/short_tests.jl +++ b/ReferenceTests/src/tests/short_tests.jl @@ -257,6 +257,31 @@ end f end +@reference_test "colorbuffer for axis" begin + fig = Figure() + ax1 = Axis(fig[1, 1]) + ax2 = Axis(fig[1, 2]) + ax3 = Axis(fig[2, 2]) + ax4 = Axis(fig[2, 1]) + scatter!(ax1, 1:10, 1:10; markersize=50, color=1:10) + scatter!(ax2, 1:10, 1:10; markersize=50, color=:red) + heatmap!(ax3, -8:0.1:8, 8:0.1:8, (x, y) -> sin(x) + cos(y)) + meshscatter!(ax4, 1:10, 1:10; markersize=1, color=:red) + img1 = colorbuffer(ax1; include_decorations=true) + img2 = colorbuffer(ax2; include_decorations=false) + img3 = colorbuffer(ax3; include_decorations=true) + img4 = colorbuffer(ax4; include_decorations=false) + f, ax5, pl = image(rotr90(img1); axis=(; aspect=DataAspect())) + ax6, pl = image(f[1, 2], rotr90(img2); axis=(; aspect=DataAspect())) + ax7, pl = image(f[2, 2], rotr90(img3); axis=(; aspect=DataAspect())) + ax8, pl = image(f[2, 1], rotr90(img4); axis=(; aspect=DataAspect())) + hidedecorations!(ax5) + hidedecorations!(ax6) + hidedecorations!(ax7) + hidedecorations!(ax8) + f +end + # Needs a way to disable autolimits on show # @reference_test "interactions after close" begin diff --git a/src/Makie.jl b/src/Makie.jl index ff0cc254311..2fc66e042f0 100644 --- a/src/Makie.jl +++ b/src/Makie.jl @@ -276,7 +276,7 @@ export abline! # until deprecation removal export Stepper, replay_events, record_events, RecordEvents, record, VideoStream export VideoStream, recordframe!, record, Record -export save +export save, colorbuffer # colormap stuff from PlotUtils, and showgradients export cgrad, available_gradients, showgradients diff --git a/src/display.jl b/src/display.jl index b06739085c0..30b7c10ef74 100644 --- a/src/display.jl +++ b/src/display.jl @@ -320,7 +320,8 @@ function FileIO.save( # If the scene already got displayed, we get the current screen its displayed on # Else, we create a new scene and update the state of the fig update && update_state_before_display!(fig) - screen = getscreen(backend, scene, io, mime; visible=false, screen_config...) + visible = !isnothing(getscreen(scene)) # if already has a screen, don't hide it! + screen = getscreen(backend, scene, io, mime; visible=visible, screen_config...) backend_show(screen, io, mime, scene) end catch e @@ -408,9 +409,16 @@ function getscreen(backend::Union{Missing, Module}, scene::Scene, args...; scree end end +function get_sub_picture(image, format::ImageStorageFormat, rect) + xmin, ymin = minimum(rect) .- (1, 0) + xmax, ymax = maximum(rect) + start = size(image, 1) - ymax + stop = size(image, 1) - ymin + return image[start:stop, xmin:xmax] +end + """ - colorbuffer(scene, format::ImageStorageFormat = JuliaNative; backend=current_backend(), screen_config...) - colorbuffer(screen, format::ImageStorageFormat = JuliaNative) + colorbuffer(scene, format::ImageStorageFormat = JuliaNative; update=true, backend=current_backend(), screen_config...) Returns the content of the given scene or screen rasterised to a Matrix of Colors. The return type is backend-dependent, but will be some form of RGB @@ -421,12 +429,19 @@ or RGBA. - `format = GLNative` : Returns a more efficient format buffer for GLMakie which can be directly used in FFMPEG without conversion - `screen_config`: Backend dependend, look up via `?Backend.Screen`/`Base.doc(Backend.Screen)` +- `update=true`: resets/updates limits. Set to false, if you want to preserver camera movements. """ function colorbuffer(fig::FigureLike, format::ImageStorageFormat = JuliaNative; update=true, backend = current_backend(), screen_config...) scene = get_scene(fig) update && update_state_before_display!(fig) - screen = getscreen(backend, scene, format; start_renderloop=false, visible=false, screen_config...) - return colorbuffer(screen, format) + visible = !isnothing(getscreen(scene)) # if already has a screen, don't hide it! + screen = getscreen(backend, scene; start_renderloop=false, visible=visible, screen_config...) + img = colorbuffer(screen, format) + if !isroot(scene) + return get_sub_picture(img, format, pixelarea(scene)[]) + else + return img + end end # Fallback for any backend that will just use colorbuffer to write out an image diff --git a/src/makielayout/blocks/axis.jl b/src/makielayout/blocks/axis.jl index 4151a899207..8656998749a 100644 --- a/src/makielayout/blocks/axis.jl +++ b/src/makielayout/blocks/axis.jl @@ -1785,3 +1785,30 @@ function attribute_examples(::Type{Axis}) ], ) end + +function axis_bounds_with_decoration(axis::Axis) + # Filter out the zoomrect + background plot + lims = Makie.data_limits(axis.blockscene.plots, p -> p isa Mesh || p isa Poly) + return Makie.parent_transform(axis.blockscene) * lims +end + +""" + colorbuffer(ax::Axis; include_decorations=true, colorbuffer_kws...) + +Gets the colorbuffer of the `Axis` in `JuliaNative` image format. +If `include_decorations=false`, only the inside of the axis is fetched. +""" +function colorbuffer(ax::Axis; include_decorations=true, update=true, colorbuffer_kws...) + if update + update_state_before_display!(ax) + end + bb = if include_decorations + bb = axis_bounds_with_decoration(ax) + Rect2{Int}(round.(Int, minimum(bb)) .+ 1, round.(Int, widths(bb))) + else + pixelarea(ax.scene)[] + end + + img = colorbuffer(root(ax.scene); update=false, colorbuffer_kws...) + return get_sub_picture(img, JuliaNative, bb) +end