<div dir="ltr">Laura,<div><br></div><div>    great to see this.  Thank you for your energy!! and welcome.</div></div><div class="gmail_extra"><br><div class="gmail_quote">On Wed, Jun 15, 2016 at 10:01 AM,  <span dir="ltr">&lt;<a href="mailto:commits@source.squeak.org" target="_blank">commits@source.squeak.org</a>&gt;</span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Laura Perez Cerrato uploaded a new version of Graphics to project The Trunk:<br>
<a href="http://source.squeak.org/trunk/Graphics-lpc.350.mcz" rel="noreferrer" target="_blank">http://source.squeak.org/trunk/Graphics-lpc.350.mcz</a><br>
<br>
==================== Summary ====================<br>
<br>
Name: Graphics-lpc.350<br>
Author: lpc<br>
Time: 15 June 2016, 2:00:37.274582 pm<br>
UUID: 5d02a1bd-65f6-469c-b8b5-456e1dfef5eb<br>
Ancestors: Graphics-mt.349<br>
<br>
Support for both the current version and the new version of JPEGReadWriter2Plugin<br>
<br>
=============== Diff against Graphics-mt.349 ===============<br>
<br>
Item was changed:<br>
  Form subclass: #ColorForm<br>
        instanceVariableNames: &#39;colors cachedDepth cachedColormap&#39;<br>
        classVariableNames: &#39;&#39;<br>
        poolDictionaries: &#39;&#39;<br>
        category: &#39;Graphics-Display Objects&#39;!<br>
+ ColorForm class<br>
+       instanceVariableNames: &#39;grayScalePalette&#39;!<br>
<br>
  !ColorForm commentStamp: &#39;&lt;historical&gt;&#39; prior: 0!<br>
  ColorForm is a normal Form plus a color map of up to 2^depth Colors. Typically, one reserves one entry in the color map for transparent. This allows 1, 3, 15, or 255 non-transparent colors in ColorForms of depths 1, 2, 4, and 8 bits per pixel. ColorForms don&#39;t support depths greater than 8 bits because that would require excessively large color maps with little real benefit, since 16-bit and 32-bit depths already support thousands and millions of colors.<br>
<br>
  ColorForms have several uses:<br>
    1) Precise colors. You can have up to 256 true colors, instead being limited to the 8-bit color palette.<br>
    2) Easy transparency. Just store (Color transparent) at the desired position in the color map.<br>
    3) Cheap color remapping by changing the color map.<br>
<br>
  A color map is an Array of up to 2^depth Color objects. A Bitmap colorMap is automatically computed and cached for rapid display. Note that if you change the color map, you must resubmit it via the colors: method to flush this cache.<br>
<br>
  ColorForms can be a bit tricky. Note that:<br>
    a) When you BitBlt from one ColorForm to another, you must remember to copy the color map of the source ColorForm to the destination ColorForm.<br>
    b) A ColorForm&#39;s color map is an array of depth-independent Color objects. BitBlt requires a BitMap of actual pixel values, adjusted to the destination depth. These are different things!! ColorForms automatically maintain a cache of the BitBlt-style color map corresponding to the colors array for the last depth on which the ColorForm was displayed, so there should be little need for clients to work with BitBlt-style color maps.<br>
    c) The default map for 8 bit depth has black in the first entry, not transparent.  Say (cform colors at: 1 put: Color transparent).<br>
  !<br>
+ ColorForm class<br>
+       instanceVariableNames: &#39;grayScalePalette&#39;!<br>
<br>
Item was added:<br>
+ ----- Method: ColorForm class&gt;&gt;grayScalePalette (in category &#39;constants&#39;) -----<br>
+ grayScalePalette<br>
+       grayScalePalette ifNil: [<br>
+               grayScalePalette := (0 to: 255) collect: [:brightness | Color gray: brightness asFloat / 255.0].<br>
+               grayScalePalette at: 1 put: Color transparent].<br>
+       ^ grayScalePalette!<br>
<br>
Item was added:<br>
+ ----- Method: ColorForm&gt;&gt;isGrayScale (in category &#39;testing&#39;) -----<br>
+ isGrayScale<br>
+       ^ self colors = ColorForm grayScalePalette.!<br>
<br>
Item was changed:<br>
  ----- Method: Form&gt;&gt;asGrayScale (in category &#39;converting&#39;) -----<br>
  asGrayScale<br>
        &quot;Assume the receiver is a grayscale image. Return a grayscale ColorForm computed by extracting the brightness levels of one color component. This technique allows a 32-bit Form to be converted to an 8-bit ColorForm to save space while retaining a full 255 levels of gray. (The usual colormapping technique quantizes to 8, 16, or 32 levels, which loses information.)&quot;<br>
+       | f32 srcForm result map bb |<br>
-       | f32 srcForm result map bb grays |<br>
        self depth = 32 ifFalse: [<br>
                f32 := Form extent: width@height depth: 32.<br>
                self displayOn: f32.<br>
                ^ f32 asGrayScale].<br>
        self unhibernate.<br>
        srcForm := Form extent: (width * 4)@height depth: 8.<br>
        srcForm bits: bits.<br>
        result := ColorForm extent: width@height depth: 8.<br>
        map := Bitmap new: 256.<br>
        2 to: 256 do: [:i | map at: i put: i - 1].<br>
        map at: 1 put: 1.  &quot;map zero pixel values to near-black&quot;<br>
        bb := (BitBlt toForm: result)<br>
                sourceForm: srcForm;<br>
                combinationRule: Form over;<br>
                colorMap: map.<br>
        0 to: width - 1 do: [:dstX |<br>
                bb  sourceRect: (((dstX * 4) + 2)@0 extent: 1@height);<br>
                        destOrigin: dstX@0;<br>
                        copyBits].<br>
<br>
        &quot;final BitBlt to zero-out pixels that were truely transparent in the original&quot;<br>
        map := Bitmap new: 512.<br>
        map at: 1 put: 16rFF.<br>
        (BitBlt toForm: result)<br>
                sourceForm: self;<br>
                sourceRect: self boundingBox;<br>
                destOrigin: 0@0;<br>
                combinationRule: Form erase;<br>
                colorMap: map;<br>
                copyBits.<br>
<br>
+<br>
+       result colors: ColorForm grayScalePalette.<br>
-       grays := (0 to: 255) collect: [:brightness | Color gray: brightness asFloat / 255.0].<br>
-       grays at: 1 put: Color transparent.<br>
-       result colors: grays.<br>
        ^ result<br>
  !<br>
<br>
Item was added:<br>
+ ----- Method: Form&gt;&gt;isGrayScale (in category &#39;testing&#39;) -----<br>
+ isGrayScale<br>
+       ^ false!<br>
<br>
Item was changed:<br>
  ----- Method: JPEGReadWriter2&gt;&gt;compress:quality: (in category &#39;public access&#39;) -----<br>
  compress: aForm quality: quality<br>
+       &quot;Encode the given Form and answer the compressed ByteArray. Quality goes from 0 (low) to 100 (high), where -1 means default.<br>
+       We can only compress:<br>
+               * 32-bit deep Forms<br>
+               * -32-bit deep Forms<br>
+               * 16-bit deep Forms<br>
+               * -16-bit deep Forms<br>
+               * GrayScale ColorForms (see #isGrayScale)&quot;<br>
-       &quot;Encode the given Form and answer the compressed ByteArray. Quality goes from 0 (low) to 100 (high), where -1 means default.&quot;<br>
-<br>
        | sourceForm jpegCompressStruct jpegErrorMgr2Struct buffer byteCount |<br>
+<br>
        aForm unhibernate.<br>
+<br>
+       sourceForm := self supports8BitGrayscaleJPEGs<br>
+               ifTrue: [<br>
+                       (aForm depth = 32) | (aForm depth = 16) | (aForm isGrayScale)<br>
+                               ifTrue: [aForm]<br>
+                               ifFalse: [aForm asFormOfDepth: 32 ]]<br>
+               ifFalse: [<br>
+                       (aForm nativeDepth &gt; 0) &amp; ((aForm depth = 32) | ((aForm depth = 16) &amp; (aForm width even)))<br>
+                               ifTrue: [aForm]<br>
+                               ifFalse: [aForm asFormOfDepth: 32 ]].<br>
+<br>
-       &quot;odd width images of depth 16 give problems; avoid them.&quot;<br>
-       sourceForm := (aForm depth = 32) | (aForm width even &amp; (aForm depth = 16))<br>
-               ifTrue: [aForm]<br>
-               ifFalse: [aForm asFormOfDepth: 32].<br>
        jpegCompressStruct := ByteArray new: self primJPEGCompressStructSize.<br>
        jpegErrorMgr2Struct := ByteArray new: self primJPEGErrorMgr2StructSize.<br>
        buffer := ByteArray new: sourceForm width * sourceForm height + 1024.<br>
        byteCount := self primJPEGWriteImage: jpegCompressStruct<br>
                onByteArray: buffer<br>
                form: sourceForm<br>
                quality: quality<br>
                progressiveJPEG: false<br>
                errorMgr: jpegErrorMgr2Struct.<br>
        byteCount = 0 ifTrue: [self error: &#39;buffer too small for compressed data&#39;].<br>
        ^ buffer copyFrom: 1 to: byteCount<br>
  !<br>
<br>
Item was changed:<br>
  ----- Method: JPEGReadWriter2&gt;&gt;nextImageSuggestedDepth: (in category &#39;public access&#39;) -----<br>
+ nextImageSuggestedDepth: suggestedDepth<br>
+       &quot;Decode and answer a Form of the given depth from my stream. Close the stream if it is a file stream.<br>
+       We can read RGB JPEGs into:<br>
+               * 32-bit Forms<br>
+               * -32-bit Forms<br>
+               * 16-bit Forms (with or without dithering!!)<br>
+               * -16-bit Forms (with or without dithering!!)<br>
+       We can read grayscale JPEGs into:<br>
+               * 32-bit Forms<br>
+               * -32-bit Forms<br>
+               * 16-bit Forms (with or without dithering!!)<br>
+               * -16-bit Forms (with or without dithering!!)<br>
+               * 8-bit grayScale ColorForms (see #isGrayScale)<br>
+               * -8-bit grayScale ColorForms (see #isGrayScale)&quot;<br>
- nextImageSuggestedDepth: depth<br>
-       &quot;Decode and answer a Form of the given depth from my stream. Close the stream if it is a file stream. Possible depths are 16-bit and 32-bit.&quot;<br>
<br>
+       | bytes width height components form jpegDecompressStruct jpegErrorMgr2Struct |<br>
-       | bytes width height form jpegDecompressStruct jpegErrorMgr2Struct depthToUse |<br>
        bytes := stream upToEnd.<br>
        stream close.<br>
        jpegDecompressStruct := ByteArray new: self primJPEGDecompressStructSize.<br>
        jpegErrorMgr2Struct := ByteArray new: self primJPEGErrorMgr2StructSize.<br>
        self<br>
                primJPEGReadHeader: jpegDecompressStruct<br>
                fromByteArray: bytes<br>
                errorMgr: jpegErrorMgr2Struct.<br>
        width := self primImageWidth: jpegDecompressStruct.<br>
        height := self primImageHeight: jpegDecompressStruct.<br>
+       components := self primImageNumComponents: jpegDecompressStruct.<br>
+       form :=<br>
+               self supports8BitGrayscaleJPEGs<br>
+                       ifTrue: [<br>
+                               components = 3<br>
+                                       ifTrue: [ Form extent: width@height depth: suggestedDepth ]<br>
+                                       ifFalse: [ (Form extent: width@height depth: suggestedDepth) asGrayScale ]]<br>
+                       ifFalse: [<br>
+                               Form<br>
+                                       extent: width@height<br>
+                                       depth:<br>
+                                               (suggestedDepth = 32<br>
+                                                       ifTrue: [ 32 ]<br>
+                                                       ifFalse: [<br>
+                                                               ((suggestedDepth = 16) &amp; (width even))<br>
+                                                                       ifTrue: [ 16 ]<br>
+                                                                       ifFalse: [ 32 ]])].<br>
+<br>
-       &quot;Odd width images of depth 16 gave problems. Avoid them (or check carefully!!)&quot;<br>
-       depthToUse := ((depth = 32) | width odd) ifTrue: [32] ifFalse: [16].<br>
-       form := Form extent: width@height depth: depthToUse.<br>
-       (width = 0 or: [height = 0]) ifTrue: [^ form].<br>
        self<br>
                primJPEGReadImage: jpegDecompressStruct<br>
                fromByteArray: bytes<br>
                onForm: form<br>
                doDithering: true<br>
                errorMgr: jpegErrorMgr2Struct.<br>
+       ^ form!<br>
-       ^ form<br>
- !<br>
<br>
Item was changed:<br>
  ----- Method: JPEGReadWriter2&gt;&gt;nextPutImage:quality:progressiveJPEG: (in category &#39;public access&#39;) -----<br>
  nextPutImage: aForm quality: quality progressiveJPEG: progressiveFlag<br>
+       &quot;Encode the given Form on my stream with the given settings. Quality goes from 0 (low) to 100 (high), where -1 means default. If progressiveFlag is true, encode as a progressive JPEG.<br>
+       We can compress:<br>
+               * 32-bit deep Forms<br>
+               * -32-bit deep Forms<br>
+               * 16-bit deep<br>
+               * -16-bit deep<br>
+               * GrayScale ColorForms (see #isGrayScale)&quot;<br>
-       &quot;Encode the given Form on my stream with the given settings. Quality goes from 0 (low) to 100 (high), where -1 means default. If progressiveFlag is true, encode as a progressive JPEG.&quot;<br>
<br>
        | sourceForm jpegCompressStruct jpegErrorMgr2Struct buffer byteCount |<br>
+<br>
        aForm unhibernate.<br>
+<br>
+       sourceForm := self supports8BitGrayscaleJPEGs<br>
+               ifTrue: [<br>
+                       (aForm depth = 32) | (aForm depth = 16) | (aForm isGrayScale)<br>
+                               ifTrue: [aForm]<br>
+                               ifFalse: [aForm asFormOfDepth: 32 ]]<br>
+               ifFalse: [<br>
+                       (aForm nativeDepth &gt; 0) &amp; ((aForm depth = 32) | ((aForm depth = 16) &amp; (aForm width even)))<br>
+                               ifTrue: [aForm]<br>
+                               ifFalse: [aForm asFormOfDepth: 32 ]].<br>
+<br>
-       &quot;odd width images of depth 16 give problems; avoid them.&quot;<br>
-       sourceForm := (aForm depth = 32) | (aForm width even &amp; (aForm depth = 16))<br>
-               ifTrue: [aForm]<br>
-               ifFalse: [aForm asFormOfDepth: 32].<br>
        jpegCompressStruct := ByteArray new: self primJPEGCompressStructSize.<br>
        jpegErrorMgr2Struct := ByteArray new: self primJPEGErrorMgr2StructSize.<br>
        buffer := ByteArray new: sourceForm width * sourceForm height + 1024.<br>
        &quot;Try to write the image. Retry with a larger buffer if needed.&quot;<br>
        [<br>
                byteCount := self primJPEGWriteImage: jpegCompressStruct<br>
                        onByteArray: buffer<br>
                        form: sourceForm<br>
                        quality: quality<br>
                        progressiveJPEG: progressiveFlag<br>
                        errorMgr: jpegErrorMgr2Struct.<br>
                byteCount = 0 and: [ buffer size &lt; (sourceForm width * sourceForm height * 3 + 1024) ] ]<br>
                        whileTrue: [ buffer := ByteArray new: buffer size * 2 ].<br>
        byteCount = 0 ifTrue: [ self error: &#39;buffer too small for compressed data&#39; ].<br>
        stream next: byteCount putAll: buffer startingAt: 1.<br>
        self close.<br>
  !<br>
<br>
Item was added:<br>
+ ----- Method: JPEGReadWriter2&gt;&gt;primImageNumComponents: (in category &#39;primitives&#39;) -----<br>
+ primImageNumComponents: aJPEGDecompressStruct<br>
+<br>
+       &lt;primitive: &#39;primImageNumComponents&#39; module: &#39;JPEGReadWriter2Plugin&#39;&gt;<br>
+       ^ 3!<br>
<br>
Item was added:<br>
+ ----- Method: JPEGReadWriter2&gt;&gt;primSupports8BitGrayscaleJPEGs (in category &#39;primitives&#39;) -----<br>
+ primSupports8BitGrayscaleJPEGs<br>
+       &lt;primitive: &#39;primSupports8BitGrayscaleJPEGs&#39; module: &#39;JPEGReadWriter2Plugin&#39;&gt;<br>
+       ^ false!<br>
<br>
Item was added:<br>
+ ----- Method: JPEGReadWriter2&gt;&gt;supports8BitGrayscaleJPEGs (in category &#39;testing&#39;) -----<br>
+ supports8BitGrayscaleJPEGs<br>
+       ^ self primSupports8BitGrayscaleJPEGs!<br>
<br>
Item was changed:<br>
  ----- Method: JPEGReadWriter2&gt;&gt;uncompress:into: (in category &#39;public access&#39;) -----<br>
  uncompress: aByteArray into: aForm<br>
+       ^ self uncompress: aByteArray into: aForm doDithering: true<br>
-       &quot;Uncompress an image from the given ByteArray into the given Form.<br>
-       Fails if the given Form has the wrong dimensions or depth.<br>
-       If aForm has depth 16, do ordered dithering.&quot;<br>
-<br>
-       | jpegDecompressStruct jpegErrorMgr2Struct w h |<br>
-       aForm unhibernate.<br>
-       jpegDecompressStruct := ByteArray new: self primJPEGDecompressStructSize.<br>
-       jpegErrorMgr2Struct := ByteArray new: self primJPEGErrorMgr2StructSize.<br>
-       self<br>
-               primJPEGReadHeader: jpegDecompressStruct<br>
-               fromByteArray: aByteArray<br>
-               errorMgr: jpegErrorMgr2Struct.<br>
-       w := self primImageWidth: jpegDecompressStruct.<br>
-       h := self primImageHeight: jpegDecompressStruct.<br>
-       ((aForm width = w) &amp; (aForm height = h)) ifFalse: [<br>
-               ^ self error: &#39;form dimensions do not match&#39;].<br>
-<br>
-       &quot;odd width images of depth 16 give problems; avoid them&quot;<br>
-       w odd<br>
-               ifTrue: [<br>
-                       aForm depth = 32 ifFalse: [^ self error: &#39;must use depth 32 with odd width&#39;]]<br>
-               ifFalse: [<br>
-                       ((aForm depth = 16) | (aForm depth = 32)) ifFalse: [^ self error: &#39;must use depth 16 or 32&#39;]].<br>
-<br>
-       self primJPEGReadImage: jpegDecompressStruct<br>
-               fromByteArray: aByteArray<br>
-               onForm: aForm<br>
-               doDithering: true<br>
-               errorMgr: jpegErrorMgr2Struct.<br>
  !<br>
<br>
Item was changed:<br>
  ----- Method: JPEGReadWriter2&gt;&gt;uncompress:into:doDithering: (in category &#39;public access&#39;) -----<br>
  uncompress: aByteArray into: aForm doDithering: ditherFlag<br>
        &quot;Uncompress an image from the given ByteArray into the given Form.<br>
        Fails if the given Form has the wrong dimensions or depth.<br>
+       We can read RGB JPEGs into:<br>
+               * 32-bit Forms<br>
+               * -32-bit Forms<br>
+               * 16-bit Forms (with or without dithering!!)<br>
+               * -16-bit Forms (with or without dithering!!)<br>
+       We can read grayscale JPEGs into:<br>
+               * 32-bit Forms<br>
+               * -32-bit Forms<br>
+               * 16-bit Forms (with or without dithering!!)<br>
+               * -16-bit Forms (with or without dithering!!)<br>
+               * 8-bit grayScale ColorForms (see #isGrayScale)<br>
+               * -8-bit grayScale ColorForms (see #isGrayScale)&quot;<br>
-       If aForm has depth 16 and ditherFlag = true, do ordered dithering.&quot;<br>
<br>
+       | jpegDecompressStruct jpegErrorMgr2Struct width height components |<br>
+<br>
-       | jpegDecompressStruct jpegErrorMgr2Struct w h |<br>
        aForm unhibernate.<br>
+<br>
        jpegDecompressStruct := ByteArray new: self primJPEGDecompressStructSize.<br>
        jpegErrorMgr2Struct := ByteArray new: self primJPEGErrorMgr2StructSize.<br>
        self<br>
                primJPEGReadHeader: jpegDecompressStruct<br>
                fromByteArray: aByteArray<br>
                errorMgr: jpegErrorMgr2Struct.<br>
+       width := self primImageWidth: jpegDecompressStruct.<br>
+       height := self primImageHeight: jpegDecompressStruct.<br>
+       components := self primImageNumComponents: jpegDecompressStruct.<br>
+<br>
+       ((aForm width = width) &amp; (aForm height = height)) ifFalse: [<br>
+               ^ self error: &#39;form dimensions do not match&#39; ].<br>
+       self supports8BitGrayscaleJPEGs<br>
-       w := self primImageWidth: jpegDecompressStruct.<br>
-       h := self primImageHeight: jpegDecompressStruct.<br>
-       ((aForm width = w) &amp; (aForm height = h)) ifFalse: [<br>
-               ^ self error: &#39;form dimensions do not match&#39;].<br>
-<br>
-       &quot;odd width images of depth 16 give problems; avoid them&quot;<br>
-       w odd<br>
                ifTrue: [<br>
+                       components = 3<br>
+                               ifTrue: [<br>
+                                       aForm depth = 8<br>
+                                               ifTrue: [ ^ self error: &#39;Cannot uncompress multi-channel JPEGs into 8-bit deep forms&#39; ]].<br>
+                       components = 1<br>
+                               ifTrue: [<br>
+                                       aForm depth = 8<br>
+                                               ifTrue: [<br>
+                                                       aForm isGrayScale<br>
+                                                               ifFalse: [ ^ self error: &#39;Cannot uncompress single-channel JPEGs into 8-bit deep forms that are not grayscale&#39; ]]]]<br>
+<br>
-                       aForm depth = 32 ifFalse: [^ self error: &#39;must use depth 32 with odd width&#39;]]<br>
                ifFalse: [<br>
+                       aForm nativeDepth &lt; 0<br>
+                               ifTrue: [ ^ self error: &#39;Current plugin version doesn&#39;&#39;t support uncompressing JPEGs into little-endian forms&#39; ]<br>
+                               ifFalse: [<br>
+                                       aForm depth = 16<br>
+                                               ifTrue: [<br>
+                                                       width odd<br>
+                                                               ifTrue: [ ^ self error: &#39;Current plugin version doesn&#39;&#39;t support uncompressing JPEGs with an odd width into 16-bit deep forms&#39; ]].<br>
+                                       aForm depth = 8<br>
+                                               ifTrue: [ ^ self error: &#39;Current plugin version doesn&#39;&#39;t support uncompressing JPEGs into 8-bit deep forms&#39; ]]].<br>
-                       ((aForm depth = 16) | (aForm depth = 32)) ifFalse: [^ self error: &#39;must use depth 16 or 32&#39;]].<br>
<br>
        self primJPEGReadImage: jpegDecompressStruct<br>
                fromByteArray: aByteArray<br>
                onForm: aForm<br>
                doDithering: ditherFlag<br>
                errorMgr: jpegErrorMgr2Struct.<br>
  !<br>
<br>
<br>
</blockquote></div><br><br clear="all"><div><br></div>-- <br><div class="gmail_signature" data-smartmail="gmail_signature"><div dir="ltr"><div><span style="font-size:small;border-collapse:separate"><div>_,,,^..^,,,_<br></div><div>best, Eliot</div></span></div></div></div>
</div>