[comp.lang.smalltalk] Some nice additions to Form in Smalltalk-80

CWatts@BNR.CA (Carl Watts) (05/17/91)

Here's some nice additions to class Form in Smalltalk-80.  There are methods to convert a file in Sun Raster File format and Sun Icon Edit Format into a Form.  All these methods were either written by me or rewritten by me to make them faster.  There is even a new Form class>readFrom: method that trys to recognize what format a file is and convert it into a form appropriately if it can.  This method should be modified to include recognition of any other foreign bitmap file format that Form has methods to c




onvert.

Theres also a couple of simple image processing methods for instances of form.  One quickly finds the smallest Rectangle which bounds all the black pixels on a Form.  Like what happens when you hold down the command key when you use the selection box tool in MacPaint.  Another 'hollowed' answers a new form with all the solid black areas of the receiving form hollowed out.  Kind of simple (but fast) edge detection.  Kind of like 'Trace edges' in MacPaint.  Anyway, here's the fileIn:

!Form methodsFor: 'display box access'!

minimumBoundingBox

"Compute and answer the smallest rectangle that encloses the black area of the form."

"| form |
 [Sensor redButtonPressed] whileFalse: [
	form _ Form fromDisplay: (Rectangle center: Sensor cursorPoint extent: 128@128).
	form border: form minimumBoundingBox width: 1.
	form display]."

	| smushForm result scanY clippingBox scanX |

	result _ self boundingBox.

	smushForm _ Form extent: self extent.

	self displayOn: smushForm at: self offset negated.
	scanY _ 2 raisedToInteger: (smushForm height floorLog: 2).
	clippingBox _ smushForm boundingBox.
	[scanY > 0] whileTrue: [
		clippingBox bottom: scanY.
		smushForm displayOn: smushForm at: 0@scanY negated clippingBox: clippingBox rule: Form under mask: Form black.
		scanY _ scanY // 2].

	scanX _ clippingBox left.
	[scanX < clippingBox right and: [(smushForm valueAt: scanX@0) = 0]]
		whileTrue: [scanX _ scanX + 1].
	result left: scanX.

	scanX _ clippingBox right - 1.
	[scanX >= clippingBox left and: [(smushForm valueAt: scanX@0) = 0]]
		whileTrue: [scanX _ scanX - 1].
	result right: (scanX + 1 max: result left).

	self displayOn: smushForm at: self offset negated.
	scanX _ 2 raisedToInteger: (smushForm width floorLog: 2).
	clippingBox _ smushForm boundingBox.
	[scanX > 0] whileTrue: [
		clippingBox right: scanX.
		smushForm displayOn: smushForm at: scanX negated@0 clippingBox: clippingBox rule: Form under mask: Form black.
		scanX _ scanX // 2].

	scanY _ clippingBox top.
	[scanY < clippingBox bottom and: [(smushForm valueAt: 0@scanY) = 0]]
		whileTrue: [scanY _ scanY + 1].
	result top: scanY.

	scanY _ clippingBox bottom - 1.
	[scanY >= clippingBox top and: [(smushForm valueAt: 0@scanY) = 0]]
		whileTrue: [scanY _ scanY - 1].
	result bottom: (scanY + 1 max: result top).

	^result! !

!Form methodsFor: 'image manipulation'!

hollowed

"Answer a form with the image of the receiver hollowed out."

	| smearForm finalForm stampForm |

	stampForm _ Form extent: self extent + (2@2).
	self displayOn: stampForm at: self offset negated + (1@1).
	smearForm _ (Form extent: stampForm extent) black.
	-1 to: 1 do: [:dx |
		-1 to: 1 do: [:dy |
			stampForm displayOn: smearForm at: dx@dy rule: Form and]].
	smearForm _ smearForm reverse.
	stampForm displayOn: smearForm rule: Form and.
	finalForm _ Form new extent: self extent offset: self offset.
	smearForm displayOn: finalForm at: -1@-1.
	^finalForm

	"[Sensor redButtonPressed] whileFalse: [
		((Form fromDisplay: (Rectangle center: Sensor cursorPoint extent: 128@128)) hollowed) display]."!

smeared

"Answer a form with the image of the receiver smeared."

	| smearForm |

	smearForm _ Form new extent: self extent + (2@2) offset: self offset - (1@1).
	0 to: 2 do: [:dx |
		0 to: 2 do: [:dy |
			self displayOn: smearForm at: self offset negated + (dx@dy) rule: Form under]].
	^smearForm

	"[Sensor redButtonPressed] whileFalse:
		[((Form fromDisplay: (Rectangle new origin: Sensor cursorPoint extent: 128@128))
			smeared)
				displayAt: 0@0]."! !

!Form class methodsFor: 'fileIn/Out'!

readFrom: fileName

"Answer an instance of the receiver with bitmap initialized from the external file named fileName.  Answer nil if the file does not contain a kind of Form."

	| file code |

	file _ fileName asFilename readStream.
	[file binary.
	 code _ file nextWord.	"reads fileCode"
	 file skip: -2.
	 code = 1 ifTrue: [^self readFormFile: file].
	 code = 16r59A6 ifTrue: [^self readSunRaster: fileName].
	 ^self readSunIconEdit: fileName    "last chance"
	 ] valueNowOrOnUnwindDo: [file close]!

readSunIconEdit: fileName

"Answer an instance of the receiver with bitmap initialized from the external file named fileName (created by Sun's IconEdit).  Answer nil if the file does not contain a kind of Form."
"By Carl Watts"

	| file width height form word wordNo |

	file _ fileName asFilename readStream.
	[(file skipToAll: 'idth') isNil ifTrue: [^nil].
	 file skip: 5; skipSeparators.
	 (width _ Integer readFrom: file) <= 0 ifTrue: [^nil].

	 (file skipToAll: 'eight') isNil ifTrue: [^nil].
	 file skip: 6; skipSeparators.
	 (height _ Integer readFrom: file) <= 0 ifTrue: [^nil].

	 form _ self new extent: width@height.

	 wordNo _ 0.
	 1 to: height do: [:row |
		1 to: (width + 15 // 16) do: [:col |   "rows are rounded up to next multiple of 8 bits"
			(file skipToAll: '0x') isNil ifTrue: [^nil]. file skip: 2.
			word _ Integer readFrom: file radix: 16.
			form bitsWordAt: (wordNo _ wordNo + 1) put: word]]]
		valueNowOrOnUnwindDo: [file close].

	^form!

readFormFile: file

"Answer an instance of the receiver with bitmap initialized from the external file."
"The file format is:  fileCode(1), extent, offset, bits."

	| form width height offsetX offsetY |

	[file binary.
	 file nextWord = 1 ifFalse: [^self error: 'Not a Form file'].	"reads fileCode"
	 width _ file nextWord.  height _ file nextWord.
	 offsetX  _ file nextWord.  offsetY _ file nextWord.
	 offsetX > 32767 ifTrue: [offsetX _ offsetX - 65536]. "stored two's-complement"
	 offsetY > 32767 ifTrue: [offsetY _ offsetY - 65536]. "stored two's-complement"
	 form _ (self extent: width@height) offset: offsetX@offsetY.
	 1 to: (width + 15 // 16) * height do: [:i | form bitsWordAt: i put: file nextWord]]
		valueNowOrOnUnwindDo: [file close].
	 ^form!

readSunRaster: aFilename 

"Answer an instance of me with bitmap initialized from a Sun Raster file..."
"Written by Carl Watts (cwatts@BNR.CA)"
"(Form readSunRaster: 'image.sun') edit"

	| file rasterType imageWords form |

	file _ aFilename asFilename readStream.
	[file binary.
	 file nextLong = 16r59A66A95 ifFalse: [self error: 'Not a Sun Raster File'].
	 form _ self extent: file nextLong@file nextLong.
	 file nextLong = 1 ifFalse: [self error: 'Raster file is not monochrome'].
	 file nextLong.   "Ignore the image length since I can't rely on it anyway."
	 imageWords _ (form width / 16) ceiling * form height.
	 rasterType _ file nextLong.
	 (rasterType between: 0 and: 2) ifFalse: [self error: 'Unknown raster file rasterType'].
	 file nextLong.  "Ignore the raster maptype."
	 file skip: file nextLong.  "Skip the color map."
	 (rasterType = 2) "run length compression of bytes"
		ifFalse: [1 to: imageWords do: [:i | form bitsWordAt: i put: file nextWord]]
		ifTrue: [ | bits a b bitStream |
			bits _ ByteArray new: imageWords * 2.
			bitStream _ bits writeStream.
			[(a _ file next) notNil] whileTrue: [
				(a = 128) ifFalse: [bitStream nextPut: a]
					ifTrue: [(b _ file next) = 0
						ifTrue: [bitStream nextPut: 128]
						ifFalse: [bitStream next: b+1 put: file next]]].
			1 to: imageWords do: [:i | form bitsWordAt: i put: (bits wordAt: i)]]] valueNowOrOnUnwindDo: [file close].
	^form! !