'From VisualWorks(R), Release 2.5.1 of September 26, 1995 on November 24, 1996 at 8:07:21 pm'! ImageReader subclass: #BMPImageReader instanceVariableNames: 'win3 fileSize imageByteOffset headerSize bottomToTop planes compression compressedImageByteSize hRes vRes numColors numImportantColors dataPosition rowEnd nextRow inHighNibble partialByte ' classVariableNames: 'BitmapHeaderOffset BI_BITFIELDS BI_RGB BI_RLE4 BI_RLE8 BMPDepth24Palette ' poolDictionaries: '' category: 'Graphics-Images'! BMPImageReader comment: 'Class BMPReader represents a reader for Windows 3.x and OS/2 BMP-format image files. Restriction: doesn''t understand extended Windows BMP formats such as embedded JPEG. Instance Variables: win3 whether the image is in Windows format, rather than OS/2 fileSize size of the image file in bytes imageByteOffset offset of the image data headerSize number of bytes in the BITMAPINFOHEADER bottomToTop whether image data is stored in bottom-to-top scanline order planes number of planes in image data compression code for the type of image compression compressedImageBytes number of bytes in the compressed image data hRes horizontal resolution, in pixels per meter vRes vertical resolution, in pixels per meter numColors number of colors used in image; zero means (2 raisedTo: bitsPerPixel) numImportantColors number of colors regarded as important; zero means that all colors are important dataPosition current offset into imageData rowEnd absolute position of the end of the current row in imageData nextRow absolute position of the beginning of the next row in imageData inHighNibble whether the current position is the high (vs. the low) nibble (4-bit pixel) at the current byte position (dataPosition) partialByte partial byte built up during nibble reading'! !BMPImageReader methodsFor: 'initialize-release'! readImage self readFileHeader. self readBitmapHeader. self readColormap. self initializeImage. self readImageData! ! !BMPImageReader methodsFor: 'attributes'! attributeKeysAndValuesDo: aBlock super attributeKeysAndValuesDo: aBlock. aBlock value: #compression value: self compression. aBlock value: #planes value: planes. aBlock value: #numberOfColors value: numColors. aBlock value: #numberOfImportantColors value: numImportantColors! compression compression = BI_RGB ifTrue: [^'BMP_RGB']. compression = BI_RLE4 ifTrue: [^'BMP_RLE4']. compression = BI_RLE8 ifTrue: [^'BMP_RLE8']. compression = BI_BITFIELDS ifTrue: [^'BMP_BITFIELDS']. ^'unknown'! format ^'Windows DIB (BMP)'! resolution ^(hRes * 0.0254) @ (vRes * 0.0254)! ! !BMPImageReader methodsFor: 'private-image data'! deltaPixelPositionByX: xOffset y: yOffset "Move the data position by the specified unsigned pixel offsets. This method assumes that bitsPerPixel is either four or eight." | verticalBytes horizontalBytes | "vertical offset" verticalBytes := yOffset * bytesPerRow. bottomToTop ifTrue: [dataPosition := dataPosition - verticalBytes. rowEnd := rowEnd - verticalBytes. nextRow := nextRow - verticalBytes] ifFalse: [dataPosition := dataPosition + verticalBytes. rowEnd := rowEnd + verticalBytes. nextRow := nextRow + verticalBytes]. "horizontal offset" "assume that the horizontal offset shouldn't move past the end of the row" bitsPerPixel = 8 ifTrue: [horizontalBytes := xOffset] ifFalse: [horizontalBytes := xOffset // 2. (xOffset odd and: [inHighNibble := inHighNibble not]) ifTrue: [horizontalBytes := horizontalBytes + 1]]. dataPosition := dataPosition + horizontalBytes. partialByte := 0 "assume that we always move to new (zero) pixels"! depth4CopyNext: pixelCount "Copy the next pixelCount pixels from the input stream to the image data. Only valid for four-bit images." inHighNibble ifTrue: [pixelCount // 2 timesRepeat: [imageData at: dataPosition put: ioStream next. dataPosition := dataPosition + 1]. pixelCount odd ifTrue: [partialByte := ioStream bitAnd: 16rF0. imageData at: dataPosition put: partialByte. inHighNibble := false]] ifFalse: [pixelCount // 2 timesRepeat: [| byte | byte := ioStream next. imageData at: dataPosition put: (partialByte + (byte bitShift: -4)). dataPosition := dataPosition + 1. partialByte := (byte bitAnd: 16r0F) bitShift: 4]. pixelCount odd ifTrue: [partialByte := partialByte + (ioStream next bitShift: 4). inHighNibble := true]. imageData at: dataPosition put: partialByte. inHighNibble ifTrue: [dataPosition := dataPosition + 1. partialByte := 0]]! depth4StoreNext: pixelCount with: twoNibbles "Store the next pixelCount pixels, alternating between the high and low nibbles of twoNibbles. Only valid for four-bit images." | nibbles count | nibbles := twoNibbles. count := pixelCount. inHighNibble ifFalse: [imageData at: dataPosition put: partialByte + (nibbles bitShift: -4). nibbles := (nibbles bitShift: -4) + ((nibbles bitAnd: 16r0F) bitShift: 4). count := count - 1. inHighNibble := true. partialByte := 0. dataPosition := dataPosition + 1]. count // 2 timesRepeat: [imageData at: dataPosition put: nibbles. dataPosition := dataPosition + 1]. count odd ifTrue: [partialByte := nibbles bitAnd: 16rF0. imageData at: dataPosition put: partialByte. inHighNibble := false]! depth8CopyNext: pixelCount "Copy the next pixelCount pixels from the input stream to the image data. Only valid for eight-bit images." pixelCount timesRepeat: [imageData at: dataPosition put: ioStream next. dataPosition := dataPosition + 1]! depth8StoreNext: pixelCount with: pixel "Store the next pixelCount pixels. Only valid for eight-bit images." pixelCount timesRepeat: [imageData at: dataPosition put: pixel. dataPosition := dataPosition + 1]! resetDataPosition "Reset the data position to the beginning of the bitmap." bottomToTop ifTrue: [dataPosition := height - 1 * bytesPerRow + 1. nextRow := dataPosition - bytesPerRow] ifFalse: [dataPosition := 1. nextRow := dataPosition + bytesPerRow]. rowEnd := dataPosition + rowEndOffset. inHighNibble := true. partialByte := 0! setDataPositionToNextRow "Set the data position to the beginning of the next row." dataPosition := nextRow. rowEnd := dataPosition + rowEndOffset. inHighNibble := true. partialByte := 0. nextRow := bottomToTop ifTrue: [nextRow - bytesPerRow] ifFalse: [nextRow + bytesPerRow]! ! !BMPImageReader methodsFor: 'private'! readBitmapHeader "Read the bitmap header." | w h bitCount | ioStream position: BitmapHeaderOffset. headerSize := self nextLSBUnsignedLong. win3 := true. win3 := headerSize >= 40. (win3 not and: [headerSize ~= 12 "OS/2 1.2"]) ifTrue: [^self formatError: 'Unknown type of BMP format.']. win3 ifTrue: [w := self nextLSBLong. h := self nextLSBLong] ifFalse: [w := self nextLSBShort. h := self nextLSBShort]. w := w abs. bottomToTop := true. h < 0 ifTrue: [bottomToTop := false. h := h negated]. planes := self nextLSBUnsignedShort. bitCount := self nextLSBUnsignedShort. self width: w height: h bitsPerPixel: bitCount. win3 ifTrue: [compression := self nextLSBUnsignedLong. compressedImageByteSize := self nextLSBUnsignedLong. hRes := self nextLSBLong. vRes := self nextLSBLong. numColors := self nextLSBUnsignedLong. numImportantColors := self nextLSBUnsignedLong] ifFalse: [compression := BI_RGB. compressedImageByteSize := hRes := vRes := numColors := numImportantColors := 0]. "check if we understand the compression" (compression = BI_RGB or: [compression = BI_RLE4 or: [compression = BI_RLE8 or: [compression = BI_BITFIELDS]]]) ifFalse: [^self formatError: 'Unknown compression type (', compression printString, ').']! readColormap "Read the colormap." bitsPerPixel = 24 ifTrue: [palette := BMPDepth24Palette. ^self]. ioStream position: BitmapHeaderOffset + headerSize. compression = BI_BITFIELDS ifTrue: [self readFixedPalette] ifFalse: [self readMappedPalette]! readFileHeader "Read the file header." ioStream reset. (ioStream next = $B asInteger and: [ioStream next = $M asInteger]) ifFalse: [^self formatError: 'Not in BMP format.']. fileSize := self nextLSBUnsignedLong. ioStream position: 10. imageByteOffset := self nextLSBUnsignedLong! readFixedPalette "Read a fixed palette, stored in three consecutive DWORDs." | redBitMask greenBitMask blueBitMask redShift greenShift blueShift | redBitMask := self nextLSBUnsignedLong. greenBitMask := self nextLSBUnsignedLong. blueBitMask := self nextLSBUnsignedLong. (redBitMask = 0 or: [greenBitMask = 0 or: [blueBitMask = 0]]) ifTrue: [^self formatError: 'Invalid BMP bitfield mask.']. redShift := redBitMask lowBit -1. greenShift := greenBitMask lowBit - 1. blueShift := blueBitMask lowBit - 1. palette := FixedPalette redShift: redShift redMask: (redBitMask bitShift: redShift negated) greenShift: greenShift greenMask: (greenBitMask bitShift: greenShift negated) blueShift: blueShift blueMask: (blueBitMask bitShift: blueShift negated)! readImageData "Read the image data." ioStream position: imageByteOffset. self resetDataPosition. Stream endOfStreamSignal handle: [:ex | ex restartDo: [self formatError: 'Image data ended prematurely.']] do: [compression = BI_RLE8 ifTrue: [^self readRLE8ImageData]. compression = BI_RLE4 ifTrue: [^self readRLE4ImageData]. (compression = BI_RGB or: [compression = BI_BITFIELDS]) ifTrue: [^self readUncompressedImageData]]. self formatError: 'Unknown compression type (', compression printString, ').'! readMappedPalette "Read a mapped palette." | paletteSize colors | paletteSize := 1 bitShift: bitsPerPixel. (win3 and: [numColors > 0]) ifTrue: [paletteSize := numColors]. colors := Array new: paletteSize. 1 to: paletteSize do: [:index | | red green blue | blue := ioStream next. green := ioStream next. red := ioStream next. win3 ifTrue: [ioStream skip: 1]. colors at: index put: (ColorValue red: red / 256.0 green: green / 256.0 blue: blue / 256.0)]. palette := MappedPalette withColors: colors! readRLE4ImageData "Read the image data." [| byte1 byte2 | byte1 := ioStream next. byte2 := ioStream next. byte1 > 0 ifTrue: [self depth4StoreNext: byte1 with: byte2] ifFalse: [byte2 = 1 "end of bitmap" ifTrue: [^self]. byte2 = 0 "end of line" ifTrue: [self setDataPositionToNextRow] ifFalse: [byte2 = 2 "delta" ifTrue: [self deltaPixelPositionByX: ioStream next y: ioStream next] ifFalse: ["else: byte2 > 2" self depth4CopyNext: byte2. ioStream skip: (4 - byte2 \\ 4 // 2)]]]] repeat! readRLE8ImageData "Read the image data." [| byte1 byte2 | byte1 := ioStream next. byte2 := ioStream next. byte1 > 0 ifTrue: [self depth8StoreNext: byte1 with: byte2] ifFalse: [byte2 = 1 "end of bitmap" ifTrue: [^self]. byte2 = 0 "end of line" ifTrue: [self setDataPositionToNextRow] ifFalse: [byte2 = 2 "delta" ifTrue: [self deltaPixelPositionByX: ioStream next y: ioStream next] ifFalse: ["else: byte2 > 2" self depth8CopyNext: byte2. byte2 odd ifTrue: [ioStream skip: 1]]]]] repeat! readUncompressedDepth16ImageData "Read the image data." height timesRepeat: [0 to: bytesPerRow - 1 by: 2 do: [:i | | byte1 byte2 | byte1 := ioStream next. byte2 := ioStream next. imageData at: dataPosition + i put: byte2; at: dataPosition + i + 1 put: byte1]. self setDataPositionToNextRow]! readUncompressedDepth24ImageData "Read the image data." height timesRepeat: [0 to: bytesPerRow - 1 by: 3 do: [:i | | byte1 byte2 byte3 | byte1 := ioStream next. byte2 := ioStream next. byte3 := ioStream next. imageData at: dataPosition + i put: byte3; at: dataPosition + i + 1 put: byte2; at: dataPosition + i + 2 put: byte1]. self setDataPositionToNextRow]! readUncompressedDepth32ImageData "Read the image data." height timesRepeat: [0 to: bytesPerRow - 1 by: 4 do: [:i | | byte1 byte2 byte3 byte4 | byte1 := ioStream next. byte2 := ioStream next. byte3 := ioStream next. byte4 := ioStream next. imageData at: dataPosition + i put: byte4; at: dataPosition + i + 1 put: byte3; at: dataPosition + i + 2 put: byte2; at: dataPosition + i + 3 put: byte1]. self setDataPositionToNextRow]! readUncompressedDepthLE8ImageData "Read the image data." height timesRepeat: [0 to: bytesPerRow - 1 do: [:i | imageData at: dataPosition + i put: ioStream next]. self setDataPositionToNextRow]! readUncompressedImageData "Read the image data." (bitsPerPixel = 1 or: [bitsPerPixel = 4 or: [bitsPerPixel = 8]]) ifTrue: [^self readUncompressedDepthLE8ImageData]. bitsPerPixel = 16 ifTrue: [^self readUncompressedDepth16ImageData]. bitsPerPixel = 24 ifTrue: [^self readUncompressedDepth24ImageData]. bitsPerPixel = 32 ifTrue: [^self readUncompressedDepth32ImageData]. self formatError: 'Invalid number of bits per pixel.'! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! BMPImageReader class instanceVariableNames: ''! !BMPImageReader class methodsFor: 'class initialization'! initialize "BMPImageReader initialize." BI_RGB := 0. BI_RLE8 := 1. BI_RLE4 := 2. BI_BITFIELDS := 3. BitmapHeaderOffset := 14. BMPDepth24Palette := FixedPalette redShift: 16 redMask: 16rFF greenShift: 8 greenMask: 16rFF blueShift: 0 blueMask: 16rFF! ! !BMPImageReader class methodsFor: 'private'! canRead: aFilenameOrString ('*.bmp' match: aFilenameOrString asString) ifTrue: [^true]. ^Object errorSignal handle: [:ex | ex returnWith: false] do: [| inputStream | inputStream := aFilenameOrString asFilename readStream. [inputStream next = $B and: [inputStream next = $M]] valueNowOrOnUnwindDo: [inputStream close]]! ! BMPImageReader initialize!