"
COPYRIGHT (c) 1994 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.
"
ImageReader subclass:#XPMReader
instanceVariableNames:''
classVariableNames:''
poolDictionaries:''
category:'Graphics-Images-Support'
!
!XPMReader class methodsFor:'documentation'!
copyright
"
COPYRIGHT (c) 1994 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
"
this class provides methods for loading x-pixmap-file (xpm) images.
These images are used (in X) for palette images (see ctwm or hp-vue for a lot
of them).
The code here is a hack - it may not work for all images
(it works for the testfiles I got here).
Limitations:
only reads the full-color specification, ignoring monochrome
and greyscale info.
Can only handle single-character index.
Only understands single-word color names (i.e. names with spaces
are not supported)
Image writing is only supported for images with less than about 50
colors (single byte encoding). If present, the mask must be a
single bit mask (i.e. no alpha channel).
Due to the algorithm, writing may be slow for big images
Suggestions: adapt & use the XPM library here.
[See also:]
Image Form Icon
BlitImageReader FaceReader GIFReader JPEGReader PBMReader PCXReader
ST80FormReader SunRasterReader TargaReader TIFFReader WindowsIconReader
XBMReader XWDReader
"
! !
!XPMReader class methodsFor:'initialization'!
initialize
"tell Image-class, that a new fileReader is present
for the '.xpm' and '.pm' extensions."
Image addReader:self suffix:'xpm'.
Image addReader:self suffix:'pm'.
"Modified: 1.2.1997 / 15:10:29 / cg"
! !
!XPMReader class methodsFor:'testing'!
canRepresent:anImage
"return true, if anImage can be represented in my file format.
Currently only images with less than 80 colors are supported."
anImage depth <= 6 ifTrue:[^ true].
anImage photometric == #palette ifTrue:[
anImage colorMap size < 80 ifTrue:[^ true].
].
^ anImage usedColors size < 80
"Modified: 27.2.1997 / 12:40:22 / cg"
!
isValidImageFile:aFileName
"return true, if aFileName contains an x-bitmap-file image"
|line inStream |
inStream := self streamReadingFile:aFileName.
inStream isNil ifTrue:[^ false].
line := inStream nextLine.
inStream close.
line isNil ifTrue:[^ false].
(line startsWith:'/* XPM') ifFalse:[^ false].
^ true
"
XPMReader isValidImageFile:'bitmaps/magtape.xpm'
XPMReader isValidImageFile:'bitmaps/ljet.xpm'
XPMReader isValidImageFile:'bitmaps/garfield.gif'
"
! !
!XPMReader methodsFor:'reading from file'!
colorNameFrom:aStream
"read either a color-name or value specified in X-notation
(#rrggbb where rr, gg and bb are 2-digit hex numbers)"
|s|
aStream peek == $# ifTrue:[
aStream next.
s := '#'.
] ifFalse:[
s := ''.
].
[aStream peek isLetterOrDigit] whileTrue:[
s := s copyWith:aStream next
].
^ s
!
fromStream:aStream
"read an XPM-image from aStream. Return the receiver
(with all relevant instance variables set for the image)
or nil on error"
|line
srcIndex "{ Class: SmallInteger }"
dstIndex "{ Class: SmallInteger }"
colorName monoName greyName symbolicName colorMapSize redMap greenMap blueMap
charsPerPixel xlation s bitsPerPixel char lineDone maskPixelValue
state|
inStream := aStream.
line := aStream nextLine.
(line notNil and:[line startsWith:'/* XPM']) ifFalse:[
'XPM: format error (expected XPM)' errorPrintNL.
^ nil
].
line := aStream nextLine.
[line notNil and:[(line startsWith:'/*') or:[line isBlank]]] whileTrue:[
line := aStream nextLine.
].
(line notNil and:[line startsWith:'static char']) ifFalse:[
'XPM: format error (expected static char)' errorPrintNL.
^ nil
].
line := aStream nextLine.
(line notNil and:[line startsWith:'/*']) ifTrue:[
[line notNil
and:[(line startsWith:'/*') or:[line startsWith:' *']]] whileTrue:[
line := aStream nextLine.
].
].
(line notNil and:[line startsWith:'"']) ifFalse:[
'XPM: format error (expected "ww hh nn mm)' errorPrintNL.
^ nil
].
s := ReadStream on:line.
s next. "skip quote"
width := Integer readFrom:s.
height := Integer readFrom:s.
colorMapSize := Integer readFrom:s.
charsPerPixel := Integer readFrom:s.
charsPerPixel ~~ 1 ifTrue:[
'XPM: can only handle single-character xpm-files' errorPrintNL.
^ nil
].
xlation := Array new:256.
redMap := ByteArray new:colorMapSize.
greenMap := ByteArray new:colorMapSize.
blueMap := ByteArray new:colorMapSize.
colorMap := Colormap redVector:redMap greenVector:greenMap blueVector:blueMap.
1 to:colorMapSize do:[:colorIndex |
|index line color t word|
line := aStream nextLine.
[line notNil and:[line startsWith:'/*']] whileTrue:[
line := aStream nextLine.
].
(line notNil and:[line startsWith:'"']) ifFalse:[
'XPM: format error (expected color spec)' errorPrintNL.
^ nil
].
s := ReadStream on:line.
s next. "skip quote"
index := s next asciiValue.
xlation at:index put:colorIndex - 1.
lineDone := false.
state := nil.
[lineDone] whileFalse:[
s skipSeparators.
s peek == $# ifTrue:[
word := self colorNameFrom:s
] ifFalse:[
word := s nextAlphaNumericWord.
].
word isNil ifTrue:[
lineDone := true
] ifFalse:[
word = 's' ifTrue:[
"/ symbolic name ...
state := $s. symbolicName := ''.
] ifFalse:[
word = 'm' ifTrue:[
"/ monochrome data
state := $m. monoName := ''.
] ifFalse:[
word = 'g' ifTrue:[
"/ grey data
state := $g. greyName := ''.
] ifFalse:[
word = 'c' ifTrue:[
"/ color data
state := $c. colorName := ''.
] ifFalse:[
"/ append to name
state isNil ifTrue:[
'XPM: format error got: ' errorPrint.
word errorPrint.
' (expected ''c'',''m'',''g'' or ''s'')' errorPrintNL.
^ nil
].
state == $m ifTrue:[
monoName := monoName , ' ' , word.
].
state == $g ifTrue:[
greyName := greyName , ' ' , word.
].
state == $c ifTrue:[
colorName := colorName , ' ' , word.
].
state == $s ifTrue:[
symbolicName := symbolicName , ' ' , word.
].
(word startsWith:'#') ifTrue:[
state := nil.
]
]
]
]
]
].
].
colorName notNil ifTrue:[
colorName := colorName withoutSeparators
].
monoName notNil ifTrue:[
monoName := monoName withoutSeparators
].
greyName notNil ifTrue:[
greyName := greyName withoutSeparators
].
symbolicName notNil ifTrue:[
symbolicName := symbolicName withoutSeparators
].
"/
"/ for now - ignore everything, except
"/ colorName (if there is one)
"/
colorName isNil ifTrue:[
colorName := greyName.
colorName isNil ifTrue:[
colorName := monoName.
]
].
((colorName = 'none') or:[colorName = 'None']) ifTrue:[
color := Color noColor. "/ white
redMap at:colorIndex put:0.
greenMap at:colorIndex put:0.
blueMap at:colorIndex put:0.
maskPixelValue := colorIndex-1.
] ifFalse:[
color := Color name:colorName.
redMap at:colorIndex put:(color red asFloat * 255.0 // 100).
greenMap at:colorIndex put:(color green asFloat * 255.0 // 100).
blueMap at:colorIndex put:(color blue asFloat * 255.0 // 100).
].
].
"actually, could make it an image with less depth most of the time ..."
"
bitsPerPixel := ((colorMapSize - 1) log:2) truncated + 1.
"
bitsPerPixel := 8.
data := ByteArray new:(width * height).
dstIndex := 1.
1 to:height do:[:row |
line := aStream nextLine withoutSpaces.
[line notNil and:[line startsWith:'/*']] whileTrue:[
line := aStream nextLine withoutSpaces.
].
(line notNil and:[line startsWith:'"']) ifFalse:[
'XPM: format error (expected pixels)' errorPrintNL.
^ nil
].
srcIndex := 2.
1 to: width do:[:col |
|char|
char := line at:srcIndex.
data at:dstIndex put:(xlation at:char asciiValue).
srcIndex := srcIndex + 1.
dstIndex := dstIndex + 1
]
].
photometric := #palette.
samplesPerPixel := 1.
bitsPerSample := Array with:bitsPerPixel.
maskPixelValue notNil ifTrue:[
self buildMaskFromColor:maskPixelValue
].
"
XPMReader fromStream:('bitmaps/ljet.xpm' asFilename readStream)
XPMReader fromStream:('bitmaps/magtape.xpm' asFilename readStream)
XPMReader fromStream:('bitmaps/pixmap.xpm' asFilename readStream)
XPMReader fromStream:('bitmaps/SBrowser.xbm' asFilename readStream)
"
"Created: 24.9.1995 / 06:20:06 / claus"
"Modified: 24.9.1995 / 07:07:33 / claus"
"Modified: 5.7.1996 / 17:27:59 / stefan"
"Modified: 24.1.1997 / 14:45:02 / cg"
! !
!XPMReader methodsFor:'writing to a file'!
colorNameOf:aColor
#(white black red green blue
yellow magenta cyan orange) do:[:aStandardColorName |
aColor = (Color name:aStandardColorName) ifTrue:[
^ aStandardColorName.
]
].
^ '#'
, (aColor redByte hexPrintString:2)
, (aColor greenByte hexPrintString:2)
, (aColor blueByte hexPrintString:2)
"Created: 27.2.1997 / 11:48:40 / cg"
!
save:image onFile:aFileName
"save image as XPM file on aFileName.
Caveat: currently, only a maximum of roughly 50 colors is handled
(i.e. very simple images)"
|usedColors nColorsUsed nColors nChars baseName map maskColorIndex
isMasked imageMask|
usedColors := image usedColors.
nColors := nColorsUsed := usedColors size.
(imageMask := image mask) notNil ifTrue:[
nColors := nColors + 1.
maskColorIndex := nColors.
].
nColors > 256 ifTrue:[
^ Image cannotRepresentImageSignal
raiseWith:image
errorString:('XPMReader cannot represent this image (too many colors)').
].
map := ($a to: $z) asOrderedCollection.
map addAll:($A to: $Z) asOrderedCollection.
map addAll:($0 to: $9) asOrderedCollection.
map addAll:#($. $, $` $^ $* $: $; $< $> $? $% $# $& $( $) $- $+ $=) asOrderedCollection.
nChars := 1.
nColors > map size ifTrue:[
^ Image cannotRepresentImageSignal
raiseWith:image
errorString:('XPMReader cannot represent this image (too many colors)').
].
outStream := FileStream newFileNamed:aFileName.
outStream isNil ifTrue:[
^ Image fileCreationErrorSignal
raiseWith:image
errorString:('file creation error: ' , aFileName asString).
].
baseName := aFileName asFilename baseName asFilename withoutSuffix asString.
outStream nextPutLine:'/* XPM */'.
outStream nextPutLine:'static char *' , baseName , '_xpm[] = {'.
outStream nextPutLine:'/* width height ncolors chars_per_pixel */'.
outStream nextPutLine:'"' , image width printString , ' '
, image height printString , ' '
, nColors printString , ' '
, nChars printString , '",'.
outStream nextPutLine:'/* colors */'.
1 to:nColorsUsed do:[:idx |
|clr|
clr := usedColors at:idx.
outStream nextPutLine:'"' , (map at:idx) asString ,
' ' , 'c ' , (self colorNameOf:clr) ,
'",'.
].
maskColorIndex notNil ifTrue:[
outStream nextPutLine:'"' , (map at:maskColorIndex) asString ,
' c none",'.
].
outStream nextPutLine:'/* pixels */'.
maskColorIndex isNil ifTrue:[
isMasked := false
].
0 to:image height-1 do:[:y |
outStream nextPutAll:'"'.
0 to:image width-1 do:[:x |
|clr idx|
maskColorIndex notNil ifTrue:[
isMasked := (imageMask valueAtX:x y:y) == 0
].
isMasked ifTrue:[
outStream nextPut:(map at:maskColorIndex)
] ifFalse:[
clr := image atX:x y:y.
idx := usedColors indexOf:clr.
outStream nextPut:(map at:idx)
]
].
outStream nextPutLine:'",'.
].
outStream nextPutLine:'};'.
outStream close.
"Modified: 1.3.1997 / 21:34:15 / cg"
! !
!XPMReader class methodsFor:'documentation'!
version
^ '$Header: /cvs/stx/stx/libview2/XPMReader.st,v 1.25 1997-03-01 20:34:32 cg Exp $'
! !
XPMReader initialize!