10c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi#! /usr/bin/env python
20c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
30c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi"""Test script for the imageop module.  This has the side
40c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi   effect of partially testing the imgfile module as well.
50c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi   Roger E. Masse
60c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi"""
70c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
80c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yifrom test.test_support import verbose, unlink, import_module, run_unittest
90c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
100c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimageop = import_module('imageop', deprecated=True)
110c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport uu, os, unittest
120c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
130c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
140c5958b1636c47ed7c284f859c8e805fd06a0e6Bill YiSIZES = (1, 2, 3, 4)
150c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi_VALUES = (1, 2, 2**10, 2**15-1, 2**15, 2**15+1, 2**31-2, 2**31-1)
160c5958b1636c47ed7c284f859c8e805fd06a0e6Bill YiVALUES = tuple( -x for x in reversed(_VALUES) ) + (0,) + _VALUES
170c5958b1636c47ed7c284f859c8e805fd06a0e6Bill YiAAAAA = "A" * 1024
180c5958b1636c47ed7c284f859c8e805fd06a0e6Bill YiMAX_LEN = 2**20
190c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
200c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
210c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiclass InputValidationTests(unittest.TestCase):
220c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
230c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    def _check(self, name, size=None, *extra):
240c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        func = getattr(imageop, name)
250c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        for height in VALUES:
260c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            for width in VALUES:
270c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                strlen = abs(width * height)
280c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                if size:
290c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                    strlen *= size
300c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                if strlen < MAX_LEN:
310c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                    data = "A" * strlen
320c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                else:
330c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                    data = AAAAA
340c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                if size:
350c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                    arguments = (data, size, width, height) + extra
360c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                else:
370c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                    arguments = (data, width, height) + extra
380c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                try:
390c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                    func(*arguments)
400c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                except (ValueError, imageop.error):
410c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi                    pass
420c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
430c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    def check_size(self, name, *extra):
440c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        for size in SIZES:
450c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            self._check(name, size, *extra)
460c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
470c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    def check(self, name, *extra):
480c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        self._check(name, None, *extra)
490c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
500c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    def test_input_validation(self):
510c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        self.check_size("crop", 0, 0, 0, 0)
520c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        self.check_size("scale", 1, 0)
530c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        self.check_size("scale", -1, -1)
540c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        self.check_size("tovideo")
550c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        self.check("grey2mono", 128)
560c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        self.check("grey2grey4")
570c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        self.check("grey2grey2")
580c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        self.check("dither2mono")
590c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        self.check("dither2grey2")
600c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        self.check("mono2grey", 0, 0)
610c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        self.check("grey22grey")
620c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        self.check("rgb2rgb8") # nlen*4 == len
630c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        self.check("rgb82rgb")
640c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        self.check("rgb2grey")
650c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        self.check("grey2rgb")
660c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
670c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
680c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yidef test_main():
690c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
700c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    run_unittest(InputValidationTests)
710c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
720c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    try:
730c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        import imgfile
740c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    except ImportError:
750c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        return
760c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
770c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # Create binary test files
780c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    uu.decode(get_qualified_path('testrgb'+os.extsep+'uue'), 'test'+os.extsep+'rgb')
790c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
800c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    image, width, height = getimage('test'+os.extsep+'rgb')
810c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
820c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # Return the selected part of image, which should by width by height
830c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # in size and consist of pixels of psize bytes.
840c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    if verbose:
850c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        print 'crop'
860c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    newimage = imageop.crop (image, 4, width, height, 0, 0, 1, 1)
870c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
880c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # Return image scaled to size newwidth by newheight. No interpolation
890c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # is done, scaling is done by simple-minded pixel duplication or removal.
900c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # Therefore, computer-generated images or dithered images will
910c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # not look nice after scaling.
920c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    if verbose:
930c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        print 'scale'
940c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    scaleimage = imageop.scale(image, 4, width, height, 1, 1)
950c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
960c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # Run a vertical low-pass filter over an image. It does so by computing
970c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # each destination pixel as the average of two vertically-aligned source
980c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # pixels. The main use of this routine is to forestall excessive flicker
990c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # if the image two vertically-aligned source pixels,  hence the name.
1000c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    if verbose:
1010c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        print 'tovideo'
1020c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    videoimage = imageop.tovideo (image, 4, width, height)
1030c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1040c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # Convert an rgb image to an 8 bit rgb
1050c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    if verbose:
1060c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        print 'rgb2rgb8'
1070c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    greyimage = imageop.rgb2rgb8(image, width, height)
1080c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1090c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # Convert an 8 bit rgb image to a 24 bit rgb image
1100c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    if verbose:
1110c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        print 'rgb82rgb'
1120c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    image = imageop.rgb82rgb(greyimage, width, height)
1130c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1140c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # Convert an rgb image to an 8 bit greyscale image
1150c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    if verbose:
1160c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        print 'rgb2grey'
1170c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    greyimage = imageop.rgb2grey(image, width, height)
1180c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1190c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # Convert an 8 bit greyscale image to a 24 bit rgb image
1200c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    if verbose:
1210c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        print 'grey2rgb'
1220c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    image = imageop.grey2rgb(greyimage, width, height)
1230c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1240c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # Convert a 8-bit deep greyscale image to a 1-bit deep image by
1250c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # thresholding all the pixels. The resulting image is tightly packed
1260c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # and is probably only useful as an argument to mono2grey.
1270c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    if verbose:
1280c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        print 'grey2mono'
1290c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    monoimage = imageop.grey2mono (greyimage, width, height, 0)
1300c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1310c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # monoimage, width, height = getimage('monotest.rgb')
1320c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # Convert a 1-bit monochrome image to an 8 bit greyscale or color image.
1330c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # All pixels that are zero-valued on input get value p0 on output and
1340c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # all one-value input pixels get value p1 on output. To convert a
1350c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # monochrome  black-and-white image to greyscale pass the values 0 and
1360c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # 255 respectively.
1370c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    if verbose:
1380c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        print 'mono2grey'
1390c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    greyimage = imageop.mono2grey (monoimage, width, height, 0, 255)
1400c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1410c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # Convert an 8-bit greyscale image to a 1-bit monochrome image using a
1420c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # (simple-minded) dithering algorithm.
1430c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    if verbose:
1440c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        print 'dither2mono'
1450c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    monoimage = imageop.dither2mono (greyimage, width, height)
1460c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1470c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # Convert an 8-bit greyscale image to a 4-bit greyscale image without
1480c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # dithering.
1490c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    if verbose:
1500c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        print 'grey2grey4'
1510c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    grey4image = imageop.grey2grey4 (greyimage, width, height)
1520c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1530c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # Convert an 8-bit greyscale image to a 2-bit greyscale image without
1540c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # dithering.
1550c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    if verbose:
1560c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        print 'grey2grey2'
1570c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    grey2image = imageop.grey2grey2 (greyimage, width, height)
1580c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1590c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # Convert an 8-bit greyscale image to a 2-bit greyscale image with
1600c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # dithering. As for dither2mono, the dithering algorithm is currently
1610c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # very simple.
1620c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    if verbose:
1630c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        print 'dither2grey2'
1640c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    grey2image = imageop.dither2grey2 (greyimage, width, height)
1650c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1660c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # Convert a 4-bit greyscale image to an 8-bit greyscale image.
1670c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    if verbose:
1680c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        print 'grey42grey'
1690c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    greyimage = imageop.grey42grey (grey4image, width, height)
1700c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1710c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # Convert a 2-bit greyscale image to an 8-bit greyscale image.
1720c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    if verbose:
1730c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        print 'grey22grey'
1740c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    image = imageop.grey22grey (grey2image, width, height)
1750c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1760c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    # Cleanup
1770c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    unlink('test'+os.extsep+'rgb')
1780c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1790c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yidef getimage(name):
1800c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    """return a tuple consisting of
1810c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi       image (in 'imgfile' format) width and height
1820c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    """
1830c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    import imgfile
1840c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    try:
1850c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        sizes = imgfile.getsizes(name)
1860c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    except imgfile.error:
1870c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        name = get_qualified_path(name)
1880c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        sizes = imgfile.getsizes(name)
1890c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    if verbose:
1900c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        print 'imgfile opening test image: %s, sizes: %s' % (name, str(sizes))
1910c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1920c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    image = imgfile.read(name)
1930c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    return (image, sizes[0], sizes[1])
1940c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
1950c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yidef get_qualified_path(name):
1960c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    """ return a more qualified path to name"""
1970c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    import sys
1980c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    import os
1990c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    path = sys.path
2000c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    try:
2010c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        path = [os.path.dirname(__file__)] + path
2020c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    except NameError:
2030c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        pass
2040c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    for dir in path:
2050c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        fullname = os.path.join(dir, name)
2060c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi        if os.path.exists(fullname):
2070c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi            return fullname
2080c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    return name
2090c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi
2100c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiif __name__ == '__main__':
2110c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi    test_main()
212