Browse Source

Joined imagedetect and imagetype

Viktor Sokolov 1 month ago
parent
commit
0d2885c131

+ 0 - 89
imagedetect/detect.go

@@ -1,89 +0,0 @@
-package imagedetect
-
-import (
-	"io"
-
-	"github.com/imgproxy/imgproxy/v3/bufreader"
-	"github.com/imgproxy/imgproxy/v3/imagetype_new"
-)
-
-const (
-	// maxDetectionLimit is maximum bytes detectors allowed to read from the source
-	maxDetectionLimit = 32768
-)
-
-// Detect attempts to detect the image type from a reader.
-// It first tries magic byte detection, then custom detectors in registration order
-func Detect(r io.Reader) (imagetype_new.Type, error) {
-	br := bufreader.New(io.LimitReader(r, maxDetectionLimit))
-
-	for _, fn := range registry.detectors {
-		br.Rewind()
-		if typ, err := fn(br); err == nil && typ != imagetype_new.Unknown {
-			return typ, nil
-		}
-	}
-
-	return imagetype_new.Unknown, newUnknownFormatError()
-}
-
-// hasMagicBytes checks if the data matches a magic byte signature
-// Supports '?' characters in signature which match any byte
-func hasMagicBytes(data []byte, magic []byte) bool {
-	if len(data) < len(magic) {
-		return false
-	}
-
-	for i, c := range magic {
-		if c != data[i] && c != '?' {
-			return false
-		}
-	}
-	return true
-}
-
-// init registers default magic bytes for common image formats
-func init() {
-	// JPEG magic bytes
-	RegisterMagicBytes([]byte("\xff\xd8"), imagetype_new.JPEG)
-
-	// JXL magic bytes
-	//
-	// NOTE: for "naked" jxl (0xff 0x0a) there is no way to ensure this is a JXL file, except to fully
-	// decode it. The data starts right after it, no additional marker bytes are provided.
-	// We stuck with the potential false positives here.
-	RegisterMagicBytes([]byte{0xff, 0x0a}, imagetype_new.JXL)                                                             // JXL codestream (can't use string due to 0x0a)
-	RegisterMagicBytes([]byte{0x00, 0x00, 0x00, 0x0C, 0x4A, 0x58, 0x4C, 0x20, 0x0D, 0x0A, 0x87, 0x0A}, imagetype_new.JXL) // JXL container (has null bytes)
-
-	// PNG magic bytes
-	RegisterMagicBytes([]byte("\x89PNG\r\n\x1a\n"), imagetype_new.PNG)
-
-	// WEBP magic bytes (RIFF container with WEBP fourcc) - using wildcard for size
-	RegisterMagicBytes([]byte("RIFF????WEBP"), imagetype_new.WEBP)
-
-	// GIF magic bytes
-	RegisterMagicBytes([]byte("GIF8?a"), imagetype_new.GIF)
-
-	// ICO magic bytes
-	RegisterMagicBytes([]byte{0, 0, 1, 0}, imagetype_new.ICO) // ICO (has null bytes)
-
-	// HEIC/HEIF magic bytes with wildcards for size
-	RegisterMagicBytes([]byte("????ftypheic"), imagetype_new.HEIC)
-	RegisterMagicBytes([]byte("????ftypheix"), imagetype_new.HEIC)
-	RegisterMagicBytes([]byte("????ftyphevc"), imagetype_new.HEIC)
-	RegisterMagicBytes([]byte("????ftypheim"), imagetype_new.HEIC)
-	RegisterMagicBytes([]byte("????ftypheis"), imagetype_new.HEIC)
-	RegisterMagicBytes([]byte("????ftyphevm"), imagetype_new.HEIC)
-	RegisterMagicBytes([]byte("????ftyphevs"), imagetype_new.HEIC)
-	RegisterMagicBytes([]byte("????ftypmif1"), imagetype_new.HEIC)
-
-	// AVIF magic bytes
-	RegisterMagicBytes([]byte("????ftypavif"), imagetype_new.AVIF)
-
-	// BMP magic bytes
-	RegisterMagicBytes([]byte("BM"), imagetype_new.BMP)
-
-	// TIFF magic bytes (little-endian and big-endian)
-	RegisterMagicBytes([]byte("II*\x00"), imagetype_new.TIFF) // Little-endian
-	RegisterMagicBytes([]byte("MM\x00*"), imagetype_new.TIFF) // Big-endian
-}

+ 0 - 41
imagedetect/detect_test.go

@@ -1,41 +0,0 @@
-package imagedetect
-
-import (
-	"os"
-	"testing"
-
-	"github.com/imgproxy/imgproxy/v3/imagetype_new"
-	"github.com/stretchr/testify/require"
-)
-
-func TestDetect(t *testing.T) {
-	tests := []struct {
-		name string
-		file string
-		want imagetype_new.Type
-	}{
-		{"JPEG", "../testdata/test-images/jpg/jpg.jpg", imagetype_new.JPEG},
-		{"JXL", "../testdata/test-images/jxl/jxl.jxl", imagetype_new.JXL},
-		{"PNG", "../testdata/test-images/png/png.png", imagetype_new.PNG},
-		{"WEBP", "../testdata/test-images/webp/webp.webp", imagetype_new.WEBP},
-		{"GIF", "../testdata/test-images/gif/gif.gif", imagetype_new.GIF},
-		{"ICO", "../testdata/test-images/ico/png-256x256.ico", imagetype_new.ICO},
-		{"SVG", "../testdata/test-images/svg/svg.svg", imagetype_new.SVG},
-		{"HEIC", "../testdata/test-images/heif/heif.heif", imagetype_new.HEIC},
-		{"BMP", "../testdata/test-images/bmp/24-bpp.bmp", imagetype_new.BMP},
-		{"TIFF", "../testdata/test-images/tiff/tiff.tiff", imagetype_new.TIFF},
-		{"SVG", "../testdata/test-images/svg/svg.svg", imagetype_new.SVG},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			f, err := os.Open(tt.file)
-			require.NoError(t, err)
-			defer f.Close()
-
-			got, err := Detect(f)
-			require.NoError(t, err)
-			require.Equal(t, tt.want, got)
-		})
-	}
-}

+ 0 - 50
imagedetect/registry.go

@@ -1,50 +0,0 @@
-package imagedetect
-
-import (
-	"github.com/imgproxy/imgproxy/v3/bufreader"
-	"github.com/imgproxy/imgproxy/v3/imagetype_new"
-)
-
-// DetectFunc is a function that detects the image type from byte data
-type DetectFunc func(r bufreader.ReadPeeker) (imagetype_new.Type, error)
-
-// Registry manages the registration and execution of image type detectors
-type Registry struct {
-	detectors []DetectFunc
-}
-
-// Global registry instance
-var registry = &Registry{}
-
-// RegisterDetector registers a custom detector function
-// Detectors are tried in the order they were registered
-func RegisterDetector(detector DetectFunc, bytesNeeded int) {
-	registry.RegisterDetector(detector, bytesNeeded)
-}
-
-// RegisterMagicBytes registers magic bytes for a specific image type
-// Magic byte detectors are always tried before custom detectors
-func RegisterMagicBytes(signature []byte, typ imagetype_new.Type) {
-	registry.RegisterMagicBytes(signature, typ)
-}
-
-// RegisterDetector registers a custom detector function on this registry instance
-func (r *Registry) RegisterDetector(detector DetectFunc, bytesNeeded int) {
-	r.detectors = append(r.detectors, detector)
-}
-
-// RegisterMagicBytes registers magic bytes for a specific image type on this registry instance
-func (r *Registry) RegisterMagicBytes(signature []byte, typ imagetype_new.Type) {
-	r.detectors = append(r.detectors, func(r bufreader.ReadPeeker) (imagetype_new.Type, error) {
-		b, err := r.Peek(len(signature))
-		if err != nil {
-			return imagetype_new.Unknown, err
-		}
-
-		if hasMagicBytes(b, signature) {
-			return typ, nil
-		}
-
-		return imagetype_new.Unknown, nil
-	})
-}

+ 0 - 47
imagedetect/registry_test.go

@@ -1,47 +0,0 @@
-package imagedetect
-
-import (
-	"testing"
-
-	"github.com/imgproxy/imgproxy/v3/bufreader"
-	"github.com/imgproxy/imgproxy/v3/imagetype_new"
-	"github.com/stretchr/testify/require"
-)
-
-func TestRegisterDetector(t *testing.T) {
-	// Create a test registry to avoid interfering with global state
-	testRegistry := &Registry{}
-
-	// Create a test detector function
-	testDetector := func(r bufreader.ReadPeeker) (imagetype_new.Type, error) {
-		b, err := r.Peek(2)
-		if err != nil {
-			return imagetype_new.Unknown, err
-		}
-		if len(b) >= 2 && b[0] == 0xFF && b[1] == 0xD8 {
-			return imagetype_new.JPEG, nil
-		}
-		return imagetype_new.Unknown, newUnknownFormatError()
-	}
-
-	// Register the detector using the method
-	testRegistry.RegisterDetector(testDetector, 64)
-
-	// Verify the detector is registered
-	require.Len(t, testRegistry.detectors, 1)
-	require.NotNil(t, testRegistry.detectors[0])
-}
-
-func TestRegisterMagicBytes(t *testing.T) {
-	// Create a test registry to avoid interfering with global state
-	testRegistry := &Registry{}
-
-	require.Empty(t, testRegistry.detectors)
-
-	// Register magic bytes for JPEG using the method
-	jpegMagic := []byte{0xFF, 0xD8}
-	testRegistry.RegisterMagicBytes(jpegMagic, imagetype_new.JPEG)
-
-	// Verify the magic bytes are registered
-	require.Len(t, testRegistry.detectors, 1)
-}

+ 191 - 0
imagetype_new/defs.go

@@ -0,0 +1,191 @@
+package imagetype_new
+
+var (
+	JPEG = RegisterType(&TypeDesc{
+		String:                "jpeg",
+		Ext:                   ".jpg",
+		Mime:                  "image/jpeg",
+		IsVector:              false,
+		SupportsAlpha:         false,
+		SupportsColourProfile: true,
+		SupportsQuality:       true,
+		SupportsAnimationLoad: false,
+		SupportsAnimationSave: false,
+		SupportsThumbnail:     false,
+	})
+
+	JXL = RegisterType(&TypeDesc{
+		String:                "jxl",
+		Ext:                   ".jxl",
+		Mime:                  "image/jxl",
+		IsVector:              false,
+		SupportsAlpha:         true,
+		SupportsColourProfile: true,
+		SupportsQuality:       true,
+		SupportsAnimationLoad: true,
+		SupportsAnimationSave: false,
+		SupportsThumbnail:     false,
+	})
+
+	PNG = RegisterType(&TypeDesc{
+		String:                "png",
+		Ext:                   ".png",
+		Mime:                  "image/png",
+		IsVector:              false,
+		SupportsAlpha:         true,
+		SupportsColourProfile: true,
+		SupportsQuality:       false,
+		SupportsAnimationLoad: false,
+		SupportsAnimationSave: false,
+		SupportsThumbnail:     false,
+	})
+
+	WEBP = RegisterType(&TypeDesc{
+		String:                "webp",
+		Ext:                   ".webp",
+		Mime:                  "image/webp",
+		IsVector:              false,
+		SupportsAlpha:         true,
+		SupportsColourProfile: true,
+		SupportsQuality:       true,
+		SupportsAnimationLoad: true,
+		SupportsAnimationSave: true,
+		SupportsThumbnail:     false,
+	})
+
+	GIF = RegisterType(&TypeDesc{
+		String:                "gif",
+		Ext:                   ".gif",
+		Mime:                  "image/gif",
+		IsVector:              false,
+		SupportsAlpha:         true,
+		SupportsColourProfile: false,
+		SupportsQuality:       false,
+		SupportsAnimationLoad: true,
+		SupportsAnimationSave: true,
+		SupportsThumbnail:     false,
+	})
+
+	ICO = RegisterType(&TypeDesc{
+		String:                "ico",
+		Ext:                   ".ico",
+		Mime:                  "image/x-icon",
+		IsVector:              false,
+		SupportsAlpha:         true,
+		SupportsColourProfile: false,
+		SupportsQuality:       false,
+		SupportsAnimationLoad: false,
+		SupportsAnimationSave: false,
+		SupportsThumbnail:     false,
+	})
+
+	SVG = RegisterType(&TypeDesc{
+		String:                "svg",
+		Ext:                   ".svg",
+		Mime:                  "image/svg+xml",
+		IsVector:              true,
+		SupportsAlpha:         true,
+		SupportsColourProfile: false,
+		SupportsQuality:       false,
+		SupportsAnimationLoad: false,
+		SupportsAnimationSave: false,
+		SupportsThumbnail:     false,
+	})
+
+	HEIC = RegisterType(&TypeDesc{
+		String:                "heic",
+		Ext:                   ".heic",
+		Mime:                  "image/heif",
+		IsVector:              false,
+		SupportsAlpha:         true,
+		SupportsColourProfile: true,
+		SupportsQuality:       true,
+		SupportsAnimationLoad: false,
+		SupportsAnimationSave: false,
+		SupportsThumbnail:     true,
+	})
+
+	AVIF = RegisterType(&TypeDesc{
+		String:                "avif",
+		Ext:                   ".avif",
+		Mime:                  "image/avif",
+		IsVector:              false,
+		SupportsAlpha:         true,
+		SupportsColourProfile: true,
+		SupportsQuality:       true,
+		SupportsAnimationLoad: false,
+		SupportsAnimationSave: false,
+		SupportsThumbnail:     true,
+	})
+
+	BMP = RegisterType(&TypeDesc{
+		String:                "bmp",
+		Ext:                   ".bmp",
+		Mime:                  "image/bmp",
+		IsVector:              false,
+		SupportsAlpha:         true,
+		SupportsColourProfile: false,
+		SupportsQuality:       false,
+		SupportsAnimationLoad: false,
+		SupportsAnimationSave: false,
+		SupportsThumbnail:     false,
+	})
+
+	TIFF = RegisterType(&TypeDesc{
+		String:                "tiff",
+		Ext:                   ".tiff",
+		Mime:                  "image/tiff",
+		IsVector:              false,
+		SupportsAlpha:         true,
+		SupportsColourProfile: false,
+		SupportsQuality:       true,
+		SupportsAnimationLoad: false,
+		SupportsAnimationSave: false,
+		SupportsThumbnail:     false,
+	})
+)
+
+// init registers default magic bytes for common image formats
+func init() {
+	// NOTE: we cannot be 100% sure of image type until we fully decode it. This is especially true
+	// for "naked" jxl (0xff 0x0a). There is no other way to ensure this is a JXL file, except to fully
+	// decode it. Two bytes are too few to reliably identify the format. The same applies to ICO.
+
+	// JPEG magic bytes
+	RegisterMagicBytes(JPEG, []byte("\xff\xd8"))
+
+	// JXL magic bytes
+	RegisterMagicBytes(JXL, []byte{0xff, 0x0a})                                                             // JXL codestream (can't use string due to 0x0a)
+	RegisterMagicBytes(JXL, []byte{0x00, 0x00, 0x00, 0x0C, 0x4A, 0x58, 0x4C, 0x20, 0x0D, 0x0A, 0x87, 0x0A}) // JXL container (has null bytes)
+
+	// PNG magic bytes
+	RegisterMagicBytes(PNG, []byte("\x89PNG\r\n\x1a\n"))
+
+	// WEBP magic bytes (RIFF container with WEBP fourcc) - using wildcard for size
+	RegisterMagicBytes(WEBP, []byte("RIFF????WEBP"))
+
+	// GIF magic bytes
+	RegisterMagicBytes(GIF, []byte("GIF8?a"))
+
+	// ICO magic bytes
+	RegisterMagicBytes(ICO, []byte{0, 0, 1, 0}) // ICO (has null bytes)
+
+	// HEIC/HEIF magic bytes with wildcards for size
+	RegisterMagicBytes(HEIC, []byte("????ftypheic"),
+		[]byte("????ftypheix"),
+		[]byte("????ftyphevc"),
+		[]byte("????ftypheim"),
+		[]byte("????ftypheis"),
+		[]byte("????ftyphevm"),
+		[]byte("????ftyphevs"),
+		[]byte("????ftypmif1"))
+
+	// AVIF magic bytes
+	RegisterMagicBytes(AVIF, []byte("????ftypavif"))
+
+	// BMP magic bytes
+	RegisterMagicBytes(BMP, []byte("BM"))
+
+	// TIFF magic bytes (little-endian and big-endian)
+	RegisterMagicBytes(TIFF, []byte("II*\x00"), []byte("MM\x00*")) // Big-Endian, Little-endian
+}

+ 1 - 1
imagedetect/errors.go → imagetype_new/errors.go

@@ -1,4 +1,4 @@
-package imagedetect
+package imagetype_new
 
 import (
 	"net/http"

+ 93 - 7
imagetype_new/registry.go

@@ -1,5 +1,16 @@
 package imagetype_new
 
+import (
+	"io"
+
+	"github.com/imgproxy/imgproxy/v3/bufreader"
+)
+
+const (
+	// maxDetectionLimit is maximum bytes detectors allowed to read from the source
+	maxDetectionLimit = 32 * 1024
+)
+
 // TypeDesc is used to store metadata about an image type.
 // It represents the minimal information needed to make imgproxy to
 // work with the type.
@@ -16,9 +27,13 @@ type TypeDesc struct {
 	SupportsThumbnail     bool
 }
 
+// DetectFunc is a function that detects the image type from byte data
+type DetectFunc func(r bufreader.ReadPeeker) (Type, error)
+
 // Registry holds the type registry
 type Registry struct {
-	types []*TypeDesc
+	detectors []DetectFunc
+	types     []*TypeDesc
 }
 
 // globalRegistry is the default registry instance
@@ -30,10 +45,10 @@ func RegisterType(desc *TypeDesc) Type {
 	return globalRegistry.RegisterType(desc)
 }
 
-// GetType returns the TypeDesc for the given Type.
+// GetTypeDesc returns the TypeDesc for the given Type.
 // Returns nil if the type is not registered.
-func GetType(t Type) *TypeDesc {
-	return globalRegistry.GetType(t)
+func GetTypeDesc(t Type) *TypeDesc {
+	return globalRegistry.GetTypeDesc(t)
 }
 
 // RegisterType registers a new image type in this registry.
@@ -43,15 +58,86 @@ func (r *Registry) RegisterType(desc *TypeDesc) Type {
 	return Type(len(r.types)) // 0 is unknown
 }
 
-// GetType returns the TypeDesc for the given Type.
+// GetTypeDesc returns the TypeDesc for the given Type.
 // Returns nil if the type is not registered.
-func (r *Registry) GetType(t Type) *TypeDesc {
-	if t == Unknown {
+func (r *Registry) GetTypeDesc(t Type) *TypeDesc {
+	if t <= 0 {
 		return nil
 	}
 
 	if int(t-1) >= len(r.types) {
 		return nil
 	}
+
 	return r.types[t-1]
 }
+
+// RegisterDetector registers a custom detector function
+// Detectors are tried in the order they were registered
+func RegisterDetector(detector DetectFunc) {
+	globalRegistry.RegisterDetector(detector)
+}
+
+// RegisterMagicBytes registers magic bytes for a specific image type
+// Magic byte detectors are always tried before custom detectors
+func RegisterMagicBytes(typ Type, signature ...[]byte) {
+	globalRegistry.RegisterMagicBytes(typ, signature...)
+}
+
+// Detect attempts to detect the image type from a reader.
+// It first tries magic byte detection, then custom detectors in registration order
+func Detect(r io.Reader) (Type, error) {
+	return globalRegistry.Detect(r)
+}
+
+// RegisterDetector registers a custom detector function on this registry instance
+func (r *Registry) RegisterDetector(detector DetectFunc) {
+	r.detectors = append(r.detectors, detector)
+}
+
+// RegisterMagicBytes registers magic bytes for a specific image type on this registry instance
+func (r *Registry) RegisterMagicBytes(typ Type, signature ...[]byte) {
+	r.detectors = append(r.detectors, func(r bufreader.ReadPeeker) (Type, error) {
+		for _, sig := range signature {
+			b, err := r.Peek(len(sig))
+			if err != nil {
+				return Unknown, err
+			}
+
+			if hasMagicBytes(b, sig) {
+				return typ, nil
+			}
+		}
+
+		return Unknown, nil
+	})
+}
+
+// Detect runs image format detection
+func (r *Registry) Detect(re io.Reader) (Type, error) {
+	br := bufreader.New(io.LimitReader(re, maxDetectionLimit))
+
+	for _, fn := range globalRegistry.detectors {
+		br.Rewind()
+		if typ, err := fn(br); err == nil && typ != Unknown {
+			return typ, nil
+		}
+	}
+
+	return Unknown, newUnknownFormatError()
+}
+
+// hasMagicBytes checks if the data matches a magic byte signature
+// Supports '?' characters in signature which match any byte
+func hasMagicBytes(data []byte, magic []byte) bool {
+	if len(data) < len(magic) {
+		return false
+	}
+
+	for i, c := range magic {
+		if c != data[i] && c != '?' {
+			return false
+		}
+	}
+	return true
+}

+ 40 - 1
imagetype_new/registry_test.go

@@ -3,6 +3,7 @@ package imagetype_new
 import (
 	"testing"
 
+	"github.com/imgproxy/imgproxy/v3/bufreader"
 	"github.com/stretchr/testify/require"
 )
 
@@ -23,7 +24,7 @@ func TestRegisterType(t *testing.T) {
 	customType := testRegistry.RegisterType(customDesc)
 
 	// Verify the type is now registered
-	result := testRegistry.GetType(customType)
+	result := testRegistry.GetTypeDesc(customType)
 	require.NotNil(t, result)
 	require.Equal(t, customDesc.String, result.String)
 	require.Equal(t, customDesc.Ext, result.Ext)
@@ -137,3 +138,41 @@ func TestTypeProperties(t *testing.T) {
 		})
 	}
 }
+
+func TestRegisterDetector(t *testing.T) {
+	// Create a test registry to avoid interfering with global state
+	testRegistry := &Registry{}
+
+	// Create a test detector function
+	testDetector := func(r bufreader.ReadPeeker) (Type, error) {
+		b, err := r.Peek(2)
+		if err != nil {
+			return Unknown, err
+		}
+		if len(b) >= 2 && b[0] == 0xFF && b[1] == 0xD8 {
+			return JPEG, nil
+		}
+		return Unknown, newUnknownFormatError()
+	}
+
+	// Register the detector using the method
+	testRegistry.RegisterDetector(testDetector)
+
+	// Verify the detector is registered
+	require.Len(t, testRegistry.detectors, 1)
+	require.NotNil(t, testRegistry.detectors[0])
+}
+
+func TestRegisterMagicBytes(t *testing.T) {
+	// Create a test registry to avoid interfering with global state
+	testRegistry := &Registry{}
+
+	require.Empty(t, testRegistry.detectors)
+
+	// Register magic bytes for JPEG using the method
+	jpegMagic := []byte{0xFF, 0xD8}
+	testRegistry.RegisterMagicBytes(JPEG, jpegMagic)
+
+	// Verify the magic bytes are registered
+	require.Len(t, testRegistry.detectors, 1)
+}

+ 5 - 6
imagedetect/svg.go → imagetype_new/svg.go

@@ -1,10 +1,9 @@
-package imagedetect
+package imagetype_new
 
 import (
 	"strings"
 
 	"github.com/imgproxy/imgproxy/v3/bufreader"
-	"github.com/imgproxy/imgproxy/v3/imagetype_new"
 
 	"github.com/tdewolff/parse/v2"
 	"github.com/tdewolff/parse/v2/xml"
@@ -12,10 +11,10 @@ import (
 
 func init() {
 	// Register SVG detector (needs at least 1000 bytes to reliably detect SVG)
-	RegisterDetector(IsSVG, 1000)
+	RegisterDetector(IsSVG)
 }
 
-func IsSVG(r bufreader.ReadPeeker) (imagetype_new.Type, error) {
+func IsSVG(r bufreader.ReadPeeker) (Type, error) {
 	l := xml.NewLexer(parse.NewInput(r))
 
 	for {
@@ -23,12 +22,12 @@ func IsSVG(r bufreader.ReadPeeker) (imagetype_new.Type, error) {
 
 		switch tt {
 		case xml.ErrorToken:
-			return imagetype_new.Unknown, nil
+			return Unknown, nil
 
 		case xml.StartTagToken:
 			tag := strings.ToLower(string(l.Text()))
 			if tag == "svg" || tag == "svg:svg" {
-				return imagetype_new.SVG, nil
+				return SVG, nil
 			}
 		}
 	}

+ 13 - 156
imagetype_new/type.go

@@ -15,164 +15,21 @@ var (
 	// Unknown is a reserved type, it has index 0. We guarantee that index 0 won't be used
 	// for any other type. This way, Unknown is a zero value for Type.
 	Unknown Type = 0
-
-	JPEG = RegisterType(&TypeDesc{
-		String:                "jpeg",
-		Ext:                   ".jpg",
-		Mime:                  "image/jpeg",
-		IsVector:              false,
-		SupportsAlpha:         false,
-		SupportsColourProfile: true,
-		SupportsQuality:       true,
-		SupportsAnimationLoad: false,
-		SupportsAnimationSave: false,
-		SupportsThumbnail:     false,
-	})
-
-	JXL = RegisterType(&TypeDesc{
-		String:                "jxl",
-		Ext:                   ".jxl",
-		Mime:                  "image/jxl",
-		IsVector:              false,
-		SupportsAlpha:         true,
-		SupportsColourProfile: true,
-		SupportsQuality:       true,
-		SupportsAnimationLoad: true,
-		SupportsAnimationSave: false,
-		SupportsThumbnail:     false,
-	})
-
-	PNG = RegisterType(&TypeDesc{
-		String:                "png",
-		Ext:                   ".png",
-		Mime:                  "image/png",
-		IsVector:              false,
-		SupportsAlpha:         true,
-		SupportsColourProfile: true,
-		SupportsQuality:       false,
-		SupportsAnimationLoad: false,
-		SupportsAnimationSave: false,
-		SupportsThumbnail:     false,
-	})
-
-	WEBP = RegisterType(&TypeDesc{
-		String:                "webp",
-		Ext:                   ".webp",
-		Mime:                  "image/webp",
-		IsVector:              false,
-		SupportsAlpha:         true,
-		SupportsColourProfile: true,
-		SupportsQuality:       true,
-		SupportsAnimationLoad: true,
-		SupportsAnimationSave: true,
-		SupportsThumbnail:     false,
-	})
-
-	GIF = RegisterType(&TypeDesc{
-		String:                "gif",
-		Ext:                   ".gif",
-		Mime:                  "image/gif",
-		IsVector:              false,
-		SupportsAlpha:         true,
-		SupportsColourProfile: false,
-		SupportsQuality:       false,
-		SupportsAnimationLoad: true,
-		SupportsAnimationSave: true,
-		SupportsThumbnail:     false,
-	})
-
-	ICO = RegisterType(&TypeDesc{
-		String:                "ico",
-		Ext:                   ".ico",
-		Mime:                  "image/x-icon",
-		IsVector:              false,
-		SupportsAlpha:         true,
-		SupportsColourProfile: false,
-		SupportsQuality:       false,
-		SupportsAnimationLoad: false,
-		SupportsAnimationSave: false,
-		SupportsThumbnail:     false,
-	})
-
-	SVG = RegisterType(&TypeDesc{
-		String:                "svg",
-		Ext:                   ".svg",
-		Mime:                  "image/svg+xml",
-		IsVector:              true,
-		SupportsAlpha:         true,
-		SupportsColourProfile: false,
-		SupportsQuality:       false,
-		SupportsAnimationLoad: false,
-		SupportsAnimationSave: false,
-		SupportsThumbnail:     false,
-	})
-
-	HEIC = RegisterType(&TypeDesc{
-		String:                "heic",
-		Ext:                   ".heic",
-		Mime:                  "image/heif",
-		IsVector:              false,
-		SupportsAlpha:         true,
-		SupportsColourProfile: true,
-		SupportsQuality:       true,
-		SupportsAnimationLoad: false,
-		SupportsAnimationSave: false,
-		SupportsThumbnail:     true,
-	})
-
-	AVIF = RegisterType(&TypeDesc{
-		String:                "avif",
-		Ext:                   ".avif",
-		Mime:                  "image/avif",
-		IsVector:              false,
-		SupportsAlpha:         true,
-		SupportsColourProfile: true,
-		SupportsQuality:       true,
-		SupportsAnimationLoad: false,
-		SupportsAnimationSave: false,
-		SupportsThumbnail:     true,
-	})
-
-	BMP = RegisterType(&TypeDesc{
-		String:                "bmp",
-		Ext:                   ".bmp",
-		Mime:                  "image/bmp",
-		IsVector:              false,
-		SupportsAlpha:         true,
-		SupportsColourProfile: false,
-		SupportsQuality:       false,
-		SupportsAnimationLoad: false,
-		SupportsAnimationSave: false,
-		SupportsThumbnail:     false,
-	})
-
-	TIFF = RegisterType(&TypeDesc{
-		String:                "tiff",
-		Ext:                   ".tiff",
-		Mime:                  "image/tiff",
-		IsVector:              false,
-		SupportsAlpha:         true,
-		SupportsColourProfile: false,
-		SupportsQuality:       true,
-		SupportsAnimationLoad: false,
-		SupportsAnimationSave: false,
-		SupportsThumbnail:     false,
-	})
 )
 
 // Mime returns the MIME type for the image type.
 func (t Type) Mime() string {
-	desc := GetType(t)
+	desc := GetTypeDesc(t)
 	if desc != nil {
 		return desc.Mime
 	}
 
-	return "octet-stream"
+	return "application/octet-stream"
 }
 
 // String returns the string representation of the image type.
 func (t Type) String() string {
-	desc := GetType(t)
+	desc := GetTypeDesc(t)
 	if desc != nil {
 		return desc.String
 	}
@@ -182,7 +39,7 @@ func (t Type) String() string {
 
 // Ext returns the file extension for the image type.
 func (t Type) Ext() string {
-	desc := GetType(t)
+	desc := GetTypeDesc(t)
 	if desc != nil {
 		return desc.Ext
 	}
@@ -202,7 +59,7 @@ func (t Type) MarshalJSON() ([]byte, error) {
 
 // IsVector checks if the image type is a vector format.
 func (t Type) IsVector() bool {
-	desc := GetType(t)
+	desc := GetTypeDesc(t)
 	if desc != nil {
 		return desc.IsVector
 	}
@@ -211,16 +68,16 @@ func (t Type) IsVector() bool {
 
 // SupportsAlpha checks if the image type supports alpha transparency.
 func (t Type) SupportsAlpha() bool {
-	desc := GetType(t)
+	desc := GetTypeDesc(t)
 	if desc != nil {
 		return desc.SupportsAlpha
 	}
-	return false
+	return true
 }
 
 // SupportsAnimationLoad checks if the image type supports animation.
 func (t Type) SupportsAnimationLoad() bool {
-	desc := GetType(t)
+	desc := GetTypeDesc(t)
 	if desc != nil {
 		return desc.SupportsAnimationLoad
 	}
@@ -229,16 +86,16 @@ func (t Type) SupportsAnimationLoad() bool {
 
 // SupportsAnimationSave checks if the image type supports saving animations.
 func (t Type) SupportsAnimationSave() bool {
-	desc := GetType(t)
+	desc := GetTypeDesc(t)
 	if desc != nil {
 		return desc.SupportsAnimationSave
 	}
 	return false
 }
 
-// SupportsMetadata checks if the image type supports metadata.
+// SupportsColourProfile checks if the image type supports metadata.
 func (t Type) SupportsColourProfile() bool {
-	desc := GetType(t)
+	desc := GetTypeDesc(t)
 	if desc != nil {
 		return desc.SupportsColourProfile
 	}
@@ -247,7 +104,7 @@ func (t Type) SupportsColourProfile() bool {
 
 // SupportsQuality checks if the image type supports quality adjustments.
 func (t Type) SupportsQuality() bool {
-	desc := GetType(t)
+	desc := GetTypeDesc(t)
 	if desc != nil {
 		return desc.SupportsQuality
 	}
@@ -256,7 +113,7 @@ func (t Type) SupportsQuality() bool {
 
 // SupportsThumbnail checks if the image type supports thumbnails.
 func (t Type) SupportsThumbnail() bool {
-	desc := GetType(t)
+	desc := GetTypeDesc(t)
 	if desc != nil {
 		return desc.SupportsThumbnail
 	}

+ 34 - 1
imagetype_new/type_test.go

@@ -1,6 +1,7 @@
 package imagetype_new
 
 import (
+	"os"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -14,7 +15,7 @@ func TestDefaultTypesRegistered(t *testing.T) {
 
 	for _, typ := range defaultTypes {
 		t.Run(typ.String(), func(t *testing.T) {
-			desc := GetType(typ)
+			desc := GetTypeDesc(typ)
 			require.NotNil(t, desc)
 
 			// Verify that the description has non-empty fields
@@ -24,3 +25,35 @@ func TestDefaultTypesRegistered(t *testing.T) {
 		})
 	}
 }
+
+func TestDetect(t *testing.T) {
+	tests := []struct {
+		name string
+		file string
+		want Type
+	}{
+		{"JPEG", "../testdata/test-images/jpg/jpg.jpg", JPEG},
+		{"JXL", "../testdata/test-images/jxl/jxl.jxl", JXL},
+		{"PNG", "../testdata/test-images/png/png.png", PNG},
+		{"WEBP", "../testdata/test-images/webp/webp.webp", WEBP},
+		{"GIF", "../testdata/test-images/gif/gif.gif", GIF},
+		{"ICO", "../testdata/test-images/ico/png-256x256.ico", ICO},
+		{"SVG", "../testdata/test-images/svg/svg.svg", SVG},
+		{"HEIC", "../testdata/test-images/heif/heif.heif", HEIC},
+		{"BMP", "../testdata/test-images/bmp/24-bpp.bmp", BMP},
+		{"TIFF", "../testdata/test-images/tiff/tiff.tiff", TIFF},
+		{"SVG", "../testdata/test-images/svg/svg.svg", SVG},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			f, err := os.Open(tt.file)
+			require.NoError(t, err)
+			defer f.Close()
+
+			got, err := Detect(f)
+			require.NoError(t, err)
+			require.Equal(t, tt.want, got)
+		})
+	}
+}