mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-06 20:46:25 +02:00
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:
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
72
src/main/scala/gitbucket/core/util/TextAvatarUtil.scala
Normal file
72
src/main/scala/gitbucket/core/util/TextAvatarUtil.scala
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user