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

Script to build image #6

Open
cormullion opened this issue Nov 21, 2020 · 9 comments
Open

Script to build image #6

cormullion opened this issue Nov 21, 2020 · 9 comments

Comments

@cormullion
Copy link
Collaborator

cormullion commented Nov 21, 2020

This Julia script builds a grid of organization logos. There are a few issues.
Screenshot 2020-11-21 at 10 29 36

newer version below

Running this the first time shows a few icons that are actually JPEGs, which Cairo/Luxor doesn't handle:

Warning: skipping FourierFlows.png
Warning: skipping GiovineItalia.png
Warning: skipping HolyLab.png
Warning: skipping Julia-Streamers.png
Warning: skipping JuliaString.png

A workaround is to get these files separately, then convert to PNG, then run the script again (which I did to make the image above).

@SaschaMann
Copy link
Collaborator

A workaround is to get these files separately, then convert to PNG, then run the script again (which I did to make the image above).

Perhaps one can iterate through all images in the folder, check the file output and then use convert to convert them? I don't know what tools Julia has for that, but bash should be able to do it in a few lines.

@cormullion
Copy link
Collaborator Author

That's probably doable. With Images might be more portable, although file is probably not available on Windows...

Or we could say: "Do you want your organization to be in our list? Make sure your icon's a PNG?" ... 😄

@cormullion
Copy link
Collaborator Author

cormullion commented Nov 21, 2020

This version converts stray JPEG icons to PNG. I found that FileIO.query() can look for "magic bytes"...

newer version below

julia-orgs-grid

@ViralBShah
Copy link
Owner

ViralBShah commented Nov 21, 2020

This is awesome! Is it possible to align the names and either shorten the long ones or overflow to next line? Basically have everything fit to a grid?

@ViralBShah
Copy link
Owner

Another nice feature would be the aspect ratio of the whole image - to make it fit on different types of screens.

@ViralBShah
Copy link
Owner

Here's my screenshot from Finder. A bit of a hack, but they naturally have worked hard on placement...
JuliaLogos

@cormullion
Copy link
Collaborator Author

I'll add aspect ratio tomorrow. And I think we can do better than Apple's crappy line breaking here...! 😃

@ViralBShah
Copy link
Owner

Yes, we can certainly do better than Apple's line breaking. I almost wonder why they don't do that already.

@cormullion
Copy link
Collaborator Author

Another version, with more attention to layout.

using Luxor, Images, FileIO

# script to make a grid of org logos

"""
    get_nrows_ncols(N;
            aspectratio=MathConstants.golden)

Return nrows, ncols for a list of length N to be arranged in
`aspectratio`.
"""
function get_nrows_ncols(N;
            aspectratio=MathConstants.golden)
    currentratio = a = b = N
    for i in 1:N
        a₁ = i
        b₁ = convert(Int, ceil(N / a₁))
        ratio = a₁/b₁
        if abs(ratio - aspectratio) < abs(currentratio - aspectratio)
            a = a₁
            b = b₁
            currentratio = ratio
        end
    end
    return a, b
end

"""
    textsplitcamelcasecentered(t, pos, width)

Draw text on a number of rows by splitting text at each uppercase letter.
"""
function textsplitcamelcasecentered(t, pos, width;
                leading = 12)

        # convert camelcase string
        t1 = split(replace(t, r"([[:upper:]])" => s" \1"), ' ', keepempty=false)
        t1 = join(t1, "\n")
        tlines = textlines(t1, width)

        y = pos.y
        for i in 1:length(tlines)
            wd = replace(tlines[i], " " => "")
            te = textextents(wd)
            text(wd, Point(pos.x, y), halign=:center)
            y += leading
        end
end

"""
    buildimagedict!(orgnamelist, workingdir, imagedict)

Make a dictionary, use text list of organization names.
Download PNG images from github.
Store them in `workingdir`.
Fill and return dictionary `imagedict`.
"""
function buildimagedict!(orgnamelist, workingdir, imagedict)
    orgnames = open(orgnamelist) do f
        split(read(f, String))
    end
    sort!(orgnames, lt = (a, b) -> lowercase(a) < lowercase(b))
    for o in orgnames
        # download file if we haven't already
        if !isfile(workingdir * o * ".png")
            @info "downloading icon for $o"
            download("https://github.com/" * o * ".png", workingdir * o * ".png")
        end
    end
    iconlist = filter(f -> endswith(f, ".png"), readdir(workingdir))
    for iconfile in iconlist
        path = joinpath(workingdir, iconfile)
        # add Cairo/Luxor image to dictionary
        try
            q = query(path)
            if typeof(q) == File{DataFormat{:JPEG}}
                @info "   Converting $path to PNG..."
                img = load(path)
                save(path, img)
            end
            imagedict[iconfile] = readpng(path)
        catch e
            # will fail if github gives us a JPEG instead of a PNG
            @warn "skipping $iconfile"
            println("\t", e)
        end
    end
    return imagedict
end

"""
    draworgicon(imagedict, key)

Draw the icon at current origin. Since they might be transparent,
place on white background.
"""
function draworgicon(imagedict, key;
        size=300)
    @layer begin
        sethue("white")
        squircle(O, size/2, size/2, :fill, rt=0.1)
        squircle(O, size/2, size/2, :clip, rt=0.1)
        img = imagedict[key]
        w, h = img.width, img.height
        sf = max(w, h)
        scale(size/sf * 0.95)
        placeimage(img, O, centered=true, 1)
        clipreset()
    end
end

function main(fname, w, h;
        orgnames   = "/tmp/orgnames.txt",
        workingdir = "/tmp/julia-org-logos/",
        aspectratio = MathConstants.golden)

    if !isdir(workingdir)
        mkdir(workingdir)
    end

    # build the dictionary
    imagedict = Dict{String, Any}()
    buildimagedict!(orgnames, workingdir, imagedict)

    # start the drawing
    d = Drawing(w, h, fname)
    origin()
    background(0.1, 0.1, 0.2)
    labelfontsize = 12

    ncols, nrows = get_nrows_ncols(length(imagedict), aspectratio=aspectratio)
    tiles = Tiler(w, h, nrows, ncols, margin=20)
    @info "grid is $nrows rows by $ncols columns"
    @info "to hold $(length(imagedict)) icons"
    fontsize(labelfontsize)
    for (n, k) in enumerate(sort(collect(keys(imagedict))))
        @layer begin
            translate(first(tiles[n]))
            S = min(tiles.tileheight, tiles.tilewidth)/2
            draworgicon(imagedict, k, size = S)
            sethue("white")
            txt = replace(k, ".png" => "")
            txtpos = O + (0, tiles.tileheight/2 - labelfontsize)
            textsplitcamelcasecentered(txt, txtpos, tiles.tilewidth, leading = 12)
        end
    end

    finish()
    return d
end

main("/tmp/julia-orgs-grid.svg", 1400, 1000, aspectratio=MathConstants.golden)

Screenshot 2020-11-22 at 08 33 28

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants