From da4414ce18a86b3de33bca32bd30e4b2a3f317c1 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 21 Jul 2023 15:44:36 +0200 Subject: [PATCH 1/2] 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 From e3835c192da3b6a04274ebcb39997767a2e8d0db Mon Sep 17 00:00:00 2001 From: Florian Date: Fri, 21 Jul 2023 15:46:44 +0200 Subject: [PATCH 2/2] Hook up ticksize attribute for Axis3 (#2354) * hook up ticksize attribute for Axis3 * project tick segments positions to pixel space * remove useless statement * remove :pixel arg --------- Co-authored-by: Anshul Singhvi Co-authored-by: Simon --- src/makielayout/blocks/axis3d.jl | 38 ++++++++++++++++++-------------- src/makielayout/types.jl | 6 +++++ 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/makielayout/blocks/axis3d.jl b/src/makielayout/blocks/axis3d.jl index 2fc0fa72e62..b9483559f67 100644 --- a/src/makielayout/blocks/axis3d.jl +++ b/src/makielayout/blocks/axis3d.jl @@ -477,10 +477,11 @@ function add_ticks_and_ticklabels!(topscene, scene, ax, dim::Int, limits, tickno map!(ticklabels, ticknode) do (values, labels) labels end + ticksize = attr(:ticksize) tick_segments = lift(topscene, limits, tickvalues, miv, min1, min2, - scene.camera.projectionview, scene.px_area) do lims, ticks, miv, min1, min2, - pview, pxa + scene.camera.projectionview, scene.px_area, ticksize) do lims, ticks, miv, min1, min2, + pview, pxa, tsize f1 = !min1 ? minimum(lims)[d1] : maximum(lims)[d1] f2 = min2 ? minimum(lims)[d2] : maximum(lims)[d2] @@ -490,23 +491,28 @@ function add_ticks_and_ticklabels!(topscene, scene, ax, dim::Int, limits, tickno diff_f1 = f1 - f1_oppo diff_f2 = f2 - f2_oppo - map(ticks) do t + o = pxa.origin + + return map(ticks) do t p1 = dpoint(t, f1, f2) p2 = if dim == 3 # special case the z axis, here it depends on azimuth in which direction the ticks go if 45 <= mod1(rad2deg(azimuth[]), 180) <= 135 - dpoint(t, f1 + 0.03 * diff_f1, f2) + dpoint(t, f1 + diff_f1, f2) else - dpoint(t, f1, f2 + 0.03 * diff_f2) + dpoint(t, f1, f2 + diff_f2) end else - dpoint(t, f1 + 0.03 * diff_f1, f2) + dpoint(t, f1 + diff_f1, f2) end - (p1, p2) - end - end + pp1 = Point2f(o + Makie.project(scene, p1)) + pp2 = Point2f(o + Makie.project(scene, p2)) + diff_pp = Makie.GeometryBasics.normalize(Point2f(pp2 - pp1)) + return (pp1, pp1 .+ Float32(tsize) .* diff_pp) + end + end # we are going to transform the 3d tick segments into 2d of the topscene # because otherwise they # be cut when they extend beyond the scene boundary @@ -516,10 +522,13 @@ function add_ticks_and_ticklabels!(topscene, scene, ax, dim::Int, limits, tickno end end - ticks = linesegments!(topscene, tick_segments_2dz, + ticks = linesegments!(topscene, tick_segments, xautolimits = false, yautolimits = false, zautolimits = false, transparency = true, inspectable = false, color = attr(:tickcolor), linewidth = attr(:tickwidth), visible = attr(:ticksvisible)) + # -10000 is an arbitrary weird constant that in preliminary testing didn't seem + # to clip into plot objects anymore + translate!(ticks, 0, 0, -10000) labels_positions = Observable{Any}() map!(topscene, labels_positions, scene.px_area, scene.camera.projectionview, @@ -528,12 +537,8 @@ function add_ticks_and_ticklabels!(topscene, scene, ax, dim::Int, limits, tickno o = pxa.origin points = map(ticksegs) do (tstart, tend) - tstartp = Point2f(o + Makie.project(scene, tstart)) - tendp = Point2f(o + Makie.project(scene, tend)) - - offset = pad * Makie.GeometryBasics.normalize( - Point2f(tendp - tstartp)) - tendp + offset + offset = pad * Makie.GeometryBasics.normalize(Point2f(tend - tstart)) + tend + offset end N = min(length(ticklabs), length(points)) @@ -609,7 +614,6 @@ function add_ticks_and_ticklabels!(topscene, scene, ax, dim::Int, limits, tickno if slight_flip offset_ang_90deg_alwaysup += pi end - offset_ang_90deg_alwaysup labelrotation = if lrotation == Makie.automatic offset_ang_90deg_alwaysup diff --git a/src/makielayout/types.jl b/src/makielayout/types.jl index 5fa462f2644..2216c311bac 100644 --- a/src/makielayout/types.jl +++ b/src/makielayout/types.jl @@ -1505,6 +1505,12 @@ end ytickwidth = 1 "The z tick width" ztickwidth = 1 + "The size of the xtick marks." + xticksize::Float64 = 12f0 + "The size of the ytick marks." + yticksize::Float64 = 12f0 + "The size of the ztick marks." + zticksize::Float64 = 12f0 "The color of x spine 1 where the ticks are displayed" xspinecolor_1 = :black "The color of y spine 1 where the ticks are displayed"