返回 DeepSeek-Reasonix
external_opener_icon.go
根目录 / desktop / external_opener_icon.go
1 package main
2
3 import (
4 "bytes"
5 "encoding/base64"
6 "fmt"
7 "image"
8 "image/png"
9 "os"
10 "path/filepath"
11 "strings"
12 "sync"
13 )
14
15 const maxExternalOpenerIconBytes = 768 << 10
16
17 var externalOpenerIconCache sync.Map
18
19 func externalOpenerIconDataURL(spec externalOpenerSpec) string {
20 source := strings.TrimSpace(spec.IconSource)
21 if source == "" {
22 return ""
23 }
24 cacheKey := source
25 if info, err := os.Stat(source); err == nil {
26 cacheKey = fmt.Sprintf("%s:%d:%d", source, info.ModTime().UnixNano(), info.Size())
27 }
28 if cached, ok := externalOpenerIconCache.Load(cacheKey); ok {
29 return cached.(string)
30 }
31 icon := platformExternalOpenerIconDataURL(spec)
32 if icon != "" {
33 externalOpenerIconCache.Store(cacheKey, icon)
34 }
35 return icon
36 }
37
38 func externalOpenerIconFileDataURL(path string) string {
39 info, err := os.Stat(path)
40 if err != nil || info.IsDir() || info.Size() <= 0 || info.Size() > maxExternalOpenerIconBytes {
41 return ""
42 }
43 data, err := os.ReadFile(path)
44 if err != nil || len(data) == 0 || len(data) > maxExternalOpenerIconBytes {
45 return ""
46 }
47 ext := strings.ToLower(filepath.Ext(path))
48 switch ext {
49 case ".png":
50 return externalOpenerPNGDataURL(data)
51 case ".jpg", ".jpeg":
52 return "data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(data)
53 case ".webp":
54 return "data:image/webp;base64," + base64.StdEncoding.EncodeToString(data)
55 case ".svg":
56 return "data:image/svg+xml;base64," + base64.StdEncoding.EncodeToString(data)
57 default:
58 return ""
59 }
60 }
61
62 func externalOpenerPNGDataURL(data []byte) string {
63 if len(data) == 0 || len(data) > maxExternalOpenerIconBytes {
64 return ""
65 }
66 return "data:image/png;base64," + base64.StdEncoding.EncodeToString(data)
67 }
68
69 func externalOpenerPNGFromBGRAComposites(black, white []byte, width, height int) []byte {
70 if width <= 0 || height <= 0 || len(black) != width*height*4 || len(white) != len(black) {
71 return nil
72 }
73 clampByte := func(value int) uint8 {
74 if value < 0 {
75 return 0
76 }
77 if value > 255 {
78 return 255
79 }
80 return uint8(value)
81 }
82 imageData := image.NewNRGBA(image.Rect(0, 0, width, height))
83 for source := 0; source < len(black); source += 4 {
84 blackB, blackG, blackR := int(black[source]), int(black[source+1]), int(black[source+2])
85 whiteB, whiteG, whiteR := int(white[source]), int(white[source+1]), int(white[source+2])
86 diff := func(light, dark int) int {
87 if light <= dark {
88 return 0
89 }
90 return int(clampByte(light - dark))
91 }
92 alpha := 255 - (diff(whiteR, blackR)+diff(whiteG, blackG)+diff(whiteB, blackB))/3
93 destination := source
94 if alpha > 0 {
95 imageData.Pix[destination] = clampByte(blackR * 255 / alpha)
96 imageData.Pix[destination+1] = clampByte(blackG * 255 / alpha)
97 imageData.Pix[destination+2] = clampByte(blackB * 255 / alpha)
98 }
99 imageData.Pix[destination+3] = uint8(alpha)
100 }
101 var encoded bytes.Buffer
102 if err := png.Encode(&encoded, imageData); err != nil {
103 return nil
104 }
105 return encoded.Bytes()
106 }
107
107 lines GO