diff --git a/buildconfig/stubs/pygame/image.pyi b/buildconfig/stubs/pygame/image.pyi
index 01591a823f..eb8c36bb17 100644
--- a/buildconfig/stubs/pygame/image.pyi
+++ b/buildconfig/stubs/pygame/image.pyi
@@ -62,9 +62,8 @@ following formats.
 .. versionaddedold:: 1.8 Saving PNG and JPEG files.
 """
 
-from typing import Literal, Optional, Union
+from typing import Literal, NamedTuple, Optional
 
-from pygame.bufferproxy import BufferProxy
 from pygame.surface import Surface
 from pygame.typing import FileLike, IntPoint, Point
 from typing_extensions import (
@@ -78,6 +77,10 @@ _to_bytes_format = Literal[
 ]
 _from_bytes_format = Literal["P", "RGB", "RGBX", "RGBA", "ARGB", "BGRA", "ABGR"]
 
+class _AnimationFrame(NamedTuple):
+    surface: Surface
+    delay_ms: float
+
 def load(file: FileLike, namehint: str = "") -> Surface:
     """Load new image from a file (or file-like object).
 
@@ -136,7 +139,7 @@ def load_sized_svg(file: FileLike, size: Point) -> Surface:
     .. versionadded:: 2.4.0
     """
 
-def load_animation(file: FileLike, namehint: str = "") -> list[tuple[Surface, float]]:
+def load_animation(file: FileLike, namehint: str = "") -> list[_AnimationFrame]:
     """Load an animation (GIF/WEBP) from a file (or file-like object) as a list of frames.
 
     Load an animation (GIF/WEBP) from a file source. You can pass either a
@@ -145,8 +148,9 @@ def load_animation(file: FileLike, namehint: str = "") -> list[tuple[Surface, fl
     namehint argument so that the file extension can be used to infer the file
     format.
 
-    This returns a list of tuples (corresponding to every frame of the animation),
-    where each tuple is a (surface, delay in milliseconds) pair for that frame.
+    This returns a list of named tuples (corresponding to every frame of the animation),
+    where each tuple is a (surface, delay in milliseconds) pair for that frame. Being
+    named tuples, the items can be accessed as frame.surface and frame.delay_ms.
 
     This function requires SDL_image 2.6.0 or above. If pygame was compiled with
     an older version, ``pygame.error`` will be raised when this function is
diff --git a/src_c/doc/image_doc.h b/src_c/doc/image_doc.h
index 9830b39291..db44b28223 100644
--- a/src_c/doc/image_doc.h
+++ b/src_c/doc/image_doc.h
@@ -2,7 +2,7 @@
 #define DOC_IMAGE "Pygame module for image transfer."
 #define DOC_IMAGE_LOAD "load(file, namehint='') -> Surface\nLoad new image from a file (or file-like object)."
 #define DOC_IMAGE_LOADSIZEDSVG "load_sized_svg(file, size) -> Surface\nLoad an SVG image from a file (or file-like object) with the given size."
-#define DOC_IMAGE_LOADANIMATION "load_animation(file, namehint='') -> list[tuple[Surface, float]]\nLoad an animation (GIF/WEBP) from a file (or file-like object) as a list of frames."
+#define DOC_IMAGE_LOADANIMATION "load_animation(file, namehint='') -> list[_AnimationFrame]\nLoad an animation (GIF/WEBP) from a file (or file-like object) as a list of frames."
 #define DOC_IMAGE_SAVE "save(surface, file, namehint='') -> None\nSave an image to file (or file-like object)."
 #define DOC_IMAGE_GETSDLIMAGEVERSION "get_sdl_image_version(linked=True) -> Optional[tuple[int, int, int]]\nGet version number of the SDL_Image library being used."
 #define DOC_IMAGE_GETEXTENDED "get_extended() -> bool\nTest if extended image formats can be loaded."
diff --git a/src_c/imageext.c b/src_c/imageext.c
index b13a6859c7..36d96eb53c 100644
--- a/src_c/imageext.c
+++ b/src_c/imageext.c
@@ -69,6 +69,7 @@
 static SDL_mutex *_pg_img_mutex = 0;
 #endif
 */
+static PyTypeObject *animation_frame_type = NULL;
 
 static char *
 iext_find_extension(char *fullname)
@@ -254,22 +255,29 @@ imageext_load_animation(PyObject *self, PyObject *arg, PyObject *kwargs)
     }
 
     for (int i = 0; i < surfs->count; i++) {
+        PyObject *delay_obj = PyFloat_FromDouble((double)surfs->delays[i]);
+        if (!delay_obj) {
+            goto error;
+        }
         PyObject *frame = (PyObject *)pgSurface_New(surfs->frames[i]);
         if (!frame) {
             /* IMG_FreeAnimation takes care of freeing of member SDL surfaces
              */
+            Py_DECREF(delay_obj);
             goto error;
         }
         /* The python surface object now "owns" the sdl surface, so set it
          * to null in the animation to prevent double free */
         surfs->frames[i] = NULL;
-
-        PyObject *listentry =
-            Py_BuildValue("(Od)", frame, (double)surfs->delays[i]);
-        Py_DECREF(frame);
+        PyObject *listentry = PyStructSequence_New(animation_frame_type);
         if (!listentry) {
+            Py_DECREF(frame);
+            Py_DECREF(delay_obj);
             goto error;
         }
+
+        PyStructSequence_SetItem(listentry, 0, frame);
+        PyStructSequence_SetItem(listentry, 1, delay_obj);
         PyList_SET_ITEM(ret, i, listentry);
     }
     IMG_FreeAnimation(surfs);
@@ -438,6 +446,16 @@ static PyMethodDef _imageext_methods[] = {
 /*DOC*/ static char _imageext_doc[] =
     /*DOC*/ "additional image loaders";
 
+static PyStructSequence_Field _namedtuple_fields[] = {
+    {"surface", NULL}, {"delay_ms", NULL}, {NULL, NULL}};
+
+static struct PyStructSequence_Desc _namedtuple_descr = {
+    .name = "_AnimationFrame",
+    .doc = NULL,
+    .fields = _namedtuple_fields,
+    .n_in_sequence = 2,
+};
+
 MODINIT_DEFINE(imageext)
 {
     static struct PyModuleDef _module = {PyModuleDef_HEAD_INIT,
@@ -462,7 +480,6 @@ MODINIT_DEFINE(imageext)
         return NULL;
     }
     import_pygame_rwobject();
-
     if (PyErr_Occurred()) {
         return NULL;
     }
@@ -477,6 +494,11 @@ MODINIT_DEFINE(imageext)
     #endif
     */
 
+    animation_frame_type = PyStructSequence_NewType(&_namedtuple_descr);
+    if (animation_frame_type == NULL) {
+        return NULL;  // exception set
+    }
+
     /* create the module */
     return PyModule_Create(&_module);
 }
diff --git a/test/image_test.py b/test/image_test.py
index eab5cc06fc..9d6d9ece42 100644
--- a/test/image_test.py
+++ b/test/image_test.py
@@ -1388,6 +1388,9 @@ def test_load_animation(self):
                 for val in s:
                     self.assertIsInstance(val, tuple)
                     frame, delay = val
+                    frameattr, delayattr = val.surface, val.delay_ms
+                    self.assertEqual(frame, frameattr)
+                    self.assertEqual(delay, delayattr)
                     self.assertIsInstance(frame, pygame.Surface)
                     self.assertEqual(frame.size, SAMPLE_SIZE)
                     self.assertIsInstance(delay, float)