├── .gitignore ├── matrix_readme.png ├── normal_readme.png ├── README.md └── poster.jl /.gitignore: -------------------------------------------------------------------------------- 1 | posters 2 | -------------------------------------------------------------------------------- /matrix_readme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wikunia/CodePoster/master/matrix_readme.png -------------------------------------------------------------------------------- /normal_readme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wikunia/CodePoster/master/normal_readme.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CodePoster 2 | 3 | You're proud of your new code project and just want to see it day and night? 4 | Make a poster out of your code with **CodePoster**. 5 | 6 | If you want to read more about the project please check out my [blog post](http://opensourc.es/blog/codeposter) 7 | 8 | ## Possible results 9 | ![Standard version](normal_readme.png) 10 | 11 | ![Matrix version](matrix_readme.png) 12 | 13 | 14 | ## Installation 15 | This project uses [Julia](http://julialang.org) and the following extensions: 16 | ``` 17 | ArgParse 18 | Colors 19 | Luxor 20 | Images 21 | FileIO 22 | ``` 23 | which you can install by running `julia` and then `] add ArgParse Colors Luxor Images FileIO` where `]` gets you into the package manager. 24 | 25 | ## How to use 26 | You need to be able to execute `poster.jl` so run sth like: `chmod +x poster.jl` 27 | 28 | Then you have the following options: 29 | 30 | ``` 31 | usage: ./poster.jl -f FOLDER [--fsize FSIZE] [--ext EXT] 32 | [--ignore IGNORE] [-t CENTER_TEXT] 33 | [--fsize_text FSIZE_TEXT] [-c CENTER_COLOR] 34 | [--code_color_range CODE_COLOR_RANGE] [--width WIDTH] 35 | [--height HEIGHT] [--dpi DPI] [--start_x START_X] 36 | [--start_y START_Y] [--line_margin LINE_MARGIN] [-h] 37 | 38 | optional arguments: 39 | -f, --folder FOLDER The code folder 40 | --fsize FSIZE The font size for the code. Will be determined 41 | automatically if not specified (type: Float64, 42 | default: -1.0) 43 | --ext EXT File extensions of the code seperate by , i.e 44 | jl,js,py (type: Regex, default: 45 | r"(\.jl|\.py|\.js|\.php)") 46 | --ignore IGNORE Ignore all paths of the following form. Form 47 | as in --ext (type: Regex, default: 48 | r"(\/\.git|test|Ideas|docs)") 49 | -t, --center_text CENTER_TEXT 50 | The text which gets displayed in the center of 51 | your poster (default: "Test") 52 | --center_fsize CENTER_FSIZE_ 53 | The font size for the center text. (type: 54 | Float64, default: 1400.0) 55 | -c, --center_color CENTER_COLOR 56 | The color of center_text specify as r,g,b 57 | (type: RGB, default: 58 | RGB{Float64}(1.0,0.73,0.0)) 59 | --code_color_range CODE_COLOR_RANGE 60 | Range for the random color of each code line 61 | i.e 0.2-0.5 for a color 62 | between RGB(0.2,0.2,0.2) and RGB(0.5,0.5,0.5) 63 | or 0.1-0.3,0.2-0.5,0-1 to specify a range for 64 | r,g and b (default: "0.2-0.5") 65 | --width WIDTH Width of the poster in cm (type: Float64, 66 | default: 70.0) 67 | --height HEIGHT Width of the poster in cm (type: Float64, 68 | default: 50.0) 69 | --dpi DPI DPI (type: Float64, default: 300.0) 70 | --start_x START_X Start value for x like a padding left and 71 | right (type: Int64, default: 10) 72 | --start_y START_Y Start value for y like a padding top and 73 | "bottom" (type: Int64, default: 10) 74 | --line_margin LINE_MARGIN 75 | Margin between two lines (type: Int64, 76 | default: 5) 77 | -h, --help show this help message and exit 78 | ``` 79 | 80 | You need to specify the folder which contains your code and probably want to change the center text as well. 81 | 82 | ``` 83 | ./poster.jl -f /path/to/folder -t TEXT 84 | ``` 85 | 86 | Additionally you can specify the size of the poster, the padding left/right and top/bottom the spacing between two lines, several colors, font sizes etc... 87 | 88 | The following code was used for the matrix version which you can see above: 89 | 90 | ``` 91 | ./poster.jl -f /path/to/folder --ext "(\.jl)" --code_color_range 0.0-0.0,0.4-0.9,0.0-0.0 -c 0,0.2,0 92 | ``` 93 | 94 | this includes only the Julia files in your code project and uses colors 95 | between `RGB(0,0.4-0.9,0)` for your code (randomly in that range) and the color of your center text is: `RGB(0,0.2,0)`. -------------------------------------------------------------------------------- /poster.jl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env julia 2 | using ArgParse 3 | using Colors 4 | 5 | function extension(filename::String) 6 | try 7 | return match(r"\.[A-Za-z0-9]+$", filename).match 8 | catch 9 | return "" 10 | end 11 | end 12 | 13 | function floodfill!(img::Matrix{<:Color}, initnode::CartesianIndex{2}, outside::Color, replace::Color; 14 | last_check=nothing, last_check_params=nothing) 15 | if isapprox(outside, replace; atol=0.05) 16 | @error "The outside color shouldn't be the same as the replace color" 17 | return img 18 | end 19 | isapprox(img[initnode],outside; atol=0.05) && return img 20 | 21 | # constants 22 | north = CartesianIndex(-1, 0) 23 | south = CartesianIndex( 1, 0) 24 | east = CartesianIndex( 0, 1) 25 | west = CartesianIndex( 0, -1) 26 | 27 | queue = [initnode] 28 | sizehint!(queue,200) 29 | c = 1 30 | wnode = nothing 31 | enode = nothing 32 | changed_at = Vector{CartesianIndex}() 33 | sizehint!(changed_at, 200) 34 | while !isempty(queue) 35 | node = pop!(queue) 36 | if !isapprox(img[node],outside; atol=0.05) && img[node] != replace 37 | wnode = node 38 | enode = node + east 39 | end 40 | # Move west until color of node does match outside color 41 | while checkbounds(Bool, img, wnode) && !isapprox(img[wnode],outside; atol=0.05) && !isapprox(img[wnode],replace; atol=0.05) 42 | push!(changed_at, wnode) 43 | img[wnode] = replace 44 | if checkbounds(Bool, img, wnode + north) && !isapprox(img[wnode + north],outside; atol=0.05) && !isapprox(img[wnode + north],replace; atol=0.05) 45 | push!(queue, wnode + north) 46 | end 47 | if checkbounds(Bool, img, wnode + south) && !isapprox(img[wnode + south],outside; atol=0.05) && !isapprox(img[wnode + south],replace; atol=0.05) 48 | push!(queue, wnode + south) 49 | end 50 | wnode += west 51 | end 52 | # Move east until color of node does match outside color 53 | while checkbounds(Bool, img, enode) && !isapprox(img[enode],outside; atol=0.05) && !isapprox(img[enode],replace; atol=0.05) 54 | push!(changed_at, enode) 55 | img[enode] = replace 56 | if checkbounds(Bool, img, enode + north) && !isapprox(img[enode + north],outside; atol=0.05) && !isapprox(img[enode + north],replace; atol=0.05) 57 | push!(queue, enode + north) 58 | end 59 | if checkbounds(Bool, img, enode + south) && !isapprox(img[enode + south],outside; atol=0.05) && !isapprox(img[enode + south],replace; atol=0.05) 60 | push!(queue, enode + south) 61 | end 62 | enode += east 63 | end 64 | c += 1 65 | end 66 | if last_check != nothing 67 | return last_check(img, changed_at, [img[x] for x in changed_at], last_check_params) 68 | end 69 | return img 70 | end 71 | 72 | function more_than(img, changed_at, changed_from, params) 73 | outside_counter = 0 74 | inside_counter = 0 75 | for yx in changed_at 76 | if params[:img][yx] == params[:outside_color] 77 | outside_counter += 1 78 | else 79 | inside_counter += 1 80 | end 81 | end 82 | if inside_counter/(inside_counter+outside_counter) > params[:more_than] 83 | return img 84 | else 85 | i = 1 86 | for yx in changed_at 87 | img[yx] = changed_from[i] 88 | i += 1 89 | end 90 | return img 91 | end 92 | end 93 | 94 | function parse_code_color(code_color_between) 95 | parts = split(code_color_between,",") 96 | color_picker = Dict{Symbol,Tuple{Float64,Float64}}() 97 | if length(parts) == 1 98 | range = parse.(Float64,split(parts[1],"-")) 99 | color_picker[:r] = (range[1], range[2]) 100 | color_picker[:g] = (range[1], range[2]) 101 | color_picker[:b] = (range[1], range[2]) 102 | return color_picker 103 | elseif length(parts) == 3 104 | range_r = parse.(Float64,split(parts[1],"-")) 105 | color_picker[:r] = (range_r[1], range_r[2]) 106 | range_g = parse.(Float64,split(parts[2],"-")) 107 | color_picker[:g] = (range_g[1], range_g[2]) 108 | range_b = parse.(Float64,split(parts[3],"-")) 109 | color_picker[:b] = (range_b[1], range_b[2]) 110 | return color_picker 111 | else 112 | @error "Your code color range parameter must be of the form Float64-Float64 i.e 0.2-0.5 or three of those values for r,g,b" 113 | @info "The default range is used now" 114 | color_picker[:r] = (0.2, 0.5) 115 | color_picker[:g] = (0.2, 0.5) 116 | color_picker[:b] = (0.2, 0.5) 117 | return color_picker 118 | end 119 | end 120 | 121 | function rand_between(bounds::Tuple{Float64,Float64}) 122 | return bounds[1]+rand()*bounds[2] 123 | end 124 | 125 | function automatic_font_size(args, size_x, size_y) 126 | folder = args["folder"] 127 | ext_pattern = args["ext"] 128 | ignore = args["ignore"] 129 | start_x = args["start_x"] 130 | start_y = args["start_y"] 131 | line_margin = args["line_margin"] 132 | 133 | test_line = "" 134 | Drawing(size_x, size_y, "code.png") 135 | nchars = 0 136 | cl = 0 137 | for (root, dirs, files) in walkdir(folder) 138 | if !occursin(ignore,root) 139 | for file in files 140 | if occursin(ext_pattern,extension(file)) 141 | open(root*"/"*file) do file 142 | for ln in eachline(file) 143 | stripped = strip(ln)*" " 144 | stripped = replace(stripped, r"\s+" => " ") 145 | nchars += length(stripped) 146 | cl += 1 147 | if cl % 10 == 0 148 | test_line *= stripped 149 | end 150 | end 151 | end 152 | end 153 | end 154 | end 155 | end 156 | fsize = 1 157 | pixel_for_chars = (size_x-2*start_x-fsize/2)*(size_y-2*start_y) 158 | possible_pixel_per_char = pixel_for_chars/nchars 159 | c_pixel_per_char = 0 160 | while c_pixel_per_char < possible_pixel_per_char 161 | fontsize(fsize) 162 | w_adv = textextents(test_line)[5] 163 | w_adv /= length(test_line) 164 | c_pixel_per_char = (line_margin+fsize)*w_adv 165 | pixel_for_chars = (size_x-2*start_x-fsize/2)*(size_y-2*start_y) 166 | possible_pixel_per_char = pixel_for_chars/nchars 167 | fsize += 0.1 168 | end 169 | fsize -= 0.2 170 | println("Automatic font size: ", fsize) 171 | finish() 172 | return fsize 173 | end 174 | 175 | function combine_text_code(center_color) 176 | text_img = load("text.png") 177 | code_img = load("code.png") 178 | 179 | last_check_params = Dict{Symbol,Any}() 180 | last_check_params[:img] = text_img 181 | last_check_params[:outside_color] = RGB(0,0,0) 182 | last_check_params[:more_than] = 0.75 # 75% 183 | 184 | size_y, size_x = size(text_img) 185 | 186 | println("Combining text and code...") 187 | @time begin 188 | for y in 1:size_y, x in 1:size_x 189 | if text_img[y,x] != RGB(0,0,0) 190 | if !isapprox(code_img[y,x],RGB(0,0,0); atol=0.05) && !isapprox(code_img[y,x],center_color; atol=0.05) 191 | yx = CartesianIndex(y,x) 192 | code_img = floodfill!(code_img, yx, RGB(0,0,0), center_color; last_check=more_than, 193 | last_check_params=last_check_params) 194 | end 195 | end 196 | end 197 | end 198 | return code_img 199 | end 200 | 201 | function create_poster(args) 202 | folder = args["folder"] 203 | fsize = args["fsize"] 204 | font_size_center_text = args["center_fsize"] 205 | ext_pattern = args["ext"] 206 | ignore = args["ignore"] 207 | center_text = args["center_text"] 208 | start_x = args["start_x"] 209 | start_y = args["start_y"] 210 | height = args["height"] 211 | width = args["width"] 212 | line_margin = args["line_margin"] 213 | center_color = args["center_color"] 214 | code_color_between = args["code_color_range"] 215 | rand_color = parse_code_color(code_color_between) 216 | 217 | size_x = convert(Int,round(0.393701*width*args["dpi"])) 218 | size_y = convert(Int,round(0.393701*height*args["dpi"])) 219 | 220 | println("Size of the poster in pixel: ", string(size_x)*"x"*string(size_y)) 221 | 222 | # estimate the fontsize if currently set to -1 223 | if fsize == -1 224 | fsize = automatic_font_size(args, size_x, size_y) 225 | end 226 | 227 | Drawing(size_x, size_y, "code.png") 228 | origin(Point(fsize,fsize)) 229 | background("black") 230 | fontsize(fsize) 231 | last_x = start_x 232 | last_y = fsize+start_y 233 | for (root, dirs, files) in walkdir(folder) 234 | if !occursin(ignore,root) 235 | for file in files 236 | if occursin(ext_pattern, extension(file)) 237 | open(root*"/"*file) do file 238 | for ln in eachline(file) 239 | stripped = strip(ln)*" " 240 | stripped = replace(stripped, r"\s+" => " ") 241 | size_ln = textextents(stripped) 242 | xadv_ln = size_ln[5] 243 | yadv_ln = size_ln[6] 244 | sethue(rand_between(rand_color[:r]), 245 | rand_between(rand_color[:g]), 246 | rand_between(rand_color[:b])) 247 | if last_x+xadv_ln > size_x-start_x 248 | # print it anyway but go to next line later 249 | origin(Point(last_x,last_y)) 250 | break_after = 1 251 | stripped_part = stripped[1:1] 252 | size_ln_part = textextents(stripped_part) 253 | while last_x+size_ln_part[5] <= size_x-start_x 254 | break_after = nextind(stripped,break_after) 255 | stripped_part = stripped[1:break_after] 256 | size_ln_part = textextents(stripped_part) 257 | end 258 | text(stripped[1:prevind(stripped,break_after)]) 259 | origin(Point(start_x,last_y+fsize+line_margin)) 260 | text(stripped[break_after:end]) 261 | last_y += fsize+line_margin 262 | last_x = textextents(stripped[break_after:end])[5] 263 | else 264 | origin(Point(last_x,last_y)) 265 | text(stripped) 266 | last_x += xadv_ln 267 | end 268 | end 269 | end 270 | end 271 | end 272 | end 273 | end 274 | println("last_y: ", last_y+fsize) 275 | if last_y+fsize > size_y 276 | @warn "You might wanna choose a different font size as at the moment some part of your awesome code isn't in the poster" 277 | end 278 | println("last_x: ", last_x) 279 | finish() 280 | println("Saved code") 281 | 282 | Drawing(size_x, size_y, "text.png") 283 | fontsize(font_size_center_text) 284 | wtext, htext = textextents(center_text)[3:4] 285 | xbtext, ybtext = textextents("a")[1:2] 286 | background("black") 287 | sethue(center_color) 288 | text(center_text, (size_x-wtext)/2, (size_y-htext)/2+htext/2-ybtext/2) 289 | finish() 290 | println("Saved text") 291 | 292 | code_img = combine_text_code(center_color) 293 | save("poster.png", code_img) 294 | println("DONE!!!") 295 | println() 296 | println("=======================================================================================================================================") 297 | println("Before you print your poster please make sure that everything looks as expected ;)") 298 | println("Take special care at the corners of the poster. If the code overflows or there is a big gap at the end try to change the font size.") 299 | println("If it doesn't work as expected please file an issue.") 300 | println("Otherwise enjoy your poster and consider a small donation via PayPal:") 301 | println("https://www.paypal.com/donate/?token=lq0Of0tLY5KGhmPV_2QDngKBt1vUucXysZNzWiC2Zs5V9lEWKXth8ksnUZBqxtL5yDCLHG&country.x=GB&locale.x=GB") 302 | println("=======================================================================================================================================") 303 | end 304 | 305 | function ArgParse.parse_item(::Type{Regex}, x::AbstractString) 306 | return Regex(x) 307 | end 308 | 309 | function ArgParse.parse_item(::Type{RGB}, x::AbstractString) 310 | parts = parse.(Float64,split(x,",")) 311 | return RGB(parts...) 312 | end 313 | 314 | function parse_commandline() 315 | s = ArgParseSettings() 316 | 317 | @add_arg_table s begin 318 | "--folder", "-f" 319 | help = "The code folder" 320 | required = true 321 | "--fsize" 322 | help = "The font size for the code. Will be determined automatically if not specified" 323 | arg_type = Float64 324 | default = -1.0 325 | "--ext" 326 | help = "File extensions of the code seperate by , i.e jl,js,py" 327 | arg_type = Regex 328 | default = r"(\.jl|\.py|\.js|\.php)" 329 | "--ignore" 330 | help = "Ignore all paths of the following form. Form as in --ext" 331 | arg_type = Regex 332 | default = r"(\/\.git|test|Ideas|docs)" 333 | "--center_text", "-t" 334 | help = "The text which gets displayed in the center of your poster" 335 | arg_type = String 336 | default = "Test" 337 | "--center_fsize" 338 | help = "The font size for the center text." 339 | arg_type = Float64 340 | default = 1400.0 341 | "--center_color", "-c" 342 | help = "The color of center_text specify as r,g,b" 343 | arg_type = RGB 344 | default = RGB(1.0,0.73,0.0) 345 | "--code_color_range" 346 | help = "Range for the random color of each code line i.e 0.2-0.5 347 | for a color between RGB(0.2,0.2,0.2) and RGB(0.5,0.5,0.5) or 0.1-0.3,0.2-0.5,0-1 to specify a range for r,g and b" 348 | arg_type = String 349 | default = "0.2-0.5" 350 | "--width" 351 | help = "Width of the poster in cm" 352 | arg_type = Float64 353 | default = 70.0 354 | "--height" 355 | help = "Width of the poster in cm" 356 | arg_type = Float64 357 | default = 50.0 358 | "--dpi" 359 | help = "DPI" 360 | arg_type = Float64 361 | default = 300.0 362 | "--start_x" 363 | help = "Start value for x like a padding left and right" 364 | arg_type = Int64 365 | default = 10 366 | "--start_y" 367 | help = "Start value for y like a padding top and \"bottom\"" 368 | arg_type = Int64 369 | default = 10 370 | "--line_margin" 371 | help = "Margin between two lines" 372 | arg_type = Int64 373 | default = 5 374 | end 375 | 376 | return parse_args(s) 377 | end 378 | 379 | if isinteractive() == false 380 | args = parse_commandline() 381 | println("Parsed arguments") 382 | using Luxor 383 | using Images 384 | using FileIO 385 | println("Included all libraries") 386 | create_poster(args) 387 | end --------------------------------------------------------------------------------