Commits

marcus  committed e1aacde

Added threading support to sdl.rwops module.
Added release primer.
Added w and h attributes to base.Rect and base.FRect classes.
Added sdl.image tests.
Fixed SDL_RWops saving for sdl.video.Surface.
Fixed package data installation.
Minor documentation updates.

  • Participants
  • Parent commits c04ff8d
  • Branches pgreloaded

Comments (0)

Files changed (24)

 
 Set PATH=%VCToolkitInstallDir%\bin;%MSSdk%\Bin;%PATH%
 Set INCLUDE=%VCToolkitInstallDir%\include;%MSSdk%\Include;%INCLUDE%
-rem Set LIB=C:\Program files\Microsoft Visual Studio .NET 2003\Vc7\lib;%VCToolkitInstallDir%\lib;%MSSdk%\Lib;%LIB%
-Set LIB=C:\Programe\Microsoft Visual Studio .NET 2003\Vc7\lib;%VCToolkitInstallDir%\lib;%MSSdk%\Lib;%LIB%
+
+rem German defaults below
+rem Set LIB=C:\Programme\Microsoft Visual Studio .NET 2003\Vc7\lib;%VCToolkitInstallDir%\lib;%MSSdk%\Lib;%LIB%
+
+rem English defaults below
+Set LIB=C:\Program files\Microsoft Visual Studio .NET 2003\Vc7\lib;%VCToolkitInstallDir%\lib;%MSSdk%\Lib;%LIB%
 
 rem Delete the previous builds
 del /S /Q build

File doc/RELEASE.txt

+##########################
+Pygame2 release management
+##########################
+
+The following sections will deal with a quick guide about what to take care
+of before issueing a new release.
+
+General
+-------
+Releases are split into three phases usually, *alpha*, *rc* and *final*.
+
+Alpha phase
+^^^^^^^^^^^
+The *alpha* phase usually takes place as soon as a *final* release was made and
+can feature as many releases as necessary. *alpha* releases should be made
+whenever large changes were made, which change the behaviour, API interfaces or
+add completely new stuff.
+
+*alpha* release versions are tagged with a *-alphaXXX* suffix, where *XXX* is
+the *XXXth* release within that alpha phase. There is no limit to the amount of
+releases to be made.
+
+Release Candidate phase
+^^^^^^^^^^^^^^^^^^^^^^^
+The *rc* phase (short for Release Candidate) enters a beta testing cycle, where
+no new features should be added, but only bug fixes be committed. It is also
+the right time for proof-reading the documentation, running the unit tests on
+all platforms again and again and to recheck all examples.
+
+*rc* release versions are tagged with a *-rcXX* suffix, where *XXX* is
+the *XXXth* release within that *rc* phase. There should not be more than 3
+*rc* releases by default and they should be released on a weekly basis, allowing
+adopters to have a minimum of one week and maximum of 3 weeks before the final
+release is made.
+
+Final phase
+^^^^^^^^^^^
+This is not directly a phase on its own, but simply the day of the final release
+for the specific version. The *final* release usually should be the same as the
+last *rc* release, but only differ by the version number and release date.
+
+Preparations for a release
+--------------------------
+Before a new release takes place, the following steps have to be made:
+
+* increase version number (see Postprocessing) in ::
+  
+    setup.py
+    doc/conf.py
+    lib/__init__.py
+    
+* update NEWS.txt with the chagnes since the last version (only for final
+  release - alpha and rc should contain the information, but not be tagged
+  explicitly)
+
+* update README.txt and doc/BuildXXX.txt to point to the latest required
+  dependency versions.
+
+Postprocessing
+--------------
+
+Right after a release has been made and the tree was tagged correctly, the
+version numbers should be increased (and revised just before the next release).
+
+The release number is split into three parts, a *MAJOR*, *MINOR* and *BUGFIX*
+part.
+
+* *BUGFIX* should be increased by one, whenever bug fixes have been made
+  between two releases.
+
+* *MINOR* should be increased, whenever changes or additions to the API were
+  made. This also will cause *BUGFIX* to be set back to 0.
+
+* *MAJOR* should be increased, whenever outstanding additions or changes to the
+  API were made, which legitimate it.
+
+How does that look like in practice? ::
+
+  Event/Action                     Version number
+  -----------------------------------------------
+  Initial release                      2.0.0
+  Some fixes done                      2.0.1
+  More fixes done                      2.0.2
+  Some additions                       2.1.0
+  Some fixes (again)                   2.1.1
+  More fixes                           2.1.2
+  More fixes                           2.1.3
+  Some additions                       2.2.0
+  Important major changes              3.0.0
+  ...                                   ...

File doc/capi/base.rst

   Rounds a floating point value to the nearest integer. The own implementation
   will only be used, if no system-specific one was found.
 
+.. cfunction:: CLAMP(x,low,high)
+  
+  Checks, whether *x* is within the boundaries of *low* and *high* and returns
+  it. If *x* is not within the boundaries, either *low* or *high* will be
+  returned, depending on which of them is larger. The own implementation will
+  only be used, if no system-specific one was found.
+
 .. cmacro:: M_PI
 
   The pi constant with 31 digits. The own definition will only be used, if no
 PySurface
 ---------
 .. ctype:: PySurface
-.. ctype:: PySurface_type
+.. ctype:: PySurface_Type
 
 The PySurface object is some sort of abstract base class, to be used by
 inheriting classes and other interfaces, so it is guaranteed that surface-like

File doc/capi/sdlrwops.rst

 
 Functions
 ---------
-.. cfunction:: SDL_RWops* RWopsFromPython (PyObject *obj)
+.. cfunction:: SDL_RWops* PyRWops_NewRO (PyObject *obj, int *canautoclose)
 
-  Creates a :ctype:`SDL_RWops` object from the passed Python object.
+  Creates a read-only :ctype:`SDL_RWops` object from the passed Python object.
   *obj* must be some file-like or buffer-like object supporting the binary read,
-  write, seek, tell and close methods to be fully usable as :ctype:`SDL_RWops`.
-  On failure, this returns NULL.
+  seek, tell and close methods to be fully usable as :ctype:`SDL_RWops`.
+  *canautoclose* indicates, whether the object can be automatically closed by
+  the matching RW function. If not, a manual call to :cfunc:`PyRWops_Close` will
+  be required.
+  On failure, this returns *NULL*.
+  
+  .. note::
 
-.. cfunction:: int RWopsCheckPython (SDL_RWops *rw)
+    This function is not suitable for threaded usage.
 
-  Checks, whether the passed :ctype:`SDL_RWops` was created from a Python
-  object. This returns 1 for a Python object, 0 if it is not a Python object and
-  -1 on failure.
+.. cfunction:: SDL_RWops* PyRWops_NewRW (PyObject *obj, int *canautoclose)
 
-.. cfunction:: SDL_RWops* RWopsFromPythonThreaded (PyObject *obj)
+  Creates a read-write :ctype:`SDL_RWops` object from the passed Python object.
+  *obj* must be some file-like or buffer-like object supporting the binary read,
+  seek, tell and close methods to be fully usable as :ctype:`SDL_RWops`.
+  *canautoclose* indicates, whether the object can be automatically closed by
+  the matching RW function. If not, a manual call to :cfunc:`PyRWops_Close` will
+  be required.
+  On failure, this returns *NULL*.
   
-  Creates a :ctype:`SDL_RWops` object with threading support from the passed
-  Python object. *obj* must be some file-like or buffer-like object supporting
-  the binary read, write, seek, tell and close methods to be fully usable as
-  :ctype:`SDL_RWops`. On failure, this returns NULL.
+  .. note::
 
-.. cfunction:: int RWopsCheckPythonThreaded (SDL_RWops *rw)
+    This function is not suitable for threaded usage.
 
-  Checks, whether the passed :ctype:`SDL_RWops` was created from a Python
-  object. This returns 1 for a Python object, 0 if it is not a Python object and
-  -1 on failure.
+.. cfunction:: SDL_RWops* PyRWops_NewRO_Threaded (PyObject *obj, int *canautoclose)
+
+  Creates a read-only :ctype:`SDL_RWops` object from the passed Python object.
+  *obj* must be some file-like or buffer-like object supporting the binary read,
+  seek, tell and close methods to be fully usable as :ctype:`SDL_RWops`.
+  *canautoclose* indicates, whether the object can be automatically closed by
+  the matching RW function. If not, a manual call to :cfunc:`PyRWops_Close` will
+  be required.
+  On failure, this returns *NULL*.
+  
+  .. note::
+  
+    If Python was built without thread support, this will default to
+    :cfunc:`PyRWops_NewRO`.
+
+.. cfunction:: SDL_RWops* PyRWops_NewRW_Threaded (PyObject *obj, int *canautoclose)
+
+  Creates a read-write :ctype:`SDL_RWops` object from the passed Python object.
+  *obj* must be some file-like or buffer-like object supporting the binary read,
+  seek, tell and close methods to be fully usable as :ctype:`SDL_RWops`.
+  *canautoclose* indicates, whether the object can be automatically closed by
+  the matching RW function. If not, a manual call to :cfunc:`PyRWops_Close` will
+  be required.
+  On failure, this returns *NULL*.
+  
+  .. note::
+  
+    If Python was built without thread support, this will default to
+    :cfunc:`PyRWops_NewRO`.
+
+.. cfunction:: void PyRWops_Close (SDL_RWops *rw, int autoclose)
+
+  Closes a :ctype:`SDL_RWops` object. if *autoclose* is not 0, the bound data
+  source will be closed, too (if it is a Python object). Otherwise it will be
+  kept open.
+ 

File doc/src/base.xml

         or equal to the :class:`FRect` floating point values.
       </desc>
     </method>
+    <attr name="h">
+      <desc>Gets or sets the height of the :class:`FRect`.</desc>
+    </attr>
     <attr name="height">
       <desc>Gets or sets the height of the :class:`FRect`.</desc>
     </attr>
         Same as FRect.union(FRect), but operates in place.
       </desc>
     </method>
+    <attr name="w">
+      <desc>Gets or sets the width of the :class:`FRect`.</desc>
+    </attr>
     <attr name="width">
       <desc>Gets or sets the width of the :class:`FRect`.</desc>
     </attr>
         or height.
       </desc>
     </method>
+    <attr name="h">
+      <desc>Gets or sets the height of the :class:`FRect`.</desc>
+    </attr>
     <attr name="height">
       <desc>Gets or sets the height of the :class:`Rect`.</desc>
     </attr>
         Same as Rect.union(Rect), but operates in place.
       </desc>
     </method>
+    <attr name="w">
+      <desc>Gets or sets the width of the :class:`Rect`.</desc>
+    </attr>
     <attr name="width">
       <desc>Gets or sets the width of the :class:`Rect`.</desc>
     </attr>
     </attr>
   </class>
 
-
   <class name="Font">
     <constructor>Font () -> Font</constructor>
     <desc>

File doc/src/sdlextnumericsurfarray.xml

   <desc>
     .. note::
 
+      This module is not supported under Python 3.x.
+
+    .. note::
+
       You should favour the usage :mod:`pygame2.sdlext.surfarray` over
       directly using the numpy or Numeric surfarray modules, if you do
       not require any special features of one of the packages. This

File lib/__init__.py

 It is written on top of the excellent SDL library. This allows you
 to create fully featured games and multimedia programs in the python
 language. The package is highly portable, with games running on
-Windows, MacOS, OS X, BeOS, FreeBSD, IRIX, and Linux.
+Windows, MacOS X, *BSD, Linux and others.
 """
 
 import os
 import modules, cfg
 from config import helpers, msys, config_modules
 
-VERSION = "2.0.0"
+VERSION = "2.0.0-alpha1"
 DEBUG = True
 
 # Minimum requirements.
     pkgdata = []
     nodirs = re.compile (r'(.svn)$')
     nofilesrev = re.compile (r'((~.*)|(cyp\..*)|(yp\..*))')
-    dirtrim = os.path.join (directory, "")
     for subd, directories, files in os.walk (directory):
         directories[:] = [d for d in directories if nodirs.match (d) is None \
                           and d not in excludedirs]
         files[:] = [f for f in files if nofilesrev.match(f[-1::-1]) is None \
                     and f not in excludefiles]
-        subd = subd.replace (dirtrim, "", 1)
+        subd = subd.replace (directory, "", 1)
+        subd.lstrip (os.path.sep)
         for f in files:
             pkgdata.append (os.path.join (subd, f))
     return pkgdata
 
     os.environ["CFLAGS"] = ""
 
+    if "bdist_msi" in sys.argv:
+        # hack the version name to a format msi doesn't have trouble with
+        VERSION = VERSION.replace("-alpha", "a")
+        VERSION = VERSION.replace("-rc", "r")
+
     if buildsystem in ("msys", "unix", "darwin") and DEBUG:
         os.environ["CFLAGS"] += " -W -Wall -Wimplicit-int " + \
                         "-Wimplicit-function-declaration " + \

File src/base/floatrect.c

     { "x", _frect_getx, _frect_setx, DOC_BASE_FRECT_X, NULL },
     { "y", _frect_gety, _frect_sety, DOC_BASE_FRECT_Y, NULL },
     { "width", _frect_getwidth, _frect_setwidth, DOC_BASE_FRECT_WIDTH, NULL },
+    { "w", _frect_getwidth, _frect_setwidth, DOC_BASE_FRECT_WIDTH, NULL },
     { "height", _frect_getheight, _frect_setheight, DOC_BASE_FRECT_HEIGHT,
       NULL },
+    { "h", _frect_getheight, _frect_setheight, DOC_BASE_FRECT_HEIGHT, NULL },
     { "size", _frect_getsize, _frect_setsize, DOC_BASE_FRECT_SIZE, NULL },
 
     { "left", _frect_getx, _frect_setx, DOC_BASE_FRECT_LEFT, NULL },

File src/base/rect.c

     { "x", _rect_getx, _rect_setx, DOC_BASE_RECT_X, NULL },
     { "y", _rect_gety, _rect_sety, DOC_BASE_RECT_Y, NULL },
     { "width", _rect_getwidth, _rect_setwidth, DOC_BASE_RECT_WIDTH, NULL },
+    { "w", _rect_getwidth, _rect_setwidth, DOC_BASE_RECT_WIDTH, NULL },
     { "height", _rect_getheight, _rect_setheight, DOC_BASE_RECT_HEIGHT, NULL },
+    { "h", _rect_getwidth, _rect_setwidth, DOC_BASE_RECT_WIDTH, NULL },
     { "size", _rect_getsize, _rect_setsize, DOC_BASE_RECT_SIZE, NULL },
 
     { "left", _rect_getx, _rect_setx, DOC_BASE_RECT_LEFT, NULL },

File src/sdl/imagemod.c

 
     if (!PyArg_ParseTuple (args, "O:load_bmp", &file))
         return NULL;
-
-    rw = PyRWops_NewRO (file, &autoclose);
+    
+    rw = PyRWops_NewRO_Threaded (file, &autoclose);
     if (!rw)
         return NULL;
 
         return NULL;
     }
 
-    rw = PyRWops_NewRW (file, &autoclose);
+    rw = PyRWops_NewRW_Threaded (file, &autoclose);
     if (!rw)
         return NULL;
     

File src/sdl/jpg.c

         ss_rows[i] = ((unsigned char*)ss_surface->pixels) +
             i * ss_surface->pitch;
     }
-    r = _write_jpeg (rw, ss_rows, surface->w, surface->h, 85);
+    r = _write_jpeg (rw, ss_rows, surface->w, surface->h, 100);
 
     free (ss_rows);
     SDL_FreeSurface (ss_surface);

File src/sdl/pgsdl.h

 
 /* RWops */
 #define PYGAME_SDLRWOPS_FIRSTSLOT 0
-#define PYGAME_SDLRWOPS_NUMSLOTS 3
+#define PYGAME_SDLRWOPS_NUMSLOTS 5
 #ifndef PYGAME_SDLRWOPS_INTERNAL
 #define PyRWops_NewRO                                                 \
     (*(SDL_RWops*(*)(PyObject*,int*))PyGameSDLRWops_C_API[PYGAME_SDLRWOPS_FIRSTSLOT])
     (*(SDL_RWops*(*)(PyObject*,int*))PyGameSDLRWops_C_API[PYGAME_SDLRWOPS_FIRSTSLOT+1])
 #define PyRWops_Close                                                 \
     (*(void(*)(SDL_RWops*,int))PyGameSDLRWops_C_API[PYGAME_SDLRWOPS_FIRSTSLOT+2])
+#define PyRWops_NewRO_Threaded                                        \
+    (*(SDL_RWops*(*)(PyObject*,int*))PyGameSDLRWops_C_API[PYGAME_SDLRWOPS_FIRSTSLOT+3])
+#define PyRWops_NewRW_Threaded                                        \
+    (*(SDL_RWops*(*)(PyObject*,int*))PyGameSDLRWops_C_API[PYGAME_SDLRWOPS_FIRSTSLOT+4])
 #endif /* PYGAME_SDLRWOPS_INTERNAL */
 
 /**

File src/sdl/rwopsmod.c

     PyObject *seek;
     PyObject *tell;
     PyObject *close;
+#ifdef WITH_THREAD
+    PyThreadState *thread;
+#endif
 } _RWWrapper;
 
 static void _bind_python_methods (_RWWrapper *wrapper, PyObject *obj);
 static int _pyobj_seek (SDL_RWops *ops, int offset, int whence);
 static int _pyobj_write (SDL_RWops *ops, const void* ptr, int size, int num);
 static int _pyobj_close (SDL_RWops *ops);
+#ifdef WITH_THREAD
+static int _pyobj_read_threaded (SDL_RWops *ops, void* ptr, int size, int num);
+static int _pyobj_seek_threaded (SDL_RWops *ops, int offset, int whence);
+static int _pyobj_write_threaded (SDL_RWops *ops, const void* ptr, int size,
+    int num);
+static int _pyobj_close_threaded (SDL_RWops *ops);
+#endif
+
+/* C API */
+static SDL_RWops* PyRWops_NewRO (PyObject *obj, int *canautoclose);
+static SDL_RWops* PyRWops_NewRO_Threaded (PyObject *obj, int *canautoclose);
+static SDL_RWops* PyRWops_NewRW (PyObject *obj, int *canautoclose);
+static SDL_RWops* PyRWops_NewRW_Threaded (PyObject *obj, int *canautoclose);
+static void PyRWops_Close (SDL_RWops *ops, int canautoclose);
+static void PyRWops_Close_Threaded (SDL_RWops *ops, int canautoclose);
 
 static void
 _bind_python_methods (_RWWrapper *wrapper, PyObject *obj)
     if (PyObject_HasAttrString (obj, "write"))
     {
         wrapper->write = PyObject_GetAttrString (obj, "write");
-        if (wrapper->write&& !PyCallable_Check (wrapper->write))
+        if (wrapper->write && !PyCallable_Check (wrapper->write))
         {
             Py_DECREF (wrapper->write);
             wrapper->write = NULL;
 }
 
 static int
-_pyobj_read (SDL_RWops *ops, void* ptr, int size, int maxnum)
+_pyobj_read (SDL_RWops *ops, void* ptr, int size, int num)
 {
     _RWWrapper *wrapper = (_RWWrapper *) ops->hidden.unknown.data1;
     PyObject *result;
     
     if (!wrapper->read)
         return -1;
-    result = PyObject_CallFunction (wrapper->read, "i", size * maxnum);
+    result = PyObject_CallFunction (wrapper->read, "i", size * num);
     if (!result)
         return -1;
     if (!Bytes_Check (result))
     if (wrapper->close)
     {
         result = PyObject_CallFunction (wrapper->close, NULL);
-        if (result)
+        if (!result)
             retval = -1;
         Py_XDECREF (result);
     }
     return retval;
 }
 
+#ifdef WITH_THREAD
+static int
+_pyobj_read_threaded (SDL_RWops *ops, void* ptr, int size, int num)
+{
+    _RWWrapper *wrapper = (_RWWrapper *) ops->hidden.unknown.data1;
+    PyObject *result;
+    int retval;
+    PyThreadState* oldstate;
+    
+    if (!wrapper->read)
+        return -1;
+    
+    PyEval_AcquireLock ();
+    oldstate = PyThreadState_Swap (wrapper->thread);
+        
+    result = PyObject_CallFunction (wrapper->read, "i", size * num);
+    if (!result)
+    {
+        PyErr_Print ();
+        retval = -1;
+        goto end;
+    }
+    
+    if (!Bytes_Check (result))
+    {
+        Py_DECREF (result);
+        PyErr_Print ();
+        retval = -1;
+        goto end;
+    }
+    
+    retval = Bytes_GET_SIZE (result);
+    memcpy (ptr, Bytes_AS_STRING (result), (size_t) retval);
+    retval /= size;
+    
+    Py_DECREF (result);
+
+end:
+    PyThreadState_Swap (oldstate);
+    PyEval_ReleaseLock ();
+    return retval;
+}
+
+static int
+_pyobj_seek_threaded (SDL_RWops *ops, int offset, int whence)
+{
+    _RWWrapper *wrapper = (_RWWrapper *) ops->hidden.unknown.data1;
+    PyObject* result;
+    int retval;
+    PyThreadState* oldstate;
+
+    if (!wrapper->seek || !wrapper->tell)
+        return -1;
+
+    PyEval_AcquireLock ();
+    oldstate = PyThreadState_Swap (wrapper->thread);
+
+    if (!(offset == 0 && whence == SEEK_CUR)) /*being called only for 'tell'*/
+    {
+        result = PyObject_CallFunction (wrapper->seek, "ii", offset, whence);
+        if (!result)
+        {
+            PyErr_Print();
+            retval = -1;
+            goto end;
+        }
+        Py_DECREF (result);
+    }
+
+    result = PyObject_CallFunction (wrapper->tell, NULL);
+    if (!result)
+    {
+        PyErr_Print ();
+        retval = -1;
+        goto end;
+    }
+    retval = PyInt_AsLong (result);
+    Py_DECREF (result);
+
+end:
+    PyThreadState_Swap (oldstate);
+    PyEval_ReleaseLock ();
+    return retval;
+}
+
+static int
+_pyobj_write_threaded (SDL_RWops *ops, const void* ptr, int size, int num)
+{
+    _RWWrapper *wrapper = (_RWWrapper *) ops->hidden.unknown.data1;
+    PyObject *result;
+    int retval;
+    PyThreadState* oldstate;
+
+    if (!wrapper->write)
+        return -1;
+
+    PyEval_AcquireLock ();
+    oldstate = PyThreadState_Swap (wrapper->thread);
+
+    result = PyObject_CallFunction (wrapper->write, "s#", ptr, size * num);
+    if (!result)
+    {
+        PyErr_Print ();
+        retval = -1;
+        goto end;
+    }
+    Py_DECREF (result);
+    retval = num;
+    
+end:
+    PyThreadState_Swap (oldstate);
+    PyEval_ReleaseLock ();
+    return retval;
+}
+
+static int
+_pyobj_close_threaded (SDL_RWops *ops)
+{
+    _RWWrapper *wrapper = (_RWWrapper *) ops->hidden.unknown.data1;
+    PyObject *result;
+    int retval = 0;
+    PyThreadState* oldstate;
+
+    PyEval_AcquireLock ();
+    oldstate = PyThreadState_Swap (wrapper->thread);
+
+    if (wrapper->close)
+    {
+        result = PyObject_CallFunction (wrapper->close, NULL);
+        if (!result)
+        {
+            PyErr_Print ();
+            retval = -1;
+        }
+        Py_XDECREF (result);
+    }
+
+    Py_XDECREF (wrapper->seek);
+    Py_XDECREF (wrapper->tell);
+    Py_XDECREF (wrapper->write);
+    Py_XDECREF (wrapper->read);
+    Py_XDECREF (wrapper->close);
+    
+    PyThreadState_Swap (oldstate);
+    PyThreadState_Clear (wrapper->thread);
+    PyThreadState_Delete (wrapper->thread);
+    PyMem_Del (wrapper);
+    
+    PyEval_ReleaseLock ();
+    
+    SDL_FreeRW (ops);
+    return retval;
+}
+#endif
+
 /* C API */
 static SDL_RWops*
 PyRWops_NewRO (PyObject *obj, int *canautoclose)
             return NULL;
         Py_XDECREF (tmp);
         *canautoclose = 1;
-        return SDL_RWFromFile ((const char *)filename, "rb");
+        ops = SDL_RWFromFile ((const char *)filename, "rb");
+        if (!ops)
+            PyErr_SetString (PyExc_PyGameError, SDL_GetError ());
+        return ops;
     }
 
     /* No text object, so its a buffer or something like that. Try to get the
      * necessary information. */
     ops = SDL_AllocRW ();
     if (!ops)
+    {
+        PyErr_SetString (PyExc_PyGameError, SDL_GetError ());
         return NULL;
+    }
     wrapper = PyMem_New (_RWWrapper, 1);
     if (!wrapper)
     {
             return NULL;
         Py_XDECREF (tmp);
         *canautoclose = 1;
-        return SDL_RWFromFile ((const char *)filename, "wb");
+        ops = SDL_RWFromFile ((const char *)filename, "wb");
+        if (!ops)
+            PyErr_SetString (PyExc_PyGameError, SDL_GetError ());
+        return ops;
+
     }
 
     /* No text object, so its a buffer or something like that. Try to get the
      * necessary information. */
     ops = SDL_AllocRW ();
     if (!ops)
+    {
+        PyErr_SetString (PyExc_PyGameError, SDL_GetError ());
         return NULL;
+    }
     wrapper = PyMem_New (_RWWrapper, 1);
     if (!wrapper)
     {
 PyRWops_Close (SDL_RWops *ops, int canautoclose)
 {
     /* internal _RWWrapper? */
+#ifdef WITH_THREAD
+    if (ops->close == _pyobj_close || ops->close == _pyobj_close_threaded)
+#else
     if (ops->close == _pyobj_close)
+#endif
     {
         if (!canautoclose) /* Do not close the underlying object. */
         {
             wrapper->close = NULL;
         }
     }
+#ifdef WITH_THREAD
+    Py_BEGIN_ALLOW_THREADS;
     SDL_RWclose (ops);
+    Py_END_ALLOW_THREADS;
+#else
+    SDL_RWclose (ops);
+#endif
 }
 
+static SDL_RWops*
+PyRWops_NewRO_Threaded (PyObject *obj, int *canautoclose)
+{
+#ifndef WITH_THREAD
+    /* Fall back to the non-threaded implementation */
+    return PyRWops_NewRO (obj, canautoclose);
+#else
+    _RWWrapper *wrapper;
+    SDL_RWops *ops;
+    PyInterpreterState* interp;
+    PyThreadState* thread;
+    
+    if (!obj || !canautoclose)
+    {
+        PyErr_SetString (PyExc_TypeError, "argument is NULL");
+        return NULL;
+    }
+    
+    /* If we have a text object, assume it is a file, which is automatically
+     * closed. */
+    if (IsTextObj (obj))
+    {
+        PyObject *tmp;
+        char *filename;
+        if (!UTF8FromObject (obj, &filename, &tmp))
+            return NULL;
+        Py_XDECREF (tmp);
+        *canautoclose = 1;
+        ops = SDL_RWFromFile ((const char *)filename, "rb");
+        if (!ops)
+            PyErr_SetString (PyExc_PyGameError, SDL_GetError ());
+        return ops;
+
+    }
+
+    /* No text object, so its a buffer or something like that. Try to get the
+     * necessary information. */
+    ops = SDL_AllocRW ();
+    if (!ops)
+    {
+        PyErr_SetString (PyExc_PyGameError, SDL_GetError ());
+        return NULL;
+    }
+    wrapper = PyMem_New (_RWWrapper, 1);
+    if (!wrapper)
+    {
+        SDL_FreeRW (ops);
+        return NULL;
+    }
+    _bind_python_methods (wrapper, obj);
+    
+    ops->read = _pyobj_read_threaded;
+    ops->write = _pyobj_write_threaded;
+    ops->seek = _pyobj_seek_threaded;
+    ops->close = _pyobj_close_threaded;
+    ops->hidden.unknown.data1 = (void*) wrapper;
+    
+    PyEval_InitThreads ();
+    thread = PyThreadState_Get ();
+    interp = thread->interp;
+    wrapper->thread = PyThreadState_New (interp);
+
+    *canautoclose = 0;
+    return ops;
+#endif
+}
+
+static SDL_RWops*
+PyRWops_NewRW_Threaded (PyObject *obj, int *canautoclose)
+{
+#ifndef WITH_THREAD
+    /* Fall back to the non-threaded implementation */
+    return PyRWops_NewRW (obj, canautoclose);
+#else
+    _RWWrapper *wrapper;
+    SDL_RWops *ops;
+    PyInterpreterState* interp;
+    PyThreadState* thread;
+    
+    if (!obj || !canautoclose)
+    {
+        PyErr_SetString (PyExc_TypeError, "argument is NULL");
+        return NULL;
+    }
+    
+    /* If we have a text object, assume it is a file, which is automatically
+     * closed. */
+    if (IsTextObj (obj))
+    {
+        PyObject *tmp;
+        char *filename;
+        if (!UTF8FromObject (obj, &filename, &tmp))
+            return NULL;
+        Py_XDECREF (tmp);
+        *canautoclose = 1;
+        ops = SDL_RWFromFile ((const char *)filename, "wb");
+        if (!ops)
+            PyErr_SetString (PyExc_PyGameError, SDL_GetError ());
+        return ops;
+
+    }
+
+    /* No text object, so its a buffer or something like that. Try to get the
+     * necessary information. */
+    ops = SDL_AllocRW ();
+    if (!ops)
+    {
+        PyErr_SetString (PyExc_PyGameError, SDL_GetError ());
+        return NULL;
+    }
+    wrapper = PyMem_New (_RWWrapper, 1);
+    if (!wrapper)
+    {
+        SDL_FreeRW (ops);
+        return NULL;
+    }
+    _bind_python_methods (wrapper, obj);
+    ops->read = _pyobj_read_threaded;
+    ops->write = _pyobj_write_threaded;
+    ops->seek = _pyobj_seek_threaded;
+    ops->close = _pyobj_close_threaded;
+    ops->hidden.unknown.data1 = (void*) wrapper;
+    
+    PyEval_InitThreads ();
+    thread = PyThreadState_Get ();
+    interp = thread->interp;
+    wrapper->thread = PyThreadState_New (interp);
+    
+    *canautoclose = 0;
+    return ops;
+#endif
+}
+
+
 #ifdef IS_PYTHON_3
 PyMODINIT_FUNC PyInit_rwops (void)
 #else
     if (!mod)
         goto fail;
 
-    c_api[PYGAME_SDLRWOPS_FIRSTSLOT] = PyRWops_NewRO;
+    c_api[PYGAME_SDLRWOPS_FIRSTSLOT+0] = PyRWops_NewRO;
     c_api[PYGAME_SDLRWOPS_FIRSTSLOT+1] = PyRWops_NewRW;
     c_api[PYGAME_SDLRWOPS_FIRSTSLOT+2] = PyRWops_Close;
-    
+    c_api[PYGAME_SDLRWOPS_FIRSTSLOT+3] = PyRWops_NewRO_Threaded;
+    c_api[PYGAME_SDLRWOPS_FIRSTSLOT+4] = PyRWops_NewRW_Threaded;
+
     c_api_obj = PyCObject_FromVoidPtr ((void *) c_api, NULL);
     if (c_api_obj)
         PyModule_AddObject (mod, PYGAME_SDLRWOPS_ENTRY, c_api_obj);    

File src/sdl/surface.c

 
     surface = ((PySDLSurface*)self)->surface;
 
-    rw = PyRWops_NewRW (file, &autoclose);
+    rw = PyRWops_NewRW_Threaded (file, &autoclose);
     if (!rw)
         return NULL;
 
     Py_BEGIN_ALLOW_THREADS;
     retval = pyg_sdlsurface_save_rw (surface, rw, type, autoclose);
     Py_END_ALLOW_THREADS;
-        
+
     if (!autoclose)
         PyRWops_Close (rw, autoclose);
     

File src/sdl/surface_save.c

             (type[1] == 'M' || type[1] == 'm') &&
             (type[2] == 'P' || type[2] == 'p'))
         {
-            retval = SDL_SaveBMP_RW (surface, rw, freerw);
+            if (SDL_SaveBMP_RW (surface, rw, freerw) == 0)
+                retval = 1;
+            else
+                retval = 0;
         }
         else if ((type[0] == 'T' || type[0] == 't') &&
             (type[1] == 'G' || type[1] == 'g') &&

File src/sdl/tga.c

 pyg_save_tga_rw (SDL_Surface *surface, SDL_RWops *out, int rle, int freerw)
 {
     SDL_Surface *linebuf = NULL;
+    int retval = 0;
     int alpha = 0;
     int ckey = -1;
     struct TGAheader h;
         return 0;
     }
 
-
     h.infolen = 0;
     SETLE16 (h.cmap_start, 0);
 
 
     if (freerw)
         SDL_RWclose (out);
+    retval = 1;
 
 error:
     if (rlebuf)
         free (rlebuf);
     SDL_FreeSurface (linebuf);
     
-    return 0;
+    return retval;
 }
 
 int
     if (!out)
         return 0;
 
-    ret = pyg_save_tga_rw (surface, out, rle, 1);
-    SDL_RWclose (out);
-    return ret;
+    return pyg_save_tga_rw (surface, out, rle, 1);
 }

File src/sdlimage/imagemod.c

     if (!PyArg_ParseTuple (args, "O|s:load", &file, &type))
         return NULL;
 
-    rw = PyRWops_NewRO (file, &autoclose);
+    rw = PyRWops_NewRO_Threaded (file, &autoclose);
     if (!rw)
         return NULL;
 

File src/sdlmixer/chunk.c

     if (!PyArg_ParseTuple (args, "O", &file))
         return -1;
 
-    rw = PyRWops_NewRO (file, &autoclose);
+    rw = PyRWops_NewRO_Threaded (file, &autoclose);
     if (!rw)
         return -1;
 

File src/sdlmixer/music.c

     if (!PyArg_ParseTuple (args, "O", &file))
         return -1;
 
-    rw = PyRWops_NewRO (file, &autoclose);
+    rw = PyRWops_NewRO_Threaded (file, &autoclose);
     if (!rw)
         return -1;
 

File src/sdlttf/font.c

         return -1;
     }
 
-    rw = PyRWops_NewRO (file, &autoclose);
+    rw = PyRWops_NewRO_Threaded (file, &autoclose);
     if (!rw)
         return -1;
 

File test/sdl_image_test.py

+import os
+try:
+    import StringIO as stringio
+except ImportError:
+    import io as stringio
+
+try:
+    import pygame2.test.pgunittest as unittest
+except:
+    import pgunittest as unittest
+
+import pygame2
+import pygame2.sdl.image as image
+import pygame2.sdl.video as video
+import pygame2.sdl.constants as constants
+
+class SDLImageTest (unittest.TestCase):
+    def test_pygame2_sdl_image_load_bmp(self):
+
+        # __doc__ (as of 2009-05-14) for pygame2.sdl.image.load_bmp:
+
+        # load_bmp (file) -> pygame2.sdl.video.Surface
+        # 
+        # Loads a BMP file and creates a pygame2.sdl.video.Surface from it.
+        # 
+        # load_bmp (file) -> pygame2.sdl.video.Surface  Loads a BMP file and
+        # creates a pygame2.sdl.video.Surface from it.  Loads a BMP file and
+        # creates a pygame2.sdl.video.Surface from it. The file argument can
+        # be either a file object or the filename.
+        imgdir = os.path.dirname (os.path.abspath (__file__))
+        sf = image.load_bmp (os.path.join (imgdir, "test.bmp"))
+        self.assert_ (sf.size == (16, 16))
+
+    def test_pygame2_sdl_image_save_bmp(self):
+
+        # __doc__ (as of 2009-05-14) for pygame2.sdl.image.save_bmp:
+
+        # save_bmp (surface, file) -> None
+        # 
+        # Saves a surface to a bitmap file.
+        # 
+        # save_bmp (surface, file) -> None  Saves a surface to a bitmap file.
+        # Saves a pygame2.sdl.video.Surface to the specified file, where file
+        # can be a filename or file object.
+        imgdir = os.path.dirname (os.path.abspath (__file__))
+        sf = image.load_bmp (os.path.join (imgdir, "test.bmp"))
+        buf = stringio.StringIO ()
+        self.assert_ (image.save_bmp (sf, buf) == None)
+        self.assertEquals (os.stat (os.path.join (imgdir, "test.bmp")).st_size,
+                           len (buf.getvalue ()))
+
+if __name__ == "__main__":
+    unittest.main ()

File test/sdl_video_surface_test.py

 except:
     import pgunittest as unittest
 
+try:
+    import StringIO as stringio
+except ImportError:
+    import io as stringio
+
 import pygame2
 import pygame2.sdl.video as video
+import pygame2.sdl.image as image
+import pygame2.sdlimage as sdlimage
 import pygame2.sdl.constants as constants
 
+def cmppixels (sf1, sf2):
+    # Simple pixel comparision
+    w1, h1 = sf1.size
+    w2, h2 = sf2.size
+    w, h = min (w1, w2), min (h1, h2)
+    getat1 = sf1.get_at
+    getat2 = sf2.get_at
+    for x in range (w):
+        for y in range (h):
+            if getat1 (x, y) != getat2 (x, y):
+                return False
+    return True
+
+def cmpcolor (sf, color, area=None):
+    # Simple color comparision with clip area support
+    getat = sf.get_at
+    sx, sy = 0, 0
+    w, h = sf.size
+    if area:
+        sx, sy = area.x, area.y 
+        w, h = area.w, area.h
+    for x in range (sx, sx + w):
+        for y in range (sy, sy + h):
+            if getat (x, y) != color:
+                return False
+    return True
+
 class SDLVideoSurfaceTest (unittest.TestCase):
 
     def todo_test_pygame2_sdl_video_Surface_blit(self):
         # | BLEND_RGBA_MULT           | The result of a multiplication of both |
         # |                           | pixel values will be used.             |
         # +---------------------------+----------------------------------------+
-
+        video.init ()
         self.fail() 
 
     def todo_test_pygame2_sdl_video_Surface_clip_rect(self):
 
         # Gets or sets the current clipping rectangle for
         # operations on the Surface.
-
+        video.init ()
         self.fail() 
 
     def todo_test_pygame2_sdl_video_Surface_convert(self):
         # 
         # This creates a new, converted surface and leaves the original one
         # untouched.
-
+        
         self.fail() 
 
-    def todo_test_pygame2_sdl_video_Surface_copy(self):
+    def test_pygame2_sdl_video_Surface_copy(self):
 
         # __doc__ (as of 2009-05-15) for pygame2.sdl.video.Surface.copy:
 
         # copy () -> Surface
         # 
         # Creates an exact copy of the Surface and its image data.
+        video.init ()
+        sf = video.Surface (10, 10, 32)
+        sfcopy = sf.copy ()
+        
+        self.assert_ (sf.size == sfcopy.size)
+        self.assert_ (sf.format.bits_per_pixel == sfcopy.format.bits_per_pixel)
+        self.assert_ (sf.format.masks == sfcopy.format.masks)
+        self.assert_ (cmppixels (sf, sfcopy) == True)
+        
+        sf.fill (pygame2.Color (200, 100, 0))
+        sfcopy = sf.copy ()
+        self.assert_ (cmppixels (sf, sfcopy) == True)
 
-        self.fail() 
-
-    def todo_test_pygame2_sdl_video_Surface_fill(self):
+    def test_pygame2_sdl_video_Surface_fill(self):
 
         # __doc__ (as of 2009-05-15) for pygame2.sdl.video.Surface.fill:
 
         # the specified area. The blendargs are the same as for the blit
         # operation, but compare the color with the specific Surface
         # pixel value.
+        video.init ()
+        sf = video.Surface (10, 20, 32)
 
-        self.fail() 
+        self.assert_ (cmpcolor (sf, pygame2.Color ("black")) == True)
+        sf.fill (pygame2.Color ("cyan"))
+        self.assert_ (cmpcolor (sf, pygame2.Color ("cyan")) == True)
+        
+        sf.fill (pygame2.Color ("cyan"))
+        self.assert_ (cmpcolor (sf, pygame2.Color ("cyan")) == True)
+
+        sf.fill (pygame2.Color ("yellow"), pygame2.Rect (5, 5, 4, 5))
+        self.assert_ (cmpcolor (sf, pygame2.Color ("yellow"),
+                      pygame2.Rect (5, 5, 4, 5)) == True)
 
     def todo_test_pygame2_sdl_video_Surface_flags(self):
 
         # __doc__ (as of 2009-05-15) for pygame2.sdl.video.Surface.flags:
 
         # The currently set flags for the Surface.
-
+        video.init ()
         self.fail() 
 
     def todo_test_pygame2_sdl_video_Surface_flip(self):
         # 
         # Swaps screen buffers for the Surface, causing a full update
         # and redraw of its whole area.
-
+        video.init ()
         self.fail() 
 
     def todo_test_pygame2_sdl_video_Surface_format(self):
 
         # Gets the (read-only) pygame2.sdl.video.PixelFormat for this
         # Surface.
-
+        video.init ()
         self.fail() 
 
     def todo_test_pygame2_sdl_video_Surface_get_alpha(self):
         # Gets the current overall alpha value of the Surface. In case the
         # surface does not support alpha transparency (SRCALPHA flag not set),
         # None will be returned.
-
+        video.init ()
         self.fail() 
 
     def todo_test_pygame2_sdl_video_Surface_get_at(self):
         # get_at (point) -> Color
         # 
         # Gets the Surface pixel value at the specified point.
-
+        video.init ()
         self.fail() 
 
     def todo_test_pygame2_sdl_video_Surface_get_colorkey(self):
         # 
         # Gets the colorkey for the Surface or None in case it has no colorkey
         # (SRCCOLORKEY flag not set).
-
+        video.init ()
         self.fail() 
 
     def todo_test_pygame2_sdl_video_Surface_get_palette(self):
         # __doc__ (as of 2009-05-15) for pygame2.sdl.video.Surface.get_palette:
 
         # get_palette () -> (Color, Color, ...)
-
+        video.init ()
         self.fail() 
 
     def todo_test_pygame2_sdl_video_Surface_h(self):
         # __doc__ (as of 2009-05-15) for pygame2.sdl.video.Surface.h:
 
         # Gets the height of the Surface.
-
+        video.init ()
         self.fail() 
 
     def todo_test_pygame2_sdl_video_Surface_height(self):
         # __doc__ (as of 2009-05-15) for pygame2.sdl.video.Surface.height:
 
         # Gets the height of the Surface.
-
+        video.init ()
         self.fail() 
 
     def todo_test_pygame2_sdl_video_Surface_lock(self):
         # lock () -> None
         # 
         # Locks the Surface for a direct access to its internal pixel data.
-
+        video.init ()
         self.fail() 
 
     def todo_test_pygame2_sdl_video_Surface_locked(self):
         # __doc__ (as of 2009-05-15) for pygame2.sdl.video.Surface.locked:
 
         # Gets, whether the Surface is currently locked.
-
+        video.init ()
         self.fail() 
 
     def todo_test_pygame2_sdl_video_Surface_pitch(self):
         # __doc__ (as of 2009-05-15) for pygame2.sdl.video.Surface.pitch:
 
         # Get the length of a surface scanline in bytes.
-
+        video.init ()
         self.fail() 
 
     def todo_test_pygame2_sdl_video_Surface_pixels(self):
         # __doc__ (as of 2009-05-15) for pygame2.sdl.video.Surface.pixels:
 
         # Gets the pixel buffer of the Surface.
-
+        video.init ()
         self.fail() 
 
-    def todo_test_pygame2_sdl_video_Surface_save(self):
+    def test_pygame2_sdl_video_Surface_save(self):
 
         # __doc__ (as of 2009-05-15) for pygame2.sdl.video.Surface.save:
 
         # If no type information is supplied and the file type cannot be
         # determined either, it will use TGA.
         video.init ()
-        sf = video.Surface (16, 16)
-        sf.fill (pygame2.Color ("red"))
-        sf.save ("tmp.png")
+        sf1 = video.Surface (16, 16, 32)
+        sf1.fill (pygame2.Color ("red"))
+        buf = stringio.StringIO ()
+        
+        
+        sf1.save (buf, "bmp")
+        buf.seek (0)
+        sf2 = image.load_bmp (buf)
+        self.assert_ (sf1.size == sf2.size)
+        self.assert_ (cmppixels (sf1, sf2) == True)
+        
+        buf.seek (0)
+        sf2 = sdlimage.load (buf, "bmp")
+        self.assert_ (sf1.size == sf2.size)
+        self.assert_ (cmppixels (sf1, sf2) == True)
+
+        buf = stringio.StringIO ()
+        sf1.save (buf, "jpg")
+        buf.seek (0)
+        sf2 = sdlimage.load (buf, "jpg")
+        self.assert_ (sf1.size == sf2.size)
+
+        buf = stringio.StringIO ()
+        sf1.save (buf, "png")
+        buf.seek (0)
+        sf2 = sdlimage.load (buf, "png")
+        self.assert_ (sf1.size == sf2.size)
+        self.assert_ (cmppixels (sf1, sf2) == True)
+
+        buf = stringio.StringIO ()
+        sf1.save (buf, "tga")
+        buf.seek (0)
+        sf2 = sdlimage.load (buf, "tga")
+        self.assert_ (sf1.size == sf2.size)
+        self.assert_ (cmppixels (sf1, sf2) == True)
 
     def todo_test_pygame2_sdl_video_Surface_set_alpha(self):
 
         # Adjusts the alpha properties of the Surface.
         # 
         # TODO
-
+        video.init ()
         self.fail() 
 
     def todo_test_pygame2_sdl_video_Surface_set_at(self):
         # set_at (point, color) -> None
         # 
         # Sets the Surface pixel value at the specified point.
-
+        video.init ()
         self.fail() 
 
     def todo_test_pygame2_sdl_video_Surface_set_colorkey(self):
         # Adjusts the colorkey of the Surface.
         # 
         # TODO
-
+        video.init ()
         self.fail() 
 
     def todo_test_pygame2_sdl_video_Surface_set_colors(self):
         # 
         # If any other error occurs, False will be returned and the
         # Surface palette should be inspected for any changes.
-
+        video.init ()
         self.fail() 
 
     def todo_test_pygame2_sdl_video_Surface_set_palette(self):
         # 
         # If any other error occurs, False will be returned and the
         # Surface palette should be inspected for any changes.
-
+        video.init ()
         self.fail() 
 
     def todo_test_pygame2_sdl_video_Surface_size(self):
         # __doc__ (as of 2009-05-15) for pygame2.sdl.video.Surface.size:
 
         # Gets the size of the Surface.
-
+        video.init ()
         self.fail() 
 
     def todo_test_pygame2_sdl_video_Surface_unlock(self):
         # unlock () -> None
         # 
         # Unlocks the Surface, releasing the direct access to the pixel data.
-
+        video.init ()
         self.fail() 
 
     def todo_test_pygame2_sdl_video_Surface_update(self):
         # 
         # Upates the given area (or areas, if a list of rects is passed)
         # on the Surface.
-
+        video.init ()
         self.fail() 
 
     def todo_test_pygame2_sdl_video_Surface_w(self):
         # __doc__ (as of 2009-05-15) for pygame2.sdl.video.Surface.w:
 
         # Gets the width of the Surface.
-
+        video.init ()
         self.fail() 
 
     def todo_test_pygame2_sdl_video_Surface_width(self):
         # __doc__ (as of 2009-05-15) for pygame2.sdl.video.Surface.width:
 
         # Gets the width of the Surface.
-
+        video.init ()
         self.fail() 
 
 if __name__ == "__main__":

File test/test.bmp

Added
New image