1
0
Эх сурвалжийг харах

bufreader, RegisterFormat updated

Viktor Sokolov 2 сар өмнө
parent
commit
ec87bb10d9

+ 35 - 57
bufreader/bufreader.go

@@ -1,102 +1,80 @@
+// bufreader provides a buffered reader that reads from io.Reader, but caches
+// the data in a bytes.Buffer to allow peeking and discarding without re-reading.
 package bufreader
 
 import (
-	"bufio"
 	"bytes"
 	"io"
-
-	"github.com/imgproxy/imgproxy/v3/imath"
 )
 
+// ReadPeeker is an interface that combines io.Reader and a method to peek at the next n bytes
+type ReadPeeker interface {
+	io.Reader
+	Peek(n int) ([]byte, error) // Peek returns the next n bytes without advancing
+}
+
+// Reader is a buffered reader that reads from an io.Reader and caches the data.
 type Reader struct {
 	r   io.Reader
 	buf *bytes.Buffer
-	cur int
+	pos int
 }
 
-func New(r io.Reader, buf *bytes.Buffer) *Reader {
+// New creates new buffered reader
+func New(r io.Reader) *Reader {
 	br := Reader{
 		r:   r,
-		buf: buf,
+		buf: bytes.NewBuffer(make([]byte, 0)),
 	}
 	return &br
 }
 
+// Read reads data into p from the buffered reader.
 func (br *Reader) Read(p []byte) (int, error) {
-	if err := br.fill(br.cur + len(p)); err != nil {
+	if err := br.fetch(br.pos + len(p)); err != nil {
 		return 0, err
 	}
 
-	n := copy(p, br.buf.Bytes()[br.cur:])
-	br.cur += n
-	return n, nil
-}
-
-func (br *Reader) ReadByte() (byte, error) {
-	if err := br.fill(br.cur + 1); err != nil {
-		return 0, err
-	}
-
-	b := br.buf.Bytes()[br.cur]
-	br.cur++
-	return b, nil
-}
-
-func (br *Reader) Discard(n int) (int, error) {
-	if n < 0 {
-		return 0, bufio.ErrNegativeCount
-	}
-	if n == 0 {
-		return 0, nil
-	}
-
-	if err := br.fill(br.cur + n); err != nil {
-		return 0, err
-	}
-
-	n = imath.Min(n, br.buf.Len()-br.cur)
-	br.cur += n
+	n := copy(p, br.buf.Bytes()[br.pos:])
+	br.pos += n
 	return n, nil
 }
 
+// Peek returns the next n bytes from the buffered reader without advancing the position.
 func (br *Reader) Peek(n int) ([]byte, error) {
-	if n < 0 {
-		return []byte{}, bufio.ErrNegativeCount
-	}
-	if n == 0 {
-		return []byte{}, nil
+	need := br.pos + n
+	err := br.fetch(need)
+	if err != nil && err != io.EOF {
+		return nil, err
 	}
 
-	if err := br.fill(br.cur + n); err != nil {
-		return []byte{}, err
+	// Return slice of buffered data without advancing position
+	available := br.buf.Bytes()[br.pos:]
+	if len(available) == 0 && err == io.EOF {
+		return nil, io.EOF
 	}
 
-	if n > br.buf.Len()-br.cur {
-		return br.buf.Bytes()[br.cur:], io.EOF
-	}
-
-	return br.buf.Bytes()[br.cur : br.cur+n], nil
+	return available[:n], nil
 }
 
-func (br *Reader) Flush() error {
-	_, err := br.buf.ReadFrom(br.r)
-	return err
+// Rewind seeks buffer to the beginning
+func (br *Reader) Rewind() {
+	br.pos = 0
 }
 
-func (br *Reader) fill(need int) error {
+// fetch ensures the buffer contains at least 'need' bytes
+func (br *Reader) fetch(need int) error {
 	n := need - br.buf.Len()
 	if n <= 0 {
 		return nil
 	}
 
-	n = imath.Max(4096, n)
-
-	if _, err := br.buf.ReadFrom(io.LimitReader(br.r, int64(n))); err != nil {
+	bytesRead, err := br.buf.ReadFrom(io.LimitReader(br.r, int64(n)))
+	if err != nil {
 		return err
 	}
 
-	// Nothing was read, it's EOF
-	if br.cur == br.buf.Len() {
+	if bytesRead == 0 {
 		return io.EOF
 	}
 

+ 97 - 0
bufreader/bufreader_test.go

@@ -0,0 +1,97 @@
+package bufreader
+
+import (
+	"io"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/suite"
+)
+
+// BufferedReaderTestSuite defines the test suite for the buffered reader
+type BufferedReaderTestSuite struct {
+	suite.Suite
+}
+
+func (s *BufferedReaderTestSuite) TestRead() {
+	data := "hello world"
+	br := New(strings.NewReader(data))
+
+	// First read
+	p1 := make([]byte, 5)
+	n1, err1 := br.Read(p1)
+	s.Require().NoError(err1)
+	s.Equal(5, n1)
+	s.Equal("hello", string(p1))
+
+	// Second read
+	p2 := make([]byte, 6)
+	n2, err2 := br.Read(p2)
+	s.Require().NoError(err2)
+	s.Equal(6, n2)
+	s.Equal(" world", string(p2))
+
+	// Verify position
+	s.Equal(11, br.pos)
+}
+
+func (s *BufferedReaderTestSuite) TestEOF() {
+	data := "hello"
+	br := New(strings.NewReader(data))
+
+	// Read all data
+	p1 := make([]byte, 5)
+	n1, err1 := br.Read(p1)
+	s.Require().NoError(err1)
+	s.Equal(5, n1)
+	s.Equal("hello", string(p1))
+
+	// Try to read more - should get EOF
+	p2 := make([]byte, 5)
+	n2, err2 := br.Read(p2)
+	s.Equal(io.EOF, err2)
+	s.Equal(0, n2)
+}
+
+func (s *BufferedReaderTestSuite) TestEOF_WhenDataExhausted() {
+	data := "hello"
+	br := New(strings.NewReader(data))
+
+	// Try to read more than available
+	p := make([]byte, 10)
+	n, err := br.Read(p)
+
+	s.Require().NoError(err)
+	s.Equal(5, n)
+	s.Equal("hello", string(p[:n]))
+}
+
+func (s *BufferedReaderTestSuite) TestPeek() {
+	data := "hello world"
+	br := New(strings.NewReader(data))
+
+	// Peek at first 5 bytes
+	peeked, err := br.Peek(5)
+	s.Require().NoError(err)
+	s.Equal("hello", string(peeked))
+	s.Equal(0, br.pos) // Position should not change
+
+	// Read the same data to verify peek didn't consume it
+	p := make([]byte, 5)
+	n, err := br.Read(p)
+	s.Require().NoError(err)
+	s.Equal(5, n)
+	s.Equal("hello", string(p))
+	s.Equal(5, br.pos) // Position should now be updated
+
+	// Peek at the next 6 bytes
+	peeked2, err := br.Peek(6)
+	s.Require().NoError(err)
+	s.Equal(" world", string(peeked2))
+	s.Equal(5, br.pos) // Position should still be 5
+}
+
+// TestBufferedReaderSuite runs the test suite
+func TestBufferedReader(t *testing.T) {
+	suite.Run(t, new(BufferedReaderTestSuite))
+}

+ 13 - 38
imagedetect/detect.go

@@ -3,48 +3,23 @@ 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) {
-	// Start with 64 bytes to cover magic bytes
-	buf := make([]byte, 64)
-
-	n, err := io.ReadFull(r, buf)
-	if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
-		return imagetype_new.Unknown, err
-	}
-
-	data := buf[:n]
-
-	// First try magic byte detection
-	for _, magic := range registry.magicBytes {
-		if hasMagicBytes(data, magic) {
-			return magic.Type, nil
-		}
-	}
-
-	// Then try custom detectors
-	for _, detector := range registry.detectors {
-		// Check if we have enough bytes for this detector
-		if len(data) < detector.BytesNeeded {
-			// Need to read more data
-			additionalBytes := detector.BytesNeeded - len(data)
-			extraBuf := make([]byte, additionalBytes)
-			extraN, err := io.ReadFull(r, extraBuf)
-
-			// It's fine if we can't read required number of bytes
-			if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
-				return imagetype_new.Unknown, err
-			}
-
-			// Extend our data buffer
-			data = append(data, extraBuf[:extraN]...)
-		}
+	br := bufreader.New(io.LimitReader(r, maxDetectionLimit))
 
-		if typ, err := detector.Func(data); err == nil && typ != imagetype_new.Unknown {
+	for _, fn := range registry.detectors {
+		br.Rewind()
+		if typ, err := fn(br); err == nil && typ != imagetype_new.Unknown {
 			return typ, nil
 		}
 	}
@@ -54,12 +29,12 @@ func Detect(r io.Reader) (imagetype_new.Type, error) {
 
 // hasMagicBytes checks if the data matches a magic byte signature
 // Supports '?' characters in signature which match any byte
-func hasMagicBytes(data []byte, magic MagicBytes) bool {
-	if len(data) < len(magic.Signature) {
+func hasMagicBytes(data []byte, magic []byte) bool {
+	if len(data) < len(magic) {
 		return false
 	}
 
-	for i, c := range magic.Signature {
+	for i, c := range magic {
 		if c != data[i] && c != '?' {
 			return false
 		}

+ 17 - 42
imagedetect/registry.go

@@ -1,32 +1,16 @@
 package imagedetect
 
 import (
-	"sync"
-
+	"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(b []byte) (imagetype_new.Type, error)
-
-// MagicBytes represents a magic byte signature for image type detection
-// Signature can contain '?' characters which match any byte
-type MagicBytes struct {
-	Signature []byte
-	Type      imagetype_new.Type
-}
-
-// Detector represents a registered detector function with its byte requirements
-type Detector struct {
-	Func        DetectFunc
-	BytesNeeded int
-}
+type DetectFunc func(r bufreader.ReadPeeker) (imagetype_new.Type, error)
 
 // Registry manages the registration and execution of image type detectors
 type Registry struct {
-	mu         sync.RWMutex
-	detectors  []Detector
-	magicBytes []MagicBytes
+	detectors []DetectFunc
 }
 
 // Global registry instance
@@ -35,41 +19,32 @@ 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.mu.Lock()
-	defer registry.mu.Unlock()
-	registry.detectors = append(registry.detectors, Detector{
-		Func:        detector,
-		BytesNeeded: bytesNeeded,
-	})
+	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.mu.Lock()
-	defer registry.mu.Unlock()
-	registry.magicBytes = append(registry.magicBytes, MagicBytes{
-		Signature: signature,
-		Type:      typ,
-	})
+	registry.RegisterMagicBytes(signature, typ)
 }
 
 // RegisterDetector registers a custom detector function on this registry instance
 func (r *Registry) RegisterDetector(detector DetectFunc, bytesNeeded int) {
-	r.mu.Lock()
-	defer r.mu.Unlock()
-	r.detectors = append(r.detectors, Detector{
-		Func:        detector,
-		BytesNeeded: bytesNeeded,
-	})
+	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.mu.Lock()
-	defer r.mu.Unlock()
-	r.magicBytes = append(r.magicBytes, MagicBytes{
-		Signature: signature,
-		Type:      typ,
+	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
 	})
 }

+ 11 - 7
imagedetect/registry_test.go

@@ -3,6 +3,7 @@ package imagedetect
 import (
 	"testing"
 
+	"github.com/imgproxy/imgproxy/v3/bufreader"
 	"github.com/imgproxy/imgproxy/v3/imagetype_new"
 	"github.com/stretchr/testify/require"
 )
@@ -12,8 +13,12 @@ func TestRegisterDetector(t *testing.T) {
 	testRegistry := &Registry{}
 
 	// Create a test detector function
-	testDetector := func(data []byte) (imagetype_new.Type, error) {
-		if len(data) >= 2 && data[0] == 0xFF && data[1] == 0xD8 {
+	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()
@@ -24,20 +29,19 @@ func TestRegisterDetector(t *testing.T) {
 
 	// Verify the detector is registered
 	require.Len(t, testRegistry.detectors, 1)
-	require.Equal(t, 64, testRegistry.detectors[0].BytesNeeded)
-	require.NotNil(t, testRegistry.detectors[0].Func)
+	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.magicBytes, 1)
-	require.Equal(t, jpegMagic, testRegistry.magicBytes[0].Signature)
-	require.Equal(t, imagetype_new.JPEG, testRegistry.magicBytes[0].Type)
+	require.Len(t, testRegistry.detectors, 1)
 }

+ 3 - 3
imagedetect/svg.go

@@ -1,9 +1,9 @@
 package imagedetect
 
 import (
-	"bytes"
 	"strings"
 
+	"github.com/imgproxy/imgproxy/v3/bufreader"
 	"github.com/imgproxy/imgproxy/v3/imagetype_new"
 
 	"github.com/tdewolff/parse/v2"
@@ -15,8 +15,8 @@ func init() {
 	RegisterDetector(IsSVG, 1000)
 }
 
-func IsSVG(b []byte) (imagetype_new.Type, error) {
-	l := xml.NewLexer(parse.NewInput(bytes.NewReader(b)))
+func IsSVG(r bufreader.ReadPeeker) (imagetype_new.Type, error) {
+	l := xml.NewLexer(parse.NewInput(r))
 
 	for {
 		tt, _ := l.Next()

+ 6 - 167
imagetype_new/registry.go

@@ -1,10 +1,5 @@
 package imagetype_new
 
-import (
-	"fmt"
-	"sync"
-)
-
 // TypeDesc is used to store metadata about an image type.
 // It represents the minimal information needed to make imgproxy to
 // work with the type.
@@ -21,10 +16,9 @@ type TypeDesc struct {
 	SupportsThumbnail     bool
 }
 
-// Registry holds the type registry and mutex for thread-safe operations
+// Registry holds the type registry
 type Registry struct {
 	types []*TypeDesc
-	mu    sync.Mutex
 }
 
 // globalRegistry is the default registry instance
@@ -32,11 +26,8 @@ var globalRegistry = &Registry{}
 
 // RegisterType registers a new image type in the global registry.
 // It panics if the type already exists (i.e., if a TypeDesc is already registered for this Type).
-func RegisterType(t Type, desc *TypeDesc) {
-	err := globalRegistry.RegisterType(t, desc)
-	if err != nil {
-		panic(err)
-	}
+func RegisterType(desc *TypeDesc) Type {
+	return globalRegistry.RegisterType(desc)
 }
 
 // GetType returns the TypeDesc for the given Type.
@@ -47,168 +38,16 @@ func GetType(t Type) *TypeDesc {
 
 // RegisterType registers a new image type in this registry.
 // It panics if the type already exists (i.e., if a TypeDesc is already registered for this Type).
-func (r *Registry) RegisterType(t Type, desc *TypeDesc) error {
-	r.mu.Lock()
-	defer r.mu.Unlock()
-
-	// Ensure the registry is large enough to hold this type
-	for len(r.types) <= int(t) {
-		r.types = append(r.types, nil)
-	}
-
-	// Check if type already exists
-	if r.types[t] != nil {
-		return fmt.Errorf("type %d is already registered", t)
-	}
-
-	// Register the type
-	r.types[t] = desc
-
-	return nil
+func (r *Registry) RegisterType(desc *TypeDesc) Type {
+	r.types = append(r.types, desc)
+	return Type(len(r.types) - 1) // -1 is unknown
 }
 
 // GetType returns the TypeDesc for the given Type.
 // Returns nil if the type is not registered.
 func (r *Registry) GetType(t Type) *TypeDesc {
-	// No mutex needed for reading as types are only modified during startup
 	if int(t) >= len(r.types) {
 		return nil
 	}
 	return r.types[t]
 }
-
-// init registers all default image types
-func init() {
-	RegisterType(JPEG, &TypeDesc{
-		String:                "jpeg",
-		Ext:                   ".jpg",
-		Mime:                  "image/jpeg",
-		IsVector:              false,
-		SupportsAlpha:         false,
-		SupportsColourProfile: true,
-		SupportsQuality:       true,
-		SupportsAnimationLoad: false,
-		SupportsAnimationSave: false,
-		SupportsThumbnail:     false,
-	})
-	RegisterType(JXL, &TypeDesc{
-		String:                "jxl",
-		Ext:                   ".jxl",
-		Mime:                  "image/jxl",
-		IsVector:              false,
-		SupportsAlpha:         true,
-		SupportsColourProfile: true,
-		SupportsQuality:       true,
-		SupportsAnimationLoad: true,
-		SupportsAnimationSave: false,
-		SupportsThumbnail:     false,
-	})
-	RegisterType(PNG, &TypeDesc{
-		String:                "png",
-		Ext:                   ".png",
-		Mime:                  "image/png",
-		IsVector:              false,
-		SupportsAlpha:         true,
-		SupportsColourProfile: true,
-		SupportsQuality:       false,
-		SupportsAnimationLoad: false,
-		SupportsAnimationSave: false,
-		SupportsThumbnail:     false,
-	})
-	RegisterType(WEBP, &TypeDesc{
-		String:                "webp",
-		Ext:                   ".webp",
-		Mime:                  "image/webp",
-		IsVector:              false,
-		SupportsAlpha:         true,
-		SupportsColourProfile: true,
-		SupportsQuality:       true,
-		SupportsAnimationLoad: true,
-		SupportsAnimationSave: true,
-		SupportsThumbnail:     false,
-	})
-	RegisterType(GIF, &TypeDesc{
-		String:                "gif",
-		Ext:                   ".gif",
-		Mime:                  "image/gif",
-		IsVector:              false,
-		SupportsAlpha:         true,
-		SupportsColourProfile: false,
-		SupportsQuality:       false,
-		SupportsAnimationLoad: true,
-		SupportsAnimationSave: true,
-		SupportsThumbnail:     false,
-	})
-	RegisterType(ICO, &TypeDesc{
-		String:                "ico",
-		Ext:                   ".ico",
-		Mime:                  "image/x-icon",
-		IsVector:              false,
-		SupportsAlpha:         true,
-		SupportsColourProfile: false,
-		SupportsQuality:       false,
-		SupportsAnimationLoad: false,
-		SupportsAnimationSave: false,
-		SupportsThumbnail:     false,
-	})
-	RegisterType(SVG, &TypeDesc{
-		String:                "svg",
-		Ext:                   ".svg",
-		Mime:                  "image/svg+xml",
-		IsVector:              true,
-		SupportsAlpha:         true,
-		SupportsColourProfile: false,
-		SupportsQuality:       false,
-		SupportsAnimationLoad: false,
-		SupportsAnimationSave: false,
-		SupportsThumbnail:     false,
-	})
-	RegisterType(HEIC, &TypeDesc{
-		String:                "heic",
-		Ext:                   ".heic",
-		Mime:                  "image/heif",
-		IsVector:              false,
-		SupportsAlpha:         true,
-		SupportsColourProfile: true,
-		SupportsQuality:       true,
-		SupportsAnimationLoad: false,
-		SupportsAnimationSave: false,
-		SupportsThumbnail:     true,
-	})
-	RegisterType(AVIF, &TypeDesc{
-		String:                "avif",
-		Ext:                   ".avif",
-		Mime:                  "image/avif",
-		IsVector:              false,
-		SupportsAlpha:         true,
-		SupportsColourProfile: true,
-		SupportsQuality:       true,
-		SupportsAnimationLoad: false,
-		SupportsAnimationSave: false,
-		SupportsThumbnail:     true,
-	})
-	RegisterType(BMP, &TypeDesc{
-		String:                "bmp",
-		Ext:                   ".bmp",
-		Mime:                  "image/bmp",
-		IsVector:              false,
-		SupportsAlpha:         true,
-		SupportsColourProfile: false,
-		SupportsQuality:       false,
-		SupportsAnimationLoad: false,
-		SupportsAnimationSave: false,
-		SupportsThumbnail:     false,
-	})
-	RegisterType(TIFF, &TypeDesc{
-		String:                "tiff",
-		Ext:                   ".tiff",
-		Mime:                  "image/tiff",
-		IsVector:              false,
-		SupportsAlpha:         true,
-		SupportsColourProfile: false,
-		SupportsQuality:       true,
-		SupportsAnimationLoad: false,
-		SupportsAnimationSave: false,
-		SupportsThumbnail:     false,
-	})
-}

+ 1 - 51
imagetype_new/registry_test.go

@@ -10,13 +10,6 @@ func TestRegisterType(t *testing.T) {
 	// Create a separate registry for testing to avoid conflicts with global registry
 	testRegistry := &Registry{}
 
-	// Use a high type number to avoid conflicts with default types
-	const customType Type = 1000
-
-	// First, verify the type is not registered
-	desc := testRegistry.GetType(customType)
-	require.Nil(t, desc)
-
 	// Register a custom type
 	customDesc := &TypeDesc{
 		String:                "custom",
@@ -27,7 +20,7 @@ func TestRegisterType(t *testing.T) {
 		SupportsColourProfile: true,
 	}
 
-	testRegistry.RegisterType(customType, customDesc)
+	customType := testRegistry.RegisterType(customDesc)
 
 	// Verify the type is now registered
 	result := testRegistry.GetType(customType)
@@ -40,38 +33,6 @@ func TestRegisterType(t *testing.T) {
 	require.Equal(t, customDesc.SupportsColourProfile, result.SupportsColourProfile)
 }
 
-func TestRegisterTypeError(t *testing.T) {
-	// Create a separate registry for testing to avoid conflicts with global registry
-	testRegistry := &Registry{}
-
-	// Use a high type number to avoid conflicts
-	const testType Type = 2000
-
-	desc1 := &TypeDesc{
-		String:                "test1",
-		Ext:                   ".test1",
-		Mime:                  "image/test1",
-		IsVector:              false,
-		SupportsAlpha:         true,
-		SupportsColourProfile: true,
-	}
-
-	desc2 := &TypeDesc{
-		String:                "test2",
-		Ext:                   ".test2",
-		Mime:                  "image/test2",
-		IsVector:              true,
-		SupportsAlpha:         false,
-		SupportsColourProfile: false,
-	}
-
-	// Register the first type
-	testRegistry.RegisterType(testType, desc1)
-
-	// Attempting to register the same type again should return an error
-	require.Error(t, testRegistry.RegisterType(testType, desc2))
-}
-
 func TestTypeProperties(t *testing.T) {
 	// Test that Type methods use TypeDesc fields correctly
 	tests := []struct {
@@ -162,17 +123,6 @@ func TestTypeProperties(t *testing.T) {
 			expectAnimationSave: false,
 			expectThumbnail:     true,
 		},
-		{
-			name:                "Unknown",
-			typ:                 Unknown,
-			expectVector:        false,
-			expectAlpha:         false,
-			expectColourProfile: false,
-			expectQuality:       false,
-			expectAnimationLoad: false,
-			expectAnimationSave: false,
-			expectThumbnail:     false,
-		},
 	}
 
 	for _, tt := range tests {

+ 145 - 13
imagetype_new/type.go

@@ -11,19 +11,151 @@ type (
 )
 
 // Supported image types
-const (
-	Unknown Type = iota
-	JPEG
-	JXL
-	PNG
-	WEBP
-	GIF
-	ICO
-	SVG
-	HEIC
-	AVIF
-	BMP
-	TIFF
+var (
+	Unknown Type = -1
+
+	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.