Change TextAvatarService to TextAvatarUtil, color detection algorithm, Add fallback to username feature when FullName can't draw by default font.

This commit is contained in:
KOUNOIKE Yuusuke
2017-03-18 10:40:48 +09:00
parent 620c3161cf
commit 4d0e0b7bd2
3 changed files with 76 additions and 54 deletions

View File

@@ -21,13 +21,13 @@ import java.util.Date
class AccountController extends AccountControllerBase
with AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator
with AccessTokenService with WebHookService with RepositoryCreationService with TextAvatarService
with AccessTokenService with WebHookService with RepositoryCreationService
trait AccountControllerBase extends AccountManagementControllerBase {
self: AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator
with AccessTokenService with WebHookService with RepositoryCreationService with TextAvatarService =>
with AccessTokenService with WebHookService with RepositoryCreationService =>
case class AccountNewForm(userName: String, password: String, fullName: String, mailAddress: String,
description: Option[String], url: Option[String], fileId: Option[String])
@@ -151,13 +151,12 @@ trait AccountControllerBase extends AccountManagementControllerBase {
get("/:userName/_avatar"){
val userName = params("userName")
getAccountByUserName(userName).map{ account =>
response.setDateHeader("Last-Modified", account.updatedDate.getTime)
account.image.map{ image =>
response.setDateHeader("Last-Modified", account.updatedDate.getTime)
RawData(FileUtil.getMimeType(image), new java.io.File(getUserUploadDir(userName), image))
}.getOrElse{
contentType = "image/png"
response.setDateHeader("Last-Modified", new Date(0).getTime())
textAvatar(account.fullName)
TextAvatarUtil.textAvatar(account.fullName).getOrElse(Thread.currentThread.getContextClassLoader.getResourceAsStream("noimage.png"))
}
}.getOrElse{
NotFound()

View File

@@ -1,49 +0,0 @@
package gitbucket.core.service
import java.io.ByteArrayOutputStream
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
import java.awt.{Color, Font, RenderingHints}
import java.awt.font.{FontRenderContext, TextLayout}
import gitbucket.core.util.StringUtil
trait TextAvatarService {
def textAvatar(nameText: String): Array[Byte] = {
val drawText = nameText.substring(0, 1)
val md5 = StringUtil.md5(nameText)
val hashedInt = Integer.parseInt(md5.substring(0, 2), 16)
val h = hashedInt / 256f
val bgColor = Color.getHSBColor(h, 1f, 1f)
val fgColor = Color.getHSBColor(h + 0.5f, 1f, 0.8f)
val size = (200, 200)
val canvas = new BufferedImage(size._1, size._2, BufferedImage.TYPE_INT_ARGB)
val g = canvas.createGraphics()
g.setColor(new Color(0, 0, 0, 0))
g.fillRect(0, 0, canvas.getWidth, canvas.getHeight)
g.setColor(bgColor)
g.fillRoundRect(0, 0, canvas.getWidth, canvas.getHeight, 60, 60)
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g.setColor(fgColor)
val font = new Font("SansSerif", Font.PLAIN, 180)
val context = g.getFontRenderContext
val txt = new TextLayout(drawText, font, context)
val bounds = txt.getBounds
val x: Int = ((size._1 - bounds.getWidth) / 2 - bounds.getX).toInt
val y: Int = ((size._2 - bounds.getHeight) / 2 - bounds.getY).toInt
g.setFont(font)
g.drawString(drawText, x, y)
g.dispose()
val stream = new ByteArrayOutputStream
ImageIO.write(canvas, "png", stream)
stream.toByteArray
}
}

View File

@@ -0,0 +1,72 @@
package gitbucket.core.util
import java.io.ByteArrayOutputStream
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
import java.awt.{Color, Font, RenderingHints}
import java.awt.font.{FontRenderContext, TextLayout}
object TextAvatarUtil {
// https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
private def relativeLuminance(c: Color): Double = {
val rgb = Seq(c.getRed, c.getGreen, c.getBlue).map{_/255.0}.map{x => if (x <= 0.03928) x / 12.92 else math.pow((x + 0.055) / 1.055, 2.4)}
0.2126 * rgb(0) + 0.7152 * rgb(1) + 0.0722 * rgb(2)
}
// https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
private def contrastRatio(c1: Color, c2: Color): Double = {
val l1 = relativeLuminance(c1)
val l2 = relativeLuminance(c2)
if (l1 > l2) (l1 + 0.05) / (l2 + 0.05) else (l2 + 0.05) / (l1 + 0.05)
}
private def goodContrastColor(base: Color, c1: Color, c2: Color): Color = {
if (contrastRatio(base, c1) > contrastRatio(base, c2)) c1 else c2
}
private def textImage(w: Int, h: Int, drawText: String, font: Font, fontSize: Int, bgColor: Color, fgColor: Color): Array[Byte] = {
val canvas = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB)
val g = canvas.createGraphics()
g.setColor(new Color(0, 0, 0, 0))
g.fillRect(0, 0, w, h)
g.setColor(bgColor)
g.fillRoundRect(0, 0, w, h, 60, 60)
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g.setColor(fgColor)
val context = g.getFontRenderContext
val txt = new TextLayout(drawText, font, context)
val bounds = txt.getBounds
val x: Int = ((w - bounds.getWidth) / 2 - bounds.getX).toInt
val y: Int = ((h - bounds.getHeight) / 2 - bounds.getY).toInt
g.setFont(font)
g.drawString(drawText, x, y)
g.dispose()
val stream = new ByteArrayOutputStream
ImageIO.write(canvas, "png", stream)
stream.toByteArray
}
def textAvatar(nameText: String): Option[Array[Byte]] = {
val drawText = nameText.substring(0, 1)
val md5 = StringUtil.md5(nameText)
val hashedInt = Integer.parseInt(md5.substring(0, 2), 16)
val bgHue = hashedInt / 256f
val bgSaturation = 0.68f
val bgBlightness = 0.73f
val bgColor = Color.getHSBColor(bgHue, bgSaturation, bgBlightness)
val fgColor = goodContrastColor(bgColor, Color.BLACK, Color.WHITE)
val size = (200, 200)
val fontSize = 180
val font = new Font(Font.SANS_SERIF, Font.PLAIN, fontSize)
if (font.canDisplayUpTo(drawText) == -1) Some(textImage(size._1, size._2, drawText, font, fontSize, bgColor, fgColor)) else None
}
}