Form.st
author Claus Gittinger <cg@exept.de>
Fri, 04 Apr 1997 20:23:52 +0200
changeset 1548 34a5a4e5a1c5
parent 1520 bc88c7620d72
child 1549 b5becd3e5f6f
permissions -rw-r--r--
protocol completeness

"
 COPYRIGHT (c) 1989 by Claus Gittinger
	      All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"

GraphicsMedium subclass:#Form
	instanceVariableNames:'depth localColorMap offset data fileName'
	classVariableNames:'VeryLightGreyForm LightGreyForm GreyForm DarkGreyForm
		VeryDarkGreyForm AdditionalBitmapDirectoryNames
		BlackAndWhiteColorMap DitherPatternArray'
	poolDictionaries:''
	category:'Graphics-Display Objects'
!

!Form class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 1989 by Claus Gittinger
	      All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"
!

documentation
"
    Instances of this class represent forms (i.e. bit- and pixmaps)
    which can be created on a drawing device. 
    In X, these device resources are XPixmaps.
    Not all devices will support forms.

    NOTICE:
	the Form class is a historic leftover and now only used for real
	device forms (i.e. on devices which support downloading bitmaps).

	In your application, you should always use Image, both for compatibility
	with ST-80 and for device independence, since Form may not be supported
	by all devices.

    WARNING:
	Forms created on some device may not be recreatable, when an
	image is restarted on a display with different display capabilities.
	For example, a 24bit truecolor form will be lost when the image is
	saved and restarted in an 8bit or monochrome display.
	Worse: the information is completely lost.

	With images, the original information is always preserved, although
	the display may be with less resolution, dithered or otherwise
	approximated.

    [See also:]
	Image DeviceDrawable

    [author:]
	Claus Gittinger
"
! !

!Form class methodsFor:'initialization'!

flushDeviceForms
    "recreate all forms on aDevice; called by Workstation, to
     have all background bitmaps at hand, when views are restored"

    Lobby do:[:aDrawable |
	aDrawable isForm ifTrue:[
	    (aDrawable graphicsDevice notNil) ifTrue:[
		"now, try to recreate it"
		aDrawable recreate.
	    ]
	]
    ]

    "Created: 18.6.1996 / 13:04:59 / cg"
    "Modified: 5.7.1996 / 17:56:02 / cg"
!

initialize
    "initialize set of dictionaries to look for bitmaps"

    AdditionalBitmapDirectoryNames isNil ifTrue:[
	AdditionalBitmapDirectoryNames := #('/usr/lib/X11/bitmaps').
    
	"want to be informed when returning from snapshot"
	ObjectMemory addDependent:self.
    ]
!

reinitializeAllOn:aDevice
    "recreate all forms on aDevice; called by Workstation, to
     have all background bitmaps at hand, when views are restored"

    Lobby do:[:aDrawable |
	(aDrawable graphicsDevice == aDevice) ifTrue:[
	    aDrawable isForm ifTrue:[
		"now, try to recreate it"
		aDrawable recreate.
	    ]
	]
    ]

    "Modified: 5.7.1996 / 17:55:58 / cg"
!

update:something with:aParameter from:changedObject
    "sent just before snapOut and just after a snapIn"

    (something == #save) ifTrue:[
	"get all bits from the device into saveable arrays"
	Lobby do:[:aDrawable |
	    aDrawable isForm ifTrue:[
		aDrawable getBits
	    ]
	]
    ].
    (something == #restarted) ifTrue:[
	"remove all left-over device info"
	Lobby do:[:aDrawable |
	    aDrawable isForm ifTrue:[
		aDrawable flushDeviceHandles.
		Lobby registerChange:aDrawable 
	    ]
	]
    ]

    "Modified: 15.6.1996 / 15:44:09 / cg"
    "Created: 15.6.1996 / 15:47:50 / cg"
! !

!Form class methodsFor:'instance creation'!

extent:ext
    "create a new, cleared form, take dimensions from ext.
     Smalltalk-80 compatibility"

    |newForm|

    newForm := self width:(ext x) height:(ext y).
    newForm fill:(Color colorId:0).
    newForm paint:(Color colorId:1).
    ^ newForm
!

extent:ext depth:d
    "create a new, cleared form.
     Smalltalk-80 compatibility"

    ^ self width:ext x height:ext y depth:d

    "Created: 27.1.1997 / 16:08:37 / cg"
    "Modified: 27.1.1997 / 16:08:50 / cg"
!

extent:ext depth:d on:aDevice
    "create a new form on device, aDevice; depth is what device likes most"

    ^ self width:ext width height:ext height depth:d on:aDevice

    "Created: 4.4.1997 / 20:23:32 / cg"
!

extent:ext fromArray:data
    "create a new form, take dimensions from ext, bits from data.
     Smalltalk-80 compatibility."

    ^ self width:(ext x) height:(ext y) offset:0@0 fromArray:data
!

extent:ext fromArray:data offset:offs
    "create a new form, take dimensions from ext, bits from data.
     Smalltalk-80 compatibility."

    ^ self width:(ext x) height:(ext y) offset:offs fromArray:data
!

extent:ext offset:anOffset
    "create a new, cleared form, take dimensions from ext.
     Smalltalk-80 compatibility"

    ^ (self extent:ext) offset:anOffset.
!

extent:ext on:aDevice
    "create a new form on device, aDevice; depth is what device likes most"

    ^ self width:ext width height:ext height on:aDevice

    "Modified: 4.4.1997 / 20:23:19 / cg"
!

width:w height:h
    "create a new form on the default device"

    ^ self width:w height:h on:Screen current

    "Modified: 4.6.1996 / 22:16:33 / cg"
!

width:w height:h depth:d
    "create a new form on the default device"

    ^ self width:w height:h depth:d on:Screen current

    "Modified: 4.6.1996 / 22:16:51 / cg"
!

width:w height:h depth:d on:aDevice
    "create a new form with depth d on device, aDevice"

    ^ (self onDevice:aDevice) width:w height:h depth:d

    "Modified: 18.1.1997 / 18:26:03 / cg"
!

width:w height:h fromArray:anArray
    "create a new form on the default device"

    ^ self width:w height:h fromArray:anArray on:Screen current

    "Modified: 4.6.1996 / 22:17:05 / cg"
!

width:w height:h fromArray:anArray on:aDevice
    "create a new form on device, aDevice and
     initialize the pixels from anArray"

    ^ (self onDevice:aDevice) width:w height:h fromArray:anArray

    "Modified: 18.1.1997 / 18:26:24 / cg"
!

width:w height:h offset:offs fromArray:anArray
    "create a new form on the default device"

    ^ (self onDevice:Screen current) width:w height:h offset:offs fromArray:anArray

    "Modified: 18.1.1997 / 18:26:28 / cg"
!

width:w height:h on:aDevice
    "create a new form on device, aDevice; depth is what device likes most"

    ^ (self onDevice:aDevice) width:w height:h

    "Modified: 18.1.1997 / 18:26:31 / cg"
! !

!Form class methodsFor:'ST-80 compatibility'!

and
    "return a constant usable as bitblt-combinationrule.
     In ST-80rel2.x, these used to be numeric constants; in ST/X,
     these are symbolic."

    ^ #and

    "Modified: 2.5.1996 / 11:41:23 / cg"
!

black
    "ST80 compatibility;
     In old st80, you could use `Form black' for drawing 
     - here we return the black color."

    ^ Color black

    "Modified: 2.5.1996 / 11:44:17 / cg"
!

darkGray
    "ST80 compatibility;
     In old st80, you could use `Form darkGray' for drawing 
     - here we return the darkGray color."

    ^ Color darkGray

    "Modified: 2.5.1996 / 11:44:06 / cg"
!

darkGrey
    "ST80 compatibility;
     In old st80, you could use `Form darkGrey' for drawing 
     - here we return the darkGrey color."

    ^ Color darkGray

    "Modified: 28.5.1996 / 20:47:54 / cg"
!

gray
    "ST80 compatibility;
     In old st80, you could use `Form gray' for drawing 
     - here we return the grey color."

    ^ Color gray

    "Modified: 2.5.1996 / 11:43:44 / cg"
!

grey
    "ST80 compatibility;
     In old st80, you could use `Form grey' for drawing 
     - here we return the grey color."

    ^ Color grey

    "Modified: 2.5.1996 / 11:43:17 / cg"
!

lightGray
    "ST80 compatibility;
     In old st80, you could use `Form lightGray' for drawing 
     - here we return the lightGray color."

    ^ Color lightGray

    "Created: 2.5.1996 / 11:40:07 / cg"
    "Modified: 2.5.1996 / 11:43:21 / cg"
!

lightGrey
    "ST80 compatibility;
     In old st80, you could use `Form lightGray' for drawing 
     - here we return the lightGray color."

    ^ Color lightGray

    "Modified: 28.5.1996 / 20:53:28 / cg"
!

over
    "return a constant usable as bitblt-combinationrule.
     In ST-80rel2.x, these used to be numeric constants; in ST/X,
     these are symbolic."

    ^ #copy

    "Modified: 2.5.1996 / 11:41:31 / cg"
!

paint
    "return a constant usable as bitblt-combinationrule.
     In ST-80rel2.x, these used to be numeric constants; in ST/X,
     these are symbolic."

    ^ #copy

    "Modified: 2.5.1996 / 11:41:40 / cg"
!

reverse
    "return a constant usable as bitblt-combinationrule.
     In ST-80rel2.x, these used to be numeric constants; in ST/X,
     these are symbolic."

    ^ #xor

    "Modified: 2.5.1996 / 11:41:45 / cg"
!

under
    "return a constant usable as bitblt-combinationrule.
     In ST-80rel2.x, these used to be numeric constants; in ST/X,
     these are symbolic."

    ^ #or

    "Modified: 2.5.1996 / 11:41:49 / cg"
!

white
    "ST80rel2.x compatibility;
     In old st80, you could use `Form white' for drawing 
     - here we return the white color."

    ^ Color white

    "Modified: 2.5.1996 / 11:42:50 / cg"
! !

!Form class methodsFor:'cleanup'!

lowSpaceCleanup
    "cleanup in low-memory situations"

    DitherPatternArray := nil
! !

!Form class methodsFor:'file search'!

findBitmapFile:fileName
    "find the bitmap file in one of the standard places;
     return the pathName or nil"

    |aStream path|

    ((fileName at:1) == $/) ifTrue:[^ fileName].
    (fileName startsWith:'../') ifTrue:[^ fileName].
    (fileName startsWith:'./') ifTrue:[^ fileName].
    fileName asFilename exists ifTrue:[^ fileName].

    aStream := Smalltalk bitmapFileStreamFor:fileName.
    aStream notNil ifTrue:[
	path := aStream pathName.
	aStream close.
	^ path
    ].
    AdditionalBitmapDirectoryNames notNil ifTrue:[
	AdditionalBitmapDirectoryNames do:[:aPath |
	    path := aPath , '/' , fileName.
	    (OperatingSystem isReadable:path) ifTrue:[
		^ path
	    ]
	]
    ].
    ^ nil
! !

!Form class methodsFor:'fileIn/Out'!

fromFile:filename
    "create a new form taking the bits from a file on the default device.
     WARNING:
     Please do no longer use this, since it will not work
     correctly in multi-display applications (creates the form on the
     default Display).
     Use #fromFile:on: and pass the devive as argument."

    self obsoleteMethodWarning:'please use Image>>fromFile:'.
    ^ self fromFile:filename on:Screen current

    "Modified: 19.12.1996 / 13:59:09 / cg"
!

fromFile:filename on:aDevice
    "create a new form on device, aDevice and
     initialize the pixels from the file filename"

    self obsoleteMethodWarning:'please use Image>>fromFile:'.
    ^ (self on:aDevice) readFromFile:filename

    "Modified: 19.12.1996 / 13:59:21 / cg"
!

fromFile:filename resolution:dpi
    "create a new form taking the bits from a file on the default device
     the data in the file is assumed to be for dpi resolution;
     if it is different from the displays resolution, magnify or
     shrink the picture (but only in integer magnification steps).
     WARNING:
     Please do no longer use this, since it will not work
     correctly in multi-display applications (creates the form on the
     default Display).
     Use #fromFile:resolution:on: and pass the devive as argument."

    self obsoleteMethodWarning:'please use Image>>fromFile:'.
    ^ (self on:Screen current) readFromFile:filename resolution:dpi

    "Modified: 21.12.1996 / 12:40:53 / cg"
!

fromFile:filename resolution:dpi on:aDevice
    "create a new form on device, aDevice and
     initialize the pixels from the file filename;
     the data in the file is assumed to be for dpi resolution;
     if it is different from the displays resolution, magnify or
     shrink the picture (but only in integer magnification steps)"

    self obsoleteMethodWarning:'please use Image>>fromFile:'.
    ^ (self on:aDevice) readFromFile:filename resolution:dpi

    "Modified: 19.12.1996 / 19:12:44 / cg"
!

readFrom:fileName
    "same as Form>>fromFile: - for ST-80 compatibility.
     WARNING:
     Please do no longer use this, since it will not work
     correctly in multi-display applications (creates the form on the
     default Display).
     Use #fromFile:on: and pass the devive as argument."

    self obsoleteMethodWarning:'please use Image>>fromFile:'.
    ^ self fromFile:fileName on:Screen current

    "Modified: 19.12.1996 / 13:59:50 / cg"
! !

!Form class methodsFor:'obsolete instance creation'!

darkGreyFormOn:aDevice
    "return a darkGrey form"

    |f|

    ((aDevice == Display) and:[DarkGreyForm notNil]) ifTrue:[
	^ DarkGreyForm
    ].

    f := self width:8 height:4 fromArray:(self darkGreyFormBits) on:aDevice.
    (aDevice == Display) ifTrue:[
	DarkGreyForm := f
    ].
    ^ f
!

grey:percent on:aDevice
    "return a form for dithering"

    (percent < 20) ifTrue:[^ Color black on:aDevice].
    (percent < 40) ifTrue:[^ self darkGreyFormOn:aDevice].
    (percent < 60) ifTrue:[^ self mediumGreyFormOn:aDevice].
    (percent < 80) ifTrue:[^ self lightGreyFormOn:aDevice].
    ^ Color white on:aDevice
!

lightGreyFormOn:aDevice
    "return a lightGrey form"

    |f|

    ((aDevice == Display) and:[LightGreyForm notNil]) ifTrue:[
	^ LightGreyForm
    ].

    f := self width:8 height:4 fromArray:(self lightGreyFormBits) on:aDevice.
    (aDevice == Display) ifTrue:[
	LightGreyForm := f
    ].
    ^ f
!

mediumGreyFormOn:aDevice
    "return a grey form"

    |f|

    ((aDevice == Display) and:[GreyForm notNil]) ifTrue:[
	^ GreyForm
    ].

    f := self width:8 height:4 fromArray:(self greyFormBits) on:aDevice.
    (aDevice == Display) ifTrue:[
	GreyForm := f
    ].
    ^ f
!

veryDarkGreyFormOn:aDevice
    "return a veryDarkGrey form"

    |f|

    ((aDevice == Display) and:[VeryDarkGreyForm notNil]) ifTrue:[
	^ VeryDarkGreyForm
    ].

    f := self width:8 height:4 fromArray:(self veryDarkGreyFormBits) on:aDevice.
    (aDevice == Display) ifTrue:[
	VeryDarkGreyForm := f
    ].
    ^ f
!

veryLightGreyFormOn:aDevice
    "return a veryLightGrey form"

    |f|

    ((aDevice == Display) and:[VeryLightGreyForm notNil]) ifTrue:[
	^ VeryLightGreyForm
    ].

    f := self width:8 height:4 fromArray:(self veryLightGreyFormBits) on:aDevice.
    (aDevice == Display) ifTrue:[
	VeryLightGreyForm := f
    ].
    ^ f
! !

!Form class methodsFor:'obsolete patterns'!

darkGreyFormBits
    "return a pattern usable to simulate darkGray on monochrome device"

    ^ #(2r10111011
	2r11101110
	2r10111011
	2r11101110)
!

grey12Bits
    "return a pattern with 12% grey, usable for dithering"

    ^ #(2r00010001
	2r00000000
	2r01000100
	2r00000000)
!

grey25Bits
    "return a pattern with 25% grey, usable for dithering"

    ^ #(2r00010001
	2r01000100
	2r00010001
	2r01000100)
!

grey37Bits
    "return a pattern with 37% grey, usable for dithering"

    ^ #(2r00010001
	2r10101010
	2r01000100
	2r10101010)
!

grey50Bits
    "return a pattern with 50% grey, usable for dithering"

    ^ #(2r01010101
	2r10101010
	2r01010101
	2r10101010)
!

grey6Bits
    "return a pattern with 6% grey, usable for dithering"

    ^ #(2r00000001
	2r00000000
	2r00010000
	2r00000000)
!

greyFormBits
    "return a pattern usable to simulate gray on monochrome device"

    ^ #(2r01010101
	2r10101010
	2r01010101
	2r10101010)
!

lightGreyFormBits
    "return a pattern usable to simulate lightGray on monochrome device"

    ^ #(2r01000100
	2r00010001
	2r01000100
	2r00010001
	2r01000100)
!

veryDarkGreyFormBits
    "return a pattern usable to simulate veryDarkGray on monochrome device"

    ^ #(2r01110111
	2r11111111
	2r11011101
	2r11111111)
!

veryLightGreyFormBits
    "return a pattern usable to simulate veryDarkGray on monochrome device"

    ^ #(2r10001000
	2r00000000
	2r00100010
	2r00000000)
! !

!Form methodsFor:'ST-80 compatibility'!

destroy
    "destroy my underlying device resource(s)"

    |id|

    (id := gcId) notNil ifTrue:[
        gcId := nil.
        device destroyGC:id.
    ].

    (id := drawableId) notNil ifTrue:[
        drawableId := nil.
        device destroyPixmap:id.
    ].

    Lobby unregister:self.

    "Modified: 2.4.1997 / 19:39:52 / cg"
!

displayOn:aGC at:aPoint
    "draw in aGC.
     Smalltalk-80 (2.x) compatibility"

    ^ self displayOn:aGC x:aPoint x y:aPoint y rule:#copy

    "Modified: 12.5.1996 / 20:16:15 / cg"
!

displayOn:aGC at:aPoint rule:rule
    "draw in aGC.
     Smalltalk-80 (2.x) compatibility"

    self displayOn:aGC x:aPoint x y:aPoint y rule:rule

    "Modified: 12.5.1996 / 20:16:02 / cg"
!

displayOn:aGC rule:rule
    "draw in aGC.
     Smalltalk-80 (2.x) compatibility"

    ^ self displayOn:aGC x:0 y:0 rule:rule

    "Modified: 12.5.1996 / 20:15:41 / cg"
!

displayOn:aGC x:x y:y
    "draw in aGC.
     Smalltalk-80 (2.x) compatibility"

    ^ self displayOn:aGC x:x y:y rule:#copy

    "Created: 12.5.1996 / 20:15:05 / cg"
!

displayOn:aGC x:x y:y rule:rule
    "draw in aGC.
     Smalltalk-80 (2.x) compatibility"

    |f|

    f := aGC function.
    aGC function:rule.
    aGC displayOpaqueForm:self x:x y:y.
    aGC function:f.

    "Created: 12.5.1996 / 20:15:26 / cg"
!

isOpen
    ^ true

    "Created: 10.2.1997 / 12:43:49 / cg"
!

offset
    "set the offset.
     Smalltalk-80 compatibility"

    ^ offset
!

offset:org
    "set the offset.
     Smalltalk-80 compatibility"

    offset := org
!

preferredBounds
    ^ self bounds

    "Created: 10.2.1997 / 12:43:00 / cg"
! !

!Form methodsFor:'accessing'!

bits
    "return a ByteArray filled with my bits -
     for depth 8 forms, 1 pixel/byte is filled;
     for depth 1 forms, 8 pixels/byte are filled
     for depth 4 forms, 2 pixels/byte are filled.
     Padding is done to the next byte-boundary 
     (i.e. for width==13 and depth==1 it will return 2 bytes per scanline)"

    |bytesPerLine   "{ Class: SmallInteger }"
     bytesPerLineIn "{ Class: SmallInteger }"
     inData tmpData info
     srcIndex "{ Class: SmallInteger }"
     dstIndex "{ Class: SmallInteger }"
     buffer|

    data notNil ifTrue:[
        ^ data
    ].
    drawableId isNil ifTrue:[
        fileName notNil ifTrue:[
            ^ (self on:Screen current) bits
        ].
        ^ nil
    ].

    bytesPerLine := (width * depth + 31) // 32 * 4.
    inData := ByteArray uninitializedNew:(bytesPerLine * height).
    info := device getBitsFromPixmapId:drawableId x:0 y:0 width:width height:height into:inData. 
    bytesPerLineIn := (info at:#bytesPerLine).                    "what I got"
    bytesPerLine := (width * depth + 7) // 8.                     "what I want"
    (bytesPerLine ~~ bytesPerLineIn) ifTrue:[
        "
         different padding - have to copy over row-wise
        "
        tmpData := inData.
        inData := ByteArray uninitializedNew:(bytesPerLine * height).
        srcIndex := 1.
        dstIndex := 1.
        1 to:height do:[:hi |
            inData replaceFrom:dstIndex to:(dstIndex + bytesPerLine - 1)
                          with:tmpData startingAt:srcIndex.
            dstIndex := dstIndex + bytesPerLine.
            srcIndex := srcIndex + bytesPerLineIn
        ]
    ] ifFalse:[
        "
         same padding - copy over all in one chunk
        "
        (bytesPerLine * height) ~~ inData size ifTrue:[
            tmpData := inData.
            inData := ByteArray uninitializedNew:(bytesPerLine * height).
            inData replaceFrom:1 to:bytesPerLine * height with:tmpData startingAt:1
        ]
    ].
    "
     have to reverse bytes, if not msbFirst ?
    "

"/    (info at:#byteOrder) ~~ #msbFirst ifTrue:[
"/        buffer := ByteArray new:bytesPerLine.
"/        dstIndex := 1.
"/        1 to:height do:[:hi |
"/            buffer replaceFrom:1 to:bytesPerLine with:inData startingAt:dstIndex.
"/            buffer reverse.
"/            inData replaceFrom:dstIndex to:dstIndex+bytesPerLine-1 with:buffer startingAt:1.
"/            dstIndex := dstIndex + bytesPerLine
"/        ]
"/    ].
    (info at:#bitOrder) ~~ #msbFirst ifTrue:[
        buffer := ByteArray new:bytesPerLine.
        dstIndex := 1.
        1 to:height do:[:hi |
            buffer replaceFrom:1 to:bytesPerLine with:inData startingAt:dstIndex.
            buffer expandPixels:8 width:bytesPerLine height:1 into:buffer
                                mapping:(ImageReader reverseBits).  "/ translate only
            inData replaceFrom:dstIndex to:dstIndex+bytesPerLine-1 with:buffer startingAt:1.
            dstIndex := dstIndex + bytesPerLine
        ]
    ].

    ^ inData.

    "Modified: 19.3.1997 / 13:44:20 / cg"
!

bits:aByteArray
    "set the forms bits; 
     for depth-8 forms, 1 pixel/byte is expected;
     for depth-1 forms, 8 pixels/byte are expected
     for depth-4 forms, 2 pixels/byte are expected.
     Padding is expected to the next byte-boundary 
     (i.e. for width==13 and depth==1 2 bytes per scanline are expected)"

    data := aByteArray

    "Modified: 23.4.1996 / 10:06:01 / cg"
!

bitsPerSample
    "for compatibility with Image class ..."

    ^ Array with:depth
!

colorMap
    "return the receivers colormap"

    ^ localColorMap
!

colorMap:anArrayOrColorMap
    "set the receivers colormap"

    localColorMap := anArrayOrColorMap

    "Modified: 7.3.1997 / 21:26:11 / cg"
!

depth
    "return the receivers depth"

    ^ depth
!

filename
    "return the filename, from which the receiver was created,
     or nil, if it was not read from a file"

    ^ fileName
!

forgetBits
    "for image, which also keeps the bits - so there is
     no need to hold them again here"

    data := nil
!

mask
    "for compatibility with images; forms have no mask/alpha channel"

    ^ nil

    "Created: 21.6.1996 / 12:52:42 / cg"
!

photometric
    "for compatibility with Image class ..."

    depth == 1 ifTrue:[
	localColorMap isNil ifTrue:[
	    ^ #whiteIs0
	].
	((localColorMap at:1) = Color white) ifTrue:[
	    ((localColorMap at:2) = Color black) ifTrue:[
		^ #whiteIs0
	    ].
	].
	((localColorMap at:1) = Color black) ifTrue:[
	    ((localColorMap at:2) = Color white) ifTrue:[
		^ #blackIs0
	    ].
	].
    ].
    ^ #palette

    "Modified: 17.6.1996 / 11:45:16 / cg"
!

samplesperPixel
    "for compatibility with Image class ..."

    ^ 1
!

valueAt:aPoint
    "return the pixel at aPoint; the coordinates start with 0@0
     in the upper left, increasing to the lower right"

    ^ self at:aPoint

    "Modified: 23.4.1996 / 10:06:55 / cg"
!

valueAt:aPoint put:value
    "set the pixel at aPoint; the coordinates start with 0@0
     in the upper left, increasing to the lower right."

    ^ self at:aPoint put:value

    "
     |f|

     f := Form width:10 height:10 depth:1.
     f clear.
     1 to:10 do:[:i |
	f valueAt:(i @ i) put:1
     ].
     f inspect
    "

    "Modified: 23.4.1996 / 10:12:48 / cg"
! !

!Form methodsFor:'binary storage'!

readBinaryContentsFrom: stream manager: manager
    "tell the newly restored Form about restoration"

    super readBinaryContentsFrom: stream manager: manager.
    device := Screen current.
    fileName := nil.
    self restored.
    self recreate.
    Lobby register:self.

    "
     |f|

     f := Form fromFile:'bitmaps/SBrowser.xbm'.
     f storeBinaryOn:'foo.bos'.

     (Form readBinaryFrom:'foo.bos') inspect
    "
!

storeBinaryDefinitionOn: stream manager: manager
    "store a binary representation of the receiver on stream.
     This is an internal interface for binary storage mechanism.
     Redefined to store the actual bits, even if I have been loaded 
     from a file."

    data isNil ifTrue:[
	data := self bits.
	super storeBinaryDefinitionOn: stream manager: manager.
	data := nil.
	^ self
    ].
    super storeBinaryDefinitionOn: stream manager: manager

    "Modified: 23.4.1996 / 09:30:47 / cg"
! !

!Form methodsFor:'converting'!

asForm
    "convert & return the receiver into a Form instance - nothing to be done here"

    ^ self

    "Modified: 23.4.1996 / 10:14:11 / cg"
!

asImage
    "convert & return the receiver into an Image instance"

    ^ Image fromForm:self

    "Modified: 23.4.1996 / 10:13:56 / cg"
! !

!Form methodsFor:'copying'!

postCopy
    "redefined to copy the colorMap as well"

    super postCopy.
    localColorMap := localColorMap copy.
    data := data copy

    "Modified: 23.4.1996 / 10:14:46 / cg"
!

shallowCopyForFinalization
    "redefined for faster creation of finalization copies
     (only device, gcId and drawableId are needed)"

    |aCopy|

    aCopy := DeviceFormHandle basicNew.
    aCopy setDevice:device id:drawableId gcId:gcId.
    ^ aCopy
! !

!Form methodsFor:'editing'!

edit
    "open an imageEditor on the receiver"

    ImageEditView openOnImage:self

    "
     (Form fromFile:'bitmaps/SBrowser.xbm') edit
    "

    "Modified: 23.4.1996 / 10:16:02 / cg"
!

show
    "open an imageView on the receiver"

    ImageView openOnImage:self

    "
     (Form fromFile:'bitmaps/SBrowser.xbm') show
    "

    "Modified: 23.4.1996 / 10:16:12 / cg"
! !

!Form methodsFor:'getting a device form'!

asFormOn:aDevice
    "convert & return the receiver into a Form instance
     and associate it to a device (i.e. download its bits).
     Added for protocol compatibility with Image."

    aDevice == device ifTrue:[
	^ self
    ].
    ^ self on:aDevice

    "Modified: 23.4.1996 / 10:17:26 / cg"
!

asMonochromeFormOn:aDevice
    "added for protocol compatiblity with Image"

    aDevice == device ifTrue:[
	depth == 1 ifTrue:[
	    ^ self
	].
    ].
    (depth == 1) ifTrue:[
	^ self on:aDevice
    ].
    ^ nil

    "Modified: 23.4.1996 / 10:18:42 / cg"
!

on:aDevice
    "associate the receiver to a device (i.e. download its bits);
     return a deviceForm (possibly different from the receiver)."

    aDevice == device ifTrue:[
        ^ self
    ].
    "create a new form ..."

    data notNil ifTrue:[
        ^ self class width:width height:height fromArray:data on:aDevice
    ].
    fileName notNil ifTrue:[
        ^ self class fromFile:fileName on:aDevice
    ].
    ^ (self class width:width height:height on:aDevice) clear

    "Modified: 6.3.1997 / 15:47:25 / cg"
!

onDevice:aDevice
    "associate the receiver to a device (i.e. download its bits);
     return a deviceForm (possibly different from the receiver)."

    ^ self on:aDevice

    "Created: 28.3.1997 / 16:12:24 / cg"
! !

!Form methodsFor:'image manipulations'!

darkened
    "return a darkened version of the receiver.
     Added for protocol compatibility with Color and Image.
     Here, the receiver is returned as a kludge 
     - actually should return a darkened image (or Color black ?) .."

    ^ self

    "Modified: 23.4.1996 / 10:19:52 / cg"
!

flipHorizontal
    "return a new form flipped horizontally"

    |dstY newForm nRows "{ Class: SmallInteger }" |

    newForm := ((self class) on:device)
				width:width
				height:height
				depth:depth.
    "expand rows"
    dstY := height - 1.
    nRows := dstY // 2.
    0 to:nRows do:[:srcY |
	newForm copyFrom:self
		       x:0 y:srcY
		     toX:0 y:dstY
		   width:width height:1.
	dstY := dstY - 1
    ].
    newForm colorMap:localColorMap.
    ^ newForm
!

flipVertical
    "return a new form flipped vertically"

    |dstX newForm nCols "{ Class: SmallInteger }" |

    newForm := ((self class) on:device)
				width:width
				height:height
				depth:depth.
    "expand cols"
    dstX := width - 1.
    nCols := dstX // 2.
    0 to:nCols do:[:srcX |
	newForm copyFrom:self
		       x:srcX y:0
		     toX:dstX y:0
		   width:1 height:height.
	dstX := dstX - 1
    ].
    newForm colorMap:localColorMap.
    ^ newForm
!

hardMagnifiedBy:extent
    "return a new form magnified by extent, aPoint.
     This method handles non-integral factors."

    "
     since Form will be replaced by Image in the long run,
     and this operation is slow anyway, use the implementation
     in Image for this."

    ^ ((Image fromForm:self) magnifiedBy:extent) asFormOn:device.

    "
     (Form fromFile:'OutputOn.64') magnifiedBy:0.5@0.5
     (Form fromFile:'OutputOn.64') magnifiedBy:1.5@1.5
    "
!

lightened
    "return a lightened version of the receiver.
     Added for protocol compatibility with Color and Image.
     Here, the receiver is returned as a kludge 
     - actually should return a lightened image (or Color white ?) .."

    ^ self

    "Modified: 23.4.1996 / 10:20:14 / cg"
!

magnifiedBy:extent
    "return a new form magnified by extent, aPoint.
     If non-integral magnify is asked for, pass the work on to 'hardMagnifiedBy:'"

    |mX mY dstX dstY newForm ext 
     factor "{ Class: SmallInteger }"
     n      "{ Class: SmallInteger }" |

    ext := extent asPoint.
    mX := ext x.
    mY := ext y.
    ((mX = 1) and:[mY = 1]) ifTrue:[^ self].

    ((mX isMemberOf:SmallInteger) and:[mY isMemberOf:SmallInteger]) ifFalse:[
	^ self hardMagnifiedBy:ext
    ].

    newForm := ((self class) on:device)
				width:(width * mX)
				height:(height * mY)
				depth:depth.

    "expand rows"
    (mY > 1) ifTrue:[
	dstY := 0.
	n := height.
	factor := mY.
	0 to:(n - 1) do:[:srcY |
	    1 to:factor do:[:i |
		newForm copyFrom:self
			       x:0 y:srcY
			     toX:0 y:dstY
			   width:width height:1.
		dstY := dstY + 1
	    ]
	]
    ].

    "expand cols"
    (mX > 1) ifTrue:[
	n := width.
	factor := mX.
	dstX := (n * factor) - 1.
	(n - 1) to:0 by:-1 do:[:srcX |
	    1 to:factor do:[:i |
		newForm copyFrom:newForm
			       x:srcX y:0
			     toX:dstX y:0
			   width:1 height:(height * mY).
		dstX := dstX - 1
	    ]
	]
    ].
    newForm colorMap:localColorMap.
    ^ newForm

    "
     (ArrowButton upArrowButtonForm:#iris on:Display) magnifiedBy:(2 @ 2)
     (Form fromFile:'bitmaps/SBrowser.xbm') magnifiedBy:(2 @ 2)
     (Form fromFile:'bitmaps/SBrowser.xbm') magnifiedBy:(0.4 @ 0.4)
    "
!

magnifiedTo:anExtent 
    "return a new form magnified to have the size specified by extent.
     This may distort the image if the arguments ratio is not the images ratio.
     See also #magnifiedPreservingRatioTo: and #magnifiedBy:"

    ^ self magnifiedBy:(anExtent / self extent)

    "Created: 18.4.1996 / 16:17:52 / cg"
!

magnifyBy:scale
    "obsolete: has been renamed to magnifiedBy: for ST-80 compatibility
     and name consistency ..."

    self obsoleteMethodWarning.
    ^ self magnifiedBy:scale
! !

!Form methodsFor:'initialization'!

createGC
    "physically create a device GC.
     Since we do not need a gc-object for the drawable until something is
     really drawn, none is created up to the first draw.
     This method is sent, when the first drawing happens.
     Redefined here to create a bitmap GC (some devices (i.e. windows) require
     different GC's for different canvases."

    gcId := device gcForBitmap:drawableId.
    Lobby registerChange:self.

    "Modified: 19.3.1997 / 11:08:49 / cg"
!

initGC
    "stop server from sending exposure events for Forms -
     (will fill up stream-queue on some stupid (i.e. sco) systems"

    "/ depth-1 forms draw differently ...

    depth == 1 ifTrue:[
	foreground isNil ifTrue:[
	    foreground := paint := Color colorId:1.
	].
	background isNil ifTrue:[
	    background := bgPaint := Color colorId:0
	]
    ].
    super initGC.
    self setGraphicsExposures:false

    "Modified: 17.7.1996 / 13:21:24 / cg"
!

initialize
    foreground := paint := Color colorId:1.
    background := bgPaint := Color colorId:0.

    super initialize.
    depth := 1.

"/    foreground isNil ifTrue:[
"/        foreground := Color colorId:1.
"/    ].
"/    paint isNil ifTrue:[
"/        paint := foreground.
"/    ].
"/    background isNil ifTrue:[
"/        background := Color colorId:0
"/    ].
"/    bgPaint isNil ifTrue:[
"/        bgPaint := background
"/    ]

    "Modified: 17.7.1996 / 13:02:01 / cg"
!

recreate
    "reconstruct the form after a snapin"

    data notNil ifTrue:[
	"
	 create one from data
	"
	(depth == 1 or:[depth == device depth]) ifTrue:[
	    drawableId := device createBitmapFromArray:data width:width height:height.
	    Lobby registerChange:self. 
	    drawableId notNil ifTrue:[
		^ self
	    ]
	].
	'FORM: cannot recreate form' errorPrintCR.
    ].
    fileName notNil ifTrue:[
	"
	 create one from a file (mhmh - this seems X-specific and will vanish)
	"
	self readFromFile:fileName.

"/        drawableId := device createBitmapFromFile:fileName for:self.
"/        Lobby registerChange:self.
	drawableId notNil ifTrue:[
	    ^ self
	].
	'FORM: cannot recreate file form: ' errorPrint. fileName errorPrintCR.
    ].

    ^ self.

    "
     create an empty one
    "
    depth == 1 ifTrue:[
	drawableId := device createBitmapWidth:width height:height
    ] ifFalse:[
	drawableId := device createPixmapWidth:width height:height depth:device depth
    ].
    Lobby registerChange:self

    "Modified: 15.6.1996 / 16:18:12 / cg"
! !

!Form methodsFor:'inspecting'!

inspectorClass
    "redefined to launch an ImageInspector
     (instead of the default InspectorView)."

    ^ ImageInspectorView
! !

!Form methodsFor:'printing & storing'!

storeOn:aStream
    "append an ascii representation of the receiver to aStream,
     from which a copy of the receiver can be reconstructed"

    aStream nextPutAll:'(Form width:'.
    width storeOn:aStream.
    aStream nextPutAll:' height:'.
    height storeOn:aStream.
    aStream nextPutAll:' fromArray:('.
    self bits storeOn:aStream.
    aStream nextPutAll:'))'

    "Modified: 23.4.1996 / 10:21:10 / cg"
! !

!Form methodsFor:'private'!

beImmediateForm
    "read the pixels from the device into a local data array. 
     This makes certain that a fileName form is independent of
     its fileName.
     To make the image smaller (i.e. not keep all those bitmaps),
     this is NOT done by default."

    data isNil ifTrue:[
	data := self bits.
	data notNil ifTrue:[
	    fileName := nil
	]
    ]

    "
     Form allInstances:[:f |
	f beImmediateForm
     ]
    "

    "Created: 7.2.1996 / 16:04:18 / cg"
!

flushDeviceHandles
    "flush device handles (sent after a restart)"

    drawableId := nil.
    gcId := nil.

    "Created: 15.6.1996 / 15:44:28 / cg"
!

getBits
    "if the receiver was not created from a file, or
     an array (i.e. it was drawn), read the pixels from the
     device into a local data array. This has to be done before
     an image is saved, or the receiver is storedBinary, since
     the information present in the device is lost after restart/reload"

    (data isNil and:[fileName isNil]) ifTrue:[
	data := self bits
    ]
!

readFromFile:fn
    "read a monochrome form from a file (in xbm-format).
     The fileName argument, fn should be a relative pathname
     such as bitmaps/foo.xbm and the corresponding file
     will be searched in the standard places (i.e. along the SEARCHPATH).
     Notice, when saving an image, only that fileName is kept with the
     form, and the file is reloaded at image startup time.
     You should therefore make certain, that the file is present at image
     reload time. (this is done to make the image smaller ...)
     If you dont like that behavior (or your application should be able to
     restart fully standAlone), send #beImmediateForm to all instances of
     Form - this will set the data instance variable to a ByteArray containing
     the actual bits and  will therefore no longer depend on the file being present.
     "

    |pathName|

    pathName := self class findBitmapFile:fn.
    pathName notNil ifTrue:[
	drawableId := device createBitmapFromFile:pathName for:self.
	drawableId isNil ifTrue:[^ nil].

"/        fileName := pathName. "/ keep the actual name (wrong)
	fileName := fn.         "/ keep the relative name (better - SEARCHPATH may be different at restart)

	offset := 0@0.
	realized := true.
	BlackAndWhiteColorMap isNil ifTrue:[
	    BlackAndWhiteColorMap := Array with:(Color white) with:(Color black)
	].
	localColorMap := BlackAndWhiteColorMap.
	Lobby registerChange:self.
	^ self
    ].
    ^ nil

    "Modified: 7.2.1996 / 16:04:25 / cg"
!

readFromFile:filename resolution:dpi
    "read a monochrome form from a file, which is assumed to have data for a dpi-resolution;
     if the actual resolution of the device differs, magnify the form.
     Read the comment in #readFromFile: on what happenes if the file is no longer present
     after an image reload."

    |dpiH mag dev|

    (self readFromFile:filename) isNil ifTrue:[^ nil].

    "if the device is within +- 50% of dpi, no magnify is needed"
    dev := device.
    dev isNil ifTrue:[
	"should not happen ..."
	dev := Screen current
    ].
    dpiH := dev horizontalPixelPerInch.
    ((dpi >= (dpiH * 0.75)) and:[dpi <= (dpiH * 1.5)]) ifTrue:[^ self].
    mag := (dpiH / dpi) rounded.
    mag == 0 ifTrue:[
	^ self
    ].
    ^ self magnifiedBy:(mag @ mag)

    "Form fromFile:'SBrowser.icn' resolution:50"

    "Modified: 7.2.1996 / 16:03:45 / cg"
!

release
    "flush device data"

    device := nil.
    drawableId := nil.
    gcId := nil.

    "Modified: 15.6.1996 / 15:43:39 / cg"
    "Created: 16.9.1996 / 21:08:50 / cg"
!

restored
    "flush device data, when restored (sent after a binaryLoad)"

    self release

    "Modified: 16.9.1996 / 21:08:57 / cg"
!

width:w height:h
    "actual create of a monochrome form"

    ((w == 0) or:[h == 0]) ifTrue:[
	self error:'invalid form extent'.
	^ nil
    ].
    width := w.
    height := h.
    depth := 1.
    offset := 0@0.
    drawableId := device createBitmapWidth:w height:h.
    drawableId isNil ifTrue:[^ nil].
    BlackAndWhiteColorMap isNil ifTrue:[
	BlackAndWhiteColorMap := Array with:(Color white) with:(Color black)
    ].
    localColorMap := BlackAndWhiteColorMap.
    realized := true.
    Lobby registerChange:self.
!

width:w height:h depth:d
    "actual create of an arbitrary deep form (but, must be supported by device)"

    width := w.
    height := h.
    offset := 0@0.
    drawableId := device createPixmapWidth:w height:h depth:d.
    drawableId isNil ifTrue:[^ nil].
    realized := true.
    depth := d.
    Lobby registerChange:self.
!

width:w height:h fromArray:anArray
    "actual create of a monochrome form from array.
     This method is somewhat more complicated as it should be due to 
     supporting both byte-wise (ST/X-type) and short-word-wise (ST-80-type)
     Arrays; in the later case, the shorts are first converted to bytes in
     a ByteArray, then passed to the device."

    |bytes bits 
     srcPerRow "{ Class: SmallInteger }"
     dstPerRow "{ Class: SmallInteger }"
     srcStart  "{ Class: SmallInteger }"
     srcIndex  "{ Class: SmallInteger }"
     dstIndex  "{ Class: SmallInteger }"
     hi        "{ Class: SmallInteger }" |

    bytes := anArray.

    anArray size ~~ (((w + 7) // 8) * h) ifTrue:[
	anArray size == (((w + 15) // 16) * h) ifTrue:[
	    "I want the bytes but got shorts (ST-80)"
	    bytes := ByteArray uninitializedNew:(((w + 7) // 8) * h).
	    srcPerRow := (w + 15) // 16.
	    dstPerRow := (w + 7) // 8.
	    srcStart := 1.
	    dstIndex := 1.
	    hi := h.
	    1 to:hi do:[:hi |
		srcIndex := srcStart.
		bits := anArray at:srcIndex.
		1 to:dstPerRow do:[:di |
		    di odd ifTrue:[
			bits := anArray at:srcIndex.
			bytes at:dstIndex put:(bits bitShift:-8)
		    ] ifFalse:[
			bytes at:dstIndex put:(bits bitAnd:16rFF).
			srcIndex := srcIndex + 1
		    ].
		    dstIndex := dstIndex + 1
		].
		srcStart := srcStart + srcPerRow
	    ]
	]
    ].
    width := w.
    height := h.
    depth := 1.
    offset := 0@0.
    drawableId := device createBitmapFromArray:bytes width:w height:h.
    drawableId isNil ifTrue:[^ nil].

    data := bytes.
    BlackAndWhiteColorMap isNil ifTrue:[
	BlackAndWhiteColorMap := Array with:(Color white) with:(Color black)
    ].
    localColorMap := BlackAndWhiteColorMap.
    realized := true.
    Lobby registerChange:self.
!

width:w height:h offset:offs fromArray:anArray
    "actual create of a monochrome form from array"

    self width:w height:h fromArray:anArray.
    offset := offs
! !

!Form methodsFor:'queries '!

bounds
    "return my bounds (added to make forms usable as VisualComponents)"

    ^ Rectangle left:0 top:0 width:width height:height

    "Modified: 13.5.1996 / 10:26:13 / cg"
!

colorFromValue:pixel
    "given a pixelValue, return the corresponding color.
     For compatibility with Images"

    localColorMap notNil ifTrue:[
        ^ localColorMap at:(pixel + 1)
    ].
    depth == 1 ifTrue:[
        pixel == 0 ifTrue:[^ White].
    ].
    ^ Black

    "Created: 28.6.1996 / 16:10:13 / cg"
    "Modified: 13.1.1997 / 23:06:25 / cg"
!

hasBits
    "return true, if the receiver has its pixel data available.
     For forms, which were created from data, this is always true.
     For forms, which were created as off-screen device forms on some
     device, this is always false."

    ^ data notNil

    "Created: 5.7.1996 / 16:21:20 / cg"
!

heightOn:aGC
    "return my height, if displayed on aGC;
     since my height is independent of the device (the number of pixels),
     return the pixel-height"

    ^ height

    "Created: 12.5.1996 / 21:35:33 / cg"
    "Modified: 13.5.1996 / 10:26:09 / cg"
!

isForm
    "return true, if the receiver is some kind of form;
     true is returned here - the method is redefined from Object."

    ^ true
!

isImageOrForm
    "return true, if the receiver is some kind of image or form;
     true is returned here - the method is redefined from Object."

    ^ true
!

widthOn:aGC
    "return my width, if displayed on aGC;
     since my width is independent of the device (the number of pixels),
     return the pixel-width"

    ^ width

    "Created: 12.5.1996 / 21:35:29 / cg"
    "Modified: 13.5.1996 / 10:26:05 / cg"
! !

!Form class methodsFor:'documentation'!

version
    ^ '$Header: /cvs/stx/stx/libview/Form.st,v 1.79 1997-04-04 18:23:52 cg Exp $'
! !
Form initialize!