diff --git a/internal/federation/dereferencing/emoji.go b/internal/federation/dereferencing/emoji.go index b8ecfa6e1..fbe2e4d98 100644 --- a/internal/federation/dereferencing/emoji.go +++ b/internal/federation/dereferencing/emoji.go @@ -51,7 +51,6 @@ func (d *Dereferencer) GetEmoji( remoteURL string, info media.AdditionalEmojiInfo, refresh bool, - async bool, ) ( *gtsmodel.Emoji, error, @@ -67,7 +66,7 @@ func (d *Dereferencer) GetEmoji( if emoji != nil { // This was an existing emoji, pass to refresh func. - return d.RefreshEmoji(ctx, emoji, info, refresh, async) + return d.RefreshEmoji(ctx, emoji, info, refresh) } if domain == "" { @@ -113,7 +112,6 @@ func (d *Dereferencer) GetEmoji( info, ) }, - async, ) } @@ -132,7 +130,6 @@ func (d *Dereferencer) RefreshEmoji( emoji *gtsmodel.Emoji, info media.AdditionalEmojiInfo, force bool, - async bool, ) ( *gtsmodel.Emoji, error, @@ -165,7 +162,7 @@ func (d *Dereferencer) RefreshEmoji( // We still want to make sure // the emoji is cached. Simply // check whether emoji is cached. - return d.RecacheEmoji(ctx, emoji, async) + return d.RecacheEmoji(ctx, emoji) } // Can't refresh local. @@ -210,7 +207,6 @@ func (d *Dereferencer) RefreshEmoji( info, ) }, - async, ) } @@ -226,7 +222,6 @@ func (d *Dereferencer) RefreshEmoji( func (d *Dereferencer) RecacheEmoji( ctx context.Context, emoji *gtsmodel.Emoji, - async bool, ) ( *gtsmodel.Emoji, error, @@ -277,24 +272,23 @@ func (d *Dereferencer) RecacheEmoji( data, ) }, - async, ) + } // processingEmojiSafely provides concurrency-safe processing of // an emoji with given shortcode+domain. if a copy of the emoji is // not already being processed, the given 'process' callback will -// be used to generate new *media.ProcessingEmoji{} instance. async -// determines whether to load it immediately, or in the background. +// be used to generate new *media.ProcessingEmoji{} instance. func (d *Dereferencer) processEmojiSafely( ctx context.Context, shortcodeDomain string, process func() (*media.ProcessingEmoji, error), - async bool, ) ( emoji *gtsmodel.Emoji, err error, ) { + // Acquire map lock. d.derefEmojisMu.Lock() @@ -315,34 +309,25 @@ func (d *Dereferencer) processEmojiSafely( // Add processing emoji media to hash map. d.derefEmojis[shortcodeDomain] = processing - } - // Unlock map. - unlock() - - if async { - emoji = processing.LoadAsync(func() { - // Remove on finish. - d.derefEmojisMu.Lock() - delete(d.derefEmojis, shortcodeDomain) - d.derefEmojisMu.Unlock() - }) - } else { defer func() { // Remove on finish. d.derefEmojisMu.Lock() delete(d.derefEmojis, shortcodeDomain) d.derefEmojisMu.Unlock() }() + } - // Perform emoji load operation. - emoji, err = processing.Load(ctx) - if err != nil { - err = gtserror.Newf("error loading emoji %s: %w", shortcodeDomain, err) + // Unlock map. + unlock() - // TODO: in time we should return checkable flags by gtserror.Is___() - // which can determine if loading error should allow remaining placeholder. - } + // Perform emoji load operation. + emoji, err = processing.Load(ctx) + if err != nil { + err = gtserror.Newf("error loading emoji %s: %w", shortcodeDomain, err) + + // TODO: in time we should return checkable flags by gtserror.Is___() + // which can determine if loading error should allow remaining placeholder. } return @@ -379,7 +364,7 @@ func (d *Dereferencer) fetchEmojis( URI: &placeholder.URI, ImageRemoteURL: &placeholder.ImageRemoteURL, ImageStaticRemoteURL: &placeholder.ImageStaticRemoteURL, - }, force, true) + }, force) if err != nil { log.Errorf(ctx, "error refreshing emoji: %v", err) @@ -411,7 +396,6 @@ func (d *Dereferencer) fetchEmojis( ImageStaticRemoteURL: &placeholder.ImageStaticRemoteURL, }, false, - true, ) if err != nil { if emoji == nil { diff --git a/internal/federation/dereferencing/emoji_test.go b/internal/federation/dereferencing/emoji_test.go index 5a4a1f601..c8c905781 100644 --- a/internal/federation/dereferencing/emoji_test.go +++ b/internal/federation/dereferencing/emoji_test.go @@ -54,7 +54,6 @@ func (suite *EmojiTestSuite) TestDereferenceEmojiBlocking() { VisibleInPicker: &emojiVisibleInPicker, }, false, - false, ) suite.NoError(err) suite.NotNil(emoji) diff --git a/internal/media/imaging.go b/internal/media/imaging.go index d575a37f7..6a0fa694c 100644 --- a/internal/media/imaging.go +++ b/internal/media/imaging.go @@ -21,8 +21,6 @@ import ( "image" "image/color" "math" - - "code.superseriousbusiness.org/gotosocial/internal/gtserror" ) // NOTE: @@ -75,15 +73,15 @@ func resizeDownLinear(img image.Image, width, height int) image.Image { // flipH flips the image horizontally (left to right). func flipH(img image.Image) image.Image { - srcW, srcH := img.Bounds().Dx(), img.Bounds().Dy() - dstW := srcW - dstH := srcH + src := newScanner(img) + dstW := src.w + dstH := src.h rowSize := dstW * 4 dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) for y := 0; y < dstH; y++ { i := y * dst.Stride srcY := y - scanImage(img, 0, srcY, srcW, srcY+1, dst.Pix[i:i+rowSize]) + src.scan(0, srcY, src.w, srcY+1, dst.Pix[i:i+rowSize]) reverse(dst.Pix[i : i+rowSize]) } return dst @@ -91,45 +89,45 @@ func flipH(img image.Image) image.Image { // flipV flips the image vertically (from top to bottom). func flipV(img image.Image) image.Image { - srcW, srcH := img.Bounds().Dx(), img.Bounds().Dy() - dstW := srcW - dstH := srcH + src := newScanner(img) + dstW := src.w + dstH := src.h rowSize := dstW * 4 dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) for y := 0; y < dstH; y++ { i := y * dst.Stride srcY := dstH - y - 1 - scanImage(img, 0, srcY, srcW, srcY+1, dst.Pix[i:i+rowSize]) + src.scan(0, srcY, src.w, srcY+1, dst.Pix[i:i+rowSize]) } return dst } // rotate90 rotates the image 90 counter-clockwise. func rotate90(img image.Image) image.Image { - srcW, srcH := img.Bounds().Dx(), img.Bounds().Dy() - dstW := srcH - dstH := srcW + src := newScanner(img) + dstW := src.h + dstH := src.w rowSize := dstW * 4 dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) for y := 0; y < dstH; y++ { i := y * dst.Stride srcX := dstH - y - 1 - scanImage(img, srcX, 0, srcX+1, srcH, dst.Pix[i:i+rowSize]) + src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize]) } return dst } // rotate180 rotates the image 180 counter-clockwise. func rotate180(img image.Image) image.Image { - srcW, srcH := img.Bounds().Dx(), img.Bounds().Dy() - dstW := srcW - dstH := srcH + src := newScanner(img) + dstW := src.w + dstH := src.h rowSize := dstW * 4 dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) for y := 0; y < dstH; y++ { i := y * dst.Stride srcY := dstH - y - 1 - scanImage(img, 0, srcY, srcW, srcY+1, dst.Pix[i:i+rowSize]) + src.scan(0, srcY, src.w, srcY+1, dst.Pix[i:i+rowSize]) reverse(dst.Pix[i : i+rowSize]) } return dst @@ -137,15 +135,15 @@ func rotate180(img image.Image) image.Image { // rotate270 rotates the image 270 counter-clockwise. func rotate270(img image.Image) image.Image { - srcW, srcH := img.Bounds().Dx(), img.Bounds().Dy() - dstW := srcH - dstH := srcW + src := newScanner(img) + dstW := src.h + dstH := src.w rowSize := dstW * 4 dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) for y := 0; y < dstH; y++ { i := y * dst.Stride srcX := y - scanImage(img, srcX, 0, srcX+1, srcH, dst.Pix[i:i+rowSize]) + src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize]) reverse(dst.Pix[i : i+rowSize]) } return dst @@ -153,30 +151,30 @@ func rotate270(img image.Image) image.Image { // transpose flips the image horizontally and rotates 90 counter-clockwise. func transpose(img image.Image) image.Image { - srcW, srcH := img.Bounds().Dx(), img.Bounds().Dy() - dstW := srcH - dstH := srcW + src := newScanner(img) + dstW := src.h + dstH := src.w rowSize := dstW * 4 dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) for y := 0; y < dstH; y++ { i := y * dst.Stride srcX := y - scanImage(img, srcX, 0, srcX+1, srcH, dst.Pix[i:i+rowSize]) + src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize]) } return dst } // transverse flips the image vertically and rotates 90 counter-clockwise. func transverse(img image.Image) image.Image { - srcW, srcH := img.Bounds().Dx(), img.Bounds().Dy() - dstW := srcH - dstH := srcW + src := newScanner(img) + dstW := src.h + dstH := src.w rowSize := dstW * 4 dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) for y := 0; y < dstH; y++ { i := y * dst.Stride srcX := dstH - y - 1 - scanImage(img, srcX, 0, srcX+1, srcH, dst.Pix[i:i+rowSize]) + src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize]) reverse(dst.Pix[i : i+rowSize]) } return dst @@ -184,12 +182,12 @@ func transverse(img image.Image) image.Image { // resizeHorizontalLinear resizes image to given width using linear resampling. func resizeHorizontalLinear(img image.Image, dstWidth int) image.Image { - srcW, srcH := img.Bounds().Dx(), img.Bounds().Dy() - dst := image.NewRGBA(image.Rect(0, 0, dstWidth, srcH)) - weights := precomputeWeightsLinear(dstWidth, srcW) - scanLine := make([]uint8, srcW*4) - for y := 0; y < srcH; y++ { - scanImage(img, 0, y, srcW, y+1, scanLine) + src := newScanner(img) + dst := image.NewRGBA(image.Rect(0, 0, dstWidth, src.h)) + weights := precomputeWeightsLinear(dstWidth, src.w) + scanLine := make([]uint8, src.w*4) + for y := 0; y < src.h; y++ { + src.scan(0, y, src.w, y+1, scanLine) j0 := y * dst.Stride for x := range weights { var r, g, b, a float64 @@ -203,12 +201,13 @@ func resizeHorizontalLinear(img image.Image, dstWidth int) image.Image { a += aw } if a != 0 { + aInv := 1 / a j := j0 + x*4 d := dst.Pix[j : j+4 : j+4] - d[0] = clampFloatTo8(r / a) - d[1] = clampFloatTo8(g / a) - d[2] = clampFloatTo8(b / a) - d[3] = clampFloatTo8(a) + d[0] = clampFloat(r * aInv) + d[1] = clampFloat(g * aInv) + d[2] = clampFloat(b * aInv) + d[3] = clampFloat(a) } } } @@ -217,12 +216,12 @@ func resizeHorizontalLinear(img image.Image, dstWidth int) image.Image { // resizeVerticalLinear resizes image to given height using linear resampling. func resizeVerticalLinear(img image.Image, height int) image.Image { - srcW, srcH := img.Bounds().Dx(), img.Bounds().Dy() - dst := image.NewNRGBA(image.Rect(0, 0, srcW, height)) - weights := precomputeWeightsLinear(height, srcH) - scanLine := make([]uint8, srcH*4) - for x := 0; x < srcW; x++ { - scanImage(img, x, 0, x+1, srcH, scanLine) + src := newScanner(img) + dst := image.NewNRGBA(image.Rect(0, 0, src.w, height)) + weights := precomputeWeightsLinear(height, src.h) + scanLine := make([]uint8, src.h*4) + for x := 0; x < src.w; x++ { + src.scan(x, 0, x+1, src.h, scanLine) for y := range weights { var r, g, b, a float64 for _, w := range weights[y] { @@ -235,12 +234,13 @@ func resizeVerticalLinear(img image.Image, height int) image.Image { a += aw } if a != 0 { + aInv := 1 / a j := y*dst.Stride + x*4 d := dst.Pix[j : j+4 : j+4] - d[0] = clampFloatTo8(r / a) - d[1] = clampFloatTo8(g / a) - d[2] = clampFloatTo8(b / a) - d[3] = clampFloatTo8(a) + d[0] = clampFloat(r * aInv) + d[1] = clampFloat(g * aInv) + d[2] = clampFloat(b * aInv) + d[3] = clampFloat(a) } } } @@ -263,14 +263,13 @@ func precomputeWeightsLinear(dstSize, srcSize int) [][]indexWeight { out := make([][]indexWeight, dstSize) tmp := make([]indexWeight, 0, dstSize*int(ru+2)*2) - for v := 0; v < len(out); v++ { + for v := 0; v < dstSize; v++ { fu := (float64(v)+0.5)*du - 0.5 begin := int(math.Ceil(fu - ru)) if begin < 0 { begin = 0 } - end := int(math.Floor(fu + ru)) if end > srcSize-1 { end = srcSize - 1 @@ -281,13 +280,9 @@ func precomputeWeightsLinear(dstSize, srcSize int) [][]indexWeight { w := resampleLinear((float64(u) - fu) / scale) if w != 0 { sum += w - tmp = append(tmp, indexWeight{ - index: u, - weight: w, - }) + tmp = append(tmp, indexWeight{index: u, weight: w}) } } - if sum != 0 { for i := range tmp { tmp[i].weight /= sum @@ -310,209 +305,204 @@ func resampleLinear(x float64) float64 { return 0 } +// scanner wraps an image.Image for +// easier size access and image type +// agnostic access to data at coords. +type scanner struct { + image image.Image + w, h int + palette []color.NRGBA +} + +// newScanner wraps an image.Image in scanner{} type. +func newScanner(img image.Image) *scanner { + b := img.Bounds() + s := &scanner{ + image: img, + + w: b.Dx(), + h: b.Dy(), + } + if img, ok := img.(*image.Paletted); ok { + s.palette = make([]color.NRGBA, len(img.Palette)) + for i := 0; i < len(img.Palette); i++ { + s.palette[i] = color.NRGBAModel.Convert(img.Palette[i]).(color.NRGBA) + } + } + return s +} + // scan scans the given rectangular region of the image into dst. -func scanImage(img image.Image, x1, y1, x2, y2 int, dst []uint8) { - switch img := img.(type) { +func (s *scanner) scan(x1, y1, x2, y2 int, dst []uint8) { + switch img := s.image.(type) { case *image.NRGBA: - scanNRGBA(img, x1, y1, x2, y2, dst) - case *image.NRGBA64: - scanNRGBA64(img, x1, y1, x2, y2, dst) - case *image.RGBA: - scanRGBA(img, x1, y1, x2, y2, dst) - case *image.RGBA64: - scanRGBA64(img, x1, y1, x2, y2, dst) - case *image.Gray: - scanGray(img, x1, y1, x2, y2, dst) - case *image.Gray16: - scanGray16(img, x1, y1, x2, y2, dst) - case *image.YCbCr: - scanYCbCr(img, x1, y1, x2, y2, dst) - case *image.Paletted: - scanPaletted(img, x1, y1, x2, y2, dst) - default: - scanAny(img, x1, y1, x2, y2, dst) - } -} - -func scanNRGBA(img *image.NRGBA, x1, y1, x2, y2 int, dst []uint8) { - size := (x2 - x1) * 4 - j := 0 - i := y1*img.Stride + x1*4 - if size == 4 { - for y := y1; y < y2; y++ { - d := dst[j : j+4 : j+4] - s := img.Pix[i : i+4 : i+4] - d[0] = s[0] - d[1] = s[1] - d[2] = s[2] - d[3] = s[3] - j += size - i += img.Stride - } - } else { - for y := y1; y < y2; y++ { - copy(dst[j:j+size], img.Pix[i:i+size]) - j += size - i += img.Stride - } - } -} - -func scanNRGBA64(img *image.NRGBA64, x1, y1, x2, y2 int, dst []uint8) { - if img == nil { - panic(gtserror.New("nil check elimination")) - } - j := 0 - for y := y1; y < y2; y++ { - i := y*img.Stride + x1*8 - for x := x1; x < x2; x++ { - s := img.Pix[i : i+8 : i+8] - d := dst[j : j+4 : j+4] - d[0] = s[0] - d[1] = s[2] - d[2] = s[4] - d[3] = s[6] - j += 4 - i += 8 - } - } -} - -func scanRGBA(img *image.RGBA, x1, y1, x2, y2 int, dst []uint8) { - if img == nil { - panic(gtserror.New("nil check elimination")) - } - j := 0 - for y := y1; y < y2; y++ { - i := y*img.Stride + x1*4 - for x := x1; x < x2; x++ { - d := dst[j : j+4 : j+4] - a := img.Pix[i+3] - switch a { - case 0: - d[0] = 0 - d[1] = 0 - d[2] = 0 - d[3] = a - case 0xff: + size := (x2 - x1) * 4 + j := 0 + i := y1*img.Stride + x1*4 + if size == 4 { + for y := y1; y < y2; y++ { + d := dst[j : j+4 : j+4] s := img.Pix[i : i+4 : i+4] d[0] = s[0] d[1] = s[1] d[2] = s[2] - d[3] = a - default: - s := img.Pix[i : i+4 : i+4] - r16 := uint16(s[0]) - g16 := uint16(s[1]) - b16 := uint16(s[2]) - a16 := uint16(a) - d[0] = uint8(r16 * 0xff / a16) // #nosec G115 -- Overflow desired. - d[1] = uint8(g16 * 0xff / a16) // #nosec G115 -- Overflow desired. - d[2] = uint8(b16 * 0xff / a16) // #nosec G115 -- Overflow desired. - d[3] = a + d[3] = s[3] + j += size + i += img.Stride + } + } else { + for y := y1; y < y2; y++ { + copy(dst[j:j+size], img.Pix[i:i+size]) + j += size + i += img.Stride } - j += 4 - i += 4 } - } -} -func scanRGBA64(img *image.RGBA64, x1, y1, x2, y2 int, dst []uint8) { - if img == nil { - panic(gtserror.New("nil check elimination")) - } - j := 0 - for y := y1; y < y2; y++ { - i := y*img.Stride + x1*8 - for x := x1; x < x2; x++ { - s := img.Pix[i : i+8 : i+8] - d := dst[j : j+4 : j+4] - a := s[6] - switch a { - case 0: - d[0] = 0 - d[1] = 0 - d[2] = 0 - case 0xff: + case *image.NRGBA64: + j := 0 + for y := y1; y < y2; y++ { + i := y*img.Stride + x1*8 + for x := x1; x < x2; x++ { + s := img.Pix[i : i+8 : i+8] + d := dst[j : j+4 : j+4] d[0] = s[0] d[1] = s[2] d[2] = s[4] - default: - r32 := uint32(s[0])<<8 | uint32(s[1]) - g32 := uint32(s[2])<<8 | uint32(s[3]) - b32 := uint32(s[4])<<8 | uint32(s[5]) - a32 := uint32(s[6])<<8 | uint32(s[7]) - d[0] = uint8((r32 * 0xffff / a32) >> 8) // #nosec G115 -- Overflow desired. - d[1] = uint8((g32 * 0xffff / a32) >> 8) // #nosec G115 -- Overflow desired. - d[2] = uint8((b32 * 0xffff / a32) >> 8) // #nosec G115 -- Overflow desired. + d[3] = s[6] + j += 4 + i += 8 } - d[3] = a - j += 4 - i += 8 } - } -} -func scanGray(img *image.Gray, x1, y1, x2, y2 int, dst []uint8) { - if img == nil { - panic(gtserror.New("nil check elimination")) - } - j := 0 - for y := y1; y < y2; y++ { - i := y*img.Stride + x1 - for x := x1; x < x2; x++ { - c := img.Pix[i] - d := dst[j : j+4 : j+4] - d[0] = c - d[1] = c - d[2] = c - d[3] = 0xff - j += 4 - i++ + case *image.RGBA: + j := 0 + for y := y1; y < y2; y++ { + i := y*img.Stride + x1*4 + for x := x1; x < x2; x++ { + d := dst[j : j+4 : j+4] + a := img.Pix[i+3] + switch a { + case 0: + d[0] = 0 + d[1] = 0 + d[2] = 0 + d[3] = a + case 0xff: + s := img.Pix[i : i+4 : i+4] + d[0] = s[0] + d[1] = s[1] + d[2] = s[2] + d[3] = a + default: + s := img.Pix[i : i+4 : i+4] + r16 := uint16(s[0]) + g16 := uint16(s[1]) + b16 := uint16(s[2]) + a16 := uint16(a) + d[0] = uint8(r16 * 0xff / a16) // #nosec G115 -- Overflow desired. + d[1] = uint8(g16 * 0xff / a16) // #nosec G115 -- Overflow desired. + d[2] = uint8(b16 * 0xff / a16) // #nosec G115 -- Overflow desired. + d[3] = a + } + j += 4 + i += 4 + } } - } -} -func scanGray16(img *image.Gray16, x1, y1, x2, y2 int, dst []uint8) { - if img == nil { - panic(gtserror.New("nil check elimination")) - } - j := 0 - for y := y1; y < y2; y++ { - i := y*img.Stride + x1*2 - for x := x1; x < x2; x++ { - c := img.Pix[i] - d := dst[j : j+4 : j+4] - d[0] = c - d[1] = c - d[2] = c - d[3] = 0xff - j += 4 - i += 2 + case *image.RGBA64: + j := 0 + for y := y1; y < y2; y++ { + i := y*img.Stride + x1*8 + for x := x1; x < x2; x++ { + s := img.Pix[i : i+8 : i+8] + d := dst[j : j+4 : j+4] + a := s[6] + switch a { + case 0: + d[0] = 0 + d[1] = 0 + d[2] = 0 + case 0xff: + d[0] = s[0] + d[1] = s[2] + d[2] = s[4] + default: + r32 := uint32(s[0])<<8 | uint32(s[1]) + g32 := uint32(s[2])<<8 | uint32(s[3]) + b32 := uint32(s[4])<<8 | uint32(s[5]) + a32 := uint32(s[6])<<8 | uint32(s[7]) + d[0] = uint8((r32 * 0xffff / a32) >> 8) // #nosec G115 -- Overflow desired. + d[1] = uint8((g32 * 0xffff / a32) >> 8) // #nosec G115 -- Overflow desired. + d[2] = uint8((b32 * 0xffff / a32) >> 8) // #nosec G115 -- Overflow desired. + } + d[3] = a + j += 4 + i += 8 + } } - } -} -func scanYCbCr(img *image.YCbCr, x1, y1, x2, y2 int, dst []uint8) { - j := 0 + case *image.Gray: + j := 0 + for y := y1; y < y2; y++ { + i := y*img.Stride + x1 + for x := x1; x < x2; x++ { + c := img.Pix[i] + d := dst[j : j+4 : j+4] + d[0] = c + d[1] = c + d[2] = c + d[3] = 0xff + j += 4 + i++ + } + } - x1 += img.Rect.Min.X - x2 += img.Rect.Min.X - y1 += img.Rect.Min.Y - y2 += img.Rect.Min.Y + case *image.Gray16: + j := 0 + for y := y1; y < y2; y++ { + i := y*img.Stride + x1*2 + for x := x1; x < x2; x++ { + c := img.Pix[i] + d := dst[j : j+4 : j+4] + d[0] = c + d[1] = c + d[2] = c + d[3] = 0xff + j += 4 + i += 2 + } + } - hy := img.Rect.Min.Y / 2 - hx := img.Rect.Min.X / 2 + case *image.YCbCr: + j := 0 + x1 += img.Rect.Min.X + x2 += img.Rect.Min.X + y1 += img.Rect.Min.Y + y2 += img.Rect.Min.Y - switch img.SubsampleRatio { - case image.YCbCrSubsampleRatio420: + hy := img.Rect.Min.Y / 2 + hx := img.Rect.Min.X / 2 for y := y1; y < y2; y++ { iy := (y-img.Rect.Min.Y)*img.YStride + (x1 - img.Rect.Min.X) - yBase := (y/2 - hy) * img.CStride + var yBase int + switch img.SubsampleRatio { + case image.YCbCrSubsampleRatio444, image.YCbCrSubsampleRatio422: + yBase = (y - img.Rect.Min.Y) * img.CStride + case image.YCbCrSubsampleRatio420, image.YCbCrSubsampleRatio440: + yBase = (y/2 - hy) * img.CStride + } for x := x1; x < x2; x++ { - ic := yBase + (x/2 - hx) + var ic int + switch img.SubsampleRatio { + case image.YCbCrSubsampleRatio444, image.YCbCrSubsampleRatio440: + ic = yBase + (x - img.Rect.Min.X) + case image.YCbCrSubsampleRatio422, image.YCbCrSubsampleRatio420: + ic = yBase + (x/2 - hx) + default: + ic = img.COffset(x, y) + } yy1 := int32(img.Y[iy]) * 0x10101 cb1 := int32(img.Cb[ic]) - 128 @@ -550,296 +540,78 @@ func scanYCbCr(img *image.YCbCr, x1, y1, x2, y2 int, dst []uint8) { } } - case image.YCbCrSubsampleRatio422: + case *image.Paletted: + j := 0 for y := y1; y < y2; y++ { - iy := (y-img.Rect.Min.Y)*img.YStride + (x1 - img.Rect.Min.X) - - yBase := (y - img.Rect.Min.Y) * img.CStride - + i := y*img.Stride + x1 for x := x1; x < x2; x++ { - ic := yBase + (x/2 - hx) - - yy1 := int32(img.Y[iy]) * 0x10101 - cb1 := int32(img.Cb[ic]) - 128 - cr1 := int32(img.Cr[ic]) - 128 - - r := yy1 + 91881*cr1 - if uint32(r)&0xff000000 == 0 { //nolint:gosec - r >>= 16 - } else { - r = ^(r >> 31) - } - - g := yy1 - 22554*cb1 - 46802*cr1 - if uint32(g)&0xff000000 == 0 { //nolint:gosec - g >>= 16 - } else { - g = ^(g >> 31) - } - - b := yy1 + 116130*cb1 - if uint32(b)&0xff000000 == 0 { //nolint:gosec - b >>= 16 - } else { - b = ^(b >> 31) - } - + c := s.palette[img.Pix[i]] d := dst[j : j+4 : j+4] - d[0] = uint8(r) // #nosec G115 -- Overflow desired. - d[1] = uint8(g) // #nosec G115 -- Overflow desired. - d[2] = uint8(b) // #nosec G115 -- Overflow desired. - d[3] = 0xff - - iy++ - j += 4 - } - } - - case image.YCbCrSubsampleRatio440: - for y := y1; y < y2; y++ { - iy := (y-img.Rect.Min.Y)*img.YStride + (x1 - img.Rect.Min.X) - - yBase := (y/2 - hy) * img.CStride - - for x := x1; x < x2; x++ { - ic := yBase + (x - img.Rect.Min.X) - - yy1 := int32(img.Y[iy]) * 0x10101 - cb1 := int32(img.Cb[ic]) - 128 - cr1 := int32(img.Cr[ic]) - 128 - - r := yy1 + 91881*cr1 - if uint32(r)&0xff000000 == 0 { //nolint:gosec - r >>= 16 - } else { - r = ^(r >> 31) - } - - g := yy1 - 22554*cb1 - 46802*cr1 - if uint32(g)&0xff000000 == 0 { //nolint:gosec - g >>= 16 - } else { - g = ^(g >> 31) - } - - b := yy1 + 116130*cb1 - if uint32(b)&0xff000000 == 0 { //nolint:gosec - b >>= 16 - } else { - b = ^(b >> 31) - } - - d := dst[j : j+4 : j+4] - d[0] = uint8(r) // #nosec G115 -- Overflow desired. - d[1] = uint8(g) // #nosec G115 -- Overflow desired. - d[2] = uint8(b) // #nosec G115 -- Overflow desired. - d[3] = 0xff - - iy++ - j += 4 - } - } - - case image.YCbCrSubsampleRatio444: - for y := y1; y < y2; y++ { - iy := (y-img.Rect.Min.Y)*img.YStride + (x1 - img.Rect.Min.X) - - yBase := (y - img.Rect.Min.Y) * img.CStride - - for x := x1; x < x2; x++ { - ic := yBase + (x - img.Rect.Min.X) - - yy1 := int32(img.Y[iy]) * 0x10101 - cb1 := int32(img.Cb[ic]) - 128 - cr1 := int32(img.Cr[ic]) - 128 - - r := yy1 + 91881*cr1 - if uint32(r)&0xff000000 == 0 { //nolint:gosec - r >>= 16 - } else { - r = ^(r >> 31) - } - - g := yy1 - 22554*cb1 - 46802*cr1 - if uint32(g)&0xff000000 == 0 { //nolint:gosec - g >>= 16 - } else { - g = ^(g >> 31) - } - - b := yy1 + 116130*cb1 - if uint32(b)&0xff000000 == 0 { //nolint:gosec - b >>= 16 - } else { - b = ^(b >> 31) - } - - d := dst[j : j+4 : j+4] - d[0] = uint8(r) // #nosec G115 -- Overflow desired. - d[1] = uint8(g) // #nosec G115 -- Overflow desired. - d[2] = uint8(b) // #nosec G115 -- Overflow desired. - d[3] = 0xff - - iy++ + d[0] = c.R + d[1] = c.G + d[2] = c.B + d[3] = c.A j += 4 + i++ } } default: + j := 0 + b := s.image.Bounds() + x1 += b.Min.X + x2 += b.Min.X + y1 += b.Min.Y + y2 += b.Min.Y for y := y1; y < y2; y++ { - iy := (y-img.Rect.Min.Y)*img.YStride + (x1 - img.Rect.Min.X) for x := x1; x < x2; x++ { - ic := img.COffset(x, y) - - yy1 := int32(img.Y[iy]) * 0x10101 - cb1 := int32(img.Cb[ic]) - 128 - cr1 := int32(img.Cr[ic]) - 128 - - r := yy1 + 91881*cr1 - if uint32(r)&0xff000000 == 0 { //nolint:gosec - r >>= 16 - } else { - r = ^(r >> 31) - } - - g := yy1 - 22554*cb1 - 46802*cr1 - if uint32(g)&0xff000000 == 0 { //nolint:gosec - g >>= 16 - } else { - g = ^(g >> 31) - } - - b := yy1 + 116130*cb1 - if uint32(b)&0xff000000 == 0 { //nolint:gosec - b >>= 16 - } else { - b = ^(b >> 31) - } - + r16, g16, b16, a16 := s.image.At(x, y).RGBA() d := dst[j : j+4 : j+4] - d[0] = uint8(r) // #nosec G115 -- Overflow desired. - d[1] = uint8(g) // #nosec G115 -- Overflow desired. - d[2] = uint8(b) // #nosec G115 -- Overflow desired. - d[3] = 0xff - - iy++ + switch a16 { + case 0xffff: + d[0] = uint8(r16 >> 8) // #nosec G115 -- Overflow desired. + d[1] = uint8(g16 >> 8) // #nosec G115 -- Overflow desired. + d[2] = uint8(b16 >> 8) // #nosec G115 -- Overflow desired. + d[3] = 0xff + case 0: + d[0] = 0 + d[1] = 0 + d[2] = 0 + d[3] = 0 + default: + d[0] = uint8(((r16 * 0xffff) / a16) >> 8) // #nosec G115 -- Overflow desired. + d[1] = uint8(((g16 * 0xffff) / a16) >> 8) // #nosec G115 -- Overflow desired. + d[2] = uint8(((b16 * 0xffff) / a16) >> 8) // #nosec G115 -- Overflow desired. + d[3] = uint8(a16 >> 8) // #nosec G115 -- Overflow desired. + } j += 4 } } } } -func scanPaletted(img *image.Paletted, x1, y1, x2, y2 int, dst []uint8) { - var palette [256]color.NRGBA - if len(palette) < len(img.Palette) { - panic(gtserror.New("bound check elimination")) - } - for i := 0; i < len(img.Palette); i++ { - palette[i] = colorToNRGBA(img.Palette[i]) - } - j := 0 - for y := y1; y < y2; y++ { - i := y*img.Stride + x1 - for x := x1; x < x2; x++ { - c := palette[img.Pix[i]] - d := dst[j : j+4 : j+4] - d[0] = c.R - d[1] = c.G - d[2] = c.B - d[3] = c.A - j += 4 - i++ - } - } -} - -// inlined from: image/color.NRGBAModel.Convert() -func colorToNRGBA(c color.Color) color.NRGBA { - if c, ok := c.(color.NRGBA); ok { - return c - } - r, g, b, a := c.RGBA() - if a == 0xffff { - return color.NRGBA{ - uint8(r >> 8), // #nosec G115 -- from stdlib - uint8(g >> 8), // #nosec G115 -- from stdlib - uint8(b >> 8), // #nosec G115 -- from stdlib - 0xff, - } - } - if a == 0 { - return color.NRGBA{ - 0, - 0, - 0, - 0, - } - } - // Since Color.RGBA returns an alpha-premultiplied color, - // we should have r <= a && g <= a && b <= a. - r = (r * 0xffff) / a - g = (g * 0xffff) / a - b = (b * 0xffff) / a - return color.NRGBA{ - uint8(r >> 8), // #nosec G115 -- from stdlib - uint8(g >> 8), // #nosec G115 -- from stdlib - uint8(b >> 8), // #nosec G115 -- from stdlib - uint8(a >> 8), // #nosec G115 -- from stdlib - } -} - -func scanAny(img image.Image, x1, y1, x2, y2 int, dst []uint8) { - j := 0 - b := img.Bounds() - x1 += b.Min.X - x2 += b.Min.X - y1 += b.Min.Y - y2 += b.Min.Y - for y := y1; y < y2; y++ { - for x := x1; x < x2; x++ { - r16, g16, b16, a16 := img.At(x, y).RGBA() - d := dst[j : j+4 : j+4] - switch a16 { - case 0xffff: - d[0] = uint8(r16 >> 8) // #nosec G115 -- Overflow desired. - d[1] = uint8(g16 >> 8) // #nosec G115 -- Overflow desired. - d[2] = uint8(b16 >> 8) // #nosec G115 -- Overflow desired. - d[3] = 0xff - case 0: - d[0] = 0 - d[1] = 0 - d[2] = 0 - d[3] = 0 - default: - d[0] = uint8(((r16 * 0xffff) / a16) >> 8) // #nosec G115 -- Overflow desired. - d[1] = uint8(((g16 * 0xffff) / a16) >> 8) // #nosec G115 -- Overflow desired. - d[2] = uint8(((b16 * 0xffff) / a16) >> 8) // #nosec G115 -- Overflow desired. - d[3] = uint8(a16 >> 8) // #nosec G115 -- Overflow desired. - } - j += 4 - } - } -} - // reverse reverses the data // in contained pixel slice. -func reverse(pix8 []uint8) { - if len(pix8) <= 4 || len(pix8)%4 != 0 { +func reverse(pix []uint8) { + if len(pix) <= 4 { return } - for i, j := 0, len(pix8)-4; i < j; i, j = i+4, j-4 { - di := pix8[i : i+4 : i+4] - dj := pix8[j : j+4 : j+4] - di[0], dj[0] = dj[0], di[0] - di[1], dj[1] = dj[1], di[1] - di[2], dj[2] = dj[2], di[2] - di[3], dj[3] = dj[3], di[3] + i := 0 + j := len(pix) - 4 + for i < j { + pi := pix[i : i+4 : i+4] + pj := pix[j : j+4 : j+4] + pi[0], pj[0] = pj[0], pi[0] + pi[1], pj[1] = pj[1], pi[1] + pi[2], pj[2] = pj[2], pi[2] + pi[3], pj[3] = pj[3], pi[3] + i += 4 + j -= 4 } } -// clampFloatTo8 rounds and clamps -// float64 value to fit into uint8. -func clampFloatTo8(x float64) uint8 { +// clampFloat rounds and clamps float64 value to fit into uint8. +func clampFloat(x float64) uint8 { v := int64(x + 0.5) if v > 255 { return 255 diff --git a/internal/media/imaging_test.go b/internal/media/imaging_test.go deleted file mode 100644 index f46268a7b..000000000 --- a/internal/media/imaging_test.go +++ /dev/null @@ -1,157 +0,0 @@ -// GoToSocial -// Copyright (C) GoToSocial Authors admin@gotosocial.org -// SPDX-License-Identifier: AGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package media - -import ( - "fmt" - "image" - "image/gif" - "image/jpeg" - "image/png" - "io" - "path" - "reflect" - "strings" - "testing" - - "golang.org/x/image/webp" -) - -func BenchmarkFlipH(b *testing.B) { - benchmarkFunc(b, func(img image.Image) { - _ = flipH(img) - }) -} - -func BenchmarkFlipV(b *testing.B) { - benchmarkFunc(b, func(img image.Image) { - _ = flipV(img) - }) -} - -func BenchmarkRotate90(b *testing.B) { - benchmarkFunc(b, func(img image.Image) { - _ = rotate90(img) - }) -} - -func BenchmarkRotate180(b *testing.B) { - benchmarkFunc(b, func(img image.Image) { - _ = rotate180(img) - }) -} - -func BenchmarkRotate270(b *testing.B) { - benchmarkFunc(b, func(img image.Image) { - _ = rotate270(img) - }) -} - -func BenchmarkTranspose(b *testing.B) { - benchmarkFunc(b, func(img image.Image) { - _ = transpose(img) - }) -} - -func BenchmarkTransverse(b *testing.B) { - benchmarkFunc(b, func(img image.Image) { - _ = transverse(img) - }) -} - -func BenchmarkResizeHorizontalLinear(b *testing.B) { - benchmarkFunc(b, func(img image.Image) { - _ = resizeHorizontalLinear(img, 64) - }) -} - -func BenchmarkResizeVerticalLinear(b *testing.B) { - benchmarkFunc(b, func(img image.Image) { - _ = resizeVerticalLinear(img, 64) - }) -} - -func benchmarkFunc(b *testing.B, fn func(image.Image)) { - b.Helper() - for _, testcase := range []struct { - Path string - Decode func(io.Reader) (image.Image, error) - }{ - { - Path: "./test/big-panda.gif", - Decode: gif.Decode, - }, - { - Path: "./test/clock-original.gif", - Decode: gif.Decode, - }, - { - Path: "./test/test-jpeg.jpg", - Decode: jpeg.Decode, - }, - { - Path: "./test/test-png-noalphachannel.png", - Decode: png.Decode, - }, - { - Path: "./test/test-png-alphachannel.png", - Decode: png.Decode, - }, - { - Path: "./test/rainbow-original.png", - Decode: png.Decode, - }, - { - Path: "./test/nb-flag-original.webp", - Decode: webp.Decode, - }, - } { - file, err := openRead(testcase.Path) - if err != nil { - panic(err) - } - - img, err := testcase.Decode(file) - if err != nil { - panic(err) - } - - info, err := file.Stat() - if err != nil { - panic(err) - } - - file.Close() - - testname := fmt.Sprintf("ext=%s type=%s size=%d", - strings.TrimPrefix(path.Ext(testcase.Path), "."), - strings.TrimPrefix(reflect.TypeOf(img).String(), "*image."), - info.Size(), - ) - - b.Run(testname, func(b *testing.B) { - b.Helper() - b.ResetTimer() - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - fn(img) - } - }) - }) - } -} diff --git a/internal/media/probe.go b/internal/media/probe.go index 5c07b04fb..c66254b90 100644 --- a/internal/media/probe.go +++ b/internal/media/probe.go @@ -138,29 +138,17 @@ func readOrientation(r *os.File) int { orientationTag = 0x0112 ) - // Setup a read buffer. - var buf byteutil.Buffer - buf.B = make([]byte, 0, 64) + // Setup a discard read buffer. + buf := new(byteutil.Buffer) + buf.Guarantee(32) // discard simply reads into buf. discard := func(n int) error { - buf.Guarantee(n) + buf.Guarantee(n) // ensure big enough _, err := io.ReadFull(r, buf.B[:n]) return err } - // readUint16 reads uint16 bytes into buffer then parses. - readUint16 := func(b binary.ByteOrder) (uint16, error) { - _, err := io.ReadFull(r, buf.B[:2]) - return b.Uint16(buf.B[:2]), err - } - - // readUint32 reads uint32 bytes into buffer then parses. - readUint32 := func(b binary.ByteOrder) (uint32, error) { - _, err := io.ReadFull(r, buf.B[:4]) - return b.Uint32(buf.B[:4]), err - } - // Skip past JPEG SOI marker. if err := discard(2); err != nil { return orientationUnspecified @@ -169,13 +157,13 @@ func readOrientation(r *os.File) int { // Find JPEG // APP1 marker. for { - marker, err := readUint16(binary.BigEndian) - if err != nil { + var marker, size uint16 + + if err := binary.Read(r, binary.BigEndian, &marker); err != nil { return orientationUnspecified } - size, err := readUint16(binary.BigEndian) - if err != nil { + if err := binary.Read(r, binary.BigEndian, &size); err != nil { return orientationUnspecified } @@ -196,9 +184,11 @@ func readOrientation(r *os.File) int { } } - // Check if EXIF header is present. - header, err := readUint32(binary.BigEndian) - if err != nil { + // Check if EXIF + // header is present. + var header uint32 + + if err := binary.Read(r, binary.BigEndian, &header); err != nil { return orientationUnspecified } @@ -210,13 +200,17 @@ func readOrientation(r *os.File) int { return orientationUnspecified } - // Read byte order info. - byteOrderTag, err := readUint16(binary.BigEndian) - if err != nil { + // Read byte + // order info. + var ( + byteOrderTag uint16 + byteOrder binary.ByteOrder + ) + + if err := binary.Read(r, binary.BigEndian, &byteOrderTag); err != nil { return orientationUnspecified } - var byteOrder binary.ByteOrder switch byteOrderTag { case byteOrderBE: byteOrder = binary.BigEndian @@ -230,9 +224,11 @@ func readOrientation(r *os.File) int { return orientationUnspecified } - // Skip the EXIF offset. - offset, err := readUint32(byteOrder) - if err != nil { + // Skip the + // EXIF offset. + var offset uint32 + + if err := binary.Read(r, byteOrder, &offset); err != nil { return orientationUnspecified } @@ -244,16 +240,19 @@ func readOrientation(r *os.File) int { return orientationUnspecified } - // Read the number of tags. - numTags, err := readUint16(byteOrder) - if err != nil { + // Read the + // number of tags. + var numTags uint16 + + if err := binary.Read(r, byteOrder, &numTags); err != nil { return orientationUnspecified } // Find the orientation tag. for i := 0; i < int(numTags); i++ { - tag, err := readUint16(byteOrder) - if err != nil { + var tag uint16 + + if err := binary.Read(r, byteOrder, &tag); err != nil { return orientationUnspecified } @@ -268,8 +267,9 @@ func readOrientation(r *os.File) int { return orientationUnspecified } - val, err := readUint16(byteOrder) - if err != nil { + var val uint16 + + if err := binary.Read(r, byteOrder, &val); err != nil { return orientationUnspecified } diff --git a/internal/media/processingemoji.go b/internal/media/processingemoji.go index feb54da2f..d28edcc0c 100644 --- a/internal/media/processingemoji.go +++ b/internal/media/processingemoji.go @@ -44,6 +44,11 @@ type ProcessingEmoji struct { mgr *Manager // mgr instance (access to db / storage) } +// ID returns the ID of the underlying emoji. +func (p *ProcessingEmoji) ID() string { + return p.emoji.ID // immutable, safe outside mutex. +} + // LoadEmoji blocks until the static and fullsize image has been processed, and then returns the completed emoji. func (p *ProcessingEmoji) Load(ctx context.Context) (*gtsmodel.Emoji, error) { emoji, done, err := p.load(ctx) @@ -58,33 +63,6 @@ func (p *ProcessingEmoji) Load(ctx context.Context) (*gtsmodel.Emoji, error) { return emoji, err } -func (p *ProcessingEmoji) LoadAsync(deferred func()) *gtsmodel.Emoji { - p.mgr.state.Workers.Dereference.Queue.Push(func(ctx context.Context) { - if deferred != nil { - defer deferred() - } - - if _, _, err := p.load(ctx); err != nil { - log.Errorf(ctx, "error loading emoji: %v", err) - } - }) - - // Placeholder returns a copy of internally stored processing placeholder, - // returning only the fields that may be known *before* completion, - // and as such all fields which are safe to concurrently read. - placeholder := new(gtsmodel.Emoji) - placeholder.ID = p.emoji.ID - placeholder.Shortcode = p.emoji.Shortcode - placeholder.Domain = p.emoji.Domain - placeholder.Cached = new(bool) - placeholder.ImageRemoteURL = p.emoji.ImageRemoteURL - placeholder.ImageStaticRemoteURL = p.emoji.ImageStaticRemoteURL - placeholder.Disabled = p.emoji.Disabled - placeholder.VisibleInPicker = p.emoji.VisibleInPicker - placeholder.CategoryID = p.emoji.CategoryID - return placeholder -} - // load is the package private form of load() that is wrapped to catch context canceled. func (p *ProcessingEmoji) load(ctx context.Context) ( emoji *gtsmodel.Emoji, diff --git a/internal/middleware/ratelimit.go b/internal/middleware/ratelimit.go index 48c54636b..6d39721ec 100644 --- a/internal/middleware/ratelimit.go +++ b/internal/middleware/ratelimit.go @@ -25,7 +25,6 @@ import ( "time" "code.superseriousbusiness.org/gotosocial/internal/gtserror" - "code.superseriousbusiness.org/gotosocial/internal/log" "code.superseriousbusiness.org/gotosocial/internal/util" "github.com/gin-gonic/gin" "github.com/ulule/limiter/v3" @@ -79,43 +78,30 @@ func RateLimit(limit int, except []netip.Prefix) gin.HandlerFunc { // Use Gin's heuristic for determining // clientIP, which accounts for reverse // proxies and trusted proxies setting. - clientIP := c.ClientIP() - - // ClientIP must be parseable. - ip, err := netip.ParseAddr(clientIP) - if err != nil { - log.Warnf( - c.Request.Context(), - "cannot do rate limiting for this request as client IP %s could not be parsed;"+ - " your upstream reverse proxy may be misconfigured: %v", - err, - ) - c.Next() - return - } + clientIP := netip.MustParseAddr(c.ClientIP()) // Check if this IP is exempt from rate // limits and skip further checks if so. for _, prefix := range except { - if prefix.Contains(ip) { + if prefix.Contains(clientIP) { c.Next() return } } - if ip.Is6() { + if clientIP.Is6() { // Convert to "net" package IP for mask. - asIP := net.IP(ip.AsSlice()) + asIP := net.IP(clientIP.AsSlice()) // Apply coarse IPv6 mask. asIP = asIP.Mask(ipv6Mask) // Convert back to netip.Addr from net.IP. - ip, _ = netip.AddrFromSlice(asIP) + clientIP, _ = netip.AddrFromSlice(asIP) } // Fetch rate limit info for this (masked) clientIP. - context, err := limiter.Get(c, ip.String()) + context, err := limiter.Get(c, clientIP.String()) if err != nil { // Since we use an in-memory cache now, // it's actually impossible for this to diff --git a/internal/processing/admin/emoji.go b/internal/processing/admin/emoji.go index 5c391bf82..b45d31e96 100644 --- a/internal/processing/admin/emoji.go +++ b/internal/processing/admin/emoji.go @@ -293,7 +293,6 @@ func (p *Processor) emojiUpdateCopy( // Ensure target emoji is locally cached. target, err := p.federator.RecacheEmoji(ctx, target, - false, ) if err != nil { err := gtserror.Newf("error recaching emoji %s: %w", target.ImageRemoteURL, err) diff --git a/internal/processing/media/getfile.go b/internal/processing/media/getfile.go index 79ab08291..2f46d6e0c 100644 --- a/internal/processing/media/getfile.go +++ b/internal/processing/media/getfile.go @@ -247,7 +247,6 @@ func (p *Processor) getEmojiContent( emoji, err = p.federator.RecacheEmoji( ctx, emoji, - false, ) if err != nil { err := gtserror.Newf("error recaching emoji: %w", err)