package converter import ( "fmt" "image" "image/gif" "image/jpeg" "image/png" "os" "strings" "golang.org/x/image/bmp" "golang.org/x/image/tiff" "golang.org/x/image/webp" ) func convertImage(inputPath, outputPath, targetFormat string) error { f, err := os.Open(inputPath) if err != nil { return fmt.Errorf("opening image: %w", err) } defer f.Close() // Decode input — Go's image package auto-registers png, jpeg, gif via import // We also need x/image decoders for bmp, tiff, webp img, format, err := image.Decode(f) if err != nil { // Try specific decoders as fallback f.Seek(0, 0) img, err = tryDecodeImage(f, inputPath) if err != nil { return fmt.Errorf("decoding image (%s): %w", format, err) } } out, err := os.Create(outputPath) if err != nil { return fmt.Errorf("creating output: %w", err) } defer out.Close() target := strings.ToLower(targetFormat) switch target { case "png": return png.Encode(out, img) case "jpg", "jpeg": return jpeg.Encode(out, img, &jpeg.Options{Quality: 92}) case "gif": return gif.Encode(out, img, &gif.Options{NumColors: 256}) case "bmp": return bmp.Encode(out, img) case "tiff", "tif": return tiff.Encode(out, img, &tiff.Options{Compression: tiff.Deflate}) case "webp": // Go doesn't have a webp encoder in stdlib. Use ffmpeg as fallback. out.Close() os.Remove(outputPath) return convertImageViaFFmpeg(inputPath, outputPath, target) case "avif": out.Close() os.Remove(outputPath) return convertImageViaFFmpeg(inputPath, outputPath, target) case "ico": // ICO is just a small PNG wrapped in ICO container for simple cases. // We'll convert to PNG via ffmpeg or write a 256x256 PNG for now. out.Close() os.Remove(outputPath) return convertImageViaFFmpeg(inputPath, outputPath, target) default: out.Close() os.Remove(outputPath) return fmt.Errorf("unsupported image target format: %s", target) } } func tryDecodeImage(f *os.File, path string) (image.Image, error) { ext := strings.ToLower(path) switch { case strings.HasSuffix(ext, ".webp"): return webp.Decode(f) case strings.HasSuffix(ext, ".bmp"): return bmp.Decode(f) case strings.HasSuffix(ext, ".tiff"), strings.HasSuffix(ext, ".tif"): return tiff.Decode(f) default: return nil, fmt.Errorf("unable to decode image: %s", path) } } func convertImageViaFFmpeg(inputPath, outputPath, format string) error { args := []string{"-y", "-i", inputPath} switch format { case "webp": args = append(args, "-quality", "90", outputPath) case "avif": args = append(args, "-c:v", "libaom-av1", "-still-picture", "1", outputPath) case "ico": // Scale to 256x256 for ICO args = append(args, "-vf", "scale=256:256:force_original_aspect_ratio=decrease,pad=256:256:(ow-iw)/2:(oh-ih)/2", outputPath) default: args = append(args, outputPath) } return mediaConvert(inputPath, outputPath, format, args) }