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  }