Aegidius
 Plüss   Aplulogo
 
in examples
Use the asterisk (*) character for wildcard searches
Searches on more than one word will be treated "as a phrase"
 
serverstatus
 www.aplu.ch
 www.java-online.eu
     Print Text 
© 2018, V10.0
 
  Jython
 
 

Download sprites used in the following examples

Image manipulation has become a frequent occupation for many people due to the wide-spread of digital cameras. Despite picture manipulation is normally performed using professional applications (like Photoshop), a basic understanding that a digital picture consists of a rectangular array of "color points" (called pixels) that are coded by integers is essential. The RGB color model uses 8 bits (numbers from 0 to 255) for each of the three colors RED, GREEN and BLUE that defines the intensity of a red, green and blue light emitting point.

Because from the intuitive understanding it is not evident that pictures are coded by integers, programming using images provides a profound insight about computer generated images. Implementing some simple imaging algorithms in Python broadens the understanding of how imaging software works and gives a good motivation for programming courses.

There are many imaging libraries available. In Python professional programmers often use the Python Imaging Library (PIL) that is very extensive, offering a wide range of facilities.

In introductory programming courses, PIL may be an overkill and a simpler imaging API will do. The GPanel class provides a simple graphics playground based on a user definable double coordinates with mouse support. Based on the underlying Java classes it was easy to add some simple image management functions that fit neatly into the GPanel API. Here we present some demonstrations of how to use them.

In the first example an image is loaded from a image file and shown as original and zoomed by a factor of 2 and rotated 60 degrees.

# ImageEx1.py

from gpanel import *

makeGPanel(-100, 100, -100, 100)
img = getImage("sprites/nemo.gif")
if img != None:
  image(img, -50, -50)
  img = GBitmap.scale(img, 2, 60)
  image(img, 0, 0)

Select ImageEx1.py (Ctrl+C to copy, Ctrl+V to paste)
Execute the program locally using WebStart.

jythonex

Discussion: We use a coordinate system in range -100..100 with x-axis pointing upwards and y-axis pointing to the left with its origin in the center of the window. getImage() returns the image reference (of class BufferedImage) or None, if the image file is not found. image() takes the lower left corner coordinates to display the picture. getImage() takes the path of the image file and searches it in the following order:

  • if application is packed into a jar archive, relative to the root of the jar archive
  • relative to current application directory or absolute
  • if the path starts with http://, from the given URL
  • if the path is prefixed with _ relative to the root of the jar archive

(The last option is used to load images from the _sprites folder of the TigerJython distribution).

 

GPanel may also be used for simple animations. To avoid flickering, the concept of double-buffering is applied: A new picture frame is not directly rendered to the screen, but constructed in a background screen buffer (also called offscreen buffer). When the image drawing is completed, the offscreen buffer is rendered by calling repaint() in one elementary operation. Each GPanel's graphics method performs an automatic repaint() to free the programmer to call it manually for line-drawing based programs. For animations, normally the whole graphics content must be erased and built-up at every image frame. By calling enableRepaint(False), the automatic repaint() is disabled and the clearing operation only effects the offscreen buffer.

In the following demonstration a picture is moved forth and back.

# ImageEx2.py

from gpanel import *

makeGPanel(-100, 100, -100, 100)
img = getImage("sprites/nemo.gif")
enableRepaint(False)

x = -50
step = 1
while True:
   x += step 
   image(img, x, 0)
   repaint()
   if x < -50 or x > 50:
      step = -step
   delay(10)
   clear()

Select ImageEx2.py (Ctrl+C to copy, Ctrl+V to paste)
Execute the program locally using WebStart.

Discussion: In the endless animation loop image() draws the picture in the offscreen buffer only and then rendered on the screen. After a display time of 10 ms the the buffer is cleared (but not the screen) and the next cycle starts. Because the automatic repaint is disabled, we must call repaint() in our own code.

To demonstrate a simple picture manipulation we change a colored picture to black-and-white (a grayscale picture). The algorithm is straightforward: We run over the image array (in vertical strips) from pixel to pixel, read the red, green and blue components, compute their average and change all three color values to the average.
# ImageEx3.py

from gpanel import *

size = 300
makeGPanel(Size(2 * size, size))
window(0, size, size, 0)    # y axis downwards
img = getImage("sprites/colorfrog.png")
w = img.getWidth()
h = img.getHeight()
image(img, 0, size)
for x in range(0, w):
    for y in range(0, h):
        col = img.getPixelColor(x, y)
        red = col.getRed()
        green = col.getGreen()
        blue = col.getBlue()
        intensity = (red + green + blue) // 3
        gray = Color(intensity, intensity, intensity)
        img.setPixelColor(x, y, gray)
image(img, size / 2, size)
Select ImageEx3.py (Ctrl+C to copy, Ctrl+V to paste)
Execute the program locally using WebStart.

jythonex

Discussion: getRGB() returns the 32-bit color value (as integer) coded as follows:

  • bit 0..7: blue (least significant byte)
  • bit 8..15: green
  • bit 16..23: red
  • bit 24..31: alpha (transparency) (most significant byte)

Thus the individual color values could be obtained with simple bit operations:

blue = rgb & 0xFF
green = (rgb >> 8) & 0xFF
red = (rgb >> 16) & 0xFF

To avoid low-level functions we prefer to convert the rgb integer to a Color reference (of class java.awt.Color) and use the methods getRed(), getGreen(), getBlue(). The grayscale color gray is created by passing r, g and b to its constructor.

Using the same algorithm we can replace a certain color by another. In the next example we exchange red and green.

# ImageEx4.py

from gpanel import *

size = 300
makeGPanel(Size(2 * size, size))
window(0, size, size, 0)    # y axis downwards
img = getImage("sprites/colorfrog.png")
w = img.getWidth()
h = img.getHeight()
image(img, 0, size)

for x in range(0, w):
   for y in range(0, h):
      rgb = img.getRGB(x, y)
      c = Color(rgb)
      red = c.getRed()
      green = c.getGreen()
      blue = c. getBlue()
      c = Color(green, red, blue)
      img.setRGB(x, y, c.getRGB())
image(img, size / 2, size)
Select ImageEx4.py (Ctrl+C to copy, Ctrl+V to paste)
Execute the program locally using WebStart.

jythonex

 

In many picture manipulations it is necessary to select a rectangular partial picture area. The selection is normally performed by a mouse press-drag-release operation. While dragging, a rectangle has to be paint and erased in fast succession, so we use the XOR paint mode where erasing is done by painting the same rectangle a second time. In the following example we extract a part of a picture using the crop() function and display it in new GPanel window.

# ImageEx5.py

from gpanel import *

def mousePressed(evt):
   global xStart
   global yStart
   global xEnd
   global yEnd
   xStart = xEnd = evt.getX()
   yStart = yEnd = evt.getY()

def mouseDragged(evt):
   global xEnd
   global yEnd
   p.color(Color.blue)
   p.rectangle(xStart, yStart, xEnd, yEnd) # erase old
   xEnd = evt.getX()
   yEnd = evt.getY()
   p.rectangle(xStart, yStart, xEnd, yEnd) # draw new

def mouseReleased(evt):
   width = abs(xStart - xEnd)
   height = abs(yStart - yEnd)
   if width < 50 or height < 50:
      return
   image = GBitmap.crop(img, xStart, yStart, xEnd, yEnd)
   p1 = GPanel(Size(501, 501))
   p1.window(0, 500, 500, 0)   # y axis downwards
   p1.image(image, 250 - width / 2, 250 + height / 2)
   
xStart = 0
yStart = 0
xEnd = 0
yEnd = 0

p = GPanel(Size(501, 501), 
   mousePressed = mousePressed, 
   mouseDragged = mouseDragged, 
   mouseReleased = mouseReleased)
p.window(0, 500, 500, 0)   # y axis downwards
p.setXORMode(Color.white)

img = p.getImage("sprites/reef.gif")
p.image(img, 0, 500)
Select ImageEx5.py (Ctrl+C to copy, Ctrl+V to paste)
Execute
the program locally using WebStart.


jythoex jythonex

Discussion: Because image pixels are normally referenced in a coordinate system with the origin in the upper-left vertex with a x-axis pointing to the right and a y-axis pointing downwards, we use the same coordinate system layout for the GPanel window by setting its size to 501x501 and calling window() with xmin = 0, xmax = 500, ymin = 500, ymax = 0.

In Python/Jython it is very simple to register mouse callbacks using named parameters in the GPanel constructor. mousePressed() is called when the left mouse button is pressed and the current mouse coordinates are retrieved by calling the getX() and getY() methods of the evt reference (of class java.awt.event.MouseEvent). In mouseDragged(), the rectangle is drawn twice in XOR-mode. When the mouse button is released, we extract the image portion by calling crop() with the rectangle vertex coordinates stored from the last mouseDragged event.

The same algorithm can be applied to change some aspects of a selected area, e.g. to remove red eye effect caused by the camera flash light reflected at the back (fundus) of the eye. After you worked through this example, red-eye remover applications are demystified.

# ImageEx6.html

from gpanel import *
from ch.aplu.util import Size, GBitmap

def mousePressed(evt):
   p.setXORMode(Color.blue);
   global xStart
   global yStart
   global xEnd
   global yEnd
   xStart = xEnd = evt.getX()
   yStart = yEnd = evt.getY()

def mouseDragged(evt):
   global xEnd
   global yEnd
   p.color(Color.red)
   p.rectangle(xStart, yStart, xEnd, yEnd) # erase old
   xEnd = evt.getX()
   yEnd = evt.getY()
   p.rectangle(xStart, yStart, xEnd, yEnd) # draw new

def mouseReleased(evt):
   global img
   p.setPaintMode();
   width = abs(xStart - xEnd)
   height = abs(yStart - yEnd)
   image = GBitmap.crop(img, xStart, yStart, xEnd, yEnd)
   reduceRed(image)
   img = GBitmap.paste(img, image, xStart, yStart)
   p.image(img, 0, 266)

def reduceRed(bi):
   w = bi.getWidth()
   h = bi.getHeight()
   for x in range(0, w):
      for y in range(0, h):
         rgb = bi.getRGB(x, y)
         c = Color(rgb)
         red = c.getRed()
         green = c.getGreen()
         blue = c. getBlue()
         c = Color(3 * red // 4, green, blue)
         bi.setRGB(x, y, c.getRGB())
   
xStart = 0
yStart = 0
xEnd = 0
yEnd = 0

p = GPanel(Size(301, 267), 
   mousePressed = mousePressed, 
   mouseDragged = mouseDragged, 
   mouseReleased = mouseReleased)
p.window(0, 300, 266, 0)   # y axis downwards

img = p.getImage("images/jenny-red.jpg")
p.image(img, 0, 266)
Select ImageEx6.py (Ctrl+C to copy, Ctrl+V to paste)
Execute
the program locally using WebStart


  jythonex   jythonex
(Picture from Guzdial & Ericson, Introduction to Computing and Programming in Python)