WindowsIconReader.st
changeset 3920 1bbb4d6fff20
parent 3897 0d38bd3b99ee
child 3931 4291708939e9
--- a/WindowsIconReader.st	Wed Feb 22 18:18:58 2017 +0100
+++ b/WindowsIconReader.st	Wed Feb 22 18:19:29 2017 +0100
@@ -41,7 +41,7 @@
 "
     this class provides methods for loading Windows and OS2 icon and bmp files.
 
-    The class name *IconReader is a bad, historic choice - it ws originally
+    The class name *IconReader is a bad, historic choice - it was originally
     written to read icons only, but evolved over time and is now also
     capable of reading/writing bmp and cursor files.
     
@@ -53,11 +53,20 @@
     The reader tries to figure out which version of BMP/ICO is used.
     It seems to be able to load most formats, but who knows ...
 
+    [notice:]
+        when reading an ICO/CUR file with multiple icons in it,
+        the first image is returned as such, holding on the other images via its
+        imageFrames instvar.
+        Thus, the imageEditor will usually present the first of the images,
+        and offer a next-in-sequence button to step through them.
+        To get a collection of all images, collect the images from the sequence, as in:
+            someIcoImage imageFrames collect:#image
+            
     [See also:]
         Image Form Icon
         BlitImageReader FaceReader GIFReader JPEGReader PBMReader PCXReader
         ST80FormReader SunRasterReader TargaReader TIFFReader
-        XBMReader XPMReader XWDReader
+        XBMReader XPMReader XWDReader MacOSXIconReader
 "
 !
 
@@ -633,7 +642,10 @@
     ] ifFalse:[    
         skip := fileBytesPerRow - imgBytesPerRow.
         1 to:h do:[:row |
-            (aStream nextBytes:imgBytesPerRow into:aByteArray startingAt:dstIdx) == imgBytesPerRow ifFalse:[
+            |nRead|
+            
+            nRead := aStream nextBytes:imgBytesPerRow into:aByteArray startingAt:dstIdx.
+            nRead == imgBytesPerRow ifFalse:[
                 ^ false
             ].
             skip ~~ 0 ifTrue:[ aStream skip:skip ].
@@ -641,6 +653,8 @@
         ].
     ].
     ^ true
+
+    "Modified: / 22-02-2017 / 16:20:50 / cg"
 !
 
 loadBMPWidth:w height:h depth:d from:aStream into:aByteArray
@@ -666,9 +680,8 @@
     d == 1 ifTrue:[
         ^ self loadBMP1From:aStream into:aByteArray
     ].
-    ((d == 16)
-    or:[ (d == 24)
-    or:[ (d == 32) ]]) ifTrue:[
+    
+    ((d == 16) or:[ (d == 24) or:[ (d == 32) ]]) ifTrue:[
         (self loadBMPWidth:w height:h bytesPerPixel:(d // 8) from:aStream into:aByteArray) ifFalse:[
             ^ false
         ].
@@ -677,8 +690,9 @@
     self fileFormatError:('unsupported depth:', d printString).
     ^ false
 
-    "Created: / 17.9.1995 / 18:48:11 / claus"
-    "Modified: / 3.2.1998 / 20:21:16 / cg"
+    "Created: / 17-09-1995 / 18:48:11 / claus"
+    "Modified: / 03-02-1998 / 20:21:16 / cg"
+    "Modified (format): / 22-02-2017 / 17:04:56 / cg"
 !
 
 loadRLECompressedBMP4From:aStream into:aByteArray
@@ -1161,6 +1175,10 @@
             "/ 'WinIconReader [info]: Win3.x ICO format' infoPrintNL.
             ^ self fromWindowsICOStream:aStream alreadyRead:header
         ].
+        (header startsWith:#(0 0 2 0)) ifTrue:[
+            "/ 'WinIconReader [info]: Win3.x CUR format' infoPrintNL.
+            ^ self fromWindowsICOStream:aStream alreadyRead:header
+        ].
     ].
     
     ^ self fileFormatError:('format not supported: %02x %02x' printfWith:(header at:1) with:(header at:2))
@@ -1169,8 +1187,8 @@
      Image fromFile:'/phys/clam//LocalLibrary/Images/OS2_icons/dos.ico'
     "
 
-    "Modified: / 17.9.1995 / 18:59:07 / claus"
-    "Modified: / 3.2.1998 / 20:18:14 / cg"
+    "Modified: / 17-09-1995 / 18:59:07 / claus"
+    "Modified: / 22-02-2017 / 18:12:21 / cg"
 !
 
 fromWindowsBMPFile: aFilename
@@ -1194,7 +1212,7 @@
     ^ self fromWindowsBMPStream:aStream alreadyRead:nil
 !
 
-fromWindowsBMPStream:aStream alreadyRead:bytesAlreadyRead
+fromWindowsBMPStream:aStream alreadyRead:fileHeaerBytesAlreadyRead
     "read an image from a windows BMP stream"
 
     | fileHeader bitmapHeader iSize inPlanes
@@ -1209,12 +1227,18 @@
 
     "read the header"
 
-    fileHeader := ByteArray new:14.
-    bytesAlreadyRead size > 0 ifTrue:[
-        fileHeader replaceFrom:1 with:bytesAlreadyRead
+    fileHeaerBytesAlreadyRead size >= 16 ifTrue:[
+        "/ used when coming from an ICO file.
+        fileHeader := nil.
+    ] ifFalse:[    
+        fileHeader := ByteArray new:14.    
+        fileHeaerBytesAlreadyRead size > 0 ifTrue:[
+            fileHeader replaceFrom:1 with:fileHeaerBytesAlreadyRead
+        ].
+        aStream nextBytes:(14-fileHeaerBytesAlreadyRead size) into:fileHeader startingAt:(1+fileHeaerBytesAlreadyRead size).
+        dataStart := fileHeader unsignedInt32At:(10 + 1) MSB:false.
     ].
-    aStream nextBytes:(14-bytesAlreadyRead size) into:fileHeader startingAt:(1+bytesAlreadyRead size).
-
+    
     iSize := aStream nextUnsignedInt32MSB:false.
     iSize > 124 ifTrue:[
         ^ self fileFormatError:'unknown format'.
@@ -1223,8 +1247,6 @@
     bitmapHeader := ByteArray new:iSize+16. "/ reserve to allow masks to be read with iSize=40 
     aStream nextBytes:(iSize-4) into:bitmapHeader startingAt:(1+4).
 
-    dataStart := fileHeader unsignedInt32At:(10 + 1) MSB:false.
-
     alphaMask := 0.
 
     ((iSize == 40) or:[iSize == 52 or:[iSize == 56 or:[iSize == 108 or:[iSize == 124]]]]) ifTrue:[    "header-size"
@@ -1234,9 +1256,13 @@
         "/ a Windows5.x BMP file (124)
         "/
         "/ 'WinIconReader [info]: Win3.x/Win4.x/Win5.x format' infoPrintCR.
-
-        width := bitmapHeader unsignedInt32At:(4 + 1) MSB:false.
-        height := bitmapHeader signedInt32At:(8 + 1) MSB:false.
+        fileHeader isNil ifTrue:[
+            self assert:(bitmapHeader unsignedInt32At:(4 + 1) MSB:false) == width.
+            self assert:(bitmapHeader signedInt32At:(8 + 1) MSB:false) == (height*2).
+        ] ifFalse:[    
+            width := bitmapHeader unsignedInt32At:(4 + 1) MSB:false.
+            height := bitmapHeader signedInt32At:(8 + 1) MSB:false.
+        ].
         inPlanes := bitmapHeader unsignedInt16At:(12 + 1) MSB:false.
         inDepth := bitmapHeader unsignedInt16At:(14 + 1) MSB:false.
         compression := bitmapHeader unsignedInt32At:(16 + 1) MSB:false.
@@ -1463,7 +1489,7 @@
     ^ self image
 
     "Modified: / 17-09-1995 / 18:48:46 / claus"
-    "Modified: / 30-05-2007 / 16:57:39 / cg"
+    "Modified: / 22-02-2017 / 18:03:10 / cg"
 !
 
 fromWindowsICOFile:aFilename
@@ -1496,6 +1522,51 @@
 fromWindowsICOStream:aStream alreadyRead:bytesAlreadyRead
     "read an image from a windows ICO stream"
 
+    |header nImages images|
+
+    inStream := aStream.
+    aStream binary.
+
+    "read the ICO/CUR header"
+    header := ByteArray uninitializedNew:6.
+    bytesAlreadyRead notEmptyOrNil ifTrue:[
+        header replaceFrom:1 with:bytesAlreadyRead
+    ].
+    aStream nextBytes:((6)-bytesAlreadyRead size) into:header startingAt:(1+bytesAlreadyRead size).
+    (header startsWith:#[0 0 1 0]) ifFalse:[
+        (header startsWith:#[0 0 2 0]) ifFalse:[
+            self fileFormatError:'not an ICO/CUR file'.
+        ].    
+    ]. 
+    
+    nImages := header unsignedInt16At:4+1 MSB:false.
+    images := ImageSequence new.
+    1 to:nImages do:[:each |
+        |img|
+        
+        img := self readSingleImageFromWindowsICOStream:aStream.
+        img imageSequence:images.
+        images add:(ImageFrame new image:img).
+    ].
+    imageSequence := images.
+    ^ images first image.
+    
+    "
+     WindowsIconReader new fromWindowsICOFile:'~/work/exept/expecco/application/expecco.ico'
+    "
+    "
+     |icons|
+     
+     icons := WindowsIconReader new fromWindowsICOFile:'~/work/exept/expecco/application/expecco.ico'
+     MacOSXIconReader save:icons onFile:'~/work/exept/expecco/application/expecco.icns'.
+    "
+
+    "Modified (comment): / 22-02-2017 / 18:14:12 / cg"
+!
+
+old_fromWindowsICOStream:aStream alreadyRead:bytesAlreadyRead
+    "read an image from a windows ICO stream"
+
     |header
      srcIndex dstIndex
      rawData tmp bytesPerRow nColor cmapSize|
@@ -1606,7 +1677,52 @@
      WindowsIconReader new fromWindowsICOFile:'/phys/clam2//LocalLibrary/Images/WIN_icons/ibm.ico'.
     "
 
-    "Modified: / 30-05-2007 / 16:58:11 / cg"
+    "Created: / 22-02-2017 / 15:19:49 / cg"
+!
+
+readSingleImageFromWindowsICOStream:aStream 
+    "read one image from a windows ICO stream"
+
+    |icondirEntryHeader nBytes fileOffset savedPosition nColor nPlanes nBitsPerPixel img|
+
+    inStream := aStream.
+    aStream binary.
+
+    "read the icondirEntryHeader"
+    icondirEntryHeader := aStream nextBytes:16.
+
+    width := icondirEntryHeader at:0+1.
+    width == 0 ifTrue:[ width := 256].
+    height := icondirEntryHeader at:1+1.
+    height == 0 ifTrue:[ height := 256].
+    nColor := icondirEntryHeader at:2+1. "/ 0 if not a palette image
+    "/ reserved := icondirEntryHeader at:3+1.
+    nPlanes := icondirEntryHeader unsignedInt16At:4+1 MSB:false.
+    nBitsPerPixel := icondirEntryHeader unsignedInt16At:6+1 MSB:false.
+    nBytes := icondirEntryHeader unsignedInt32At:(8+1) MSB:false.
+    fileOffset := icondirEntryHeader unsignedInt32At:(12+1) MSB:false.
+
+    savedPosition := aStream position.
+    aStream position:fileOffset.
+    (aStream nextBytes:(PNGReader pngHeader size)) = PNGReader pngHeader ifTrue:[
+        "/ PNG incl bitmapFileHeader
+        aStream position:fileOffset.
+        img := PNGReader fromStream:aStream.
+    ] ifFalse:[
+        "/ BMP without bitmapFileHeader
+        aStream position:fileOffset.
+        img := self fromWindowsBMPStream:aStream alreadyRead:icondirEntryHeader.
+    ].
+    aStream position:savedPosition.
+    
+    ^ img
+
+    "
+     WindowsIconReader new fromWindowsICOFile:'/phys/clam2//LocalLibrary/Images/WIN_icons/ibm.ico'.
+    "
+
+    "Created: / 22-02-2017 / 15:22:09 / cg"
+    "Modified (comment): / 22-02-2017 / 17:42:48 / cg"
 ! !
 
 !WindowsIconReader methodsFor:'writing'!