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

Fix blurry text #1494

Merged
merged 6 commits into from
Dec 6, 2021
Merged
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
2 changes: 1 addition & 1 deletion CairoMakie/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ excludes = Set([
"Unicode Marker",
"Depth Shift"
])
excludes2 = Set(["short_tests_90", "short_tests_111", "short_tests_35", "short_tests_13", "short_tests_3"])
excludes2 = Set(["short_tests_90", "short_tests_111", "short_tests_35", "short_tests_3"])

functions = [:volume, :volume!, :uv_mesh]
database = database_filtered(excludes, excludes2, functions=functions)
Expand Down
6 changes: 4 additions & 2 deletions GLMakie/assets/shader/distance_shape.frag
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ flat in vec4 f_uv_texture_bbox;
// These versions of aastep assume that `dist` is a signed distance function
// which has been scaled to be in units of pixels.
float aastep(float threshold1, float dist) {
return min(1.0, f_viewport_from_u_scale)*smoothstep(threshold1-ANTIALIAS_RADIUS, threshold1+ANTIALIAS_RADIUS, dist);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should probably check if that changed anything. For text it was irrelevant, but maybe it matters for scatter?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a difference in characters scatters. I guess it's fine

return smoothstep(threshold1-ANTIALIAS_RADIUS, threshold1+ANTIALIAS_RADIUS, dist);
}
float aastep(float threshold1, float threshold2, float dist) {
return smoothstep(threshold1-ANTIALIAS_RADIUS, threshold1+ANTIALIAS_RADIUS, dist) -
Expand Down Expand Up @@ -154,10 +154,12 @@ void main(){
// But note that this may interfere with object picking.
//if (final_color == f_bg_color)
// discard;

write2framebuffer(final_color, f_id);

// Debug tools:
// * Show the background of the sprite.
// write2framebuffer(mix(final_color, vec4(1,0,0,1), 0.2), f_id);
// write2framebuffer(mix(final_color, vec4(1,0,0,1), 0.2), f_id);
// * Show the antialiasing border around glyphs
// write2framebuffer(vec4(vec3(abs(signed_distance)),1), f_id);
}
59 changes: 51 additions & 8 deletions GLMakie/src/GLVisualize/visualize/particles.jl
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,35 @@ function _default(
sprites(p, s, data)
end


# To map (scale, scale_x, scale_y, scale_z) -> scale
combine_scales(scale, x::Nothing, y::Nothing, z::Nothing) = scale
combine_scales(s::Nothing, x, y, z::Nothing) = Vec2f.(x, y)
combine_scales(s::Nothing, x, y, z) = Vec3f.(x, y, z)

function char_scale_factor(char, font)
# uv * size(ta.data) / Makie.PIXELSIZE_IN_ATLAS[] is the padded glyph size
# normalized to the size the glyph was generated as.
ta = Makie.get_texture_atlas()
lbrt = glyph_uv_width!(ta, char, font)
width = Vec(lbrt[3] - lbrt[1], lbrt[4] - lbrt[2])
width * Vec2f(size(ta.data)) / Makie.PIXELSIZE_IN_ATLAS[]
end

# This works the same for x being widths and offsets
rescale_glyph(char::Char, font, x) = x * char_scale_factor(char, font)
function rescale_glyph(char::Char, font, xs::Vector)
f = char_scale_factor(char, font)
map(xs -> f * x, xs)
end
function rescale_glyph(str::String, font, x)
[x * char_scale_factor(char, font) for char in collect(str)]
end
function rescale_glyph(str::String, font, xs::Vector)
map((char, x) -> x * char_scale_factor(char, font), collect(str), xs)
end


"""
Main assemble functions for sprite particles.
Sprites are anything like distance fields, images and simple geometries
Expand All @@ -271,6 +300,23 @@ function sprites(p, s, data)
rot = vec2quaternion(rot)
delete!(data, :rotation)

# Rescale to include glyph padding and shape
if isa(to_value(p[1]), Char)
scale = map(combine_scales,
pop!(data, :scale, Observable(nothing)),
pop!(data, :scale_x, Observable(nothing)),
pop!(data, :scale_y, Observable(nothing)),
pop!(data, :scale_z, Observable(nothing))
)
font = get(data, :font, Observable(Makie.defaultfont()))
offset = get(data, :offset, Observable(Vec2f(0)))

# The same scaling that needs to be applied to scale also needs to apply
# to offset.
data[:offset] = map(rescale_glyph, p[1], font, offset)
data[:scale] = map(rescale_glyph, p[1], font, scale)
end

@gen_defaults! data begin
shape = const_lift(x-> Int32(primitive_shape(x)), p[1])
position = p[2] => GLBuffer
Expand All @@ -286,10 +332,6 @@ function sprites(p, s, data)
rotation = rot => GLBuffer
image = nothing => Texture
end
# TODO don't make this dependant on some shady type dispatch
if isa(to_value(p[1]), Char) && !isa(to_value(scale), Union{StaticVector, AbstractVector{<: StaticVector}}) # correct dimensions
data[:scale] = const_lift(correct_scale, p[1], scale)
end

@gen_defaults! data begin
offset = primitive_offset(p[1], scale) => GLBuffer
Expand Down Expand Up @@ -350,7 +392,6 @@ function _default(main::TOrSignal{S}, s::Style, data::Dict) where S <: AbstractS
font = to_font("default")
scale_primitive = true
position = const_lift(calc_position, main, start_position, relative_scale, font, atlas)
offset = const_lift(calc_offset, main, relative_scale, font, atlas)
prerender = () -> begin
glDisable(GL_DEPTH_TEST)
glDepthMask(GL_TRUE)
Expand All @@ -360,10 +401,12 @@ function _default(main::TOrSignal{S}, s::Style, data::Dict) where S <: AbstractS
uv_offset_width = const_lift(main) do str
Vec4f[glyph_uv_width!(atlas, c, font) for c = str]
end
scale = const_lift(main, relative_scale) do str, s
Vec2f[glyph_scale!(atlas, c, font, s) for c = str]
end
end

# Rescale to include glyph padding and shape
data[:offset] = map(rescale_glyph, main, data[:font], data[:offset])
data[:scale] = map(rescale_glyph, main, data[:font], data[:scale])

delete!(data, :font)
_default((DISTANCEFIELD, position), s, data)
end
1 change: 0 additions & 1 deletion GLMakie/src/gl_backend.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ using .GLAbstraction
const atlas_texture_cache = Dict{Any, Tuple{Texture{Float16, 2}, Function}}()

function get_texture!(atlas)
Makie.set_glyph_resolution!(Makie.High)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/JuliaPlots/Makie.jl/blob/8bab35403db92bb921738380d4c5ecff8df6ec37/WGLMakie/src/WGLMakie.jl#L49

Is this necessary? If it is we should probably have GLMakie set the glyph resolution on init, not here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is we should probably have GLMakie set the glyph resolution on init, not here.

That's already the case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uh, I think I added this, to make sure Makie gets the correct texture atlas - sadly this is a Makie global variable... E.g., when using GLMakie, then WGLMakie and then switching back to GLMakie you'll get pretty weird artifacts

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, nvm, this works on an existing texture atlas...
activate! is the right place: https://github.com/JuliaPlots/Makie.jl/blob/master/GLMakie/src/GLMakie.jl#L42

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used to get artifacts when doing empty!(Makie.global_texture_atlas) and removing the cached files. But I'm not getting them anymore. I don't think I got when switching between WGLMakie and GLMakie either.

# clean up dead context!
filter!(atlas_texture_cache) do (ctx, tex_func)
if GLAbstraction.context_alive(ctx)
Expand Down
32 changes: 30 additions & 2 deletions WGLMakie/src/particles.jl
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,45 @@ primitive_shape(::Type{<:Rect2}) = Cint(RECTANGLE)
primitive_shape(::Type{T}) where {T} = error("Type $(T) not supported")
primitive_shape(x::Shape) = Cint(x)

function char_scale_factor(char, font)
# uv * size(ta.data) / Makie.PIXELSIZE_IN_ATLAS[] is the padded glyph size
# normalized to the size the glyph was generated as.
ta = Makie.get_texture_atlas()
lbrt = glyph_uv_width!(ta, char, font)
width = Vec(lbrt[3] - lbrt[1], lbrt[4] - lbrt[2])
width * Vec2f(size(ta.data)) / Makie.PIXELSIZE_IN_ATLAS[]
end

# This works the same for x being widths and offsets
rescale_glyph(char::Char, font, x) = x * char_scale_factor(char, font)
function rescale_glyph(char::Char, font, xs::Vector)
f = char_scale_factor(char, font)
map(xs -> f * x, xs)
end
function rescale_glyph(str::String, font, x)
[x * char_scale_factor(char, font) for char in collect(str)]
end
function rescale_glyph(str::String, font, xs::Vector)
map((char, x) -> x * char_scale_factor(char, font), collect(str), xs)
end

using Makie: to_spritemarker

function scatter_shader(scene::Scene, attributes)
# Potentially per instance attributes
per_instance_keys = (:offset, :rotations, :markersize, :color, :intensity,
:uv_offset_width, :marker_offset)
uniform_dict = Dict{Symbol,Any}()

if haskey(attributes, :marker) && attributes[:marker][] isa Union{Char, Vector{Char},String}
font = get(attributes, :font, Observable(Makie.defaultfont()))
attributes[:markersize] = map(rescale_glyph, attributes[:marker], font, attributes[:markersize])
attributes[:marker_offset] = map(rescale_glyph, attributes[:marker], font, attributes[:marker_offset])
end

if haskey(attributes, :marker) && attributes[:marker][] isa Union{Vector{Char},String}
x = pop!(attributes, :marker)
attributes[:uv_offset_width] = lift(x -> Makie.glyph_uv_width!.(collect(x)),
x)
attributes[:uv_offset_width] = lift(x -> Makie.glyph_uv_width!.(collect(x)), x)
uniform_dict[:shape_type] = Cint(3)
end

Expand Down
2 changes: 1 addition & 1 deletion WGLMakie/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ excludes = Set([
"Record Video"
])

excludes2 = Set(["short_tests_83", "short_tests_78", "short_tests_40", "short_tests_13", "short_tests_5", "short_tests_41"])
excludes2 = Set(["short_tests_83", "short_tests_78", "short_tests_40", "short_tests_5", "short_tests_41"])
database = database_filtered(excludes, excludes2)

recorded = joinpath(@__DIR__, "recorded")
Expand Down
11 changes: 7 additions & 4 deletions src/layouting/layouting.jl
Original file line number Diff line number Diff line change
Expand Up @@ -310,12 +310,14 @@ function text_quads(positions, glyphs::AbstractVector, fonts::AbstractVector, te
offsets = Vec2f[]
uv = Vec4f[]
scales = Vec2f[]
pad = GLYPH_PADDING[] / PIXELSIZE_IN_ATLAS[]
broadcast_foreach(positions, glyphs, fonts, textsizes) do offs, c, font, pixelsize
# for (c, font, pixelsize) in zipx(glyphs, fonts, textsizes)
push!(uv, glyph_uv_width!(atlas, c, font))
glyph_bb, extent = FreeTypeAbstraction.metrics_bb(c, font, pixelsize)
push!(scales, widths(glyph_bb))
push!(offsets, minimum(glyph_bb))

push!(scales, widths(glyph_bb) .+ pixelsize * 2pad)
push!(offsets, minimum(glyph_bb) .- pixelsize * pad)
end
return positions, offsets, uv, scales
end
Expand All @@ -326,13 +328,14 @@ function text_quads(positions, glyphs, fonts, textsizes::Vector{<:ScalarOrVector
offsets = Vec2f[]
uv = Vec4f[]
scales = Vec2f[]
pad = GLYPH_PADDING[] / PIXELSIZE_IN_ATLAS[]

broadcast_foreach(positions, glyphs, fonts, textsizes) do positions, glyphs, fonts, textsizes
broadcast_foreach(positions, glyphs, fonts, textsizes) do offs, c, font, pixelsize
push!(uv, glyph_uv_width!(atlas, c, font))
glyph_bb, extent = FreeTypeAbstraction.metrics_bb(c, font, pixelsize)
push!(scales, widths(glyph_bb))
push!(offsets, minimum(glyph_bb))
push!(scales, widths(glyph_bb) .+ pixelsize * 2pad)
push!(offsets, minimum(glyph_bb) .- pixelsize * pad)
end
end

Expand Down
65 changes: 39 additions & 26 deletions src/utilities/texture_atlas.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,29 @@ Base.size(atlas::TextureAtlas) = size(atlas.data)

@enum GlyphResolution High Low

const TEXTURE_RESOLUTION = Ref((2048, 2048))
const CACHE_RESOLUTION_PREFIX = Ref("high")

const HIGH_PIXELSIZE = 64
const LOW_PIXELSIZE = 32

const CACHE_RESOLUTION_PREFIX = Ref("high")
const TEXTURE_RESOLUTION = Ref((2048, 2048))
const PIXELSIZE_IN_ATLAS = Ref(HIGH_PIXELSIZE)
# Small padding results in artifacts during downsampling. It seems like roughly
# 1.5px padding is required for a scaled glyph to be displayed without artifacts.
# E.g. for textsize = 8px we need 1.5/8 * 64 = 12px+ padding (in each direction)
# for things to look clear with a 64px glyph size.
const GLYPH_PADDING = Ref(12)

function set_glyph_resolution!(res::GlyphResolution)
if res == High
TEXTURE_RESOLUTION[] = (2048, 2048)
CACHE_RESOLUTION_PREFIX[] = "high"
PIXELSIZE_IN_ATLAS[] = HIGH_PIXELSIZE
GLYPH_PADDING[] = 12
else
TEXTURE_RESOLUTION[] = (1024, 1024)
CACHE_RESOLUTION_PREFIX[] = "low"
PIXELSIZE_IN_ATLAS[] = LOW_PIXELSIZE
GLYPH_PADDING[] = 6
end
end

Expand Down Expand Up @@ -176,37 +182,44 @@ function glyph_uv_width!(c::Char)
return glyph_uv_width!(get_texture_atlas(), c, defaultfont())
end

function glyph_boundingbox(c::Char, font::NativeFont, pixelsize)
if FT_Get_Char_Index(font, c) == 0
for afont in alternativefonts()
if FT_Get_Char_Index(afont, c) != 0
font = afont
break
end
end
end
bb, ext = FreeTypeAbstraction.metrics_bb(c, font, pixelsize)
return bb
end
# function glyph_boundingbox(c::Char, font::NativeFont, pixelsize)
# if FT_Get_Char_Index(font, c) == 0
# for afont in alternativefonts()
# if FT_Get_Char_Index(afont, c) != 0
# font = afont
# break
# end
# end
# end
# bb, ext = FreeTypeAbstraction.metrics_bb(c, font, pixelsize)
# return bb
# end

function insert_glyph!(atlas::TextureAtlas, glyph::Char, font::NativeFont)
return get!(atlas.mapping, (glyph, FreeTypeAbstraction.fontname(font))) do
downsample = 5 # render font 5x larger, and then downsample back to desired pixelsize
pad = 8 # padd rendered font by 6 pixel in each direction
# We save glyphs as signed distance fields, i.e. we save the distance
# a pixel is away from the edge of a symbol (continuous at the edge).
# To get accurate distances we want to draw the symbol at high
# resolution and then downsample to the PIXELSIZE_IN_ATLAS.
downsample = 5
# To draw a symbol from a sdf we essentially do `color * (sdf > 0)`. For
# antialiasing we smooth out the step function `sdf > 0`. That means we
# need a few values outside the symbol. To guarantee that we have those
# at all relevant scales we add padding to the rendered bitmap and the
# resulting sdf.
pad = GLYPH_PADDING[]

uv_pixel = render(atlas, glyph, font, downsample, pad)
tex_size = Vec2f(size(atlas.data) .- 1) # starts at 1

idx_left_bottom = minimum(uv_pixel)# 0 based!!!
# 0 based
idx_left_bottom = minimum(uv_pixel)
idx_right_top = maximum(uv_pixel)

# include padding
left_bottom_pad = idx_left_bottom .+ pad .- 1
# -1 for indexing offset
right_top_pad = idx_right_top .- pad


# transform to normalized texture coordinates
uv_left_bottom_pad = (left_bottom_pad) ./ tex_size
uv_right_top_pad = (right_top_pad) ./ tex_size
# -1 for indexing offset
uv_left_bottom_pad = (idx_left_bottom) ./ tex_size
uv_right_top_pad = (idx_right_top .- 1) ./ tex_size

uv_offset_rect = Vec4f(uv_left_bottom_pad..., uv_right_top_pad...)
i = atlas.index
Expand Down