Image.st
changeset 89 ea2bf46eb669
parent 86 032006651226
child 100 1b0b86c77397
--- a/Image.st	Mon Feb 06 01:30:10 1995 +0100
+++ b/Image.st	Mon Feb 06 01:38:04 1995 +0100
@@ -28,7 +28,7 @@
 COPYRIGHT (c) 1991 by Claus Gittinger
 	      All Rights Reserved
 
-$Header: /cvs/stx/stx/libview/Image.st,v 1.18 1994-11-28 21:00:57 claus Exp $
+$Header: /cvs/stx/stx/libview/Image.st,v 1.19 1995-02-06 00:37:05 claus Exp $
 '!
 
 !Image class methodsFor:'documentation'!
@@ -49,7 +49,7 @@
 
 version
 "
-$Header: /cvs/stx/stx/libview/Image.st,v 1.18 1994-11-28 21:00:57 claus Exp $
+$Header: /cvs/stx/stx/libview/Image.st,v 1.19 1995-02-06 00:37:05 claus Exp $
 "
 !
 
@@ -138,6 +138,12 @@
 "
 ! !
 
+!Image class methodsFor:'queries'!
+
+imageDepth
+    self shouldNotImplement
+! !
+
 !Image class methodsFor:'misc'!
 
 dither:aSymbol
@@ -280,7 +286,11 @@
 !
 
 fromView:aView
-    "return an image taken from a view"
+    "return an image taken from a views contents as currently
+     on the screen. The returned image has the same depth and photometric
+     as the Display. Notice, that for invisible or partial covered
+     views, the returned Image is NOT correct. You may want to raise
+     the view before using this method."
 
     |org dev|
 
@@ -290,39 +300,112 @@
 			   to:(DisplayRootView on:dev) id.
     ^ self fromScreen:(org extent:aView extent) on:dev
 
-    "Image fromView:(Launcher allInstances first topView)"
-    "Image fromView:(SystemBrowser allInstances first topView)"
+    "
+     Image fromView:(Launcher allInstances first topView)
+     Image fromView:(SystemBrowser allInstances first topView)
+    "
 ! !
 
 !Image class methodsFor:'instance creation'!
 
+new
+    "create a new image. Redefined to set the photometric to
+     greyScale with black being 0 as default."
+
+    ^ super new photometric:#blackIs0
+!
+
+fromForm:aForm
+    "create & return an Image given a form"
+
+    |cls|
+
+    cls := self.
+    cls == Image ifTrue:[
+	cls := self implementorForDepth:aForm depth
+    ].
+    ^ (cls new) fromForm:aForm.
+
+    "
+     |f|
+
+     f := Form width:16 height:16.
+     f clear.
+     f displayLineFromX:0 y:0 toX:15 y:15.
+     f inspect.
+     (Image fromForm:f) inspect
+    "
+!
+
+fromImage:anImage
+    "create & return an Image given another image. This can be used to
+     convert an image to another depth."
+
+    (self == Image or:[anImage class == self]) ifTrue:[^ anImage].
+    ^ self new fromImage:anImage.
+
+    "
+     |i1 i8|
+
+     i1 := Image fromFile:'bitmaps/SBrowser.xbm'.
+     i8 := Depth8Image fromImage:i1.
+     i8 inspect
+    "
+!
+
+width:w height:h
+    "create a new image, given width, height. Assume a depth of 1."
+
+    |cls|
+
+    cls := self.
+    cls == Image ifTrue:[
+	cls := self implementorForDepth:1
+    ].
+    ^ cls new width:w height:h depth:1 
+!
+
 width:w height:h depth:d
-    "create a new form on the default device"
-
-    ^ (self implementorForDepth:d) new width:w height:h depth:d
+    "create a new image, given width, height and depth"
+
+    ^ (self implementorForDepth:d) width:w height:h depth:d
 !
 
 width:w height:h fromArray:anArray
-    "create a new form on the default device - assume depth of 1"
-
-    ^ (self implementorForDepth:1) new width:w height:h depth:1 fromArray:anArray
+    "create a new image, given width, height. Assume a depth of 1 of the
+     receiving class is Image.
+     Data must be a ByteArray containing correctly aligned bits for depth 1
+     (i.e. 8 bits per byte)."
+
+    |cls d|
+
+    cls := self.
+    cls == Image ifTrue:[
+	cls := self implementorForDepth:1.
+	d := 1.
+    ] ifFalse:[
+	d := cls imageDepth
+    ].
+    ^ cls new width:w height:h depth:d fromArray:anArray
 
     "
      Image width:8 
 	   height:8 
-	   fromArray:#(2r11001100
+	   fromArray:#[2r11001100
 		       2r00110011
 		       2r11001100
 		       2r00110011
 		       2r11001100
 		       2r00110011
 		       2r11001100
-		       2r00110011).
+		       2r00110011].
     "
 !
 
 width:w height:h depth:d fromArray:pixelData
-    "create a new form on the default device"
+    "create a new image, given width, height, depth and data.
+     Data must be a ByteArray containing correctly aligned bits for the specified
+     depth."
 
     ^ (self implementorForDepth:d) new width:w height:h depth:d fromArray:pixelData
 
@@ -330,28 +413,42 @@
      Image width:8 
 	   height:8
 	   depth:1
-	   fromArray:#(2r11001100
+	   fromArray:#[2r11001100
 		       2r00110011
 		       2r11001100
 		       2r00110011
 		       2r11001100
 		       2r00110011
 		       2r11001100
-		       2r00110011).
+		       2r00110011].
     "
 
     "
      Image width:8 
 	   height:8
 	   depth:2 
-	   fromArray:#(4r1100 4r1100
+	   fromArray:#[4r1100 4r1100
 		       4r0011 4r0011
 		       4r1100 4r1100
 		       4r0011 4r0011
 		       4r1100 4r1100
 		       4r0011 4r0011
 		       4r1100 4r1100
-		       4r0011 4r0011).
+		       4r0011 4r0011].
+    "
+
+    "
+     Image width:8 
+	   height:8
+	   depth:4 
+	   fromArray:#[4r0001 4r0001
+		       4r0011 4r0011
+		       4r1100 4r1100
+		       4r0011 4r0011
+		       4r1100 4r1100
+		       4r0011 4r0011
+		       4r1100 4r1100
+		       4r0011 4r0011].
     "
 ! !
 
@@ -362,27 +459,31 @@
      out the file format itself (by the extension and by contents)
      and lets the appropriate reader read the file."
 
-    |readerClass image name|
+    |image name nm|
 
     "
      before trying each reader, check if file is readable
     "
-    aFileName asFilename isReadable ifFalse:[
-	('IMAGE: ' , aFileName , ' is not existing or not readable') errorPrintNL.
-	^ nil
+    name := aFileName.
+    name asFilename isReadable ifFalse:[
+	name := 'bitmaps/' , name.
+	name asFilename isReadable ifFalse:[
+	    ('IMAGE: ' , aFileName , ' is not existing or not readable') errorPrintNL.
+	    ^ nil
+	].
     ].
 
     "
      get the imageReader class from the files extension
     "
-    name := aFileName.
+    nm := name.
     (name endsWith:'.Z') ifTrue:[
-	name := name copyTo:(name size - 2)
+	nm := name copyTo:(name size - 2)
     ].
     FileFormats keysAndValuesDo:[:suffix :readerClass |
-	(name endsWith:suffix) ifTrue:[
+	(nm endsWith:suffix) ifTrue:[
 	    readerClass notNil ifTrue:[
-		image := readerClass fromFile:aFileName.
+		image := readerClass fromFile:name.
 		image notNil ifTrue:[^ image].
 	    ]
 	]
@@ -394,8 +495,8 @@
     "
     FileFormats do:[:readerClass |
 	readerClass notNil ifTrue:[
-	    (readerClass isValidImageFile:aFileName) ifTrue:[
-		^ readerClass fromFile:aFileName
+	    (readerClass isValidImageFile:name) ifTrue:[
+		^ readerClass fromFile:name 
 	    ]
 	]
     ].
@@ -404,21 +505,23 @@
     'IMAGE: unknown image file format: ' errorPrint. aFileName errorPrintNL.
     ^ nil
 
-    "Image fromFile:'bitmaps/dano.tiff'"
-    "Image fromFile:'bitmaps/test.fax'"
-    "Image fromFile:'bitmaps/voice.tiff'"
-    "Image fromFile:'voice.tiff'"
-
-    "Image fromFile:'../fileIn/bitmaps/claus.gif'"
-    "Image fromFile:'../fileIn/bitmaps/garfield.gif'"
-
-    "Image fromFile:'../fileIn/bitmaps/founders.im8'"
-    "Image fromFile:'../goodies/faces/next.com/steve.face'"
-
-    "Image fromFile:'/LocalLibrary/Images/OS2/dos3.ico'"
-    "Image fromFile:'bitmaps/globe1.xbm'"
-    "Image fromFile:'bitmaps/globe1.xbm.Z'"
-    "Image fromFile:'bitmaps/hello_world.icon'"
+    "
+     Image fromFile:'bitmaps/dano.tiff'
+     Image fromFile:'bitmaps/test.fax'
+     Image fromFile:'bitmaps/voice.tiff'
+     Image fromFile:'voice.tiff'
+
+     Image fromFile:'../fileIn/bitmaps/claus.gif'
+     Image fromFile:'../fileIn/bitmaps/garfield.gif'
+
+     Image fromFile:'../fileIn/bitmaps/founders.im8'
+     Image fromFile:'../goodies/faces/next.com/steve.face'
+
+     Image fromFile:'/LocalLibrary/Images/OS2/dos3.ico'
+     Image fromFile:'bitmaps/globe1.xbm'
+     Image fromFile:'bitmaps/globe1.xbm.Z'
+     Image fromFile:'bitmaps/hello_world.icon'
+    "
 ! !
 
 !Image class methodsFor:'queries'!
@@ -682,7 +785,29 @@
      very slow ...
      (it is meant to access individual pixels - for example, in a bitmap editor)"
 
-    ^ self subclassResponsibility
+    |pixel maxPixel r g b|
+
+    pixel := self valueAtX:x y:y.
+    photometric == #blackIs0 ifTrue:[
+	maxPixel := (1 bitShift:self bitsPerPixel) - 1.
+	^ Color grey:(pixel * (100 / maxPixel)).
+    ].
+    photometric == #whiteIs0 ifTrue:[
+	maxPixel := (1 bitShift:self bitsPerPixel) - 1.
+	^ Color grey:100 - (pixel * (100 / maxPixel)).
+    ].
+    photometric == #palette ifTrue:[
+	^ colorMap at:(pixel + 1)
+    ].
+    photometric == #rgb ifTrue:[
+	r := (pixel bitShift:16) bitAnd:16rFF.
+	g := (pixel bitShift:8) bitAnd:16rFF.
+	b := pixel bitAnd:16rFF.
+	^ Color red:r / 255 * 100
+	      green:g / 255 * 100
+	       blue:b / 255 * 100
+    ].
+    self error:'invalid photometric'
 !
 
 at:aPoint put:aColor
@@ -700,11 +825,36 @@
     "set the pixel at x/y to aColor.
      Pixels start at 0@0 for the upper left pixel, end at
      (width-1)@(height-1) for the lower right pixel.
-     You should not use this method for image-processing, its
-     very slow ...
+     This method checks if the color can be stored in the image.
+     (i.e. if the receiver is a palette image, the color must be present in there).
+     You should not use this method for image-processing, it is very slow ...
      (it is meant to access individual pixels - for example, in a bitmap editor)"
 
-    ^ self subclassResponsibility
+    |pixel maxPixel|
+
+    photometric == #whiteIs0 ifTrue:[
+	maxPixel := (1 bitShift:self bitsPerPixel) - 1.
+	pixel := maxPixel - (aColor brightness * maxPixel) rounded.
+    ] ifFalse:[
+	photometric == #blackIs0 ifTrue:[
+	    maxPixel := (1 bitShift:self bitsPerPixel) - 1.
+	    pixel := (aColor brightness * maxPixel) rounded.
+	] ifFalse:[
+	    photometric ~~ #palette ifTrue:[
+		self error:'format not supported'.
+		^ nil
+	    ].
+	    pixel := colorMap indexOf:aColor.
+	    pixel == 0 ifTrue:[
+		"
+		 the color to be stored is not in the images colormap
+		"
+		self error:'invalid color'
+	    ].
+	    pixel := pixel - 1
+	]
+    ].
+    self atX:x y:y putValue:pixel.
 !
 
 valueAt:aPoint
@@ -746,7 +896,7 @@
     ^ self subclassResponsibility
 ! !
 
-!Image methodsFor:'enumeration'!
+!Image methodsFor:'enumerating'!
 
 valueAtY:y from:x1 to:x2 do:aBlock
     "perform aBlock for each pixelValue from x1 to x2 in row y.
@@ -828,6 +978,15 @@
 
 !Image methodsFor:'queries'!
 
+brightness
+    "return the brightness of the image.
+     This usually only makes sense for textures and patterns
+     (i.e. to compute shadow & light colors for viewBackgrounds).
+     Notice, that for the above purpose, only a subimage is inspected here"
+
+    ^ (self averageColorIn:(0@0 corner:7@7)) brightness
+!
+
 averageColor
     "return the average color of the image.
      This usually only makes sense for textures and patterns
@@ -889,6 +1048,48 @@
 	bytesPerRow := bytesPerRow + 1
     ].
     ^ bytesPerRow
+!
+
+usedValues
+    "return a collection of color values used in the receiver.
+     Notice, that the interpretation of the pixels depends on the photometric
+     of the image.
+     This is a general and therefore slow implementation; subclasses
+     may want to redefine this method for more performance."
+
+    |set|
+
+    set := IdentitySet new.
+    self valuesFromX:0 y:0 toX:(self width-1) y:(self height-1) do:[:x :y :pixel |
+	set add:pixel 
+    ].
+    ^ set
+
+    "
+     (Image fromFile:'bitmaps/garfield.gif') usedValues
+     (Image fromFile:'bitmaps/SBrowser.xbm') usedValues
+     (Image fromFile:'ttt.tiff') usedValues  
+    "
+!
+
+usedColors
+    "return a collection of colors used in the receiver."
+
+    |usedValues max|
+
+    usedValues := self usedValues asArray.
+    photometric ~~ #palette ifTrue:[
+	max := (1 bitShift:self depth) - 1.
+	^ usedValues collect:[:val | (Color grey:(100 * val / max ))]
+    ].
+
+    ^ usedValues collect:[:val | (colorMap at:val+1)]
+
+    "
+     (Image fromFile:'bitmaps/garfield.gif') usedColors
+     (Image fromFile:'bitmaps/SBrowser.xbm') usedColors
+     (Image fromFile:'ttt.tiff') usedColors  
+    "
 ! !
 
 !Image methodsFor:'printing & storing'!
@@ -930,8 +1131,8 @@
      h        "{ Class: SmallInteger }"
      dstIndex "{ Class: SmallInteger }" 
      srcIndex "{ Class: SmallInteger }" 
-     inData tmpData usedColors nUsed 
-     rMap gMap bMap bitsPerPixel bytesPerLine
+     inData tmpData usedPixels mapSize 
+     map bitsPerPixel bytesPerLine
      info bytesPerLineIn curs cid rootView|
 
     curs := Cursor sourceForm:(Form fromFile:'Camera.xbm')
@@ -1038,25 +1239,20 @@
 	 what we have now are the color numbers - still need the r/g/b values.
 	 find out, which colors are in the picture
 	"
-	usedColors := inData usedValues.
-	nUsed := usedColors max + 1.
+	usedPixels := inData usedValues.
+	mapSize := usedPixels max + 1.
 
 	"get the palette"
-	rMap := Array new:nUsed.
-	gMap := Array new:nUsed.
-	bMap := Array new:nUsed.
-	usedColors do:[:colorIndex |
-	    |i scale|
+	map := Array new:mapSize.
+	usedPixels do:[:colorIndex |
+	    |i|
 
 	    i := colorIndex + 1.
-	    scale := 255.0 / 100.0.
 	    aDevice getRGBFrom:colorIndex into:[:r :g :b |
-		rMap at:i put:(r * scale) rounded.
-		gMap at:i put:(g * scale) rounded.
-		bMap at:i put:(b * scale) rounded
+		map at:i put:(Color red:r green:g blue:b)
 	    ]
 	].
-	colorMap := Array with:rMap with:gMap with:bMap.
+	colorMap := map.
     ].
 
     aDevice ungrabPointer.
@@ -1212,6 +1408,105 @@
     ].
 
     ^ form
+!
+
+fromImage:anImage
+    "setup the receiver from another image.
+     Color precision may be lost, if conversion is from a higher depth
+     image. This implementation is a slow fallback (the loop over the
+     source pixels is very slow). If this method is used heavily, you
+     may want to redefine it in concrete subclasses for common source images."
+
+    width := anImage width.
+    height := anImage height.
+    bytes := ByteArray uninitializedNew:(self bytesPerRow * height).
+    bitsPerSample := self bitsPerSample.
+    samplesPerPixel := self samplesPerPixel.
+    samplesPerPixel == 3 ifTrue:[
+	photometric := #rgb
+    ] ifFalse:[
+	photometric := anImage photometric.
+	photometric == #palette ifTrue:[
+	    colorMap := anImage colorMap copy.
+	    "
+	     must compress the colormap, if source image has higher depth
+	     than myself. 
+	    "
+	    anImage bitsPerPixel > self bitsPerPixel ifTrue:[
+		"
+		 get used colors are extracted into our colorMap
+		 (the at-put below will set the pixelValue according the
+		 new colorIndex
+		"
+		colorMap := anImage usedColors asArray.
+		colorMap size > (1 bitShift:self bitsPerPixel) ifTrue:[
+		    'IMAGE: possibly too many colors in image' errorPrintNL
+		]
+	    ]
+	]
+    ].
+    anImage colorsFromX:0 y:0 toX:(width-1) y:(height-1) do:[:x :y :clr |
+	self atX:x y:y put:clr
+    ].
+
+    "
+     |i i2 i4 i8 i24|
+
+     i := Image fromFile:'bitmaps/SBrowser.xbm'.
+     i inspect.
+     i2 := Depth2Image fromImage:i.
+     i2 inspect.
+     i4 := Depth4Image fromImage:i.
+     i4 inspect.
+     i8 := Depth8Image fromImage:i.
+     i8 inspect.
+     i24 := Depth24Image fromImage:i.
+     i24 inspect.
+    "
+!
+
+fromForm:aForm
+    "setup receiver from a form"
+
+    |newImage cls map c0 c1 redMap greenMap blueMap|
+
+    width := aForm width.
+    height := aForm height.
+    bytes := aForm bits.
+    bitsPerSample := self bitsPerSample.
+    samplesPerPixel := self samplesPerPixel.
+    map := aForm colorMap.
+
+    aForm depth == 1 ifTrue:[
+	map isNil ifTrue:[
+	    photometric := #whiteIs0
+	] ifFalse:[
+	    c0 := map at:1.
+	    c1 := map at:2.
+	    ((c0 = Color white)
+	    and:[c1 = Color black]) ifTrue:[
+		photometric := #whiteIs0
+	    ] ifFalse:[
+		((c0 = Color black)
+		and:[c1 = Color white]) ifTrue:[
+		    photometric := #blackIs0
+		] ifFalse:[
+		    photometric := #palette.
+		    colorMap := Array with:c0 with:c1.
+		]
+	    ]
+	]
+    ] ifFalse:[
+	map notNil ifTrue:[
+	    photometric := #palette.
+	    colorMap := map copy.
+	] ifFalse:[
+	    "
+	     photometric stays at default
+	     (which is rgb for d24, greyscale for others)
+	    "
+	]
+    ].
 ! !
 
 !Image methodsFor:'converting rgb images'!
@@ -1353,6 +1648,26 @@
 paletteImageAsPseudoFormOn:aDevice
     "return a pseudo-deviceForm from the palette image."
 
+    |tempImage d temp8|
+
+    d := self depth.
+    (#(1 2 4 8) includes:d) ifTrue:[ 
+	"
+	 fallback code for some depth's:
+	 create a temporary Depth8Image and use its conversion method
+	"
+	temp8 := ByteArray uninitializedNew:(width * height).
+
+	bytes expandPixels:d      
+		     width:width 
+		   height:height
+		     into:temp8
+		  mapping:nil.
+
+	tempImage := Image width:width height:height depth:8 fromArray:temp8.
+	tempImage colorMap:colorMap.
+	^ tempImage paletteImageAsPseudoFormOn:aDevice
+    ].
     ^ self subclassResponsibility
 !
 
@@ -1653,10 +1968,12 @@
 
 !Image methodsFor:'image manipulations'!
 
-copyWithColorMapProcessingRed:rBlock green:gBlock blue:bBlock
+copyWithColorMapProcessing:aBlock
     "a helper to create & return new images based on the receiver with
-     some colorMap processing. The arguments are called
-     for each color component and are supposed to return new values."
+     some colorMap processing. The receiver is copied, and the copied images
+     colormap is modified by replacing entries with the result of the processing block,
+     which is called with the original color values. The block is supposed to return
+     a color."
 
     |newImage|
 
@@ -1670,94 +1987,84 @@
      the code below manipulates the colormap.
      For non-palette images, special code is required
     "
-    newImage colorMapProcessingRed:rBlock green:gBlock blue:bBlock.
+    newImage colorMapProcessing:aBlock.
     ^ newImage
 
     "
      leave red component only:
 
      (Image fromFile:'bitmaps/claus.gif') 
-	copyWithColorMapProcessingRed:[:r | r] 
-				green:[:g | 0]
-				 blue:[:b | 0]
+	copyWithColorMapProcessing:[:clr | Color red:(clr red) green:0 blue:0] 
     "
+
     "
      make it reddish:
 
      (Image fromFile:'bitmaps/claus.gif') 
-	copyWithColorMapProcessingRed:[:r | (r * 2) min:255] 
-				green:[:g | g] 
-				 blue:[:b | b]
+	copyWithColorMapProcessing:[:clr | Color red:((clr red * 2) min:100) green:clr green blue:clr blue] 
     "
+
     "
      invert:
 
      (Image fromFile:'bitmaps/claus.gif') 
-	copyWithColorMapProcessingRed:[:r | 255-r] 
-				green:[:g | 255-g] 
-				 blue:[:b | 255-b]
+	copyWithColorMapProcessing:[:clr | Color red:(100 - clr red) green:(100 - clr green) blue:(100 - clr green)] 
     "
+
     "
      lighter:
 
      (Image fromFile:'bitmaps/claus.gif') 
-	copyWithColorMapProcessingRed:[:r | r + (255-r//2)] 
-				green:[:g | g + (255-g//2)] 
-				 blue:[:b | b + (255-b//2)]
+	copyWithColorMapProcessing:[:clr | |r g b|
+						r := clr red.  g := clr green.  b := clr blue.
+						Color red:(r + (100-r//2)) 
+						      green:(g + (100-g//2)) 
+						      blue:(b + (100-b//2))]
     "
+
     "
      darker:
 
      (Image fromFile:'bitmaps/claus.gif') 
-	copyWithColorMapProcessingRed:[:r | r//2] 
-				green:[:g | g//2] 
-				 blue:[:b | b//2]
+	copyWithColorMapProcessing:[:clr | Color red:(clr red//2) green:(clr green // 2) blue:(clr blue // 2)] 
     "
 !
 
-colorMapProcessingRed:rBlock green:gBlock blue:bBlock
+colorMapProcessing:aBlock
     "a helper for all kinds of colormap manipulations.
-     The argument blocks are called for each pixel r/g/b and 
-     are supposed to return new r/g/b values.
+     The argument aBlocks is called for every colormap entry, and the returned value
+     will replace that entry in the map.
      This will fail for non-palette images.
-     see examples in Image>>copyWithColorMapProcessingRed:green:blue:"
-
-    |rMap gMap bMap nColors|
+     see examples in Image>>copyWithColorMapProcessing:"
+
+    |nColors "{ Class: SmallInteger }"|
 
     colorMap isNil ifTrue:[
 	^ self error:'image has no colormap'
     ].
 
-    "
-     the code below manipulates the colormap.
-     For non-palette images, special code is required
-    "
-    rMap := colorMap at:1.
-    gMap := colorMap at:2.
-    bMap := colorMap at:3.
-    nColors := rMap size.
-
+    nColors := colorMap size.
     1 to:nColors do:[:index |
-	|red green blue|
-
-	red := (rMap at:index).
-	green := (gMap at:index).
-	blue := (bMap at:index).
-
-	rMap at:index put:(rBlock value:red).
-	gMap at:index put:(gBlock value:green).
-	bMap at:index put:(bBlock value:blue).
+	|clr|
+
+	clr := colorMap at:index.
+	clr notNil ifTrue:[
+	    colorMap at:index put:(aBlock value:clr)
+	]
     ]
 !
 
 lightened
-    "return a new image which is slightly darker than the receiver.
+    "return a new image which is slightly brighter than the receiver.
+     The receiver must be a palette image (currently).
      Need an argument, which specifies by how much it should be lighter."
 
      ^ self 
-	copyWithColorMapProcessingRed:[:r | r + (255-r//2)] 
-				green:[:g | g + (255-g//2)] 
-				 blue:[:b | b + (255-b//2)]
+	copyWithColorMapProcessing:[:clr | |r g b|
+					   r := clr red. g := clr green. b := clr blue.
+					   Color red:(r + (255-r//2)) 
+						 green:(g + (255-g//2))
+						 blue:(b + (255-b//2))]
 
     "
      (Image fromFile:'bitmaps/claus.gif') inspect
@@ -1769,12 +2076,11 @@
 
 darkened
     "return a new image which is slightly darker than the receiver.
+     The receiver must be a palette image (currently).
      Need an argument, which specifies by how much it should be darker."
 
      ^ self 
-	copyWithColorMapProcessingRed:[:r | r // 2] 
-				green:[:g | g // 2] 
-				 blue:[:b | b // 2]
+	copyWithColorMapProcessing:[:clr | Color red:(clr r // 2) green:(clr green // 2) blue:(clr blue // 2)] 
 
     "
      (Image fromFile:'bitmaps/claus.gif') inspect
@@ -1929,6 +2235,16 @@
     "((Image fromFile:'bitmaps/claus.gif') magnifyBy:0.5@0.5)"
 !
 
+magnifyTo:anExtent 
+    "return a new image magnified to have the size specified by extent."
+
+    ^ self magnifyBy:(anExtent / self extent)
+
+    "
+     ((Image fromFile:'bitmaps/garfield.gif') magnifyTo:100@100)
+    "
+!
+
 flipHorizontal
     "inplace horizontal flip"
 
@@ -2066,9 +2382,34 @@
 
 !Image methodsFor: 'binary storage'!
 
+storeBinaryDefinitionOn: stream manager: manager
+    "store a binary representation of the receiver on stream.
+     Redefined to not store the device form (which is recreated at
+     load time anyway)"
+
+    |tDevice tDeviceForm tMonoDeviceForm tFullColorDeviceForm|
+
+    tDevice := device.
+    tDeviceForm := deviceForm.
+    tMonoDeviceForm := monoDeviceForm.
+    tFullColorDeviceForm := fullColorDeviceForm.
+
+    device := nil.
+    deviceForm := nil.
+    monoDeviceForm := nil.
+    fullColorDeviceForm := nil.
+
+    super storeBinaryDefinitionOn: stream manager: manager.
+
+    device := tDevice.
+    deviceForm := tDeviceForm.
+    monoDeviceForm := tMonoDeviceForm.
+    fullColorDeviceForm := tFullColorDeviceForm.
+!
+
 readBinaryContentsFrom: stream manager: manager
     "read a binary representation of an image from stream.
-     Redefined to fLush any device data."
+     Redefined to flush any device data."
 
     super readBinaryContentsFrom: stream manager: manager.
     device := nil.