| 
									
										
										
										
											2023-03-12 16:00:57 +01:00
										 |  |  | // 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 <http://www.gnu.org/licenses/>. | 
					
						
							| 
									
										
										
										
											2021-05-17 19:06:58 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | package media | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	"bufio" | 
					
						
							| 
									
										
										
										
											2021-12-28 16:36:00 +01:00
										 |  |  | 	"image" | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	"image/color" | 
					
						
							|  |  |  | 	"image/draw" | 
					
						
							| 
									
										
										
										
											2021-12-28 16:36:00 +01:00
										 |  |  | 	"image/jpeg" | 
					
						
							|  |  |  | 	"image/png" | 
					
						
							| 
									
										
										
										
											2022-01-16 18:52:55 +01:00
										 |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	"sync" | 
					
						
							| 
									
										
										
										
											2021-05-17 19:06:58 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-28 16:36:00 +01:00
										 |  |  | 	"github.com/buckket/go-blurhash" | 
					
						
							| 
									
										
										
										
											2022-08-10 14:05:14 +02:00
										 |  |  | 	"github.com/disintegration/imaging" | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/iotools" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// import to init webp encode/decoding. | 
					
						
							|  |  |  | 	_ "golang.org/x/image/webp" | 
					
						
							| 
									
										
										
										
											2021-05-17 19:06:58 +02:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | var ( | 
					
						
							|  |  |  | 	// pngEncoder provides our global PNG encoding with | 
					
						
							|  |  |  | 	// specified compression level, and memory pooled buffers. | 
					
						
							|  |  |  | 	pngEncoder = png.Encoder{ | 
					
						
							|  |  |  | 		CompressionLevel: png.DefaultCompression, | 
					
						
							|  |  |  | 		BufferPool:       &pngEncoderBufferPool{}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	// jpegBufferPool is a memory pool | 
					
						
							|  |  |  | 	// of byte buffers for JPEG encoding. | 
					
						
							|  |  |  | 	jpegBufferPool sync.Pool | 
					
						
							| 
									
										
										
										
											2021-12-28 16:36:00 +01:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | // gtsImage is a thin wrapper around the standard library image | 
					
						
							|  |  |  | // interface to provide our own useful helper functions for image | 
					
						
							|  |  |  | // size and aspect ratio calculations, streamed encoding to various | 
					
						
							|  |  |  | // types, and creating reduced size thumbnail images. | 
					
						
							|  |  |  | type gtsImage struct{ image image.Image } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // blankImage generates a blank image of given dimensions. | 
					
						
							|  |  |  | func blankImage(width int, height int) *gtsImage { | 
					
						
							|  |  |  | 	// create a rectangle with the same dimensions as the video | 
					
						
							|  |  |  | 	img := image.NewRGBA(image.Rect(0, 0, width, height)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// fill the rectangle with our desired fill color. | 
					
						
							|  |  |  | 	draw.Draw(img, img.Bounds(), &image.Uniform{ | 
					
						
							|  |  |  | 		color.RGBA{42, 43, 47, 0}, | 
					
						
							|  |  |  | 	}, image.Point{}, draw.Src) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return >sImage{image: img} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // decodeImage will decode image from reader stream and return image wrapped in our own gtsImage{} type. | 
					
						
							|  |  |  | func decodeImage(r io.Reader, opts ...imaging.DecodeOption) (*gtsImage, error) { | 
					
						
							|  |  |  | 	img, err := imaging.Decode(r, opts...) | 
					
						
							| 
									
										
										
										
											2021-12-28 16:36:00 +01:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	return >sImage{image: img}, nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-12-28 16:36:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | // Width returns the image width in pixels. | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | func (m *gtsImage) Width() int { | 
					
						
							|  |  |  | 	return m.image.Bounds().Size().X | 
					
						
							| 
									
										
										
										
											2021-12-28 16:36:00 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | // Height returns the image height in pixels. | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | func (m *gtsImage) Height() int { | 
					
						
							|  |  |  | 	return m.image.Bounds().Size().Y | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2021-12-28 16:36:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | // Size returns the total number of image pixels. | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | func (m *gtsImage) Size() int { | 
					
						
							|  |  |  | 	return m.image.Bounds().Size().X * | 
					
						
							|  |  |  | 		m.image.Bounds().Size().Y | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // AspectRatio returns the image ratio of width:height. | 
					
						
							|  |  |  | func (m *gtsImage) AspectRatio() float32 { | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// note: we cast bounds to float64 to prevent truncation | 
					
						
							|  |  |  | 	// and only at the end aspect ratio do we cast to float32 | 
					
						
							|  |  |  | 	// (as the sizes are likely to be much larger than ratio). | 
					
						
							|  |  |  | 	return float32(float64(m.image.Bounds().Size().X) / | 
					
						
							|  |  |  | 		float64(m.image.Bounds().Size().Y)) | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2021-12-28 16:36:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | // Thumbnail returns a small sized copy of gtsImage{}, limited to 512x512 if not small enough. | 
					
						
							|  |  |  | func (m *gtsImage) Thumbnail() *gtsImage { | 
					
						
							|  |  |  | 	const ( | 
					
						
							|  |  |  | 		// max thumb | 
					
						
							|  |  |  | 		// dimensions. | 
					
						
							|  |  |  | 		maxWidth  = 512 | 
					
						
							|  |  |  | 		maxHeight = 512 | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check the receiving image is within max thumnail bounds. | 
					
						
							|  |  |  | 	if m.Width() <= maxWidth && m.Height() <= maxHeight { | 
					
						
							|  |  |  | 		return >sImage{image: imaging.Clone(m.image)} | 
					
						
							| 
									
										
										
										
											2021-12-28 16:36:00 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	// Image is too large, needs to be resized to thumbnail max. | 
					
						
							|  |  |  | 	img := imaging.Fit(m.image, maxWidth, maxHeight, imaging.Linear) | 
					
						
							|  |  |  | 	return >sImage{image: img} | 
					
						
							| 
									
										
										
										
											2021-12-28 16:36:00 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | // Blurhash calculates the blurhash for the receiving image data. | 
					
						
							|  |  |  | func (m *gtsImage) Blurhash() (string, error) { | 
					
						
							|  |  |  | 	// for generating blurhashes, it's more cost effective to | 
					
						
							|  |  |  | 	// lose detail since it's blurry, so make a tiny version. | 
					
						
							|  |  |  | 	tiny := imaging.Resize(m.image, 32, 0, imaging.NearestNeighbor) | 
					
						
							| 
									
										
										
										
											2022-12-17 05:38:56 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	// Encode blurhash from resized version | 
					
						
							|  |  |  | 	return blurhash.Encode(4, 3, tiny) | 
					
						
							| 
									
										
										
										
											2022-12-17 05:38:56 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | // ToJPEG creates a new streaming JPEG encoder from receiving image, and a size ptr | 
					
						
							|  |  |  | // which stores the number of bytes written during the image encoding process. | 
					
						
							|  |  |  | func (m *gtsImage) ToJPEG(opts *jpeg.Options) io.Reader { | 
					
						
							|  |  |  | 	return iotools.StreamWriteFunc(func(w io.Writer) error { | 
					
						
							|  |  |  | 		// Get encoding buffer | 
					
						
							|  |  |  | 		bw := getJPEGBuffer(w) | 
					
						
							| 
									
										
										
										
											2022-01-10 18:36:09 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 		// Encode JPEG to buffered writer. | 
					
						
							|  |  |  | 		err := jpeg.Encode(bw, m.image, opts) | 
					
						
							| 
									
										
										
										
											2022-01-10 18:36:09 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 		// Replace buffer. | 
					
						
							|  |  |  | 		// | 
					
						
							|  |  |  | 		// NOTE: jpeg.Encode() already | 
					
						
							|  |  |  | 		// performs a bufio.Writer.Flush(). | 
					
						
							|  |  |  | 		putJPEGBuffer(bw) | 
					
						
							| 
									
										
										
										
											2022-08-10 14:05:14 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 		return err | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-12-28 16:36:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | // ToPNG creates a new streaming PNG encoder from receiving image, and a size ptr | 
					
						
							|  |  |  | // which stores the number of bytes written during the image encoding process. | 
					
						
							|  |  |  | func (m *gtsImage) ToPNG() io.Reader { | 
					
						
							|  |  |  | 	return iotools.StreamWriteFunc(func(w io.Writer) error { | 
					
						
							|  |  |  | 		return pngEncoder.Encode(w, m.image) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-12-28 16:36:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | // getJPEGBuffer fetches a reset JPEG encoding buffer from global JPEG buffer pool. | 
					
						
							|  |  |  | func getJPEGBuffer(w io.Writer) *bufio.Writer { | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	v := jpegBufferPool.Get() | 
					
						
							|  |  |  | 	if v == nil { | 
					
						
							|  |  |  | 		v = bufio.NewWriter(nil) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	buf := v.(*bufio.Writer) | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	buf.Reset(w) | 
					
						
							|  |  |  | 	return buf | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-01-09 18:41:22 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | // putJPEGBuffer resets the given bufio writer and places in global JPEG buffer pool. | 
					
						
							|  |  |  | func putJPEGBuffer(buf *bufio.Writer) { | 
					
						
							|  |  |  | 	buf.Reset(nil) | 
					
						
							|  |  |  | 	jpegBufferPool.Put(buf) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-12-28 16:36:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | // pngEncoderBufferPool implements png.EncoderBufferPool. | 
					
						
							|  |  |  | type pngEncoderBufferPool sync.Pool | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (p *pngEncoderBufferPool) Get() *png.EncoderBuffer { | 
					
						
							|  |  |  | 	buf, _ := (*sync.Pool)(p).Get().(*png.EncoderBuffer) | 
					
						
							|  |  |  | 	return buf | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-01-09 18:41:22 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | func (p *pngEncoderBufferPool) Put(buf *png.EncoderBuffer) { | 
					
						
							|  |  |  | 	(*sync.Pool)(p).Put(buf) | 
					
						
							| 
									
										
										
										
											2021-12-28 16:36:00 +01:00
										 |  |  | } |