Class: LetterAvatar

Inherits:
Object
  • Object
show all
Defined in:
lib/letter_avatar.rb

Defined Under Namespace

Classes: Identity

Constant Summary collapse

VERSION =

BUMP UP if avatar algorithm changes

5
FULLSIZE =

CHANGE these values to support more pixel ratios

120 * 3
POINTSIZE =
280
COLORS =

palette of optimally distinct colors cf. tools.medialab.sciences-po.fr/iwanthue/index.php parameters used:

- H: 0 - 360
- C: 0 - 2
- L: 0.75 - 1.5
[
  [198, 125, 40],
  [61, 155, 243],
  [74, 243, 75],
  [238, 89, 166],
  [52, 240, 224],
  [177, 156, 155],
  [240, 120, 145],
  [111, 154, 78],
  [237, 179, 245],
  [237, 101, 95],
  [89, 239, 155],
  [43, 254, 70],
  [163, 212, 245],
  [65, 152, 142],
  [165, 135, 246],
  [181, 166, 38],
  [187, 229, 206],
  [77, 164, 25],
  [179, 246, 101],
  [234, 93, 37],
  [225, 155, 115],
  [142, 140, 188],
  [223, 120, 140],
  [249, 174, 27],
  [244, 117, 225],
  [137, 141, 102],
  [75, 191, 146],
  [188, 239, 142],
  [164, 199, 145],
  [173, 120, 149],
  [59, 195, 89],
  [222, 198, 220],
  [68, 145, 187],
  [236, 204, 179],
  [159, 195, 72],
  [188, 121, 189],
  [166, 160, 85],
  [181, 233, 37],
  [236, 177, 85],
  [121, 147, 160],
  [234, 218, 110],
  [241, 157, 191],
  [62, 200, 234],
  [133, 243, 34],
  [88, 149, 110],
  [59, 228, 248],
  [183, 119, 118],
  [251, 195, 45],
  [113, 196, 122],
  [197, 115, 70],
  [80, 175, 187],
  [103, 231, 238],
  [240, 72, 133],
  [228, 149, 241],
  [180, 188, 159],
  [172, 132, 85],
  [180, 135, 251],
  [236, 194, 58],
  [217, 176, 109],
  [88, 244, 199],
  [186, 157, 239],
  [113, 230, 96],
  [206, 115, 165],
  [244, 178, 163],
  [230, 139, 26],
  [241, 125, 89],
  [83, 160, 66],
  [107, 190, 166],
  [197, 161, 210],
  [198, 203, 245],
  [238, 117, 19],
  [228, 119, 116],
  [131, 156, 41],
  [145, 178, 168],
  [139, 170, 220],
  [233, 95, 125],
  [87, 178, 230],
  [157, 200, 119],
  [237, 140, 76],
  [229, 185, 186],
  [144, 206, 212],
  [236, 209, 158],
  [185, 189, 79],
  [34, 208, 66],
  [84, 238, 129],
  [133, 140, 134],
  [67, 157, 94],
  [168, 179, 25],
  [140, 145, 240],
  [151, 241, 125],
  [67, 162, 107],
  [200, 156, 21],
  [169, 173, 189],
  [226, 116, 189],
  [133, 231, 191],
  [194, 161, 63],
  [241, 77, 99],
  [241, 217, 53],
  [123, 204, 105],
  [210, 201, 119],
  [229, 108, 155],
  [240, 91, 72],
  [187, 115, 210],
  [240, 163, 100],
  [178, 217, 57],
  [179, 135, 116],
  [204, 211, 24],
  [186, 135, 57],
  [223, 176, 135],
  [204, 148, 151],
  [116, 223, 50],
  [95, 195, 46],
  [123, 160, 236],
  [181, 172, 131],
  [142, 220, 202],
  [240, 140, 112],
  [172, 145, 164],
  [228, 124, 45],
  [135, 151, 243],
  [42, 205, 125],
  [192, 233, 116],
  [119, 170, 114],
  [158, 138, 26],
  [73, 190, 183],
  [185, 229, 243],
  [227, 107, 55],
  [196, 205, 202],
  [132, 143, 60],
  [233, 192, 237],
  [62, 150, 220],
  [205, 201, 141],
  [106, 140, 190],
  [161, 131, 205],
  [135, 134, 158],
  [198, 139, 81],
  [115, 171, 32],
  [101, 181, 67],
  [149, 137, 119],
  [37, 142, 183],
  [183, 130, 175],
  [168, 125, 133],
  [124, 142, 87],
  [236, 156, 171],
  [232, 194, 91],
  [219, 200, 69],
  [144, 219, 34],
  [219, 95, 187],
  [145, 154, 217],
  [165, 185, 100],
  [127, 238, 163],
  [224, 178, 198],
  [119, 153, 120],
  [124, 212, 92],
  [172, 161, 105],
  [231, 155, 135],
  [157, 132, 101],
  [122, 185, 146],
  [53, 166, 51],
  [70, 163, 90],
  [150, 190, 213],
  [210, 107, 60],
  [166, 152, 185],
  [159, 194, 159],
  [39, 141, 222],
  [202, 176, 161],
  [95, 140, 229],
  [168, 142, 87],
  [93, 170, 203],
  [159, 142, 54],
  [14, 168, 39],
  [94, 150, 149],
  [187, 206, 136],
  [157, 224, 166],
  [235, 158, 208],
  [109, 232, 216],
  [141, 201, 87],
  [208, 124, 118],
  [142, 125, 214],
  [19, 237, 174],
  [72, 219, 41],
  [234, 102, 111],
  [168, 142, 79],
  [188, 135, 35],
  [95, 155, 143],
  [148, 173, 116],
  [223, 112, 95],
  [228, 128, 236],
  [206, 114, 54],
  [195, 119, 88],
  [235, 140, 94],
  [235, 202, 125],
  [233, 155, 153],
  [214, 214, 238],
  [246, 200, 35],
  [151, 125, 171],
  [132, 145, 172],
  [131, 142, 118],
  [199, 126, 150],
  [61, 162, 123],
  [58, 176, 151],
  [215, 141, 69],
  [225, 154, 220],
  [220, 77, 167],
  [233, 161, 64],
  [130, 221, 137],
  [81, 191, 129],
  [169, 162, 140],
  [174, 177, 222],
  [236, 174, 47],
  [233, 188, 180],
  [69, 222, 172],
  [71, 232, 93],
  [118, 211, 238],
  [157, 224, 83],
  [218, 105, 73],
  [126, 169, 36],
]

Class Method Summary collapse

Class Method Details

.cache_pathObject



30
31
32
# File 'lib/letter_avatar.rb', line 30

def cache_path
  "tmp/letter_avatars/#{version}"
end

.cached_path(identity, size) ⇒ Object



58
59
60
61
62
# File 'lib/letter_avatar.rb', line 58

def cached_path(identity, size)
  dir = "#{cache_path}/#{identity.letter}/#{identity.color.join("_")}"
  FileUtils.mkdir_p(dir)
  File.expand_path "#{dir}/#{size}.png"
end

.cleanup_oldObject



116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/letter_avatar.rb', line 116

def cleanup_old
  begin
    skip = File.basename(cache_path)
    parent_path = File.dirname(cache_path)
    Dir
      .entries(parent_path)
      .each do |path|
        FileUtils.rm_rf(parent_path + "/" + path) unless %w[. ..].include?(path) || path == skip
      end
  rescue Errno::ENOENT
    # no worries, folder doesn't exists
  end
end

.fullsize_path(identity) ⇒ Object



64
65
66
# File 'lib/letter_avatar.rb', line 64

def fullsize_path(identity)
  File.expand_path cached_path(identity, FULLSIZE)
end

.generate(username, size, opts = nil) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/letter_avatar.rb', line 34

def generate(username, size, opts = nil)
  DistributedMutex.synchronize("letter_avatar_#{version}_#{username}") do
    identity = (opts && opts[:identity]) || LetterAvatar::Identity.from_username(username)

    cache = true
    cache = false if opts && opts[:cache] == false

    size = FULLSIZE if size > FULLSIZE
    filename = cached_path(identity, size)

    return filename if cache && File.exist?(filename)

    fullsize = fullsize_path(identity)
    generate_fullsize(identity) if !cache || !File.exist?(fullsize)

    # Optimizing here is dubious, it can save up to 2x for large images (eg 359px)
    # BUT... we are talking 2400 bytes down to 1200 bytes, both fit in one packet
    # The cost of this is huge, its a 40% perf hit
    OptimizedImage.resize(fullsize, filename, size, size)

    filename
  end
end

.generate_fullsize(identity) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/letter_avatar.rb', line 68

def generate_fullsize(identity)
  color = identity.color
  letter = identity.letter

  filename = fullsize_path(identity)

  instructions = %W[
    -size
    #{FULLSIZE}x#{FULLSIZE}
    xc:#{to_rgb(color)}
    -pointsize
    #{POINTSIZE}
    -fill
    #FFFFFFCC
    -font
    Helvetica
    -gravity
    Center
    -annotate
    -0+26
    #{letter}
    -depth
    8
    #{filename}
  ]

  Discourse::Utils.execute_command("convert", *instructions)

  ## do not optimize image, it will end up larger than original
  filename
end

.image_magick_versionObject



105
106
107
108
109
110
111
112
113
114
# File 'lib/letter_avatar.rb', line 105

def image_magick_version
  @image_magick_version ||=
    begin
      Thread.new do
        sleep 2
        cleanup_old
      end
      Digest::MD5.hexdigest(`convert --version` << `convert -list font`)
    end
end

.to_rgb(color) ⇒ Object



100
101
102
103
# File 'lib/letter_avatar.rb', line 100

def to_rgb(color)
  r, g, b = color
  "rgb(#{r},#{g},#{b})"
end

.versionObject



26
27
28
# File 'lib/letter_avatar.rb', line 26

def version
  "#{VERSION}_#{image_magick_version}"
end