--- a/PNGReader.st Fri Feb 17 10:51:06 2017 +0100
+++ b/PNGReader.st Fri Feb 17 11:57:52 2017 +0100
@@ -16,8 +16,10 @@
ImageReader subclass:#PNGReader
instanceVariableNames:'colorType bitsPerChannel depth compressionMethod filterMethod
interlaceMode bytesPerScanline globalDataChunk thisScanline
- prevScanline processTextChunks specialChunkHandlers'
- classVariableNames:'Verbose'
+ prevScanline processTextChunks specialChunkHandlers
+ paletteAlphaEntries paletteIndexForMaskedPixels image'
+ classVariableNames:'Verbose ColorTypeGray ColorTypeRGB ColorTypePalette
+ ColorTypeGrayAlpha ColorTypeRGBAlpha'
poolDictionaries:''
category:'Graphics-Images-Readers'
!
@@ -93,6 +95,14 @@
MIMETypes defineImageType:'image/png' suffix:'png' reader:self.
"/ backward compatibility from times when png was not yet so common...
MIMETypes defineImageType:'image/x-png' suffix:nil reader:self.
+
+ ColorTypeGray := 0.
+ ColorTypeRGB := 2.
+ ColorTypePalette := 3.
+ ColorTypeGrayAlpha := 4.
+ ColorTypeRGBAlpha := 6.
+
+ "Modified: / 16-02-2017 / 19:41:40 / cg"
! !
!PNGReader class methodsFor:'queries'!
@@ -138,6 +148,21 @@
"Modified: 21.6.1996 / 20:38:46 / cg"
! !
+!PNGReader methodsFor:'accessing'!
+
+makeImage
+ image isNil ifTrue:[
+ image := super makeImage.
+ ].
+ ^ image
+
+ "Created: / 17-02-2017 / 11:14:45 / cg"
+!
+
+pngHeader
+ ^ #[137 80 78 71 13 10 26 10]
+! !
+
!PNGReader methodsFor:'hooks'!
specialChunkHandlerAt:chunkType put:aHandlerBlock
@@ -153,7 +178,309 @@
"Created: / 14-02-2017 / 11:43:33 / cg"
! !
-!PNGReader methodsFor:'private-chunks'!
+!PNGReader methodsFor:'reading'!
+
+fromStream:aStream
+ "read a stream containing a PNG image.
+ Leave the image description in instance variables
+ (i.e. to get the image, ask with image)."
+
+ |header|
+
+ inStream := aStream.
+ aStream binary.
+
+ "PNG-files are always msb (network-world)"
+ byteOrder := #msb.
+
+ header := ByteArray new:8.
+ aStream nextBytes:8 into:header.
+
+ header = (self pngHeader) ifFalse:[
+ Logger warning:'PNGReader: not a png file (%1)' with:aStream.
+ Image badImageFormatQuerySignal raiseRequestErrorString:'PNGReader: not a png file'.
+ ^ nil
+ ].
+
+ (self getIHDRChunk) ifFalse:[
+ Logger warning:'PNGReader: required IHDR chunk missing (%1)' with:aStream.
+ Image badImageFormatQuerySignal raiseRequestErrorString:'PNGReader: required IHDR chunk missing'.
+ ^ nil
+ ].
+
+ compressionMethod ~~ 0 ifTrue:[
+ ('PNGReader: compressionMethod %s not supported.' printfWith:compressionMethod printString) infoPrintCR.
+ Image badImageFormatQuerySignal raiseRequestErrorString:'PNGReader: unsupported compression method'.
+ ^ nil
+ ].
+ filterMethod > 0 ifTrue:[
+ 'PNGReader: invalid filterMethod' infoPrintCR.
+ Image badImageFormatQuerySignal raiseRequestErrorString:'PNGReader: invalid/unsupported filterMethod'.
+ ^ nil.
+ ].
+ interlaceMode > 1 ifTrue:[
+ Image badImageFormatQuerySignal raiseRequestErrorString:'PNGReader: invalid/unsupported interlaceMode'.
+ 'PNGReader: invalid interlaceMode' infoPrintCR.
+ ^ nil.
+ ].
+ (self setColorType:colorType) ifFalse:[
+ ^ nil
+ ].
+
+ [self getChunk] whileTrue.
+
+ bytesPerScanline := self bytesPerRow.
+ data := ByteArray new:(bytesPerScanline * height).
+
+ globalDataChunk notNil ifTrue:[
+ self processGlobalIDATChunk.
+ globalDataChunk := nil.
+ ].
+
+ photometric == #palette ifTrue:[
+ colorMap isNil ifTrue:[
+ Image badImageFormatQuerySignal raiseRequestErrorString:'PNGReader: palette chunk missing'.
+ 'PNGReader: palette chunk missing.' infoPrintCR.
+ "/ ^ nil
+ ].
+ ].
+
+ paletteAlphaEntries notNil ifTrue:[
+ "/ this vector now provides alpha values.
+ "/ for now, we can only deal with 0/255 alpha values...
+ (paletteAlphaEntries conform:[:a | (a == 0) or:[a == 255]]) ifFalse:[
+ "/ must generate real alpha info (eg. rgba)
+ ] ifTrue:[
+ self generateMaskFromPaletteAlphaEntries
+ ].
+ ].
+
+ "
+ PNGReader fromFile:'/home/cg/libpng-0.89c/pngtest.png'
+ "
+
+ "Modified: / 17-02-2017 / 11:34:01 / cg"
+! !
+
+!PNGReader methodsFor:'reading-private'!
+
+generateMaskFromPaletteAlphaEntries
+ image isNil ifTrue:[
+ self makeImage
+ ].
+
+ colorType == ColorTypePalette ifTrue:[
+ "/ if there is only one entry to care for...
+ (paletteAlphaEntries occurrencesOf:0) == 1 ifTrue:[
+ (paletteAlphaEntries conform:[:alpha | (alpha == 0) or:[alpha == 255]]) ifTrue:[
+ mask := self class buildMaskFromColor:(paletteAlphaEntries indexOf:0)-1 for:data width:width height:height.
+ ^ self
+ ].
+ ].
+
+ "/ multiple masked pixels...
+ paletteAlphaEntries keysAndValuesDo:[:pixelIndex :alpha |
+ alpha == 0 ifTrue:[
+ image createMaskForPixelValue:pixelIndex
+ ] ifFalse:[
+ alpha == 255 ifTrue:[
+ "/ ok; already
+ ] ifFalse:[
+ self error:'should generate an alpha image'
+ ]
+ ].
+ ].
+ ^ self.
+ ].
+
+ colorType == ColorTypeGray ifTrue:[
+ |maskPixel|
+
+ maskPixel := paletteAlphaEntries unsignedInt16At:1 MSB:true.
+ image createMaskForPixelValue:maskPixel.
+ ^ self
+ ].
+ colorType == ColorTypeRGB ifTrue:[
+ |mr mg mb maskPixel|
+
+ mr := paletteAlphaEntries unsignedInt16At:1 MSB:true.
+ mg := paletteAlphaEntries unsignedInt16At:3 MSB:true.
+ mb := paletteAlphaEntries unsignedInt16At:5 MSB:true.
+ depth <= 32 ifTrue:[
+ "/ only use the low bits...
+ mr > 255 ifTrue:[ self error:'only 8bit RGB supported'].
+ mg > 255 ifTrue:[ self error:'only 8bit RGB supported'].
+ mb > 255 ifTrue:[ self error:'only 8bit RGB supported'].
+ maskPixel := ((mr bitShift:16) bitOr:(mg bitShift:8)) bitOr:mb.
+ ] ifFalse:[
+ maskPixel := ((mr bitShift:32) bitOr:(mg bitShift:16)) bitOr:mb.
+ ].
+ image createMaskForPixelValue:maskPixel.
+ ^ self
+ ].
+
+ "Created: / 17-02-2017 / 08:07:04 / cg"
+ "Modified: / 17-02-2017 / 11:55:57 / cg"
+!
+
+getChunk
+ |len type crc|
+
+ inStream atEnd ifTrue:[^ false].
+
+ len := inStream nextInt32MSB:true.
+ type := String new:4.
+ (inStream nextBytes:4 into:type) ~~ 4 ifTrue:[^ false].
+
+ Verbose == true ifTrue:[
+ 'len: ' infoPrint. len infoPrint. ' type: ' infoPrint. type infoPrintCR.
+ ].
+
+ type = 'IEND' ifTrue:[^ false].
+
+ (self processChunk:type len:len) ifFalse:[^ false].
+
+ crc := inStream nextInt32MSB:true. "/ ignored - for now
+ ^ true
+
+ "Created: 21.6.1996 / 21:09:36 / cg"
+ "Modified: 21.6.1996 / 21:20:26 / cg"
+!
+
+getIHDRChunk
+ |len type crc|
+
+ len := inStream nextInt32MSB:true.
+
+ type := String new:4.
+ inStream nextBytes:4 into:type.
+
+ type = 'IHDR' ifFalse:[
+ Logger warning:'PNGReader: expected IHDR magic (%1)' with:inStream.
+ ^ false
+ ].
+ len == 13 ifFalse:[
+ Logger warning:'PNGReader: bad IHDR size (%1)' with:inStream.
+ ^ false
+ ].
+
+ width := inStream nextInt32MSB:true.
+ height := inStream nextInt32MSB:true.
+
+ (width <= 0 or:[height <= 0]) ifTrue:[
+ Logger warning:'PNGReader: invalid dimension(s) (%1)' with:inStream.
+ ^ false.
+ ].
+ self reportDimension.
+
+ bitsPerChannel := inStream nextByte.
+ depth := bitsPerChannel. "/ will be changed by setColorType.
+ colorType := inStream nextByte.
+
+ compressionMethod := inStream nextByte.
+ filterMethod := inStream nextByte.
+ interlaceMode := inStream nextByte.
+
+ Verbose == true ifTrue:[
+ 'PNGReader: IHDR:' infoPrintCR.
+ 'PNGReader: width: ' infoPrint. width infoPrintCR.
+ 'PNGReader: height: ' infoPrint. height infoPrintCR.
+ 'PNGReader: bitsPerChannel: ' infoPrint. bitsPerChannel infoPrintCR.
+ 'PNGReader: colorType: ' infoPrint. colorType infoPrintCR.
+ 'PNGReader: compressionMethod: ' infoPrint. compressionMethod infoPrintCR.
+ 'PNGReader: filterMethod: ' infoPrint. filterMethod infoPrintCR.
+ 'PNGReader: interlaceMode: ' infoPrint. interlaceMode infoPrintCR.
+ ].
+
+ crc := inStream nextInt32MSB:true.
+ ^ true
+
+ "Modified: / 17-02-2017 / 11:35:20 / cg"
+!
+
+handleText:text keyword:keyword
+ "/ things like exif data etc.
+ "/ self halt.
+
+ "/Standard keywords for text chunks:
+ "/
+ "/ Title Short (one line) title or caption for image
+ "/ Author Name of image's creator
+ "/ Description Description of image (possibly long)
+ "/ Copyright Copyright notice
+ "/ Creation Time Time of original image creation
+ "/ Software Software used to create the image
+ "/ Disclaimer Legal disclaimer
+ "/ Warning Warning of nature of content
+ "/ Source Device used to create the image
+ "/ Comment Miscellaneous comment; conversion from
+ "/ GIF comment
+!
+
+setColorType:colorType
+ colorType == 0 ifTrue:[
+ photometric := #blackIs0.
+ samplesPerPixel := 1.
+ bitsPerSample := Array with:bitsPerChannel.
+ depth := bitsPerChannel.
+ ^ true.
+ ].
+
+ colorType == 2 ifTrue:[
+ bitsPerChannel < 8 ifTrue:[
+ 'PNGReader: invalid colorType/depth combination' infoPrintCR.
+ ^ false.
+ ].
+ photometric := #rgb.
+ samplesPerPixel := 3.
+ bitsPerSample := Array with:bitsPerChannel with:bitsPerChannel with:bitsPerChannel.
+ depth := bitsPerChannel * 3.
+ ^ true.
+ ].
+
+ colorType == 3 ifTrue:[
+ bitsPerChannel == 16 ifTrue:[
+ 'PNGReader: invalid colorType/depth combination' infoPrintCR.
+ ^ false.
+ ].
+ photometric := #palette.
+ samplesPerPixel := 1.
+ bitsPerSample := Array with:bitsPerChannel.
+ depth := bitsPerChannel.
+ ^ true.
+ ].
+
+ colorType == 4 ifTrue:[
+ bitsPerChannel < 8 ifTrue:[
+ 'PNGReader: invalid colorType/depth combination' infoPrintCR.
+ ^ false.
+ ].
+ photometric := #blackIs0.
+ samplesPerPixel := 2.
+ bitsPerSample := Array with:bitsPerChannel with:bitsPerChannel.
+ depth := bitsPerChannel * 2.
+ ^ true.
+ ].
+
+ colorType == 6 ifTrue:[
+ bitsPerChannel < 8 ifTrue:[
+ 'PNGReader: invalid colorType/depth combination' infoPrintCR.
+ ^ false.
+ ].
+ photometric := #rgba.
+ samplesPerPixel := 4.
+ bitsPerSample := Array with:bitsPerChannel with:bitsPerChannel with:bitsPerChannel with:bitsPerChannel.
+ depth := bitsPerChannel * 4.
+ ^ true.
+ ].
+
+ ('PNGReader: invalid colorType: ' , colorType printString , '.') infoPrintCR.
+ ^ false
+
+ "Modified: / 16-02-2017 / 17:19:19 / cg"
+! !
+
+!PNGReader methodsFor:'reading-private chunks'!
doPass: pass
"Certain interlace passes are skipped with certain small image dimensions"
@@ -237,6 +564,8 @@
type = 'iTXt' ifTrue:[^ self processITXTChunkLen:len].
type = 'iCCP' ifTrue:[^ self processICCPChunkLen:len].
+ type = 'hIST' ifTrue:[^ self processHISTChunkLen:len].
+ type = 'spAL' ifTrue:[^ self processSPALChunkLen:len].
specialChunkHandlers notNil ifTrue:[
|handler chunkData|
@@ -257,7 +586,7 @@
^ true.
"Created: / 21-06-1996 / 21:10:37 / cg"
- "Modified: / 14-02-2017 / 22:37:29 / cg"
+ "Modified: / 17-02-2017 / 11:32:06 / cg"
!
processGAMAChunkLen:len
@@ -274,6 +603,16 @@
^ self processInterlacedGlobalDATA
!
+processHISTChunkLen:len
+ "/ ignored
+ "/ inStream nextBytes:len.
+
+ inStream skip:len.
+ ^ true
+
+ "Created: / 17-02-2017 / 11:31:36 / cg"
+!
+
processICCPChunkLen:len
"/ ignored
"/ inStream nextBytes:len.
@@ -336,9 +675,10 @@
] ifFalse:[
compressionFlag == 1 ifTrue:[
"/ for now, only zlib compression is supported...
- 'zlib compression currently unsupported' infoPrintCR.
+ "/ ... no - not even that is
+ Logger warning:'PNG: zlib compressed text currently unsupported'.
] ifFalse:[
- 'unsupported compression method' infoPrintCR.
+ Logger warning:'PNG: unsupported text compression method'.
].
].
text := text utf8Decoded.
@@ -348,7 +688,8 @@
].
^ true
- "Created: 21.6.1996 / 21:15:58 / cg"
+ "Created: / 21-06-1996 / 21:15:58 / cg"
+ "Modified: / 16-02-2017 / 19:51:21 / cg"
!
processInterlacedDATA:len
@@ -549,6 +890,16 @@
"Created: 21.6.1996 / 21:13:09 / cg"
!
+processSPALChunkLen:len
+ "/ ignored
+ "/ inStream nextBytes:len.
+
+ inStream skip:len.
+ ^ true
+
+ "Created: / 17-02-2017 / 11:31:40 / cg"
+!
+
processTEXTChunkLen:len
"/ textual data in iso8859 coding.
@@ -583,8 +934,10 @@
!
processTRNSChunkLen:len
- inStream skip:len.
+ paletteAlphaEntries := inStream nextBytes:len.
^ true
+
+ "Modified: / 17-02-2017 / 09:39:25 / cg"
!
processZTXTChunkLen:len
@@ -597,7 +950,7 @@
"Created: 21.6.1996 / 21:15:58 / cg"
! !
-!PNGReader methodsFor:'private-filtering'!
+!PNGReader methodsFor:'reading-private filtering'!
filterAverage:count
"Use the average of the pixel to the left and the pixel above as a predictor"
@@ -801,7 +1154,7 @@
^ al
! !
-!PNGReader methodsFor:'private-pixel copy'!
+!PNGReader methodsFor:'reading-private pixel copy'!
copyPixels:y at:startX by:incX
"Handle interlaced pixels of supported colorTypes"
@@ -810,17 +1163,18 @@
"/ per color type copy methods
s := #(
- #copyPixelsGray:at:by: "/ 0
+ #copyPixelsGray:at:by: "/ 0 = ColorTypeGray
nil
- #copyPixelsRGB:at:by: "/ 2
- #copyPixelsIndexed:at:by: "/ 3
- #copyPixelsGrayAlpha:at:by: "/ 4
+ #copyPixelsRGB:at:by: "/ 2 = ColorTypeRGB
+ #copyPixelsIndexed:at:by: "/ 3 = ColorTypePalette
+ #copyPixelsGrayAlpha:at:by: "/ 4 = ColorTypeGrayAlpha
nil
- #copyPixelsRGBA:at:by: "/ 6
+ #copyPixelsRGBA:at:by: "/ 6 = ColorTypeRGBA
) at:colorType + 1.
self perform:s with:y with:startX with:incX
"Modified: / 03-05-2011 / 12:02:45 / cg"
+ "Modified (comment): / 16-02-2017 / 19:42:47 / cg"
!
copyPixelsGray:y at:startX by:incX
@@ -995,237 +1349,7 @@
].
! !
-!PNGReader methodsFor:'private-reading'!
-
-getChunk
- |len type crc|
-
- inStream atEnd ifTrue:[^ false].
-
- len := inStream nextInt32MSB:true.
- type := String new:4.
- (inStream nextBytes:4 into:type) ~~ 4 ifTrue:[^ false].
-
- Verbose == true ifTrue:[
- 'len: ' infoPrint. len infoPrint. ' type: ' infoPrint. type infoPrintCR.
- ].
-
- type = 'IEND' ifTrue:[^ false].
-
- (self processChunk:type len:len) ifFalse:[^ false].
-
- crc := inStream nextInt32MSB:true. "/ ignored - for now
- ^ true
-
- "Created: 21.6.1996 / 21:09:36 / cg"
- "Modified: 21.6.1996 / 21:20:26 / cg"
-!
-
-getIHDRChunk
- |len type crc|
-
- len := inStream nextInt32MSB:true.
-
- type := String new:4.
- inStream nextBytes:4 into:type.
-
- type = 'IHDR' ifFalse:[self halt:'expected IHDR magic'. ^ false].
- len == 13 ifFalse:[self halt:'unexpected IHDR length'. ^ false].
-
- width := inStream nextInt32MSB:true.
- height := inStream nextInt32MSB:true.
-
- (width <= 0 or:[height <= 0]) ifTrue:[
- 'PNGReader: invalid dimension(s)' infoPrintCR.
- ^ false.
- ].
- self reportDimension.
-
- bitsPerChannel := inStream nextByte.
- depth := bitsPerChannel. "/ will be changed by setColorType.
- colorType := inStream nextByte.
-
- compressionMethod := inStream nextByte.
- filterMethod := inStream nextByte.
- interlaceMode := inStream nextByte.
-
- Verbose == true ifTrue:[
- 'PNGReader: IHDR:' infoPrintCR.
- 'PNGReader: width: ' infoPrint. width infoPrintCR.
- 'PNGReader: height: ' infoPrint. height infoPrintCR.
- 'PNGReader: bitsPerChannel: ' infoPrint. bitsPerChannel infoPrintCR.
- 'PNGReader: colorType: ' infoPrint. colorType infoPrintCR.
- 'PNGReader: compressionMethod: ' infoPrint. compressionMethod infoPrintCR.
- 'PNGReader: filterMethod: ' infoPrint. filterMethod infoPrintCR.
- 'PNGReader: interlaceMode: ' infoPrint. interlaceMode infoPrintCR.
- ].
-
- crc := inStream nextInt32MSB:true.
- ^ true
-
- "Modified: 21.6.1996 / 21:38:35 / cg"
-!
-
-handleText:text keyword:keyword
- "/ things like exif data etc.
- "/ self halt.
-
- "/Standard keywords for text chunks:
- "/
- "/ Title Short (one line) title or caption for image
- "/ Author Name of image's creator
- "/ Description Description of image (possibly long)
- "/ Copyright Copyright notice
- "/ Creation Time Time of original image creation
- "/ Software Software used to create the image
- "/ Disclaimer Legal disclaimer
- "/ Warning Warning of nature of content
- "/ Source Device used to create the image
- "/ Comment Miscellaneous comment; conversion from
- "/ GIF comment
-!
-
-setColorType:colorType
- colorType == 0 ifTrue:[
- photometric := #blackIs0.
- samplesPerPixel := 1.
- bitsPerSample := Array with:bitsPerChannel.
- depth := bitsPerChannel.
- ^ true.
- ].
-
- colorType == 2 ifTrue:[
- bitsPerChannel < 8 ifTrue:[
- 'PNGReader: invalid colorType/depth combination' infoPrintCR.
- ^ false.
- ].
- photometric := #rgb.
- samplesPerPixel := 3.
- bitsPerSample := Array with:bitsPerChannel with:bitsPerChannel with:bitsPerChannel.
- depth := bitsPerChannel * 3.
- ^ true.
- ].
-
- colorType == 3 ifTrue:[
- bitsPerChannel == 16 ifTrue:[
- 'PNGReader: invalid colorType/depth combination' infoPrintCR.
- ^ false.
- ].
- photometric := #palette.
- samplesPerPixel := 1.
- bitsPerSample := Array with:bitsPerChannel.
- depth := bitsPerChannel.
- ^ true.
- ].
-
- colorType == 4 ifTrue:[
- bitsPerChannel < 8 ifTrue:[
- 'PNGReader: invalid colorType/depth combination' infoPrintCR.
- ^ false.
- ].
- photometric := #blackIs0.
- samplesPerPixel := 2.
- bitsPerSample := Array with:bitsPerChannel with:bitsPerChannel.
- depth := bitsPerChannel * 2.
- ^ true.
- ].
-
- colorType == 6 ifTrue:[
- bitsPerChannel < 8 ifTrue:[
- 'PNGReader: invalid colorType/depth combination' infoPrintCR.
- ^ false.
- ].
- photometric := #rgba.
- samplesPerPixel := 4.
- bitsPerSample := Array with:bitsPerChannel with:bitsPerChannel with:bitsPerChannel with:bitsPerChannel.
- depth := bitsPerChannel * 4.
- ^ true.
- ].
-
- ('PNGReader: invalid colorType: ' , colorType printString , '.') infoPrintCR.
- ^ false
-
- "Modified: / 16-02-2017 / 17:19:19 / cg"
-! !
-
-!PNGReader methodsFor:'reading'!
-
-fromStream:aStream
- "read a stream containing a PNG image.
- Leave image description in instance variables."
-
- |header|
-
- inStream := aStream.
- aStream binary.
-
- "PNG-files are always msb (network-world)"
- byteOrder := #msb.
-
- header := ByteArray new:8.
- aStream nextBytes:8 into:header.
-
- header ~= (self pngHeader) ifTrue:[
- 'PNGReader: not a png file.' infoPrintCR.
- Image badImageFormatQuerySignal raiseRequestErrorString:'PNGReader: not a png file'.
- ^ nil
- ].
-
- (self getIHDRChunk) ifFalse:[
- 'PNGReader: required IHDR chunk missing.' infoPrintCR.
- Image badImageFormatQuerySignal raiseRequestErrorString:'PNGReader: required IHDR chunk missing'.
- ^ nil
- ].
-
- compressionMethod ~~ 0 ifTrue:[
- ('PNGReader: compressionMethod %s not supported.' printfWith:compressionMethod printString) infoPrintCR.
- Image badImageFormatQuerySignal raiseRequestErrorString:'PNGReader: unsupported compression method'.
- ^ nil
- ].
- filterMethod > 0 ifTrue:[
- 'PNGReader: invalid filterMethod' infoPrintCR.
- Image badImageFormatQuerySignal raiseRequestErrorString:'PNGReader: invalid/unsupported filterMethod'.
- ^ nil.
- ].
- interlaceMode > 1 ifTrue:[
- Image badImageFormatQuerySignal raiseRequestErrorString:'PNGReader: invalid/unsupported interlaceMode'.
- 'PNGReader: invalid interlaceMode' infoPrintCR.
- ^ nil.
- ].
- (self setColorType:colorType) ifFalse:[
- ^ nil
- ].
-
- [self getChunk] whileTrue.
-
- bytesPerScanline := self bytesPerRow.
- data := ByteArray new:(bytesPerScanline * height).
-
- globalDataChunk notNil ifTrue:[
- self processGlobalIDATChunk.
- globalDataChunk := nil.
- ].
-
- photometric == #palette ifTrue:[
- colorMap isNil ifTrue:[
- Image badImageFormatQuerySignal raiseRequestErrorString:'PNGReader: palette chunk missing'.
- 'PNGReader: palette chunk missing.' infoPrintCR.
- "/ ^ nil
- ].
- ].
-
- "
- PNGReader fromFile:'/home/cg/libpng-0.89c/pngtest.png'
- "
-
- "Modified: / 03-05-2011 / 12:17:34 / cg"
-! !
-
-!PNGReader methodsFor:'writing to file'!
-
-pngHeader
- ^ #[137 80 78 71 13 10 26 10]
-!
+!PNGReader methodsFor:'writing'!
save:image onStream:aStream
"save image in PNG-file-format onto aStream"
@@ -1243,15 +1367,71 @@
data := image bits.
mask := image mask.
depth := self bitsPerPixel.
+
+ outStream nextPutAll:(self pngHeader).
- outStream nextPutAll:(self pngHeader).
+ image mask notNil ifTrue:[
+ (photometric ~~ #rgba) ifTrue: [
+ image maskedPixelsAre0 ifTrue:[
+ paletteIndexForMaskedPixels := 0
+ ] ifFalse:[
+ self determinePaletteIndexForMaskedPixels
+ ]
+ ].
+ ].
+
self writeIHDRChunk.
photometric == #palette ifTrue: [self writePaletteChunk].
+ paletteIndexForMaskedPixels notNil ifTrue: [self writeTRNSChunk].
self writeImageDataChunk.
self writeEndChunk
- "Modified: / 16-02-2017 / 16:35:51 / cg"
-!
+ "Modified: / 16-02-2017 / 21:18:05 / cg"
+! !
+
+!PNGReader methodsFor:'writing-private'!
+
+determinePaletteIndexForMaskedPixels
+ "/ if all masked pixels are 0, and 0 is not used elsewhere in the image,
+ "/ write it as such.
+ "/ Otherwise, find an unallocated palette index, and assign masked pixels to it.
+
+ |usedPixels freePixels pixelIdx|
+
+ colorMap size < 256 ifTrue:[
+ paletteIndexForMaskedPixels := colorMap size.
+ ] ifFalse:[
+ usedPixels := data usedValues.
+ usedPixels size == 256 ifTrue:[
+ self error:'cannot represent image (no palette slot for mask)'
+ ].
+ freePixels := (0 to:255) asSet removeAllFoundIn:usedPixels.
+ paletteIndexForMaskedPixels := freePixels first.
+ ].
+
+ "/ rewrite data: wherever masked, change pixel to paletteIndexForMaskedPixels
+
+ data := data copy.
+ pixelIdx := 1.
+ 0 to:height-1 do:[:y |
+ 0 to:width-1 do:[:x |
+ (mask pixelAtX:x y:y) == 0 ifTrue:[
+ data at:pixelIdx put:paletteIndexForMaskedPixels.
+ ].
+ pixelIdx := pixelIdx + 1
+ ]
+ ].
+
+ "
+ PNGReader save:(ToolbarIconLibrary systemBrowserIcon) onFile:'/tmp/icon.png'
+ ImageEditor openOnFile:'/tmp/icon.png'
+ "
+
+ "Created: / 16-02-2017 / 19:59:39 / cg"
+ "Modified: / 17-02-2017 / 09:25:57 / cg"
+! !
+
+!PNGReader methodsFor:'writing-private chunks'!
writeChunk:chunkTypeChars size:len with:aBlock
|crc realOutStream chunkBytes|
@@ -1279,25 +1459,25 @@
with:[ ]
!
-writeFileHeader
- outStream nextPutAll:(self pngHeader)
-!
-
writeIHDRChunk
|hasMask|
hasMask := 0.
((photometric == #whiteIs0) or:[ (photometric == #blackIs0)]) ifTrue:[
- colorType := 0.
+ colorType := ColorTypeGray.
hasMask := (samplesPerPixel > 1)
].
- ((photometric == #rgb) or:[(photometric == #rgba)]) ifTrue:[
- colorType := 2.
+ (photometric == #rgb) ifTrue:[
+ colorType := ColorTypeRGB.
+ hasMask := (samplesPerPixel > 3)
+ ].
+ (photometric == #rgba) ifTrue:[
+ colorType := ColorTypeRGBAlpha.
hasMask := (samplesPerPixel > 3)
].
(photometric == #palette) ifTrue:[
- colorType := 3.
+ colorType := ColorTypePalette.
hasMask := (samplesPerPixel > 1)
].
colorType isNil ifTrue:[
@@ -1305,12 +1485,13 @@
].
mask notNil ifTrue:[
- depth == 24 ifFalse:[self error:'non-depth24 images with mask are (currently) not supported'].
hasMask := true.
].
-
hasMask ifTrue:[
- colorType := colorType + 4. "/ +alpha
+ "/ make it grayAlpha / rgba
+ ((colorType == ColorTypeGray) or:[colorType == ColorTypeGray]) ifTrue:[
+ colorType := colorType bitOr:4. "/ +alpha
+ ].
].
self
@@ -1326,7 +1507,7 @@
outStream nextPut: 0 "Non-interlaced"
]
- "Modified: / 16-02-2017 / 16:35:59 / cg"
+ "Modified (format): / 16-02-2017 / 21:24:22 / cg"
!
writeImageDataChunk
@@ -1339,7 +1520,7 @@
zlibWriter := ZipStream writeOpenAsZipStreamOn:compressedByteStream suppressHeaderAndChecksum:false.
zlibWriter binary.
- mask notNil ifTrue:[
+ (mask notNil and:[photometric ~~ #palette]) ifTrue:[
"/ on-the-fly expand mask into the alpha channel.
"/ for now - only support depth24 + mask
@@ -1390,7 +1571,7 @@
outStream nextPutAll:compressedBytes
]
- "Modified: / 16-02-2017 / 16:36:50 / cg"
+ "Modified: / 16-02-2017 / 21:13:26 / cg"
!
writePaletteChunk
@@ -1405,6 +1586,17 @@
nextPut: color blueByte
]
]
+!
+
+writeTRNSChunk
+ self
+ writeChunk: 'tRNS'
+ size: 1
+ with:[
+ outStream nextPut: paletteIndexForMaskedPixels
+ ]
+
+ "Created: / 16-02-2017 / 19:58:06 / cg"
! !
!PNGReader class methodsFor:'documentation'!