[IMAGE-SIG] Dib GDI leak/fix + contributed plugin

Roger Burnham rburnham@cri-inc.com
Sat, 2 May 1998 15:49:27 -5000


Hi,

version: Imaging-0.3a3

I've found that deleting an ImageWin.Dib instance does not free the 
GDI resources created with:
   dib->bitmap = CreateDIBSection(...)

In the app I'm developing, this caused the GDI resources to be 
consumed after acquiring/deleting ~200 images.

The fix is

------------------dib.h------------------:
   ...
   struct ImagingDIBInstance {
      ...
      HGDIOBJ origobj; /* remember the original selected object */
   };
   ...
------------------dib.h------------------:


------------------dib.c------------------:
in ImagingNewDIB(...)
change:
    SelectObject(dib->dc, dib->bitmap);
to:
    dib->origobj = SelectObject(dib->dc, dib->bitmap);

change ImagingDeleteDIB(...) to be:

void
ImagingDeleteDIB(ImagingDIB dib)
{
    /* Clean up */

    if (dib->palette)
       DeleteObject(dib->palette);
    if (dib->dc) {
        SelectObject(dib->dc, dib->origobj);
        DeleteDC(dib->dc);
    }
    if (dib->bitmap)
    DeleteObject(dib->bitmap);
    free(dib->info);
}

E.g. the problem was that the bitmap was freed while still selected 
in the device context.


Also, here is a module I wrote to present a "stack" of bmp images as
a single image object.  In the app alluded to, we collect a set of four
images, and calculate a fifth and sixth plane.  These are stored as a unit,
and when displayed, the user can flip thru the planes with the up/down keys.


------------------BMPStackImagePlugin.py------------------:

'''
Imaging module plugin to handle a stack of BMP images.  Weve also added
a persistent dictionary attached to each image.
'''

import os
import Image, BmpImagePlugin
import cPickle, cStringIO


_bmpStackPrefix = 'BMPSTK'


def _accept(prefix):
    '''Image.open will call to see if we can handle prefix.
    '''
    return prefix[:6] == _bmpStackPrefix


class BMPStackFile(BmpImagePlugin.BmpImageFile):
    '''A stack of BmpImageFile images.
    '''
    format = _bmpStackPrefix
    format_description = "Stack of Windows Bitmaps"

    def _open(self):
        '''See if our magic is at the file head.  If so, reposition at the
        file start.
        '''
        s = self.fp.read(6)
        if s[:6] != _bmpStackPrefix:
            raise SyntaxError, "Not a BMP stack"

        self.frame = -1
        self.fp2 = self.fp
        self.offset = self.fp.tell()
        self.seek(0)

    def seek(self, frame):
        '''Read the next image in the file.
        '''
        if frame != self.frame + 1:
            raise ValueError, "cannot seek to frame %d" % frame
        self.frame = frame

        self.fp = self.fp2
        self.fp.seek(self.offset)

        try:
            self.info = cPickle.Unpickler(self.fp).load()
        except:
            raise SyntaxError, "file does not contain an info dict"

        s = self.fp.read(14)
        if s[:2] != "BM":
            raise SyntaxError, "stack does not contain a BM image"

        self._bitmap()

        offset = self.tile[0][2]
        stride = self.tile[0][3][1]
        self.offset = offset + stride*self.size[1]

    def tell(self):
        '''Return which image number we are positioned at.
        '''
        return self.frame


def _save(im, fp, filename):
    '''Image.save will call us to save the stack to a file.
    '''
    fp.write(_bmpStackPrefix)
 
    params = im.encoderinfo

    for img in im:
        infoFd = cStringIO.StringIO()
        cPickle.Pickler(infoFd, 1).dump(img.info)
        fp.write(infoFd.getvalue())
        img.params = params
        img.encoderconfig = ()
        BmpImagePlugin._save(img, fp, filename)


Image.register_open(BMPStackFile.format, BMPStackFile, _accept)
Image.register_save(BMPStackFile.format, _save)
Image.register_extension(BMPStackFile.format, '.bmpstk')


class BMPStack(Image.Image):
    '''Class that presents a stack of images with the same interface as
    a single image.  The active image is set via the member variable
    "cursor".
    '''
    __vdict = None
    __vdict_name = '_BMPStack__vdict'

    def __init__(self, imageObj):
        '''Given a file path, or an image object, wrap it as a stack.
        '''
        self.__dict__[self.__vdict_name] = {}
        
        Image.Image.__init__(self)

        if type(imageObj) == type(''):
            path = imageObj
            img = Image.open(path)
            size = img.size
            mode = img.mode
            i = 1
            imgs = []
            while 1:
                try:
                    tile = img.tile
                    if img.size != size:
                        raise ValueError, 'Images in stack must have same size'
                    next = img.convert(mode)
                    next.tile = tile
                    imgs.append(next)
                    img.seek(i)
                    i = i + 1
                except:
                    break
        else:
            mode = 'L'
            size = imageObj.size
            img = Image.new(mode, size)
            imgs = [imageObj]
            path = None

        self.__vdict['mode'] = mode
        self.__vdict['size'] = size
        self.__vdict['format'] = _bmpStackPrefix
        self.__vdict['filename'] = path
        self.__vdict['cursor'] = 0
        self.__vdict['len'] = len(imgs)
        self.__vdict['imgs'] = imgs

    def __del__(self):
        del self.__vdict
        
    def __getattr__(self, name):
        '''Handle image.attr references.  If attr==cursor, return the
        "active" image plane number (0...).  If the active image has
        the attribute, return it.  Otherwise attempt to get it from
        the wrapping object.
        '''
        if name == 'cursor':
            return self.__vdict['cursor']
        if self.__vdict.has_key('imgs'):
            if hasattr(self.__vdict['imgs'][self.__vdict['cursor']], name):
                return getattr(self.__vdict['imgs'][self.__vdict['cursor']],
                               name)
            else:
                return self.__vdict[name]
        else:
            return self.__vdict[name]

    def __getitem__(self, i):
        '''Handle stack indexing, e.g. image[i] references, but do not change
        the cursor location.
        '''
        return self.__vdict['imgs'][i]

    def __len__(self):
        '''Return the number of images in the stack.
        '''
        return len(self.__vdict['imgs'])

    def __setattr__(self, name, value):
        '''Handle image.attr = value references.  Look for cursor setting and 
        wrap the value into the allowable range.  If the stack does not exist
        yet, set the attr value in the wrapper context, otherwise, set the
        attribute for the current image (e.g. image[cursor].name = value.
        '''
        if name == 'cursor':
            if value >= self.__vdict['len']:
                self.__vdict['cursor'] = 0
            elif value < 0:
                self.__vdict['cursor'] = self.__vdict['len'] - 1
            else:
                self.__vdict['cursor'] = value
            return
        if self.__vdict.has_key('imgs'):
            setattr(self.__vdict['imgs'][self.__vdict['cursor']], name, value)
        else:
            self.__vdict[name] = value

    def __setitem__(self, index, img):
        '''Handle image[i] = img references.  If set beyond the current length,
        fill missing values with empyt strings (NEED to rethink this...).
        '''
        imgs = len(self.__vdict['imgs'])
        if index >= imgs:
            diff = index - imgs + 1
            while diff > 0:
                self.__vdict['imgs'].append('')
                diff = diff - 1
        self.__vdict['imgs'][index] = img
        self.__vdict['len'] = len(self.__vdict['imgs'])

    def append(self, img):
        '''Handle image.append(img) references in the obvious way...
        '''
        self.__vdict['imgs'].append(img)
        self.__vdict['len'] = len(self.__vdict['imgs'])


------------------BMPStackImagePlugin.py------------------:


Cheers,
Roger Burnham
Cambridge Research & Instrumentation
80 Ashford Street
Boston, MA 02134
rburnham@cri-inc.com
www.cri-inc.com