ViralBShah / JuliaOrgLogos

MIT License
9 stars 5 forks source link

Script to build image #6

Open cormullion opened 3 years ago

cormullion commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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

ViralBShah commented 3 years ago

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

cormullion commented 3 years ago

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

ViralBShah commented 3 years ago

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

cormullion commented 3 years ago

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