JuicyPixels 3.3.5 → 3.3.9
raw patch · 17 files changed
Files
- JuicyPixels.cabal +25/−14
- changelog +26/−0
- docimages/pixelgraph.svg +264/−264
- src/Codec/Picture/Bitmap.hs +4/−0
- src/Codec/Picture/ColorQuant.hs +1/−1
- src/Codec/Picture/ConvGraph.hs +10/−10
- src/Codec/Picture/InternalHelper.hs +2/−21
- src/Codec/Picture/Jpg.hs +11/−4
- src/Codec/Picture/Jpg/Internal/DefaultTable.hs +5/−1
- src/Codec/Picture/Jpg/Internal/Types.hs +426/−110
- src/Codec/Picture/Metadata.hs +2/−0
- src/Codec/Picture/Metadata/Exif.hs +7/−11
- src/Codec/Picture/Png.hs +6/−7
- src/Codec/Picture/Png/Internal/Export.hs +1/−1
- src/Codec/Picture/Png/Internal/Type.hs +2/−0
- src/Codec/Picture/Tiff/Internal/Types.hs +22/−8
- src/Codec/Picture/VectorByteConversion.hs +9/−2
JuicyPixels.cabal view
@@ -1,5 +1,5 @@ Name: JuicyPixels -Version: 3.3.5 +Version: 3.3.9 Synopsis: Picture loading/serialization (in png, jpeg, bitmap, gif, tga, tiff and radiance) Description: <<data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADABAMAAACg8nE0AAAAElBMVEUAAABJqDSTWEL/qyb///8AAABH/1GTAAAAAXRSTlMAQObYZgAAAN5JREFUeF7s1sEJgFAQxFBbsAV72v5bEVYWPwT/XDxmCsi7zvHXavYREBDI3XP2GgICqBBYuwIC+/rVayPUAyAg0HvIXBcQoDFDGnUBgWQQ2Bx3AYFaRoBpAQHWb3bt2ARgGAiCYFFuwf3X5HA/McgGJWI2FdykCv4aBYzmKwDwvl6NVmUAAK2vlwEALK7fo88GANB6HQsAAAAAAAAA7P94AQCzswEAAAAAAAAAAAAAAAAAAICzh4UAO4zWAYBfRutHA4Bn5C69JhowAMGoBaMWDG0wCkbBKBgFo2AUAACPmegUST/IJAAAAABJRU5ErkJggg==>> @@ -14,11 +14,21 @@ Category: Codec, Graphics, Image Stability: Stable Build-type: Simple - --- Constraint on the version of Cabal needed to build this package. -Cabal-version: >= 1.18 +cabal-version: 1.18 +tested-with: + GHC == 9.8.1 + GHC == 9.6.4 + GHC == 9.4.8 + GHC == 9.2.8 + GHC == 9.0.2 + GHC == 8.10.7 + GHC == 8.8.4 + GHC == 8.6.5 + GHC == 8.4.4 + GHC == 8.2.2 + GHC == 8.0.2 -extra-source-files: changelog, docimages/*.png, docimages/*.svg, README.md +extra-doc-files: changelog, docimages/*.png, docimages/*.svg, README.md extra-doc-files: docimages/*.png, docimages/*.svg Source-Repository head @@ -28,7 +38,7 @@ Source-Repository this Type: git Location: git://github.com/Twinside/Juicy.Pixels.git - Tag: v3.3.5 + Tag: v3.3.8 Flag Mmap Description: Enable the file loading via mmap (memory map) @@ -37,6 +47,7 @@ Library hs-source-dirs: src Default-Language: Haskell2010 + default-extensions: TypeOperators Exposed-modules: Codec.Picture, Codec.Picture.Bitmap, Codec.Picture.Gif, @@ -66,16 +77,16 @@ Codec.Picture.Tiff.Internal.Types Ghc-options: -O3 -Wall - Build-depends: base >= 4.8 && < 6, - bytestring >= 0.9 && < 0.11, - mtl >= 1.1 && < 2.3, - binary >= 0.8.1 && < 0.9, - zlib >= 0.5.3.1 && < 0.7, + Build-depends: base >= 4.8 && < 5, + bytestring >= 0.9 && < 0.13, + mtl >= 1.1 && < 2.4, + binary >= 0.8.1 && < 0.9, + zlib >= 0.5.3.1 && < 0.8, transformers >= 0.2, - vector >= 0.10 && < 0.13, + vector >= 0.12.3.1, primitive >= 0.4, - deepseq >= 1.1 && < 1.5, - containers >= 0.4.2 && < 0.7 + deepseq >= 1.1 && < 1.6, + containers >= 0.4.2 && < 0.8 -- Modules not exported by this package. Other-modules: Codec.Picture.BitWriter,
changelog view
@@ -1,6 +1,32 @@ Change log ========== +v3.3.9 June 2024 +---------------- + + * Something something compilation + +v3.3.7 July 2022 +---------------- + + * Dependence fidling + * Jpg: do not call "error" in the parser, use fail instead. + +v3.3.7 March 2022 +----------------- + + * Jpg: Fixing renderng bug with MCUs with single block in width and + multiple in height + +v3.3.6 October 2021 +------------------- + + * Bytestring bound bump + * Fix bug #187. (Some JPEGs are misidentified as SourceTiff.) + * Fix EXIF handling of strings of four characters or fewer. + * Fix endianness bug in short ExifString and ExifUndefined. + + v3.3.5 January 2020 -------------------
docimages/pixelgraph.svg view
@@ -1,264 +1,264 @@-<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" - "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<!-- Generated by graphviz version 2.22.2 (20090313.1817) - --> -<!-- Title: pixelGraphic Pages: 1 --> -<svg width="620pt" height="464pt" - viewBox="0.00 0.00 620.00 464.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> -<g id="graph1" class="graph" transform="scale(1 1) rotate(0) translate(4 460)"> -<title>pixelGraphic</title> -<polygon fill="white" stroke="white" points="-4,5 -4,-460 617,-460 617,5 -4,5"/> -<!-- PixelRGB8 --> -<g id="node1" class="node"><title>PixelRGB8</title> -<ellipse fill="none" stroke="black" cx="246" cy="-438" rx="31.7878" ry="18"/> -<text text-anchor="middle" x="246" y="-436.3" font-family="sans-serif" font-size="8.00">PixelRGB8</text> -</g> -<!-- PixelRGBA8 --> -<g id="node6" class="node"><title>PixelRGBA8</title> -<ellipse fill="none" stroke="black" cx="441" cy="-354" rx="34.8223" ry="18"/> -<text text-anchor="middle" x="441" y="-352.3" font-family="sans-serif" font-size="8.00">PixelRGBA8</text> -</g> -<!-- PixelRGB8->PixelRGBA8 --> -<g id="edge4" class="edge"><title>PixelRGB8->PixelRGBA8</title> -<path fill="none" stroke="black" d="M274.25,-429.506C295.421,-422.833 324.934,-412.874 350,-402 351.523,-401.339 381.994,-385.244 407.261,-371.871"/> -<polygon fill="black" stroke="black" points="409.071,-374.874 416.272,-367.102 405.796,-368.687 409.071,-374.874"/> -<text text-anchor="middle" x="393" y="-394.3" font-family="sans-serif" font-size="8.00">promotePixel</text> -</g> -<!-- PixelRGBF --> -<g id="node8" class="node"><title>PixelRGBF</title> -<ellipse fill="none" stroke="black" cx="33" cy="-102" rx="31.7878" ry="18"/> -<text text-anchor="middle" x="33" y="-100.3" font-family="sans-serif" font-size="8.00">PixelRGBF</text> -</g> -<!-- PixelRGB8->PixelRGBF --> -<g id="edge6" class="edge"><title>PixelRGB8->PixelRGBF</title> -<path fill="none" stroke="black" d="M214.594,-434.005C148.276,-424.689 0,-398.847 0,-354 0,-354 0,-354 0,-186 0,-165.437 8.67323,-143.88 17.1268,-127.753"/> -<polygon fill="black" stroke="black" points="20.228,-129.378 22.0115,-118.934 14.1045,-125.986 20.228,-129.378"/> -<text text-anchor="middle" x="23" y="-268.3" font-family="sans-serif" font-size="8.00">promotePixel</text> -</g> -<!-- Pixel8 --> -<g id="node20" class="node"><title>Pixel8</title> -<ellipse fill="none" stroke="black" cx="308" cy="-270" rx="27" ry="18"/> -<text text-anchor="middle" x="308" y="-268.3" font-family="sans-serif" font-size="8.00">Pixel8</text> -</g> -<!-- PixelRGB8->Pixel8 --> -<g id="edge60" class="edge"><title>PixelRGB8->Pixel8</title> -<path fill="none" stroke="black" d="M214.516,-433.811C149.341,-423.852 9.37863,-395.049 55,-336 81.2893,-301.973 207.23,-282.163 270.929,-274.163"/> -<polygon fill="black" stroke="black" points="271.46,-277.624 280.961,-272.936 270.61,-270.676 271.46,-277.624"/> -<text text-anchor="middle" x="80" y="-352.3" font-family="sans-serif" font-size="8.00">computeLuma</text> -</g> -<!-- PixelCMYK8 --> -<g id="node28" class="node"><title>PixelCMYK8</title> -<ellipse fill="none" stroke="black" cx="253" cy="-354" rx="34.8223" ry="18"/> -<text text-anchor="middle" x="253" y="-352.3" font-family="sans-serif" font-size="8.00">PixelCMYK8</text> -</g> -<!-- PixelRGB8->PixelCMYK8 --> -<g id="edge40" class="edge"><title>PixelRGB8->PixelCMYK8</title> -<path fill="none" stroke="black" d="M224.748,-423.894C213.935,-414.831 204.182,-402.459 210,-390 212.758,-384.095 217.013,-378.817 221.794,-374.241"/> -<polygon fill="black" stroke="black" points="224.225,-376.77 229.601,-367.64 219.705,-371.424 224.225,-376.77"/> -<text text-anchor="middle" x="232" y="-394.3" font-family="sans-serif" font-size="8.00">convertPixel</text> -</g> -<!-- PixelYCbCr8 --> -<g id="node30" class="node"><title>PixelYCbCr8</title> -<ellipse fill="none" stroke="black" cx="150" cy="-354" rx="35.9084" ry="18"/> -<text text-anchor="middle" x="150" y="-352.3" font-family="sans-serif" font-size="8.00">PixelYCbCr8</text> -</g> -<!-- PixelRGB8->PixelYCbCr8 --> -<g id="edge42" class="edge"><title>PixelRGB8->PixelYCbCr8</title> -<path fill="none" stroke="black" d="M214.399,-434.415C177.053,-429.451 118.29,-419.109 105,-402 97.106,-391.837 104.874,-381.324 116.049,-372.736"/> -<polygon fill="black" stroke="black" points="118.35,-375.401 124.612,-366.856 114.387,-369.631 118.35,-375.401"/> -<text text-anchor="middle" x="127" y="-394.3" font-family="sans-serif" font-size="8.00">convertPixel</text> -</g> -<!-- PixelRGB16 --> -<g id="node2" class="node"><title>PixelRGB16</title> -<ellipse fill="none" stroke="black" cx="429" cy="-102" rx="34.1425" ry="18"/> -<text text-anchor="middle" x="429" y="-100.3" font-family="sans-serif" font-size="8.00">PixelRGB16</text> -</g> -<!-- PixelRGBA16 --> -<g id="node4" class="node"><title>PixelRGBA16</title> -<ellipse fill="none" stroke="black" cx="343" cy="-18" rx="37.1721" ry="18"/> -<text text-anchor="middle" x="343" y="-16.3" font-family="sans-serif" font-size="8.00">PixelRGBA16</text> -</g> -<!-- PixelRGB16->PixelRGBA16 --> -<g id="edge2" class="edge"><title>PixelRGB16->PixelRGBA16</title> -<path fill="none" stroke="black" d="M400.098,-92.2134C386.67,-86.4097 371.409,-77.8363 361,-66 355.909,-60.2106 352.202,-52.8433 349.526,-45.6287"/> -<polygon fill="black" stroke="black" points="352.846,-44.5187 346.482,-36.0479 346.174,-46.6383 352.846,-44.5187"/> -<text text-anchor="middle" x="384" y="-58.3" font-family="sans-serif" font-size="8.00">promotePixel</text> -</g> -<!-- Pixel16 --> -<g id="node16" class="node"><title>Pixel16</title> -<ellipse fill="none" stroke="black" cx="296" cy="-186" rx="27" ry="18"/> -<text text-anchor="middle" x="296" y="-184.3" font-family="sans-serif" font-size="8.00">Pixel16</text> -</g> -<!-- PixelRGB16->Pixel16 --> -<g id="edge58" class="edge"><title>PixelRGB16->Pixel16</title> -<path fill="none" stroke="black" d="M424.573,-120.149C421.265,-130.199 415.765,-142.199 407,-150 386.436,-168.304 356.427,-177.32 332.795,-181.75"/> -<polygon fill="black" stroke="black" points="332.056,-178.325 322.773,-183.431 333.214,-185.228 332.056,-178.325"/> -<text text-anchor="middle" x="441" y="-142.3" font-family="sans-serif" font-size="8.00">computeLuma</text> -</g> -<!-- PixelCMYK16 --> -<g id="node26" class="node"><title>PixelCMYK16</title> -<ellipse fill="none" stroke="black" cx="553" cy="-18" rx="37.1721" ry="18"/> -<text text-anchor="middle" x="553" y="-16.3" font-family="sans-serif" font-size="8.00">PixelCMYK16</text> -</g> -<!-- PixelRGB16->PixelCMYK16 --> -<g id="edge38" class="edge"><title>PixelRGB16->PixelCMYK16</title> -<path fill="none" stroke="black" d="M451.832,-88.3606C462.615,-81.7841 475.593,-73.6731 487,-66 499.46,-57.6189 512.915,-47.9503 524.406,-39.4968"/> -<polygon fill="black" stroke="black" points="526.807,-42.074 532.761,-33.3106 522.641,-36.4482 526.807,-42.074"/> -<text text-anchor="middle" x="525" y="-58.3" font-family="sans-serif" font-size="8.00">convertPixel</text> -</g> -<!-- PixelRGBA16->PixelRGB16 --> -<g id="edge44" class="edge"><title>PixelRGBA16->PixelRGB16</title> -<path fill="none" stroke="black" d="M371.789,-29.3863C383.871,-35.2918 397.329,-43.4955 407,-54 412.378,-59.8422 416.636,-67.2266 419.915,-74.4406"/> -<polygon fill="black" stroke="black" points="416.794,-76.0584 423.794,-84.0115 423.282,-73.4291 416.794,-76.0584"/> -<text text-anchor="middle" x="449" y="-58.3" font-family="sans-serif" font-size="8.00">dropTransparency</text> -</g> -<!-- PixelRGBA8->PixelRGB8 --> -<g id="edge46" class="edge"><title>PixelRGBA8->PixelRGB8</title> -<path fill="none" stroke="black" d="M437.029,-372.038C433.845,-382.309 428.318,-394.571 419,-402 399.083,-417.88 332.208,-428.306 287.678,-433.672"/> -<polygon fill="black" stroke="black" points="287.239,-430.2 277.712,-434.834 288.05,-437.153 287.239,-430.2"/> -<text text-anchor="middle" x="463" y="-394.3" font-family="sans-serif" font-size="8.00">dropTransparency</text> -</g> -<!-- PixelRGBA8->Pixel8 --> -<g id="edge52" class="edge"><title>PixelRGBA8->Pixel8</title> -<path fill="none" stroke="black" d="M413.829,-342.599C399.476,-336.131 381.815,-327.451 367,-318 354.878,-310.267 342.394,-300.428 332.048,-291.673"/> -<polygon fill="black" stroke="black" points="334.114,-288.833 324.258,-284.946 329.539,-294.131 334.114,-288.833"/> -<text text-anchor="middle" x="392" y="-310.3" font-family="sans-serif" font-size="8.00">computeLuma</text> -</g> -<!-- PixelF --> -<g id="node14" class="node"><title>PixelF</title> -<ellipse fill="none" stroke="black" cx="106" cy="-186" rx="27" ry="18"/> -<text text-anchor="middle" x="106" y="-184.3" font-family="sans-serif" font-size="8.00">PixelF</text> -</g> -<!-- PixelRGBF->PixelF --> -<g id="edge56" class="edge"><title>PixelRGBF->PixelF</title> -<path fill="none" stroke="black" d="M27.0137,-120.147C24.9943,-129.716 24.4635,-141.251 30,-150 38.9021,-164.067 54.9514,-172.745 70.015,-178.044"/> -<polygon fill="black" stroke="black" points="69.0981,-181.424 79.6867,-181.06 71.1825,-174.741 69.0981,-181.424"/> -<text text-anchor="middle" x="55" y="-142.3" font-family="sans-serif" font-size="8.00">computeLuma</text> -</g> -<!-- PixelYA16 --> -<g id="node9" class="node"><title>PixelYA16</title> -<ellipse fill="none" stroke="black" cx="259" cy="-102" rx="31.7878" ry="18"/> -<text text-anchor="middle" x="259" y="-100.3" font-family="sans-serif" font-size="8.00">PixelYA16</text> -</g> -<!-- PixelYA16->PixelRGBA16 --> -<g id="edge8" class="edge"><title>PixelYA16->PixelRGBA16</title> -<path fill="none" stroke="black" d="M258.152,-83.5434C258.613,-73.883 260.687,-62.36 267,-54 275.273,-43.0444 287.722,-35.28 300.163,-29.8405"/> -<polygon fill="black" stroke="black" points="301.586,-33.0421 309.607,-26.1198 299.02,-26.5294 301.586,-33.0421"/> -<text text-anchor="middle" x="290" y="-58.3" font-family="sans-serif" font-size="8.00">promotePixel</text> -</g> -<!-- PixelYA16->Pixel16 --> -<g id="edge48" class="edge"><title>PixelYA16->Pixel16</title> -<path fill="none" stroke="black" d="M229.98,-109.854C206.015,-117.845 177.897,-131.656 191,-150 199.293,-161.611 233.125,-171.835 260.175,-178.391"/> -<polygon fill="black" stroke="black" points="259.609,-181.853 270.143,-180.72 261.201,-175.037 259.609,-181.853"/> -<text text-anchor="middle" x="225" y="-142.3" font-family="sans-serif" font-size="8.00">dropTransparency</text> -</g> -<!-- PixelYA8 --> -<g id="node11" class="node"><title>PixelYA8</title> -<ellipse fill="none" stroke="black" cx="439" cy="-186" rx="28.8445" ry="18"/> -<text text-anchor="middle" x="439" y="-184.3" font-family="sans-serif" font-size="8.00">PixelYA8</text> -</g> -<!-- PixelYA8->PixelRGB8 --> -<g id="edge12" class="edge"><title>PixelYA8->PixelRGB8</title> -<path fill="none" stroke="black" d="M464.762,-194.278C489.627,-203.81 525.815,-222.121 542,-252 574.918,-312.769 554.613,-358.387 501,-402 484.752,-415.217 355.671,-428.373 287.639,-434.476"/> -<polygon fill="black" stroke="black" points="287.321,-430.99 277.669,-435.359 287.938,-437.963 287.321,-430.99"/> -<text text-anchor="middle" x="580" y="-310.3" font-family="sans-serif" font-size="8.00">promotePixel</text> -</g> -<!-- PixelYA8->PixelRGBA8 --> -<g id="edge10" class="edge"><title>PixelYA8->PixelRGBA8</title> -<path fill="none" stroke="black" d="M460.035,-198.84C467.824,-204.817 475.781,-212.68 480,-222 497.598,-260.871 496.093,-278.904 479,-318 476.725,-323.204 473.295,-328.013 469.411,-332.309"/> -<polygon fill="black" stroke="black" points="466.792,-329.975 462.079,-339.464 471.681,-334.985 466.792,-329.975"/> -<text text-anchor="middle" x="515" y="-268.3" font-family="sans-serif" font-size="8.00">promotePixel</text> -</g> -<!-- PixelYA8->Pixel8 --> -<g id="edge50" class="edge"><title>PixelYA8->Pixel8</title> -<path fill="none" stroke="black" d="M412.102,-193.018C393.267,-198.789 368.145,-208.269 349,-222 340.05,-228.419 331.808,-237.196 325.098,-245.49"/> -<polygon fill="black" stroke="black" points="322.247,-243.456 318.929,-253.518 327.797,-247.721 322.247,-243.456"/> -<text text-anchor="middle" x="383" y="-226.3" font-family="sans-serif" font-size="8.00">dropTransparency</text> -</g> -<!-- PixelYA8->Pixel8 --> -<g id="edge62" class="edge"><title>PixelYA8->Pixel8</title> -<path fill="none" stroke="black" d="M434.558,-204.131C431.244,-214.175 425.744,-226.175 417,-234 397.039,-251.864 367.99,-260.893 344.899,-265.441"/> -<polygon fill="black" stroke="black" points="344.002,-262.045 334.763,-267.23 345.219,-268.938 344.002,-262.045"/> -<text text-anchor="middle" x="451" y="-226.3" font-family="sans-serif" font-size="8.00">computeLuma</text> -</g> -<!-- PixelF->PixelRGBF --> -<g id="edge14" class="edge"><title>PixelF->PixelRGBF</title> -<path fill="none" stroke="black" d="M100.45,-168.312C96.8232,-158.653 91.3784,-146.886 84,-138 78.5966,-131.492 71.7476,-125.606 64.8367,-120.568"/> -<polygon fill="black" stroke="black" points="66.3898,-117.392 56.1539,-114.657 62.4504,-123.178 66.3898,-117.392"/> -<text text-anchor="middle" x="115" y="-142.3" font-family="sans-serif" font-size="8.00">promotePixel</text> -</g> -<!-- Pixel16->PixelRGB16 --> -<g id="edge18" class="edge"><title>Pixel16->PixelRGB16</title> -<path fill="none" stroke="black" d="M313.43,-172.019C326.239,-162.023 344.257,-148.532 361,-138 371.689,-131.277 383.756,-124.61 394.725,-118.875"/> -<polygon fill="black" stroke="black" points="396.336,-121.982 403.628,-114.295 393.134,-115.757 396.336,-121.982"/> -<text text-anchor="middle" x="384" y="-142.3" font-family="sans-serif" font-size="8.00">promotePixel</text> -</g> -<!-- Pixel16->PixelRGBA16 --> -<g id="edge16" class="edge"><title>Pixel16->PixelRGBA16</title> -<path fill="none" stroke="black" d="M305.084,-168.741C307.903,-162.903 310.835,-156.272 313,-150 325.048,-115.091 333.609,-73.3023 338.424,-46.2333"/> -<polygon fill="black" stroke="black" points="341.89,-46.732 340.143,-36.2822 334.992,-45.5405 341.89,-46.732"/> -<text text-anchor="middle" x="353" y="-100.3" font-family="sans-serif" font-size="8.00">promotePixel</text> -</g> -<!-- Pixel16->PixelYA16 --> -<g id="edge20" class="edge"><title>Pixel16->PixelYA16</title> -<path fill="none" stroke="black" d="M280.78,-170.7C275.635,-164.701 270.35,-157.469 267,-150 264.277,-143.93 262.473,-137.052 261.279,-130.438"/> -<polygon fill="black" stroke="black" points="264.727,-129.827 259.862,-120.415 257.796,-130.807 264.727,-129.827"/> -<text text-anchor="middle" x="290" y="-142.3" font-family="sans-serif" font-size="8.00">promotePixel</text> -</g> -<!-- Pixel8->PixelRGB8 --> -<g id="edge24" class="edge"><title>Pixel8->PixelRGB8</title> -<path fill="none" stroke="black" d="M312.919,-288.095C319.136,-314.953 326.712,-366.057 306,-402 300.622,-411.332 291.773,-418.518 282.501,-423.926"/> -<polygon fill="black" stroke="black" points="280.83,-420.85 273.581,-428.577 284.067,-427.057 280.83,-420.85"/> -<text text-anchor="middle" x="342" y="-352.3" font-family="sans-serif" font-size="8.00">promotePixel</text> -</g> -<!-- Pixel8->PixelRGBA8 --> -<g id="edge22" class="edge"><title>Pixel8->PixelRGBA8</title> -<path fill="none" stroke="black" d="M334.939,-272.185C360.073,-275.561 397.177,-284.15 421,-306 426.855,-311.37 431.048,-318.72 434.03,-326.054"/> -<polygon fill="black" stroke="black" points="430.836,-327.531 437.387,-335.858 437.458,-325.263 430.836,-327.531"/> -<text text-anchor="middle" x="452" y="-310.3" font-family="sans-serif" font-size="8.00">promotePixel</text> -</g> -<!-- Pixel8->PixelYA8 --> -<g id="edge26" class="edge"><title>Pixel8->PixelYA8</title> -<path fill="none" stroke="black" d="M290.77,-255.865C280.734,-245.919 271.57,-232.553 280,-222 287.559,-212.538 356.092,-199.635 400.682,-192.122"/> -<polygon fill="black" stroke="black" points="401.335,-195.562 410.626,-190.469 400.187,-188.657 401.335,-195.562"/> -<text text-anchor="middle" x="303" y="-226.3" font-family="sans-serif" font-size="8.00">promotePixel</text> -</g> -<!-- Pixel8->PixelF --> -<g id="edge28" class="edge"><title>Pixel8->PixelF</title> -<path fill="none" stroke="black" d="M281.145,-267.812C249.741,-264.331 196.969,-255.554 157,-234 145.577,-227.84 134.814,-218.473 126.146,-209.643"/> -<polygon fill="black" stroke="black" points="128.492,-207.028 119.104,-202.117 123.381,-211.81 128.492,-207.028"/> -<text text-anchor="middle" x="180" y="-226.3" font-family="sans-serif" font-size="8.00">promotePixel</text> -</g> -<!-- Pixel8->Pixel16 --> -<g id="edge30" class="edge"><title>Pixel8->Pixel16</title> -<path fill="none" stroke="black" d="M282.206,-263.957C256.398,-257.327 219.359,-246.018 211,-234 196.025,-212.469 230.216,-199.429 259.604,-192.482"/> -<polygon fill="black" stroke="black" points="260.687,-195.828 269.71,-190.275 259.193,-188.989 260.687,-195.828"/> -<text text-anchor="middle" x="234" y="-226.3" font-family="sans-serif" font-size="8.00">promotePixel</text> -</g> -<!-- PixelCMYK16->PixelRGB16 --> -<g id="edge32" class="edge"><title>PixelCMYK16->PixelRGB16</title> -<path fill="none" stroke="black" d="M564.03,-35.4654C568.707,-45.2873 571.707,-57.2873 565,-66 553.678,-80.7091 507.858,-90.6423 472.496,-96.2857"/> -<polygon fill="black" stroke="black" points="471.73,-92.862 462.373,-97.8319 472.787,-99.7817 471.73,-92.862"/> -<text text-anchor="middle" x="590" y="-58.3" font-family="sans-serif" font-size="8.00">convertPixel</text> -</g> -<!-- PixelCMYK8->PixelRGB8 --> -<g id="edge34" class="edge"><title>PixelCMYK8->PixelRGB8</title> -<path fill="none" stroke="black" d="M257.173,-372.299C258.645,-381.28 259.584,-392.241 258,-402 257.535,-404.865 256.875,-407.811 256.1,-410.724"/> -<polygon fill="black" stroke="black" points="252.737,-409.753 253.143,-420.34 259.427,-411.811 252.737,-409.753"/> -<text text-anchor="middle" x="280" y="-394.3" font-family="sans-serif" font-size="8.00">convertPixel</text> -</g> -<!-- PixelYCbCr8->PixelRGB8 --> -<g id="edge36" class="edge"><title>PixelYCbCr8->PixelRGB8</title> -<path fill="none" stroke="black" d="M149.836,-372.162C150.645,-381.97 153.174,-393.727 160,-402 171.431,-415.854 189.112,-424.462 205.475,-429.768"/> -<polygon fill="black" stroke="black" points="204.847,-433.23 215.426,-432.663 206.802,-426.509 204.847,-433.23"/> -<text text-anchor="middle" x="182" y="-394.3" font-family="sans-serif" font-size="8.00">convertPixel</text> -</g> -<!-- PixelYCbCr8->Pixel8 --> -<g id="edge54" class="edge"><title>PixelYCbCr8->Pixel8</title> -<path fill="none" stroke="black" d="M174.688,-340.875C202.411,-326.136 247.432,-302.201 277.376,-286.281"/> -<polygon fill="black" stroke="black" points="279.34,-289.201 286.527,-281.416 276.054,-283.02 279.34,-289.201"/> -<text text-anchor="middle" x="263" y="-310.3" font-family="sans-serif" font-size="8.00">computeLuma</text> -</g> -</g> -</svg> +<?xml version="1.0" encoding="UTF-8" standalone="no"?>+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">+<!-- Generated by graphviz version 2.22.2 (20090313.1817)+ -->+<!-- Title: pixelGraphic Pages: 1 -->+<svg width="620pt" height="464pt"+ viewBox="0.00 0.00 620.00 464.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">+<g id="graph1" class="graph" transform="scale(1 1) rotate(0) translate(4 460)">+<title>pixelGraphic</title>+<polygon fill="white" stroke="white" points="-4,5 -4,-460 617,-460 617,5 -4,5"/>+<!-- PixelRGB8 -->+<g id="node1" class="node"><title>PixelRGB8</title>+<ellipse fill="none" stroke="black" cx="246" cy="-438" rx="31.7878" ry="18"/>+<text text-anchor="middle" x="246" y="-436.3" font-family="sans-serif" font-size="8.00">PixelRGB8</text>+</g>+<!-- PixelRGBA8 -->+<g id="node6" class="node"><title>PixelRGBA8</title>+<ellipse fill="none" stroke="black" cx="441" cy="-354" rx="34.8223" ry="18"/>+<text text-anchor="middle" x="441" y="-352.3" font-family="sans-serif" font-size="8.00">PixelRGBA8</text>+</g>+<!-- PixelRGB8->PixelRGBA8 -->+<g id="edge4" class="edge"><title>PixelRGB8->PixelRGBA8</title>+<path fill="none" stroke="black" d="M274.25,-429.506C295.421,-422.833 324.934,-412.874 350,-402 351.523,-401.339 381.994,-385.244 407.261,-371.871"/>+<polygon fill="black" stroke="black" points="409.071,-374.874 416.272,-367.102 405.796,-368.687 409.071,-374.874"/>+<text text-anchor="middle" x="393" y="-394.3" font-family="sans-serif" font-size="8.00">promotePixel</text>+</g>+<!-- PixelRGBF -->+<g id="node8" class="node"><title>PixelRGBF</title>+<ellipse fill="none" stroke="black" cx="33" cy="-102" rx="31.7878" ry="18"/>+<text text-anchor="middle" x="33" y="-100.3" font-family="sans-serif" font-size="8.00">PixelRGBF</text>+</g>+<!-- PixelRGB8->PixelRGBF -->+<g id="edge6" class="edge"><title>PixelRGB8->PixelRGBF</title>+<path fill="none" stroke="black" d="M214.594,-434.005C148.276,-424.689 0,-398.847 0,-354 0,-354 0,-354 0,-186 0,-165.437 8.67323,-143.88 17.1268,-127.753"/>+<polygon fill="black" stroke="black" points="20.228,-129.378 22.0115,-118.934 14.1045,-125.986 20.228,-129.378"/>+<text text-anchor="middle" x="23" y="-268.3" font-family="sans-serif" font-size="8.00">promotePixel</text>+</g>+<!-- Pixel8 -->+<g id="node20" class="node"><title>Pixel8</title>+<ellipse fill="none" stroke="black" cx="308" cy="-270" rx="27" ry="18"/>+<text text-anchor="middle" x="308" y="-268.3" font-family="sans-serif" font-size="8.00">Pixel8</text>+</g>+<!-- PixelRGB8->Pixel8 -->+<g id="edge60" class="edge"><title>PixelRGB8->Pixel8</title>+<path fill="none" stroke="black" d="M214.516,-433.811C149.341,-423.852 9.37863,-395.049 55,-336 81.2893,-301.973 207.23,-282.163 270.929,-274.163"/>+<polygon fill="black" stroke="black" points="271.46,-277.624 280.961,-272.936 270.61,-270.676 271.46,-277.624"/>+<text text-anchor="middle" x="80" y="-352.3" font-family="sans-serif" font-size="8.00">computeLuma</text>+</g>+<!-- PixelCMYK8 -->+<g id="node28" class="node"><title>PixelCMYK8</title>+<ellipse fill="none" stroke="black" cx="253" cy="-354" rx="34.8223" ry="18"/>+<text text-anchor="middle" x="253" y="-352.3" font-family="sans-serif" font-size="8.00">PixelCMYK8</text>+</g>+<!-- PixelRGB8->PixelCMYK8 -->+<g id="edge40" class="edge"><title>PixelRGB8->PixelCMYK8</title>+<path fill="none" stroke="black" d="M224.748,-423.894C213.935,-414.831 204.182,-402.459 210,-390 212.758,-384.095 217.013,-378.817 221.794,-374.241"/>+<polygon fill="black" stroke="black" points="224.225,-376.77 229.601,-367.64 219.705,-371.424 224.225,-376.77"/>+<text text-anchor="middle" x="232" y="-394.3" font-family="sans-serif" font-size="8.00">convertPixel</text>+</g>+<!-- PixelYCbCr8 -->+<g id="node30" class="node"><title>PixelYCbCr8</title>+<ellipse fill="none" stroke="black" cx="150" cy="-354" rx="35.9084" ry="18"/>+<text text-anchor="middle" x="150" y="-352.3" font-family="sans-serif" font-size="8.00">PixelYCbCr8</text>+</g>+<!-- PixelRGB8->PixelYCbCr8 -->+<g id="edge42" class="edge"><title>PixelRGB8->PixelYCbCr8</title>+<path fill="none" stroke="black" d="M214.399,-434.415C177.053,-429.451 118.29,-419.109 105,-402 97.106,-391.837 104.874,-381.324 116.049,-372.736"/>+<polygon fill="black" stroke="black" points="118.35,-375.401 124.612,-366.856 114.387,-369.631 118.35,-375.401"/>+<text text-anchor="middle" x="127" y="-394.3" font-family="sans-serif" font-size="8.00">convertPixel</text>+</g>+<!-- PixelRGB16 -->+<g id="node2" class="node"><title>PixelRGB16</title>+<ellipse fill="none" stroke="black" cx="429" cy="-102" rx="34.1425" ry="18"/>+<text text-anchor="middle" x="429" y="-100.3" font-family="sans-serif" font-size="8.00">PixelRGB16</text>+</g>+<!-- PixelRGBA16 -->+<g id="node4" class="node"><title>PixelRGBA16</title>+<ellipse fill="none" stroke="black" cx="343" cy="-18" rx="37.1721" ry="18"/>+<text text-anchor="middle" x="343" y="-16.3" font-family="sans-serif" font-size="8.00">PixelRGBA16</text>+</g>+<!-- PixelRGB16->PixelRGBA16 -->+<g id="edge2" class="edge"><title>PixelRGB16->PixelRGBA16</title>+<path fill="none" stroke="black" d="M400.098,-92.2134C386.67,-86.4097 371.409,-77.8363 361,-66 355.909,-60.2106 352.202,-52.8433 349.526,-45.6287"/>+<polygon fill="black" stroke="black" points="352.846,-44.5187 346.482,-36.0479 346.174,-46.6383 352.846,-44.5187"/>+<text text-anchor="middle" x="384" y="-58.3" font-family="sans-serif" font-size="8.00">promotePixel</text>+</g>+<!-- Pixel16 -->+<g id="node16" class="node"><title>Pixel16</title>+<ellipse fill="none" stroke="black" cx="296" cy="-186" rx="27" ry="18"/>+<text text-anchor="middle" x="296" y="-184.3" font-family="sans-serif" font-size="8.00">Pixel16</text>+</g>+<!-- PixelRGB16->Pixel16 -->+<g id="edge58" class="edge"><title>PixelRGB16->Pixel16</title>+<path fill="none" stroke="black" d="M424.573,-120.149C421.265,-130.199 415.765,-142.199 407,-150 386.436,-168.304 356.427,-177.32 332.795,-181.75"/>+<polygon fill="black" stroke="black" points="332.056,-178.325 322.773,-183.431 333.214,-185.228 332.056,-178.325"/>+<text text-anchor="middle" x="441" y="-142.3" font-family="sans-serif" font-size="8.00">computeLuma</text>+</g>+<!-- PixelCMYK16 -->+<g id="node26" class="node"><title>PixelCMYK16</title>+<ellipse fill="none" stroke="black" cx="553" cy="-18" rx="37.1721" ry="18"/>+<text text-anchor="middle" x="553" y="-16.3" font-family="sans-serif" font-size="8.00">PixelCMYK16</text>+</g>+<!-- PixelRGB16->PixelCMYK16 -->+<g id="edge38" class="edge"><title>PixelRGB16->PixelCMYK16</title>+<path fill="none" stroke="black" d="M451.832,-88.3606C462.615,-81.7841 475.593,-73.6731 487,-66 499.46,-57.6189 512.915,-47.9503 524.406,-39.4968"/>+<polygon fill="black" stroke="black" points="526.807,-42.074 532.761,-33.3106 522.641,-36.4482 526.807,-42.074"/>+<text text-anchor="middle" x="525" y="-58.3" font-family="sans-serif" font-size="8.00">convertPixel</text>+</g>+<!-- PixelRGBA16->PixelRGB16 -->+<g id="edge44" class="edge"><title>PixelRGBA16->PixelRGB16</title>+<path fill="none" stroke="black" d="M371.789,-29.3863C383.871,-35.2918 397.329,-43.4955 407,-54 412.378,-59.8422 416.636,-67.2266 419.915,-74.4406"/>+<polygon fill="black" stroke="black" points="416.794,-76.0584 423.794,-84.0115 423.282,-73.4291 416.794,-76.0584"/>+<text text-anchor="middle" x="449" y="-58.3" font-family="sans-serif" font-size="8.00">dropTransparency</text>+</g>+<!-- PixelRGBA8->PixelRGB8 -->+<g id="edge46" class="edge"><title>PixelRGBA8->PixelRGB8</title>+<path fill="none" stroke="black" d="M437.029,-372.038C433.845,-382.309 428.318,-394.571 419,-402 399.083,-417.88 332.208,-428.306 287.678,-433.672"/>+<polygon fill="black" stroke="black" points="287.239,-430.2 277.712,-434.834 288.05,-437.153 287.239,-430.2"/>+<text text-anchor="middle" x="463" y="-394.3" font-family="sans-serif" font-size="8.00">dropTransparency</text>+</g>+<!-- PixelRGBA8->Pixel8 -->+<g id="edge52" class="edge"><title>PixelRGBA8->Pixel8</title>+<path fill="none" stroke="black" d="M413.829,-342.599C399.476,-336.131 381.815,-327.451 367,-318 354.878,-310.267 342.394,-300.428 332.048,-291.673"/>+<polygon fill="black" stroke="black" points="334.114,-288.833 324.258,-284.946 329.539,-294.131 334.114,-288.833"/>+<text text-anchor="middle" x="392" y="-310.3" font-family="sans-serif" font-size="8.00">computeLuma</text>+</g>+<!-- PixelF -->+<g id="node14" class="node"><title>PixelF</title>+<ellipse fill="none" stroke="black" cx="106" cy="-186" rx="27" ry="18"/>+<text text-anchor="middle" x="106" y="-184.3" font-family="sans-serif" font-size="8.00">PixelF</text>+</g>+<!-- PixelRGBF->PixelF -->+<g id="edge56" class="edge"><title>PixelRGBF->PixelF</title>+<path fill="none" stroke="black" d="M27.0137,-120.147C24.9943,-129.716 24.4635,-141.251 30,-150 38.9021,-164.067 54.9514,-172.745 70.015,-178.044"/>+<polygon fill="black" stroke="black" points="69.0981,-181.424 79.6867,-181.06 71.1825,-174.741 69.0981,-181.424"/>+<text text-anchor="middle" x="55" y="-142.3" font-family="sans-serif" font-size="8.00">computeLuma</text>+</g>+<!-- PixelYA16 -->+<g id="node9" class="node"><title>PixelYA16</title>+<ellipse fill="none" stroke="black" cx="259" cy="-102" rx="31.7878" ry="18"/>+<text text-anchor="middle" x="259" y="-100.3" font-family="sans-serif" font-size="8.00">PixelYA16</text>+</g>+<!-- PixelYA16->PixelRGBA16 -->+<g id="edge8" class="edge"><title>PixelYA16->PixelRGBA16</title>+<path fill="none" stroke="black" d="M258.152,-83.5434C258.613,-73.883 260.687,-62.36 267,-54 275.273,-43.0444 287.722,-35.28 300.163,-29.8405"/>+<polygon fill="black" stroke="black" points="301.586,-33.0421 309.607,-26.1198 299.02,-26.5294 301.586,-33.0421"/>+<text text-anchor="middle" x="290" y="-58.3" font-family="sans-serif" font-size="8.00">promotePixel</text>+</g>+<!-- PixelYA16->Pixel16 -->+<g id="edge48" class="edge"><title>PixelYA16->Pixel16</title>+<path fill="none" stroke="black" d="M229.98,-109.854C206.015,-117.845 177.897,-131.656 191,-150 199.293,-161.611 233.125,-171.835 260.175,-178.391"/>+<polygon fill="black" stroke="black" points="259.609,-181.853 270.143,-180.72 261.201,-175.037 259.609,-181.853"/>+<text text-anchor="middle" x="225" y="-142.3" font-family="sans-serif" font-size="8.00">dropTransparency</text>+</g>+<!-- PixelYA8 -->+<g id="node11" class="node"><title>PixelYA8</title>+<ellipse fill="none" stroke="black" cx="439" cy="-186" rx="28.8445" ry="18"/>+<text text-anchor="middle" x="439" y="-184.3" font-family="sans-serif" font-size="8.00">PixelYA8</text>+</g>+<!-- PixelYA8->PixelRGB8 -->+<g id="edge12" class="edge"><title>PixelYA8->PixelRGB8</title>+<path fill="none" stroke="black" d="M464.762,-194.278C489.627,-203.81 525.815,-222.121 542,-252 574.918,-312.769 554.613,-358.387 501,-402 484.752,-415.217 355.671,-428.373 287.639,-434.476"/>+<polygon fill="black" stroke="black" points="287.321,-430.99 277.669,-435.359 287.938,-437.963 287.321,-430.99"/>+<text text-anchor="middle" x="580" y="-310.3" font-family="sans-serif" font-size="8.00">promotePixel</text>+</g>+<!-- PixelYA8->PixelRGBA8 -->+<g id="edge10" class="edge"><title>PixelYA8->PixelRGBA8</title>+<path fill="none" stroke="black" d="M460.035,-198.84C467.824,-204.817 475.781,-212.68 480,-222 497.598,-260.871 496.093,-278.904 479,-318 476.725,-323.204 473.295,-328.013 469.411,-332.309"/>+<polygon fill="black" stroke="black" points="466.792,-329.975 462.079,-339.464 471.681,-334.985 466.792,-329.975"/>+<text text-anchor="middle" x="515" y="-268.3" font-family="sans-serif" font-size="8.00">promotePixel</text>+</g>+<!-- PixelYA8->Pixel8 -->+<g id="edge50" class="edge"><title>PixelYA8->Pixel8</title>+<path fill="none" stroke="black" d="M412.102,-193.018C393.267,-198.789 368.145,-208.269 349,-222 340.05,-228.419 331.808,-237.196 325.098,-245.49"/>+<polygon fill="black" stroke="black" points="322.247,-243.456 318.929,-253.518 327.797,-247.721 322.247,-243.456"/>+<text text-anchor="middle" x="383" y="-226.3" font-family="sans-serif" font-size="8.00">dropTransparency</text>+</g>+<!-- PixelYA8->Pixel8 -->+<g id="edge62" class="edge"><title>PixelYA8->Pixel8</title>+<path fill="none" stroke="black" d="M434.558,-204.131C431.244,-214.175 425.744,-226.175 417,-234 397.039,-251.864 367.99,-260.893 344.899,-265.441"/>+<polygon fill="black" stroke="black" points="344.002,-262.045 334.763,-267.23 345.219,-268.938 344.002,-262.045"/>+<text text-anchor="middle" x="451" y="-226.3" font-family="sans-serif" font-size="8.00">computeLuma</text>+</g>+<!-- PixelF->PixelRGBF -->+<g id="edge14" class="edge"><title>PixelF->PixelRGBF</title>+<path fill="none" stroke="black" d="M100.45,-168.312C96.8232,-158.653 91.3784,-146.886 84,-138 78.5966,-131.492 71.7476,-125.606 64.8367,-120.568"/>+<polygon fill="black" stroke="black" points="66.3898,-117.392 56.1539,-114.657 62.4504,-123.178 66.3898,-117.392"/>+<text text-anchor="middle" x="115" y="-142.3" font-family="sans-serif" font-size="8.00">promotePixel</text>+</g>+<!-- Pixel16->PixelRGB16 -->+<g id="edge18" class="edge"><title>Pixel16->PixelRGB16</title>+<path fill="none" stroke="black" d="M313.43,-172.019C326.239,-162.023 344.257,-148.532 361,-138 371.689,-131.277 383.756,-124.61 394.725,-118.875"/>+<polygon fill="black" stroke="black" points="396.336,-121.982 403.628,-114.295 393.134,-115.757 396.336,-121.982"/>+<text text-anchor="middle" x="384" y="-142.3" font-family="sans-serif" font-size="8.00">promotePixel</text>+</g>+<!-- Pixel16->PixelRGBA16 -->+<g id="edge16" class="edge"><title>Pixel16->PixelRGBA16</title>+<path fill="none" stroke="black" d="M305.084,-168.741C307.903,-162.903 310.835,-156.272 313,-150 325.048,-115.091 333.609,-73.3023 338.424,-46.2333"/>+<polygon fill="black" stroke="black" points="341.89,-46.732 340.143,-36.2822 334.992,-45.5405 341.89,-46.732"/>+<text text-anchor="middle" x="353" y="-100.3" font-family="sans-serif" font-size="8.00">promotePixel</text>+</g>+<!-- Pixel16->PixelYA16 -->+<g id="edge20" class="edge"><title>Pixel16->PixelYA16</title>+<path fill="none" stroke="black" d="M280.78,-170.7C275.635,-164.701 270.35,-157.469 267,-150 264.277,-143.93 262.473,-137.052 261.279,-130.438"/>+<polygon fill="black" stroke="black" points="264.727,-129.827 259.862,-120.415 257.796,-130.807 264.727,-129.827"/>+<text text-anchor="middle" x="290" y="-142.3" font-family="sans-serif" font-size="8.00">promotePixel</text>+</g>+<!-- Pixel8->PixelRGB8 -->+<g id="edge24" class="edge"><title>Pixel8->PixelRGB8</title>+<path fill="none" stroke="black" d="M312.919,-288.095C319.136,-314.953 326.712,-366.057 306,-402 300.622,-411.332 291.773,-418.518 282.501,-423.926"/>+<polygon fill="black" stroke="black" points="280.83,-420.85 273.581,-428.577 284.067,-427.057 280.83,-420.85"/>+<text text-anchor="middle" x="342" y="-352.3" font-family="sans-serif" font-size="8.00">promotePixel</text>+</g>+<!-- Pixel8->PixelRGBA8 -->+<g id="edge22" class="edge"><title>Pixel8->PixelRGBA8</title>+<path fill="none" stroke="black" d="M334.939,-272.185C360.073,-275.561 397.177,-284.15 421,-306 426.855,-311.37 431.048,-318.72 434.03,-326.054"/>+<polygon fill="black" stroke="black" points="430.836,-327.531 437.387,-335.858 437.458,-325.263 430.836,-327.531"/>+<text text-anchor="middle" x="452" y="-310.3" font-family="sans-serif" font-size="8.00">promotePixel</text>+</g>+<!-- Pixel8->PixelYA8 -->+<g id="edge26" class="edge"><title>Pixel8->PixelYA8</title>+<path fill="none" stroke="black" d="M290.77,-255.865C280.734,-245.919 271.57,-232.553 280,-222 287.559,-212.538 356.092,-199.635 400.682,-192.122"/>+<polygon fill="black" stroke="black" points="401.335,-195.562 410.626,-190.469 400.187,-188.657 401.335,-195.562"/>+<text text-anchor="middle" x="303" y="-226.3" font-family="sans-serif" font-size="8.00">promotePixel</text>+</g>+<!-- Pixel8->PixelF -->+<g id="edge28" class="edge"><title>Pixel8->PixelF</title>+<path fill="none" stroke="black" d="M281.145,-267.812C249.741,-264.331 196.969,-255.554 157,-234 145.577,-227.84 134.814,-218.473 126.146,-209.643"/>+<polygon fill="black" stroke="black" points="128.492,-207.028 119.104,-202.117 123.381,-211.81 128.492,-207.028"/>+<text text-anchor="middle" x="180" y="-226.3" font-family="sans-serif" font-size="8.00">promotePixel</text>+</g>+<!-- Pixel8->Pixel16 -->+<g id="edge30" class="edge"><title>Pixel8->Pixel16</title>+<path fill="none" stroke="black" d="M282.206,-263.957C256.398,-257.327 219.359,-246.018 211,-234 196.025,-212.469 230.216,-199.429 259.604,-192.482"/>+<polygon fill="black" stroke="black" points="260.687,-195.828 269.71,-190.275 259.193,-188.989 260.687,-195.828"/>+<text text-anchor="middle" x="234" y="-226.3" font-family="sans-serif" font-size="8.00">promotePixel</text>+</g>+<!-- PixelCMYK16->PixelRGB16 -->+<g id="edge32" class="edge"><title>PixelCMYK16->PixelRGB16</title>+<path fill="none" stroke="black" d="M564.03,-35.4654C568.707,-45.2873 571.707,-57.2873 565,-66 553.678,-80.7091 507.858,-90.6423 472.496,-96.2857"/>+<polygon fill="black" stroke="black" points="471.73,-92.862 462.373,-97.8319 472.787,-99.7817 471.73,-92.862"/>+<text text-anchor="middle" x="590" y="-58.3" font-family="sans-serif" font-size="8.00">convertPixel</text>+</g>+<!-- PixelCMYK8->PixelRGB8 -->+<g id="edge34" class="edge"><title>PixelCMYK8->PixelRGB8</title>+<path fill="none" stroke="black" d="M257.173,-372.299C258.645,-381.28 259.584,-392.241 258,-402 257.535,-404.865 256.875,-407.811 256.1,-410.724"/>+<polygon fill="black" stroke="black" points="252.737,-409.753 253.143,-420.34 259.427,-411.811 252.737,-409.753"/>+<text text-anchor="middle" x="280" y="-394.3" font-family="sans-serif" font-size="8.00">convertPixel</text>+</g>+<!-- PixelYCbCr8->PixelRGB8 -->+<g id="edge36" class="edge"><title>PixelYCbCr8->PixelRGB8</title>+<path fill="none" stroke="black" d="M149.836,-372.162C150.645,-381.97 153.174,-393.727 160,-402 171.431,-415.854 189.112,-424.462 205.475,-429.768"/>+<polygon fill="black" stroke="black" points="204.847,-433.23 215.426,-432.663 206.802,-426.509 204.847,-433.23"/>+<text text-anchor="middle" x="182" y="-394.3" font-family="sans-serif" font-size="8.00">convertPixel</text>+</g>+<!-- PixelYCbCr8->Pixel8 -->+<g id="edge54" class="edge"><title>PixelYCbCr8->Pixel8</title>+<path fill="none" stroke="black" d="M174.688,-340.875C202.411,-326.136 247.432,-302.201 277.376,-286.281"/>+<polygon fill="black" stroke="black" points="279.34,-289.201 286.527,-281.416 276.054,-283.02 279.34,-289.201"/>+<text text-anchor="middle" x="263" y="-310.3" font-family="sans-serif" font-size="8.00">computeLuma</text>+</g>+</g>+</svg>
src/Codec/Picture/Bitmap.hs view
@@ -517,7 +517,11 @@ -- | Helper method to cast a 'B.ByteString' to a 'VS.Vector' of some type. castByteString :: VS.Storable a => B.ByteString -> VS.Vector a +#if MIN_VERSION_bytestring(0,11,0) +castByteString (BI.BS fp len) = VS.unsafeCast $ VS.unsafeFromForeignPtr fp 0 len +#else castByteString (BI.PS fp offset len) = VS.unsafeCast $ VS.unsafeFromForeignPtr fp offset len +#endif decodeImageRGBA8 :: RGBABmpFormat -> BmpV5Header -> B.ByteString -> Image PixelRGBA8 decodeImageRGBA8 pixelFormat (BmpV5Header { width = w, height = h, bitPerPixel = bpp }) str = Image wi hi stArray where
src/Codec/Picture/ColorQuant.hs view
@@ -278,7 +278,7 @@ -- Based on the OCaml implementation: -- http://rosettacode.org/wiki/Color_quantization --- which is in turn based on: www.leptonica.com/papers/mediancut.pdf. +-- which is in turn based on: www.leptonica.org/papers/mediancut.pdf. -- We use the product of volume and population to determine the next cluster -- to split and determine the placement of each color by compating it to the -- mean of the parent cluster. So median cut is a bit of a misnomer, since one
src/Codec/Picture/ConvGraph.hs view
@@ -1,10 +1,10 @@--- $graph --- --- The following graph describe the differents way to convert between pixel types, --- --- * Nodes describe pixel type --- --- * Arrows describe functions --- --- <<docimages/pixelgraph.svg>> --- +-- $graph+--+-- The following graph describe the differents way to convert between pixel types,+--+-- * Nodes describe pixel type+--+-- * Arrows describe functions+--+-- <<docimages/pixelgraph.svg>>+--
src/Codec/Picture/InternalHelper.hs view
@@ -13,39 +13,20 @@ ) import qualified Data.Binary.Get as G -#if MIN_VERSION_binary(0,6,4) -#else -import Control.Applicative( (<$>) ) -import qualified Control.Exception as E --- I feel so dirty. :( -import System.IO.Unsafe( unsafePerformIO ) -#endif - decode :: (Binary a) => B.ByteString -> Either String a decode = runGetStrict get runGet :: Get a -> L.ByteString -> Either String a -#if MIN_VERSION_binary(0,6,4) runGet act = unpack . G.runGetOrFail act where unpack (Left (_, _, str)) = Left str unpack (Right (_, _, element)) = Right element -#else -runGet act str = unsafePerformIO $ E.catch - (Right <$> E.evaluate (G.runGet act str)) - (\msg -> return . Left $ show (msg :: E.SomeException)) -#endif runGetStrict :: Get a -> B.ByteString -> Either String a runGetStrict act buffer = runGet act $ L.fromChunks [buffer] getRemainingBytes :: Get B.ByteString -getRemainingBytes = do - rest <- getRemainingLazyByteString - return $ case L.toChunks rest of - [] -> B.empty - [a] -> a - lst -> B.concat lst +getRemainingBytes = L.toStrict <$> getRemainingLazyByteString getRemainingLazyBytes :: Get L.ByteString -getRemainingLazyBytes = getRemainingLazyByteString +getRemainingLazyBytes = getRemainingLazyByteString
src/Codec/Picture/Jpg.hs view
@@ -422,9 +422,16 @@ forM_ lst $ \(params, str) -> do let componentsInfo = V.fromList params compReader = initBoolStateJpg . B.concat $ L.toChunks str - maxiW = maximum [fst $ subSampling c | (c,_) <- params] - maxiH = maximum [snd $ subSampling c | (c,_) <- params] + maxiSubSampW = maximum [fst $ subSampling c | (c,_) <- params] + maxiSubSampH = maximum [snd $ subSampling c | (c,_) <- params] + (maxiW, maxiH) = + if length params > 1 then + (maximum [componentWidth c | (c,_) <- params], + maximum [componentHeight c | (c,_) <- params]) + else + (maxiSubSampW, maxiSubSampH) + imageBlockWidth = toBlockSize imgWidth imageBlockHeight = toBlockSize imgHeight @@ -574,7 +581,7 @@ let (st, arr) = decodeBaseline jfifMeta = foldMap extractMetadatas $ app0JFifMarker st exifMeta = foldMap extractTiffMetadata $ app1ExifMarker st - meta = sizeMeta <> jfifMeta <> exifMeta + meta = jfifMeta <> exifMeta <> sizeMeta in (, meta) <$> dynamicOfColorSpace (colorSpaceOfState st) imgWidth imgHeight arr @@ -582,7 +589,7 @@ let (st, arr) = decodeProgressive jfifMeta = foldMap extractMetadatas $ app0JFifMarker st exifMeta = foldMap extractTiffMetadata $ app1ExifMarker st - meta = sizeMeta <> jfifMeta <> exifMeta + meta = jfifMeta <> exifMeta <> sizeMeta in (, meta) <$> dynamicOfColorSpace (colorSpaceOfState st) imgWidth imgHeight arr
src/Codec/Picture/Jpg/Internal/DefaultTable.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE FlexibleContexts #-} -- | Module used by the jpeg decoder internally, shouldn't be used @@ -33,6 +34,7 @@ , defaultDcLumaHuffmanTable ) where +import Control.DeepSeq( NFData(..) ) import Data.Int( Int16 ) import Foreign.Storable ( Storable ) import Control.Monad.ST( runST ) @@ -42,6 +44,7 @@ import Data.Word( Word8, Word16 ) import Data.List( foldl' ) import qualified Data.Vector.Storable.Mutable as M +import GHC.Generics( Generic ) import Codec.Picture.BitWriter @@ -108,7 +111,8 @@ -- | Enumeration used to search in the tables for different components. data DctComponent = DcComponent | AcComponent - deriving (Eq, Show) + deriving (Eq, Show, Generic) +instance NFData DctComponent -- | Transform parsed coefficients from the jpeg header to a -- tree which can be used to decode data.
src/Codec/Picture/Jpg/Internal/Types.hs view
@@ -1,6 +1,13 @@+{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE CPP #-} + +-- | A good explanation of the JPEG format, including diagrams, is given at: +-- <https://github.com/corkami/formats/blob/master/image/jpeg.md> +-- +-- The full spec (excluding EXIF): https://www.w3.org/Graphics/JPEG/itu-t81.pdf module Codec.Picture.Jpg.Internal.Types( MutableMacroBlock , createEmptyMutableMacroBlock , printMacroBlock @@ -21,8 +28,19 @@ , JpgAdobeApp14( .. ) , JpgJFIFApp0( .. ) , JFifUnit( .. ) + , TableList( .. ) + , RestartInterval( .. ) + , getJpgImage , calculateSize , dctBlockSize + , parseECS + , parseECS_simple + , skipUntilFrames + , skipFrameMarker + , parseFrameOfKind + , parseFrames + , parseFrameKinds + , parseToFirstFrameHeader ) where @@ -30,10 +48,13 @@ import Control.Applicative( pure, (<*>), (<$>) ) #endif +import Control.DeepSeq( NFData(..) ) import Control.Monad( when, replicateM, forM, forM_, unless ) import Control.Monad.ST( ST ) import Data.Bits( (.|.), (.&.), unsafeShiftL, unsafeShiftR ) import Data.List( partition ) +import Data.Maybe( maybeToList ) +import GHC.Generics( Generic ) #if !MIN_VERSION_base(4,11,0) import Data.Monoid( (<>) ) @@ -48,8 +69,9 @@ import qualified Data.ByteString as B import qualified Data.ByteString.Char8 as BC import qualified Data.ByteString.Lazy as L +import qualified Data.ByteString.Unsafe as BU -import Data.Int( Int16 ) +import Data.Int( Int16, Int64 ) import Data.Word(Word8, Word16 ) import Data.Binary( Binary(..) ) @@ -59,7 +81,11 @@ , getByteString , skip , bytesRead + , lookAhead + , ByteOffset + , getLazyByteString ) +import qualified Data.Binary.Get.Internal as GetInternal import Data.Binary.Put( Put , putWord8 @@ -75,7 +101,6 @@ import Codec.Picture.Tiff.Internal.Metadata( exifOffsetIfd ) import Codec.Picture.Metadata.Exif -{-import Debug.Trace-} import Text.Printf -- | Type only used to make clear what kind of integer we are carrying @@ -108,7 +133,8 @@ | JpgRestartInterval | JpgRestartIntervalEnd Word8 - deriving (Eq, Show) + deriving (Eq, Show, Generic) +instance NFData JpgFrameKind data JpgFrame = JpgAppFrame !Word8 B.ByteString @@ -118,10 +144,11 @@ | JpgExtension !Word8 B.ByteString | JpgQuantTable ![JpgQuantTableSpec] | JpgHuffmanTable ![(JpgHuffmanTableSpec, HuffmanPackedTree)] - | JpgScanBlob !JpgScanHeader !L.ByteString + | JpgScanBlob !JpgScanHeader !L.ByteString -- ^ The @ByteString@ is the ECS (Entropy-Coded Segment), typically the largest part of compressed image data. | JpgScans !JpgFrameKind !JpgFrameHeader | JpgIntervalRestart !Word16 - deriving Show + deriving (Eq, Show, Generic) +instance NFData JpgFrame data JpgColorSpace = JpgColorSpaceYCbCr @@ -133,13 +160,15 @@ | JpgColorSpaceCMYK | JpgColorSpaceRGB | JpgColorSpaceRGBA - deriving Show + deriving (Eq, Show, Generic) +instance NFData JpgColorSpace data AdobeTransform = AdobeUnknown -- ^ Value 0 | AdobeYCbCr -- ^ value 1 | AdobeYCck -- ^ value 2 - deriving Show + deriving (Eq, Show, Generic) +instance NFData AdobeTransform data JpgAdobeApp14 = JpgAdobeApp14 { _adobeDctVersion :: !Word16 @@ -147,14 +176,16 @@ , _adobeFlag1 :: !Word16 , _adobeTransform :: !AdobeTransform } - deriving Show + deriving (Eq, Show, Generic) +instance NFData JpgAdobeApp14 -- | Size: 1 data JFifUnit = JFifUnitUnknown -- ^ 0 | JFifPixelsPerInch -- ^ 1 | JFifPixelsPerCentimeter -- ^ 2 - deriving Show + deriving (Eq, Show, Generic) +instance NFData JFifUnit instance Binary JFifUnit where put v = putWord8 $ case v of @@ -175,7 +206,8 @@ , _jfifDpiY :: !Word16 , _jfifThumbnail :: !(Maybe {- (Image PixelRGB8) -} Int) } - deriving Show + deriving (Eq, Show, Generic) +instance NFData JpgJFIFApp0 instance Binary JpgJFIFApp0 where get = do @@ -260,7 +292,8 @@ , jpgImageComponentCount :: !Word8 , jpgComponents :: ![JpgComponent] } - deriving Show + deriving (Eq, Show, Generic) +instance NFData JpgFrameHeader instance SizeCalculable JpgFrameHeader where @@ -275,13 +308,15 @@ , verticalSamplingFactor :: !Word8 , quantizationTableDest :: !Word8 } - deriving Show + deriving (Eq, Show, Generic) +instance NFData JpgComponent instance SizeCalculable JpgComponent where calculateSize _ = 3 data JpgImage = JpgImage { jpgFrame :: [JpgFrame] } - deriving Show + deriving (Eq, Show, Generic) +instance NFData JpgImage data JpgScanSpecification = JpgScanSpecification { componentSelector :: !Word8 @@ -291,7 +326,8 @@ , acEntropyCodingTable :: !Word8 } - deriving Show + deriving (Eq, Show, Generic) +instance NFData JpgScanSpecification instance SizeCalculable JpgScanSpecification where calculateSize _ = 2 @@ -310,7 +346,8 @@ -- | Encoded as 4 bits , successiveApproxLow :: !Word8 } - deriving Show + deriving (Eq, Show, Generic) +instance NFData JpgScanHeader instance SizeCalculable JpgScanHeader where calculateSize hdr = 2 + 1 @@ -327,7 +364,8 @@ , quantTable :: MacroBlock Int16 } - deriving Show + deriving (Eq, Show, Generic) +instance NFData JpgQuantTableSpec class SizeCalculable a where calculateSize :: a -> Int @@ -382,7 +420,8 @@ , huffSizes :: !(VU.Vector Word8) , huffCodes :: !(V.Vector (VU.Vector Word8)) } - deriving Show + deriving (Eq, Show, Generic) +instance NFData JpgHuffmanTableSpec instance SizeCalculable JpgHuffmanTableSpec where calculateSize table = 1 + 16 + sum [fromIntegral e | e <- VU.toList $ huffSizes table] @@ -416,15 +455,28 @@ putWord8 0xFF >> putWord8 0xD8 >> mapM_ putFrame frames >> putWord8 0xFF >> putWord8 0xD9 + -- | Consider using `getJpgImage` instead for a non-semi-lazy implementation. get = do - let startOfImageMarker = 0xD8 - -- endOfImageMarker = 0xD9 - checkMarker commonMarkerFirstByte startOfImageMarker - eatUntilCode - frames <- parseFrames + skipUntilFrames + frames <- parseFramesSemiLazy + -- let endOfImageMarker = 0xD9 {-checkMarker commonMarkerFirstByte endOfImageMarker-} return JpgImage { jpgFrame = frames } +-- | Like `get` from `instance Binary JpgImage`, but without the legacy +-- semi-lazy implementation. +getJpgImage :: Get JpgImage +getJpgImage = do + skipUntilFrames + frames <- parseFrames + return JpgImage { jpgFrame = frames } + +skipUntilFrames :: Get () +skipUntilFrames = do + let startOfImageMarker = 0xD8 + checkMarker commonMarkerFirstByte startOfImageMarker + eatUntilCode + eatUntilCode :: Get () eatUntilCode = do code <- getWord8 @@ -436,7 +488,7 @@ getByteString (fromIntegral size - 2) putFrame :: JpgFrame -> Put -putFrame (JpgAdobeAPP14 adobe) = +putFrame (JpgAdobeAPP14 adobe) = put (JpgAppSegment 14) >> putWord16be 14 >> put adobe putFrame (JpgJFIF jfif) = put (JpgAppSegment 0) >> putWord16be (14+2) >> put jfif @@ -469,41 +521,162 @@ when (rb1 /= b1 || rb2 /= b2) (fail "Invalid marker used") -extractScanContent :: L.ByteString -> (L.ByteString, L.ByteString) -extractScanContent str = aux 0 - where maxi = fromIntegral $ L.length str - 1 +-- | Simpler implementation of `parseECS` to allow an easier understanding +-- of the logic, and to provide a comparison for correctness. +parseECS_simple :: Get L.ByteString +parseECS_simple = do + -- There's no efficient way in `binary` to parse byte-by-byte while assembling a + -- resulting ByteString (without using `.Internal` modules, which is what + -- `parseECS` does), so instead first compute the length of the content + -- byte-by-byte inside a `lookAhead` (not advancing the parser offset), and + -- then efficiently take that long a ByteString (advancing the parser offset). + -- + -- This is still slow compared to `parseECS` because parser functions + -- (`getWord8`) are used repeatedly, instead of plain loops over ByteString contents. + -- The slowdown is ~2x on GHC 8.10.7 on an Intel Core i7-7500U. + n <- lookAhead getContentLength + getLazyByteString n + where + getContentLength :: Get ByteOffset + getContentLength = do + bytesReadBeforeContent <- bytesRead + let loop :: Word8 -> Get ByteOffset + loop !v = do + vNext <- getWord8 + let isReset = 0xD0 <= vNext && vNext <= 0xD7 + let vIsSegmentMarker = v == 0xFF && vNext /= 0 && not isReset + if not vIsSegmentMarker + then loop vNext + else do + bytesReadAfterContentPlus2 <- bytesRead -- "plus 2" because we've also read the segment marker (0xFF and `vNext`) + let !contentLength = (bytesReadAfterContentPlus2 - 2) - bytesReadBeforeContent + return contentLength - aux n | n >= maxi = (str, L.empty) - | v == 0xFF && vNext /= 0 && not isReset = L.splitAt n str - | otherwise = aux (n + 1) - where v = str `L.index` n - vNext = str `L.index` (n + 1) - isReset = 0xD0 <= vNext && vNext <= 0xD7 + v_first <- getWord8 + loop v_first -parseAdobe14 :: B.ByteString -> [JpgFrame] -> [JpgFrame] -parseAdobe14 str lst = go where - go = case runGetStrict get str of - Left _err -> lst - Right app14 -> JpgAdobeAPP14 app14 : lst +-- Replace by `Data.ByteString.dropEnd` once we require `bytestring >= 0.11.1.0`. +bsDropEnd :: Int -> B.ByteString -> B.ByteString +bsDropEnd n bs + | n <= 0 = bs + | n >= len = B.empty + | otherwise = B.take (len - 1) bs + where + len = B.length bs +{-# INLINE bsDropEnd #-} +-- | Parses a Scan's ECS (Entropy-Coded Segment, the largest part of compressed image data) +-- from the `Get` stream. +-- +-- When this function is called, the parser's offset should be +-- immediately behind the SOS tag. +-- +-- As described on e.g. https://www.ccoderun.ca/programming/2017-01-31_jpeg/, +-- +-- > To find the next segment after the SOS, you must keep reading until you +-- > find a 0xFF bytes which is not immediately followed by 0x00 (see "byte stuffing") +-- > [or a reset marker's byte: 0xD0 through 0xD7]. +-- > Normally, this will be the EOI segment that comes at the end of the file. +-- +-- where the 0xFF is the next segment's marker. +-- See https://github.com/corkami/formats/blob/master/image/jpeg.md#entropy-coded-segment +-- for more details. +-- +-- This function returns the ECS, not including the next segment's +-- marker on its trailing end. +parseECS :: Get L.ByteString +parseECS = do + -- For a simpler but slower implementation of this function, see + -- `parseECS_simple`. + + v_first <- getWord8 + -- TODO: Compare with what `scan` from `binary-parsers` does. + -- Probably we cannot use it because it does not allow us to set the parser state + -- to be _before_ the segment marker which would be convenient to not have to + -- make a special case the function that calls this function. + -- But `scan` works on pointers into the bytestring chunks. Why, for performance? + -- I've asked on https://github.com/winterland1989/binary-parsers/issues/7 + -- If that is for performance, we may want to replicate the same thing here. + -- + -- An orthogonal idea is to use `Data.ByteString.elemIndex` to fast-forward + -- to the next 0xFF using `memchr`, but the `unsafe` call to `memchr` might + -- have too much overhead, since 0xFF bytes appear statistically every 256 bytes. + -- See https://stackoverflow.com/questions/14519905/how-much-does-it-cost-for-haskell-ffi-to-go-into-c-and-back + + -- `withInputChunks` allows us to work on chunks of ByteStrings, + -- reducing the number of higher-overhead `Get` functions called. + -- It also allows to easily assemble the ByteString to return, + -- which may be cross-chunk. + -- `withInputChunks` terminates when we return a + -- Right (consumed :: ByteString, unconsumed :: ByteString) + -- from `consumeChunk`, setting the `Get` parser's offset to just before `unconsumed`. + -- Because the segment marker we seek may be the 2 bytes across chunk boundaries, + -- we need to keep a reference to the previous chunk (initialised as `B.empty`), + -- so that we can set `consumed` properly, because this function is supposed + -- to not consume the start of the segment marker (see code dropping the last + -- byte of the previous chunk below). + GetInternal.withInputChunks + (v_first, B.empty) + consumeChunk + ( L.fromChunks . (B.singleton v_first :)) -- `v_first` also belongs to the returned BS + (return . L.fromChunks . (B.singleton v_first :)) -- `v_first` also belongs to the returned BS + where + consumeChunk :: GetInternal.Consume (Word8, B.ByteString) -- which is: (Word8, B.ByteString) -> B.ByteString -> Either (Word8, B.ByteString) (B.ByteString, B.ByteString) + consumeChunk (!v_chunk_start, !prev_chunk) !chunk + -- If `withInputChunks` hands us an empty chunk (which `binary` probably + -- won't do, but since that's not documented, handle it anyway) then skip over it, + -- so that we always remember the last `prev_chunk` that actually has data in it, + -- since we `bsDropEnd 1 prev_chunk` in the `case` below. + | B.null chunk = Left (v_chunk_start, prev_chunk) + | otherwise = loop v_chunk_start 0 + where + loop :: Word8 -> Int -> Either (Word8, B.ByteString) (B.ByteString, B.ByteString) + loop !v !offset_in_chunk + | offset_in_chunk >= B.length chunk = Left (v, chunk) + | otherwise = + let !vNext = BU.unsafeIndex chunk offset_in_chunk -- bounds check is done above + !isReset = 0xD0 <= vNext && vNext <= 0xD7 + !vIsSegmentMarker = v == 0xFF && vNext /= 0 && not isReset + in + if not vIsSegmentMarker + then loop vNext (offset_in_chunk+1) + else + -- Set the parser state to _before_ the segment marker. + -- The first case, where the segment marker's 2 bytes are exactly + -- at the chunk boundary, requires us to allocate a new BS with + -- `B.cons`; luckily this case should be rare. + let (!consumed, !unconsumed) = case () of + () | offset_in_chunk == 0 -> (bsDropEnd 1 prev_chunk, v `B.cons` chunk) -- segment marker starts at `v`, which is the last byte of the previous chunk + | offset_in_chunk == 1 -> (B.empty, chunk) -- segment marker starts exactly at `chunk` + | otherwise -> B.splitAt (offset_in_chunk - 1) chunk -- segment marker starts at `v`, which is 1 before `vNext` (which is at `offset_in_chunk`) + in Right $! (consumed, unconsumed) + + + +parseAdobe14 :: B.ByteString -> Maybe JpgFrame +parseAdobe14 str = case runGetStrict get str of + Left _err -> Nothing + Right app14 -> Just $! JpgAdobeAPP14 app14 + -- | Parse JFIF or JFXX information. Right now only JFIF. -parseJF__ :: B.ByteString -> [JpgFrame] -> [JpgFrame] -parseJF__ str lst = go where - go = case runGetStrict get str of - Left _err -> lst - Right jfif -> JpgJFIF jfif : lst +parseJF__ :: B.ByteString -> Maybe JpgFrame +parseJF__ str = case runGetStrict get str of + Left _err -> Nothing + Right jfif -> Just $! JpgJFIF jfif -parseExif :: B.ByteString -> [JpgFrame] -> [JpgFrame] -parseExif str lst - | exifHeader `B.isPrefixOf` str = go - | otherwise = lst +parseExif :: B.ByteString -> Maybe JpgFrame +parseExif str + | exifHeader `B.isPrefixOf` str = + let + tiff = B.drop (B.length exifHeader) str + in + case runGetStrict (getP tiff) tiff of + Left _err -> Nothing + Right (_hdr :: TiffHeader, []) -> Nothing + Right (_hdr :: TiffHeader, ifds : _) -> Just $! JpgExif ifds + | otherwise = Nothing where exifHeader = BC.pack "Exif\0\0" - tiff = B.drop (B.length exifHeader) str - go = case runGetStrict (getP tiff) tiff of - Left _err -> lst - Right (_hdr :: TiffHeader, []) -> lst - Right (_hdr :: TiffHeader, ifds : _) -> JpgExif ifds : lst putExif :: [ImageFileDirectory] -> Put putExif ifds = putAll where @@ -515,7 +688,7 @@ ifdList = case partition (isInIFD0 . ifdIdentifier) ifds of (ifd0, []) -> [ifd0] (ifd0, ifdExif) -> [ifd0 <> pure exifOffsetIfd, ifdExif] - + exifBlob = runPut $ do putByteString $ BC.pack "Exif\0\0" putP BC.empty (hdr, ifdList) @@ -525,48 +698,191 @@ putWord16be . fromIntegral $ L.length exifBlob + 2 putLazyByteString exifBlob -parseFrames :: Get [JpgFrame] -parseFrames = do - kind <- get - let parseNextFrame = do - word <- getWord8 - when (word /= 0xFF) $ do - readedData <- bytesRead - fail $ "Invalid Frame marker (" ++ show word - ++ ", bytes read : " ++ show readedData ++ ")" - parseFrames +skipFrameMarker :: Get () +skipFrameMarker = do + word <- getWord8 + when (word /= 0xFF) $ do + readedData <- bytesRead + fail $ "Invalid Frame marker (" ++ show word + ++ ", bytes read : " ++ show readedData ++ ")" +-- | Parses a single frame. +-- +-- Returns `Nothing` when we encounter a frame we want to skip. +parseFrameOfKind :: JpgFrameKind -> Get (Maybe JpgFrame) +parseFrameOfKind kind = do case kind of - JpgEndOfImage -> return [] - JpgAppSegment 0 -> - parseJF__ <$> takeCurrentFrame <*> parseNextFrame - JpgAppSegment 1 -> - parseExif <$> takeCurrentFrame <*> parseNextFrame - JpgAppSegment 14 -> - parseAdobe14 <$> takeCurrentFrame <*> parseNextFrame - JpgAppSegment c -> - (\frm lst -> JpgAppFrame c frm : lst) <$> takeCurrentFrame <*> parseNextFrame - JpgExtensionSegment c -> - (\frm lst -> JpgExtension c frm : lst) <$> takeCurrentFrame <*> parseNextFrame + JpgEndOfImage -> return Nothing + JpgAppSegment 0 -> parseJF__ <$> takeCurrentFrame + JpgAppSegment 1 -> parseExif <$> takeCurrentFrame + JpgAppSegment 14 -> parseAdobe14 <$> takeCurrentFrame + JpgAppSegment c -> Just . JpgAppFrame c <$> takeCurrentFrame + JpgExtensionSegment c -> Just . JpgExtension c <$> takeCurrentFrame JpgQuantizationTable -> - (\(TableList quants) lst -> JpgQuantTable quants : lst) <$> get <*> parseNextFrame + (\(TableList quants) -> Just $! JpgQuantTable quants) <$> get JpgRestartInterval -> - (\(RestartInterval i) lst -> JpgIntervalRestart i : lst) <$> get <*> parseNextFrame + (\(RestartInterval i) -> Just $! JpgIntervalRestart i) <$> get JpgHuffmanTableMarker -> - (\(TableList huffTables) lst -> - JpgHuffmanTable [(t, packHuffmanTree . buildPackedHuffmanTree $ huffCodes t) | t <- huffTables] : lst) - <$> get <*> parseNextFrame - JpgStartOfScan -> - (\frm imgData -> - let (d, other) = extractScanContent imgData - in - case runGet parseFrames (L.drop 1 other) of - Left _ -> [JpgScanBlob frm d] - Right lst -> JpgScanBlob frm d : lst - ) <$> get <*> getRemainingLazyBytes + (\(TableList huffTables) -> Just $! + JpgHuffmanTable [(t, packHuffmanTree . buildPackedHuffmanTree $ huffCodes t) | t <- huffTables]) + <$> get + JpgStartOfScan -> do + scanHeader <- get + ecs <- parseECS + return $! Just $! JpgScanBlob scanHeader ecs + _ -> Just . JpgScans kind <$> get - _ -> (\hdr lst -> JpgScans kind hdr : lst) <$> get <*> parseNextFrame +-- | Parse a list of `JpgFrame`s. +-- +-- This function has various quirks; consider the below with great caution +-- when using this function. +-- +-- While @data JpgFrame = ... | JpgScanBlob !...` itself has strict fields, +-- +-- This function is written in such a way that that it can construct +-- the @[JpgFrame]@ "lazily" such that the expensive byte-by-byte traversal +-- in `parseECS` to create a `JpgScanBlob` can be avoided if only +-- list elements before that `JpgScanBlob` are evaluated. +-- +-- That means the user can write code such as +-- +-- > let mbFirstScan = +-- > case runGetOrFail (get @JPG.JpgImage) hugeImageByteString of -- (`get @JPG.JpgImage` uses `parseFramesSemiLazy`) +-- > Right (_restBs, _offset, res) -> +-- > find (\frame -> case frame of { JPG.JpgScans{} -> True; _ -> False }) (JPG.jpgFrame res) +-- +-- with the guarantee that only the bytes before the ECS (large compressed image data) +-- will be inspected, assuming that indeed there is at least 1 `JpgScan` in front +-- of the `JpgScanBlob` that contains the ECS. +-- +-- This guarantee can be useful to e.g. quickly read just the image +-- dimensions (width, height) without traversing the large data. +-- +-- Also note that this `Get` parser does not correctly maintain the parser byte offset +-- (`Data.Binary.Get.bytesRead`), because as soon as a `JpgStartOfScan` is returned, +-- it uses `Data.Binary.Get.getRemainingLazyBytes` to provide: +-- +-- 1. the laziness described above, and +-- 2. the ability to ignore any parser failure after the first successfully-parsed +-- `JpgScanBlob` (it is debatable whether this behaviour is a desirable behaviour of this +-- library, but it is historically so and existing exposed functions do not break +-- this for backwards compatibility with existing uses of this library). +-- This fact also means that even `parseNextFrameStrict` cannot maintain +-- correct parser byte offsets. +-- +-- Further note that if you are reading a huge JPEG image from disk strictly, +-- this will already incur a full traversal (namely creation) of the `hugeImageByteString`. +-- Thus, `parseNextFrameLazy` only provides any benefit if you: +-- +-- - read the image from disk using lazy IO (not recommended!) such as via +-- `Data.ByteString.Lazy.readFile`, +-- - or do something similar, such as creating the `hugeImageByteString` via @mmap()@ +-- +-- This function is called "semi lazy" because only the first `JpgScanBlob` returned +-- in the `[JpgFrame]` is returned lazily; frames of other types, or multiple +-- `JpgScanBlob`s, are confusingly not dealt with lazily. +-- +-- If as a caller you do not want to deal with any of these quirks, +-- and use proper strict IO and/or via `Data.Binary.Get`'s incremental input interface: +-- +-- - If you want the whole `[JpgFrame]`: use `parseFrames`. +-- - If you want parsing to terminate early as in the example shown above, +-- use in combination with just the right amount of `get :: Get JpgFrameKind`, +-- `parseFrameOfKind`, and `skipFrameMarker`. +parseFramesSemiLazy :: Get [JpgFrame] +parseFramesSemiLazy = do + kind <- get + case kind of + -- The end-of-image case needs to be here because `_ ->` default case below + -- unconditionally uses `skipFrameMarker` which does not exist after `JpgEndOfImage`. + JpgEndOfImage -> pure [] + JpgStartOfScan -> do + scanHeader <- get + remainingBytes <- getRemainingLazyBytes + -- It is after the above `getRemainingLazyBytes` that the `Get` parser lazily succeeds, + -- allowing consumers of `parseFramesSemiLazy` evaluate all `[JpgFrame]` list elements + -- until (excluding) the cons-cell around the `JpgScanBlob ...` we construct below. + + return $ case runGet parseECS remainingBytes of + Left _ -> + -- Construct invalid `JpgScanBlob` even when the compressed JPEG + -- data is truncated or otherwise invalid, because that's what JuicyPixels's + -- `parseFramesSemiLazy` function did in the past, for backwards compat. + [JpgScanBlob scanHeader remainingBytes] + Right ecs -> + JpgScanBlob scanHeader ecs + : + -- TODO Why `drop 1` instead of `runGet (skipFrameMarker *> parseFramesSemiLazy) remainingBytes` that would check that the dropped 1 Byte is really a frame marker? + case runGet parseFramesSemiLazy (L.drop (L.length ecs + 1) remainingBytes) of + -- After we've encountered the first scan blob containing encoded image data, + -- we accept anything else after to fail parsing, ignoring that failure, + -- end emitting no further frames. + -- TODO: Explain why JuicyPixel chose to use this logic, insteaed of failing. + Left _ -> [] + Right remainingFrames -> remainingFrames + _ -> do + mbFrame <- parseFrameOfKind kind + skipFrameMarker + remainingFrames <- parseFramesSemiLazy + return $ maybeToList mbFrame ++ remainingFrames + +-- | Parse a list of `JpgFrame`s. +parseFrames :: Get [JpgFrame] +parseFrames = do + kind <- get + case kind of + JpgEndOfImage -> pure [] + _ -> do + mbFrame <- parseFrameOfKind kind + skipFrameMarker + remainingFrames <- parseFrames + return $ maybeToList mbFrame ++ remainingFrames + +-- | Parse a list of `JpgFrameKind`s with their corresponding offsets and lengths +-- (not counting the segment and frame markers into the lengths). +-- +-- Useful for debugging. +parseFrameKinds :: Get [(JpgFrameKind, Int64, Int64)] +parseFrameKinds = do + kindMarkerOffset :: Int64 <- bytesRead + kind <- get + case kind of + JpgEndOfImage -> pure [(JpgEndOfImage, kindMarkerOffset, 0)] + _ -> do + parserOffsetBefore <- bytesRead + _ <- parseFrameOfKind kind + parserOffsetAfter <- bytesRead + let !segmentLengthWithoutMarker = parserOffsetAfter - parserOffsetBefore + skipFrameMarker + remainingKinds <- parseFrameKinds + return $ (kind, kindMarkerOffset, segmentLengthWithoutMarker):remainingKinds + +-- | Parses forward, returning the first scan header encountered. +-- +-- Should be used after `skipUntilFrames`. +-- +-- Fails parsing when an SOS segment marker (`JpgStartOfScan`, resulting +-- in `JpgScanBlob`) is encountered before an SOF segment marker (that +-- results in `JpgScans` carrying the `JpgFrameHeader`). +parseToFirstFrameHeader :: Get (Maybe JpgFrameHeader) +parseToFirstFrameHeader = do + kind <- get + case kind of + JpgEndOfImage -> return Nothing + JpgStartOfScan -> fail "parseToFirstFrameHeader: Encountered SOS frame marker before frame header that tells its dimensions" + _ -> do + mbFrame <- parseFrameOfKind kind + case mbFrame of + Nothing -> continueSearching + Just frame -> case frame of + JpgScans _ frameHeader -> return $ Just $! frameHeader + _ -> continueSearching + where + continueSearching = do + skipFrameMarker + parseToFirstFrameHeader + buildPackedHuffmanTree :: V.Vector (VU.Vector Word8) -> HuffmanTree buildPackedHuffmanTree = buildHuffmanTree . map VU.toList . V.toList @@ -603,29 +919,29 @@ -- no lookahead :( {-word <- getWord8-} word2 <- getWord8 - return $ case word2 of - 0xC0 -> JpgBaselineDCTHuffman - 0xC1 -> JpgExtendedSequentialDCTHuffman - 0xC2 -> JpgProgressiveDCTHuffman - 0xC3 -> JpgLosslessHuffman - 0xC4 -> JpgHuffmanTableMarker - 0xC5 -> JpgDifferentialSequentialDCTHuffman - 0xC6 -> JpgDifferentialProgressiveDCTHuffman - 0xC7 -> JpgDifferentialLosslessHuffman - 0xC9 -> JpgExtendedSequentialArithmetic - 0xCA -> JpgProgressiveDCTArithmetic - 0xCB -> JpgLosslessArithmetic - 0xCD -> JpgDifferentialSequentialDCTArithmetic - 0xCE -> JpgDifferentialProgressiveDCTArithmetic - 0xCF -> JpgDifferentialLosslessArithmetic - 0xD9 -> JpgEndOfImage - 0xDA -> JpgStartOfScan - 0xDB -> JpgQuantizationTable - 0xDD -> JpgRestartInterval - a | a >= 0xF0 -> JpgExtensionSegment a - | a >= 0xE0 -> JpgAppSegment (a - 0xE0) - | a >= 0xD0 && a <= 0xD7 -> JpgRestartIntervalEnd a - | otherwise -> error ("Invalid frame marker (" ++ show a ++ ")") + case word2 of + 0xC0 -> return JpgBaselineDCTHuffman + 0xC1 -> return JpgExtendedSequentialDCTHuffman + 0xC2 -> return JpgProgressiveDCTHuffman + 0xC3 -> return JpgLosslessHuffman + 0xC4 -> return JpgHuffmanTableMarker + 0xC5 -> return JpgDifferentialSequentialDCTHuffman + 0xC6 -> return JpgDifferentialProgressiveDCTHuffman + 0xC7 -> return JpgDifferentialLosslessHuffman + 0xC9 -> return JpgExtendedSequentialArithmetic + 0xCA -> return JpgProgressiveDCTArithmetic + 0xCB -> return JpgLosslessArithmetic + 0xCD -> return JpgDifferentialSequentialDCTArithmetic + 0xCE -> return JpgDifferentialProgressiveDCTArithmetic + 0xCF -> return JpgDifferentialLosslessArithmetic + 0xD9 -> return JpgEndOfImage + 0xDA -> return JpgStartOfScan + 0xDB -> return JpgQuantizationTable + 0xDD -> return JpgRestartInterval + a | a >= 0xF0 -> return $! JpgExtensionSegment a + | a >= 0xE0 -> return $! JpgAppSegment (a - 0xE0) + | a >= 0xD0 && a <= 0xD7 -> return $! JpgRestartIntervalEnd a + | otherwise -> fail ("Invalid frame marker (" ++ show a ++ ")") put4BitsOfEach :: Word8 -> Word8 -> Put put4BitsOfEach a b = put $ (a `unsafeShiftL` 4) .|. b
src/Codec/Picture/Metadata.hs view
@@ -43,6 +43,8 @@ , dotsPerCentiMeterToDotPerInch ) where +import Prelude hiding (Foldable(..)) + #if !MIN_VERSION_base(4,8,0) import Data.Monoid( Monoid, mempty, mappend ) import Data.Word( Word )
src/Codec/Picture/Metadata/Exif.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE DeriveGeneric #-} + -- | This module provide a totally partial and incomplete maping -- of Exif values. Used for Tiff parsing and reused for Exif extraction. module Codec.Picture.Metadata.Exif ( ExifTag( .. ) @@ -14,6 +16,7 @@ import Data.Word( Word16, Word32 ) import qualified Data.Vector as V import qualified Data.ByteString as B +import GHC.Generics( Generic ) -- | Tag values used for exif fields. Completly incomplete data ExifTag @@ -72,7 +75,8 @@ | TagExifOffset | TagUnknown !Word16 - deriving (Eq, Ord, Show) + deriving (Eq, Ord, Show, Generic) +instance NFData ExifTag -- | Convert a value to it's corresponding Exif tag. -- Will often be written as 'TagUnknown' @@ -206,13 +210,5 @@ | ExifRational !Word32 !Word32 | ExifSignedRational !Int32 !Int32 | ExifIFD ![(ExifTag, ExifData)] - deriving Show - -instance NFData ExifTag where - rnf a = a `seq` () - -instance NFData ExifData where - rnf (ExifIFD ifds) = rnf ifds `seq` () - rnf (ExifLongs l) = rnf l `seq` () - rnf (ExifShorts l) = rnf l `seq` () - rnf a = a `seq` () + deriving (Eq, Show, Generic) +instance NFData ExifData
src/Codec/Picture/Png.hs view
@@ -366,8 +366,8 @@ deinterlacer :: PngIHdr -> B.ByteString -> ST s (Either (V.Vector Word8) (V.Vector Word16)) deinterlacer (PngIHdr { width = w, height = h, colourType = imgKind , interlaceMethod = method, bitDepth = depth }) str = do - let compCount = sampleCountOfImageType imgKind - arraySize = fromIntegral $ w * h * compCount + let compCount = fromIntegral $ sampleCountOfImageType imgKind + arraySize = (fromIntegral w) * (fromIntegral h) * compCount deinterlaceFunction = case method of PngNoInterlace -> scanLineInterleaving PngInterlaceAdam7 -> adam7Unpack @@ -377,10 +377,9 @@ imgArray <- M.new arraySize let mutableImage = MutableImage (fromIntegral w) (fromIntegral h) imgArray deinterlaceFunction iBitDepth - (fromIntegral compCount) + compCount (fromIntegral w, fromIntegral h) - (scanlineUnpacker8 iBitDepth (fromIntegral compCount) - mutableImage) + (scanlineUnpacker8 iBitDepth compCount mutableImage) str Left <$> V.unsafeFreeze imgArray @@ -388,9 +387,9 @@ imgArray <- M.new arraySize let mutableImage = MutableImage (fromIntegral w) (fromIntegral h) imgArray deinterlaceFunction iBitDepth - (fromIntegral compCount) + compCount (fromIntegral w, fromIntegral h) - (shortUnpacker (fromIntegral compCount) mutableImage) + (shortUnpacker compCount mutableImage) str Right <$> V.unsafeFreeze imgArray
src/Codec/Picture/Png/Internal/Export.hs view
@@ -93,7 +93,7 @@ -- * 'Codec.Picture.Metadata.Warning' -- * 'Codec.Picture.Metadata.Unknown' using the key present in the constructor. -- - -- the followings metadata will bes tored in the `gAMA` chunk. + -- the followings metadata will be stored in the `gAMA` chunk. -- -- * 'Codec.Picture.Metadata.Gamma' --
src/Codec/Picture/Png/Internal/Type.hs view
@@ -14,6 +14,8 @@ , APngFrameControl( .. ) , parsePalette , pngComputeCrc + , pngSignature + , iHDRSignature , pLTESignature , iDATSignature , iENDSignature
src/Codec/Picture/Tiff/Internal/Types.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE CPP #-} +{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleContexts #-} @@ -32,6 +33,7 @@ import Control.Applicative( (<$>), (<*>), pure ) #endif +import Control.DeepSeq( NFData(..) ) import Control.Monad( forM_, when, replicateM, ) import Data.Bits( (.&.), unsafeShiftR ) import Data.Binary( Binary( .. ) ) @@ -53,6 +55,7 @@ import qualified Data.ByteString as B import Data.Int( Int32 ) import Data.Word( Word8, Word16, Word32 ) +import GHC.Generics( Generic ) import Codec.Picture.Metadata.Exif {-import Debug.Trace-} @@ -154,7 +157,8 @@ | TypeSignedRational | TypeFloat | TypeDouble - deriving Show + deriving (Eq, Show, Generic) +instance NFData IfdType instance BinaryParam Endianness IfdType where getP endianness = getP endianness >>= conv where @@ -240,6 +244,16 @@ getVec count = V.replicateM (fromIntegral count) + immediateBytes ofs = + let bytes = [fromIntegral $ (ofs .&. 0xFF000000) `unsafeShiftR` (3 * 8) + ,fromIntegral $ (ofs .&. 0x00FF0000) `unsafeShiftR` (2 * 8) + ,fromIntegral $ (ofs .&. 0x0000FF00) `unsafeShiftR` (1 * 8) + ,fromIntegral $ ofs .&. 0x000000FF + ] + in case endianness of + EndianLittle -> reverse bytes + EndianBig -> bytes + fetcher ImageFileDirectory { ifdIdentifier = TagExifOffset , ifdType = TypeLong , ifdCount = 1 } = do @@ -262,13 +276,12 @@ align ifd $ ExifUndefined <$> getByteString (fromIntegral count) fetcher ImageFileDirectory { ifdType = TypeUndefined, ifdOffset = ofs } = pure . ExifUndefined . B.pack $ take (fromIntegral $ ifdCount ifd) - [fromIntegral $ ofs .&. 0xFF000000 `unsafeShiftR` (3 * 8) - ,fromIntegral $ ofs .&. 0x00FF0000 `unsafeShiftR` (2 * 8) - ,fromIntegral $ ofs .&. 0x0000FF00 `unsafeShiftR` (1 * 8) - ,fromIntegral $ ofs .&. 0x000000FF - ] - fetcher ImageFileDirectory { ifdType = TypeAscii, ifdCount = count } | count > 1 = + (immediateBytes ofs) + fetcher ImageFileDirectory { ifdType = TypeAscii, ifdCount = count } | count > 4 = align ifd $ ExifString <$> getByteString (fromIntegral count) + fetcher ImageFileDirectory { ifdType = TypeAscii, ifdOffset = ofs } = + pure . ExifString . B.pack $ take (fromIntegral $ ifdCount ifd) + (immediateBytes ofs) fetcher ImageFileDirectory { ifdType = TypeShort, ifdCount = 2, ifdOffset = ofs } = pure . ExifShorts $ V.fromListN 2 valList where high = fromIntegral $ ofs `unsafeShiftR` 16 @@ -394,7 +407,8 @@ , ifdOffset :: !Word32 , ifdExtended :: !ExifData } - deriving Show + deriving (Eq, Show, Generic) +instance NFData ImageFileDirectory instance BinaryParam Endianness ImageFileDirectory where getP endianness =
src/Codec/Picture/VectorByteConversion.hs view
@@ -21,12 +21,19 @@ import Codec.Picture.Types +mkBS :: ForeignPtr Word8 -> Int -> Int -> S.ByteString +#if MIN_VERSION_bytestring(0,11,0) +mkBS fptr off = S.BS (fptr `S.plusForeignPtr` off) +#else +mkBS = S.PS +#endif + blitVector :: Vector Word8 -> Int -> Int -> B.ByteString -blitVector vec atIndex = S.PS ptr (offset + atIndex) +blitVector vec atIndex = mkBS ptr (offset + atIndex) where (ptr, offset, _length) = unsafeToForeignPtr vec toByteString :: forall a. (Storable a) => Vector a -> B.ByteString -toByteString vec = S.PS (castForeignPtr ptr) offset (len * size) +toByteString vec = mkBS (castForeignPtr ptr) offset (len * size) where (ptr, offset, len) = unsafeToForeignPtr vec size = sizeOf (undefined :: a)