最後活躍 1727271116

Jigsaw1601 已修改 1727271116. 還原成這個修訂版本

1 file changed, 117 insertions

image_optimizer.go(檔案已創建)

@@ -0,0 +1,117 @@
1 + // Package optimizer provides an interface for optimizing images. It provides both an in-process
2 + // optimizer that supports JPEG outputs, and an imaginary optimizer that supports WEBP outputs.
3 + package optimizer
4 +
5 + import (
6 + "bytes"
7 + "context"
8 + "image"
9 + "image/jpeg"
10 + "io"
11 +
12 + _ "image/png"
13 +
14 + "github.com/hay-kot/httpkit/errtrace"
15 + "github.com/nfnt/resize"
16 + )
17 +
18 + const (
19 + MaxWidth = 800
20 + )
21 +
22 + type OptimizerResult struct {
23 + Body io.ReadCloser
24 + ContentType string
25 + ContentLength int64
26 + Extension string
27 + }
28 +
29 + type Optimizer interface {
30 + Optimize(ctx context.Context, body io.Reader, contentType string) (*OptimizerResult, error)
31 + }
32 +
33 + var _ Optimizer = &InProcessOptimizer{}
34 +
35 + type InProcessOptimizer struct {
36 + sem chan struct{}
37 + }
38 +
39 + func NewInProcessOptimizer() *InProcessOptimizer {
40 + return &InProcessOptimizer{
41 + sem: make(chan struct{}, 10),
42 + }
43 + }
44 +
45 + // UnlimitedConcurrency disables the semaphore, allowing unlimited concurrency.
46 + // This is useful for testing purposes, or if you have significant compute resources.
47 + func (i *InProcessOptimizer) UnlimitedConcurrency() {
48 + i.sem = nil
49 + }
50 +
51 + func (i *InProcessOptimizer) Optimize(ctx context.Context, body io.Reader, contentType string) (*OptimizerResult, error) {
52 + if i.sem != nil {
53 + // Acquire a semaphore slot
54 + i.sem <- struct{}{}
55 +
56 + // Release the semaphore slot
57 + defer func() {
58 + <-i.sem
59 + }()
60 + }
61 +
62 + // Check if the content type is supported
63 + if contentType == "image/webp" {
64 + buff := &bytes.Buffer{}
65 + nRead, err := io.Copy(buff, body)
66 + if err != nil {
67 + return nil, errtrace.Wrap(err)
68 + }
69 +
70 + return &OptimizerResult{
71 + Body: io.NopCloser(buff),
72 + ContentType: contentType,
73 + ContentLength: nRead,
74 + Extension: "webp",
75 + }, nil
76 + }
77 +
78 + // Decode the image
79 + img, _, err := image.Decode(body)
80 + if err != nil {
81 + return nil, errtrace.Wrap(err)
82 +
83 + // This may not be necessary, keeping it to refer to later.
84 + /* // try webp, sometimes it's not detected correctly */
85 + /* var webpErr error */
86 + /* img, webpErr = webp.Decode(body) */
87 + /* if webpErr != nil { */
88 + /* return nil, errtrace.Wrapf(err, "failed to decode image: %s", webpErr.Error()) */
89 + /* } */
90 + }
91 +
92 + // Calculate the new dimensions while maintaining the aspect ratio
93 + newWidth := uint(MaxWidth)
94 + newHeight := uint(float64(newWidth) / float64(img.Bounds().Dx()) * float64(img.Bounds().Dy()))
95 +
96 + // Resize the image
97 + resizedImg := resize.Resize(newWidth, newHeight, img, resize.Lanczos3)
98 +
99 + // Encode the resized image as JPEG with 75% quality
100 + jpegOptions := jpeg.Options{
101 + Quality: 75,
102 + }
103 +
104 + // Encode the resized image
105 + buf := new(bytes.Buffer)
106 + err = jpeg.Encode(buf, resizedImg, &jpegOptions)
107 + if err != nil {
108 + return nil, errtrace.Wrap(err)
109 + }
110 +
111 + return &OptimizerResult{
112 + Body: io.NopCloser(buf),
113 + ContentType: "image/jpeg",
114 + ContentLength: int64(buf.Len()),
115 + Extension: "jpg",
116 + }, nil
117 + }
上一頁 下一頁