github.com/status-im/status-go@v1.1.0/images/decode.go (about) 1 package images 2 3 import ( 4 "bytes" 5 "errors" 6 "image" 7 "image/gif" 8 "image/jpeg" 9 "image/png" 10 "io" 11 "io/ioutil" 12 "net/http" 13 "os" 14 "regexp" 15 "time" 16 "unicode/utf8" 17 18 "golang.org/x/image/webp" 19 20 "github.com/ethereum/go-ethereum/log" 21 ) 22 23 var ( 24 htmlCommentRegex = regexp.MustCompile(`(?i)<!--([\\s\\S]*?)-->`) 25 svgRegex = regexp.MustCompile(`(?i)^\s*(?:<\?xml[^>]*>\s*)?(?:<!doctype svg[^>]*>\s*)?<svg[^>]*>[^*]*<\/svg>\s*$`) 26 ) 27 28 // IsSVG returns true if the given buffer is a valid SVG image. 29 func IsSVG(buf []byte) bool { 30 var isBinary bool 31 if len(buf) < 24 { 32 isBinary = false 33 } 34 for i := 0; i < 14; i++ { 35 charCode, _ := utf8.DecodeRuneInString(string(buf[i])) 36 if charCode == 65533 || charCode <= 8 { 37 isBinary = true 38 } 39 } 40 return !isBinary && svgRegex.Match(htmlCommentRegex.ReplaceAll(buf, []byte{})) 41 } 42 43 func Decode(fileName string) (image.Image, error) { 44 file, err := os.Open(fileName) 45 if err != nil { 46 return nil, err 47 } 48 defer file.Close() 49 50 fb, err := prepareFileForDecode(file) 51 if err != nil { 52 return nil, err 53 } 54 55 return DecodeImageData(fb, file) 56 } 57 58 func DecodeFromURL(path string) (image.Image, error) { 59 client := http.Client{ 60 Timeout: 5 * time.Second, 61 } 62 res, err := client.Get(path) 63 if err != nil { 64 return nil, err 65 } 66 67 defer func() { 68 if err := res.Body.Close(); err != nil { 69 log.Error("failed to close profile pic http request body", "err", err) 70 } 71 }() 72 73 if res.StatusCode >= 400 { 74 return nil, errors.New(http.StatusText(res.StatusCode)) 75 } 76 77 bodyBytes, err := ioutil.ReadAll(res.Body) 78 if err != nil { 79 return nil, err 80 } 81 82 return DecodeImageData(bodyBytes, bytes.NewReader(bodyBytes)) 83 } 84 85 func prepareFileForDecode(file *os.File) ([]byte, error) { 86 // Read the first 14 bytes, used for performing image type checks before parsing the image data 87 fb := make([]byte, 14) 88 _, err := file.Read(fb) 89 if err != nil { 90 return nil, err 91 } 92 93 // Reset the read cursor 94 _, err = file.Seek(0, 0) 95 if err != nil { 96 return nil, err 97 } 98 99 return fb, nil 100 } 101 102 func DecodeImageData(buf []byte, r io.Reader) (img image.Image, err error) { 103 switch GetType(buf) { 104 case JPEG: 105 img, err = jpeg.Decode(r) 106 case PNG: 107 img, err = png.Decode(r) 108 case GIF: 109 img, err = gif.Decode(r) 110 case WEBP: 111 img, err = webp.Decode(r) 112 case UNKNOWN: 113 fallthrough 114 default: 115 return nil, errors.New("unsupported file type") 116 } 117 if err != nil { 118 return nil, err 119 } 120 121 return img, nil 122 } 123 124 func GetType(buf []byte) ImageType { 125 switch { 126 case IsJpeg(buf): 127 return JPEG 128 case IsPng(buf): 129 return PNG 130 case IsGif(buf): 131 return GIF 132 case IsWebp(buf): 133 return WEBP 134 case IsIco(buf): 135 return ICO 136 default: 137 return UNKNOWN 138 } 139 } 140 141 func GetMimeType(buf []byte) (string, error) { 142 switch { 143 case IsJpeg(buf): 144 return "jpeg", nil 145 case IsPng(buf): 146 return "png", nil 147 case IsGif(buf): 148 return "gif", nil 149 case IsWebp(buf): 150 return "webp", nil 151 case IsIco(buf): 152 return "ico", nil 153 case IsSVG(buf): 154 return "svg", nil 155 default: 156 return "", errors.New("image format not supported") 157 } 158 } 159 160 func IsJpeg(buf []byte) bool { 161 return len(buf) > 2 && 162 buf[0] == 0xFF && 163 buf[1] == 0xD8 && 164 buf[2] == 0xFF 165 } 166 167 func IsPng(buf []byte) bool { 168 return len(buf) > 3 && 169 buf[0] == 0x89 && buf[1] == 0x50 && 170 buf[2] == 0x4E && buf[3] == 0x47 171 } 172 173 func IsGif(buf []byte) bool { 174 return len(buf) > 2 && 175 buf[0] == 0x47 && buf[1] == 0x49 && buf[2] == 0x46 176 } 177 178 func IsWebp(buf []byte) bool { 179 return len(buf) > 11 && 180 buf[8] == 0x57 && buf[9] == 0x45 && 181 buf[10] == 0x42 && buf[11] == 0x50 182 } 183 184 func IsIco(buf []byte) bool { 185 return len(buf) > 4 && 186 buf[0] == 0 && buf[1] == 0 && buf[2] == 1 || buf[2] == 2 && 187 buf[4] > 0 188 } 189 190 func GetImageDimensions(imgBytes []byte) (int, int, error) { 191 // Decode image bytes 192 img, _, err := image.Decode(bytes.NewReader(imgBytes)) 193 if err != nil { 194 return 0, 0, err 195 } 196 // Get the image dimensions 197 bounds := img.Bounds() 198 width := bounds.Max.X - bounds.Min.X 199 height := bounds.Max.Y - bounds.Min.Y 200 return width, height, nil 201 }