You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

avatar.go 4.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package avatar
  4. import (
  5. "bytes"
  6. "errors"
  7. "fmt"
  8. "image"
  9. "image/color"
  10. "image/png"
  11. _ "image/gif" // for processing gif images
  12. _ "image/jpeg" // for processing jpeg images
  13. "code.gitea.io/gitea/modules/avatar/identicon"
  14. "code.gitea.io/gitea/modules/setting"
  15. "golang.org/x/image/draw"
  16. _ "golang.org/x/image/webp" // for processing webp images
  17. )
  18. // DefaultAvatarSize is the target CSS pixel size for avatar generation. It is
  19. // multiplied by setting.Avatar.RenderedSizeFactor and the resulting size is the
  20. // usual size of avatar image saved on server, unless the original file is smaller
  21. // than the size after resizing.
  22. const DefaultAvatarSize = 256
  23. // RandomImageSize generates and returns a random avatar image unique to input data
  24. // in custom size (height and width).
  25. func RandomImageSize(size int, data []byte) (image.Image, error) {
  26. // we use white as background, and use dark colors to draw blocks
  27. imgMaker, err := identicon.New(size, color.White, identicon.DarkColors...)
  28. if err != nil {
  29. return nil, fmt.Errorf("identicon.New: %w", err)
  30. }
  31. return imgMaker.Make(data), nil
  32. }
  33. // RandomImage generates and returns a random avatar image unique to input data
  34. // in default size (height and width).
  35. func RandomImage(data []byte) (image.Image, error) {
  36. return RandomImageSize(DefaultAvatarSize*setting.Avatar.RenderedSizeFactor, data)
  37. }
  38. // processAvatarImage process the avatar image data, crop and resize it if necessary.
  39. // the returned data could be the original image if no processing is needed.
  40. func processAvatarImage(data []byte, maxOriginSize int64) ([]byte, error) {
  41. imgCfg, imgType, err := image.DecodeConfig(bytes.NewReader(data))
  42. if err != nil {
  43. return nil, fmt.Errorf("image.DecodeConfig: %w", err)
  44. }
  45. // for safety, only accept known types explicitly
  46. if imgType != "png" && imgType != "jpeg" && imgType != "gif" && imgType != "webp" {
  47. return nil, errors.New("unsupported avatar image type")
  48. }
  49. // do not process image which is too large, it would consume too much memory
  50. if imgCfg.Width > setting.Avatar.MaxWidth {
  51. return nil, fmt.Errorf("image width is too large: %d > %d", imgCfg.Width, setting.Avatar.MaxWidth)
  52. }
  53. if imgCfg.Height > setting.Avatar.MaxHeight {
  54. return nil, fmt.Errorf("image height is too large: %d > %d", imgCfg.Height, setting.Avatar.MaxHeight)
  55. }
  56. // If the origin is small enough, just use it, then APNG could be supported,
  57. // otherwise, if the image is processed later, APNG loses animation.
  58. // And one more thing, webp is not fully supported, for animated webp, image.DecodeConfig works but Decode fails.
  59. // So for animated webp, if the uploaded file is smaller than maxOriginSize, it will be used, if it's larger, there will be an error.
  60. if len(data) < int(maxOriginSize) {
  61. return data, nil
  62. }
  63. img, _, err := image.Decode(bytes.NewReader(data))
  64. if err != nil {
  65. return nil, fmt.Errorf("image.Decode: %w", err)
  66. }
  67. // try to crop and resize the origin image if necessary
  68. img = cropSquare(img)
  69. targetSize := DefaultAvatarSize * setting.Avatar.RenderedSizeFactor
  70. img = scale(img, targetSize, targetSize, draw.BiLinear)
  71. // try to encode the cropped/resized image to png
  72. bs := bytes.Buffer{}
  73. if err = png.Encode(&bs, img); err != nil {
  74. return nil, err
  75. }
  76. resized := bs.Bytes()
  77. // usually the png compression is not good enough, use the original image (no cropping/resizing) if the origin is smaller
  78. if len(data) <= len(resized) {
  79. return data, nil
  80. }
  81. return resized, nil
  82. }
  83. // ProcessAvatarImage process the avatar image data, crop and resize it if necessary.
  84. // the returned data could be the original image if no processing is needed.
  85. func ProcessAvatarImage(data []byte) ([]byte, error) {
  86. return processAvatarImage(data, setting.Avatar.MaxOriginSize)
  87. }
  88. // scale resizes the image to width x height using the given scaler.
  89. func scale(src image.Image, width, height int, scale draw.Scaler) image.Image {
  90. rect := image.Rect(0, 0, width, height)
  91. dst := image.NewRGBA(rect)
  92. scale.Scale(dst, rect, src, src.Bounds(), draw.Over, nil)
  93. return dst
  94. }
  95. // cropSquare crops the largest square image from the center of the image.
  96. // If the image is already square, it is returned unchanged.
  97. func cropSquare(src image.Image) image.Image {
  98. bounds := src.Bounds()
  99. if bounds.Dx() == bounds.Dy() {
  100. return src
  101. }
  102. var rect image.Rectangle
  103. if bounds.Dx() > bounds.Dy() {
  104. // width > height
  105. size := bounds.Dy()
  106. rect = image.Rect((bounds.Dx()-size)/2, 0, (bounds.Dx()+size)/2, size)
  107. } else {
  108. // width < height
  109. size := bounds.Dx()
  110. rect = image.Rect(0, (bounds.Dy()-size)/2, size, (bounds.Dy()+size)/2)
  111. }
  112. dst := image.NewRGBA(rect)
  113. draw.Draw(dst, rect, src, rect.Min, draw.Src)
  114. return dst
  115. }