package main import ( "context" "fmt" "image" "image/color" "image/draw" _ "image/gif" "image/jpeg" _ "image/png" "io" "net/http" "os" "path/filepath" "time" _ "golang.org/x/image/webp" ) const maxImageBytes = 50 << 20 type Downloader struct { client *http.Client } func newDownloader() *Downloader { return &Downloader{client: &http.Client{Timeout: 45 * time.Second}} } func (d *Downloader) download(ctx context.Context, apiURL, destination string) error { req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil) if err != nil { return fmt.Errorf("创建图片请求: %w", err) } req.Header.Set("User-Agent", "GoWallpaper/1.0") req.Header.Set("Accept", "image/*") resp, err := d.client.Do(req) if err != nil { return fmt.Errorf("请求图片 API: %w", err) } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { return fmt.Errorf("图片 API 返回 HTTP %d", resp.StatusCode) } if resp.ContentLength > maxImageBytes { return fmt.Errorf("图片超过 %d MB 限制", maxImageBytes>>20) } limited := io.LimitReader(resp.Body, maxImageBytes+1) img, _, err := image.Decode(limited) if err != nil { return fmt.Errorf("API 响应不是支持的图片: %w", err) } if img.Bounds().Dx() <= 0 || img.Bounds().Dy() <= 0 { return fmt.Errorf("API 返回了无效尺寸的图片") } // JPEG 没有透明通道,先铺黑色背景可避免透明图片出现不可预测颜色。 bounds := img.Bounds() flat := image.NewRGBA(image.Rect(0, 0, bounds.Dx(), bounds.Dy())) draw.Draw(flat, flat.Bounds(), &image.Uniform{C: color.Black}, image.Point{}, draw.Src) draw.Draw(flat, flat.Bounds(), img, bounds.Min, draw.Over) tmp, err := os.CreateTemp(filepath.Dir(destination), ".download-*.jpg") if err != nil { return fmt.Errorf("创建缓存文件: %w", err) } tmpName := tmp.Name() defer os.Remove(tmpName) if err := jpeg.Encode(tmp, flat, &jpeg.Options{Quality: 92}); err != nil { tmp.Close() return fmt.Errorf("保存图片: %w", err) } if err := tmp.Sync(); err != nil { tmp.Close() return err } if err := tmp.Close(); err != nil { return err } if err := os.Rename(tmpName, destination); err != nil { return fmt.Errorf("保存缓存图片: %w", err) } return nil }