From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- image/AnimationSurfaceProvider.cpp | 291 ++ image/AnimationSurfaceProvider.h | 104 + image/BMPHeaders.h | 38 + image/ClippedImage.cpp | 565 ++++ image/ClippedImage.h | 101 + image/CopyOnWrite.h | 250 ++ image/DecodePool.cpp | 340 +++ image/DecodePool.h | 105 + image/DecodedSurfaceProvider.cpp | 224 ++ image/DecodedSurfaceProvider.h | 89 + image/Decoder.cpp | 521 ++++ image/Decoder.h | 562 ++++ image/DecoderFactory.cpp | 350 +++ image/DecoderFactory.h | 193 ++ image/DecoderFlags.h | 42 + image/Downscaler.cpp | 352 +++ image/Downscaler.h | 189 ++ image/DownscalingFilter.h | 380 +++ image/DrawResult.h | 89 + image/DynamicImage.cpp | 347 +++ image/DynamicImage.h | 75 + image/FrameAnimator.cpp | 887 ++++++ image/FrameAnimator.h | 323 +++ image/FrozenImage.cpp | 122 + image/FrozenImage.h | 73 + image/ICOFileHeaders.h | 81 + image/IDecodingTask.cpp | 172 ++ image/IDecodingTask.h | 126 + image/IProgressObserver.h | 58 + image/ISurfaceProvider.h | 290 ++ image/Image.cpp | 150 + image/Image.h | 323 +++ image/ImageCacheKey.cpp | 167 ++ image/ImageCacheKey.h | 75 + image/ImageFactory.cpp | 288 ++ image/ImageFactory.h | 95 + image/ImageLogging.h | 161 ++ image/ImageMetadata.h | 99 + image/ImageOps.cpp | 150 + image/ImageOps.h | 102 + image/ImageRegion.h | 173 ++ image/ImageURL.h | 151 + image/ImageWrapper.cpp | 323 +++ image/ImageWrapper.h | 85 + image/LookupResult.h | 90 + image/MultipartImage.cpp | 346 +++ image/MultipartImage.h | 90 + image/Orientation.h | 62 + image/OrientedImage.cpp | 354 +++ image/OrientedImage.h | 79 + image/PlaybackType.h | 44 + image/ProgressTracker.cpp | 570 ++++ image/ProgressTracker.h | 234 ++ image/RasterImage.cpp | 1724 +++++++++++ image/RasterImage.h | 514 ++++ image/SVGDocumentWrapper.cpp | 460 +++ image/SVGDocumentWrapper.h | 151 + image/ScriptedNotificationObserver.cpp | 62 + image/ScriptedNotificationObserver.h | 37 + image/ShutdownTracker.cpp | 75 + image/ShutdownTracker.h | 46 + image/SourceBuffer.cpp | 682 +++++ image/SourceBuffer.h | 459 +++ image/StreamingLexer.h | 750 +++++ image/SurfaceCache.cpp | 1164 ++++++++ image/SurfaceCache.h | 427 +++ image/SurfaceCacheUtils.cpp | 20 + image/SurfaceCacheUtils.h | 34 + image/SurfaceFilters.h | 893 ++++++ image/SurfaceFlags.h | 73 + image/SurfacePipe.cpp | 199 ++ image/SurfacePipe.h | 793 ++++++ image/SurfacePipeFactory.h | 249 ++ image/VectorImage.cpp | 1350 +++++++++ image/VectorImage.h | 140 + image/build/moz.build | 22 + image/build/nsImageModule.cpp | 136 + image/build/nsImageModule.h | 20 + image/decoders/EXIF.cpp | 331 +++ image/decoders/EXIF.h | 75 + image/decoders/GIF2.h | 65 + image/decoders/iccjpeg.c | 195 ++ image/decoders/iccjpeg.h | 67 + image/decoders/icon/android/moz.build | 13 + image/decoders/icon/android/nsIconChannel.cpp | 145 + image/decoders/icon/android/nsIconChannel.h | 46 + image/decoders/icon/gtk/moz.build | 16 + image/decoders/icon/gtk/nsIconChannel.cpp | 427 +++ image/decoders/icon/gtk/nsIconChannel.h | 43 + image/decoders/icon/mac/moz.build | 11 + image/decoders/icon/mac/nsIconChannel.h | 59 + image/decoders/icon/mac/nsIconChannelCocoa.mm | 565 ++++ image/decoders/icon/moz.build | 32 + image/decoders/icon/nsIconModule.cpp | 60 + image/decoders/icon/nsIconProtocolHandler.cpp | 126 + image/decoders/icon/nsIconProtocolHandler.h | 26 + image/decoders/icon/nsIconURI.cpp | 699 +++++ image/decoders/icon/nsIconURI.h | 63 + image/decoders/icon/win/moz.build | 11 + image/decoders/icon/win/nsIconChannel.cpp | 841 ++++++ image/decoders/icon/win/nsIconChannel.h | 66 + image/decoders/moz.build | 47 + image/decoders/nsBMPDecoder.cpp | 1067 +++++++ image/decoders/nsBMPDecoder.h | 235 ++ image/decoders/nsGIFDecoder2.cpp | 1085 +++++++ image/decoders/nsGIFDecoder2.h | 152 + image/decoders/nsICODecoder.cpp | 673 +++++ image/decoders/nsICODecoder.h | 137 + image/decoders/nsIconDecoder.cpp | 128 + image/decoders/nsIconDecoder.h | 67 + image/decoders/nsJPEGDecoder.cpp | 1006 +++++++ image/decoders/nsJPEGDecoder.h | 125 + image/decoders/nsPNGDecoder.cpp | 1113 ++++++++ image/decoders/nsPNGDecoder.h | 158 ++ image/encoders/bmp/moz.build | 15 + image/encoders/bmp/nsBMPEncoder.cpp | 764 +++++ image/encoders/bmp/nsBMPEncoder.h | 157 + image/encoders/ico/moz.build | 18 + image/encoders/ico/nsICOEncoder.cpp | 542 ++++ image/encoders/ico/nsICOEncoder.h | 99 + image/encoders/jpeg/moz.build | 11 + image/encoders/jpeg/nsJPEGEncoder.cpp | 532 ++++ image/encoders/jpeg/nsJPEGEncoder.h | 84 + image/encoders/moz.build | 12 + image/encoders/png/moz.build | 15 + image/encoders/png/nsPNGEncoder.cpp | 758 +++++ image/encoders/png/nsPNGEncoder.h | 83 + image/imgFrame.cpp | 939 ++++++ image/imgFrame.h | 599 ++++ image/imgICache.idl | 65 + image/imgIContainer.idl | 549 ++++ image/imgIContainerDebug.idl | 25 + image/imgIEncoder.idl | 135 + image/imgILoader.idl | 98 + image/imgINotificationObserver.idl | 58 + image/imgIOnloadBlocker.idl | 32 + image/imgIRequest.idl | 206 ++ image/imgIScriptedNotificationObserver.idl | 22 + image/imgITools.idl | 152 + image/imgLoader.cpp | 2993 ++++++++++++++++++++ image/imgLoader.h | 574 ++++ image/imgRequest.cpp | 1305 +++++++++ image/imgRequest.h | 293 ++ image/imgRequestProxy.cpp | 1084 +++++++ image/imgRequestProxy.h | 248 ++ image/imgTools.cpp | 356 +++ image/imgTools.h | 38 + image/moz.build | 119 + image/nsIIconURI.idl | 84 + image/test/browser/animated.gif | Bin 0 -> 71479 bytes image/test/browser/animated2.gif | Bin 0 -> 66647 bytes image/test/browser/big.png | Bin 0 -> 129497 bytes image/test/browser/browser.ini | 14 + image/test/browser/browser_bug666317.js | 140 + image/test/browser/browser_docshell_type_editor.js | 92 + image/test/browser/browser_image.js | 195 ++ image/test/browser/head.js | 26 + image/test/browser/image.html | 24 + image/test/browser/imageX2.html | 15 + image/test/crashtests/1205923-1.html | 36 + image/test/crashtests/1210745-1.gif | Bin 0 -> 23 bytes image/test/crashtests/1212954-1.svg | 16 + image/test/crashtests/1235605.gif | Bin 0 -> 2360 bytes image/test/crashtests/1241728-1.html | 17 + image/test/crashtests/1241729-1.bmp | Bin 0 -> 548 bytes image/test/crashtests/1241729-1.html | 5 + image/test/crashtests/1242093-1.html | 22 + image/test/crashtests/1242778-1.png | Bin 0 -> 15929 bytes image/test/crashtests/1249576-1.png | Bin 0 -> 1169 bytes image/test/crashtests/1251091-1.html | 51 + image/test/crashtests/1251091-1.png | Bin 0 -> 95370 bytes image/test/crashtests/1253362-1.html | 11 + image/test/crashtests/256-height.ico | Bin 0 -> 154 bytes image/test/crashtests/256-width.ico | Bin 0 -> 154 bytes image/test/crashtests/463696.bmp | Bin 0 -> 1272 bytes image/test/crashtests/523528-1.gif | Bin 0 -> 132 bytes image/test/crashtests/523528-2.gif | Bin 0 -> 132 bytes image/test/crashtests/570451.png | Bin 0 -> 114 bytes image/test/crashtests/681190.html | 10 + image/test/crashtests/694165-1.xhtml | 510 ++++ image/test/crashtests/732319-1.html | 2 + image/test/crashtests/83804-1.gif | Bin 0 -> 37 bytes image/test/crashtests/844403-1.html | 10 + image/test/crashtests/856616.gif | Bin 0 -> 27 bytes image/test/crashtests/89341-1.gif | Bin 0 -> 769 bytes image/test/crashtests/944353.jpg | Bin 0 -> 610965 bytes image/test/crashtests/colormap-range.gif | Bin 0 -> 5657 bytes image/test/crashtests/crashtests.list | 51 + image/test/crashtests/delayedframe.sjs | 44 + image/test/crashtests/delaytest.html | 44 + image/test/crashtests/discardframe.htm | 1 + image/test/crashtests/ie.png | Bin 0 -> 466589 bytes .../test/crashtests/invalid-disposal-method-1.gif | Bin 0 -> 167 bytes .../test/crashtests/invalid-disposal-method-2.gif | Bin 0 -> 167 bytes .../test/crashtests/invalid-disposal-method-3.gif | Bin 0 -> 167 bytes image/test/crashtests/invalid-icc-profile.jpg | Bin 0 -> 2568 bytes .../test/crashtests/invalid-size-second-frame.gif | Bin 0 -> 673 bytes image/test/crashtests/invalid-size.gif | Bin 0 -> 329 bytes image/test/crashtests/invalid_ico_height.ico | Bin 0 -> 894 bytes image/test/crashtests/invalid_ico_width.ico | Bin 0 -> 894 bytes image/test/crashtests/multiple-png-hassize.ico | Bin 0 -> 18096 bytes image/test/crashtests/ownerdiscard.html | 49 + image/test/crashtests/threeframes-end.gif | Bin 0 -> 16 bytes image/test/crashtests/threeframes-start.gif | Bin 0 -> 92 bytes image/test/crashtests/truncated-second-frame.png | Bin 0 -> 72247 bytes image/test/crashtests/unsized-svg.svg | 1 + image/test/gtest/Common.cpp | 673 +++++ image/test/gtest/Common.h | 419 +++ image/test/gtest/TestADAM7InterpolatingFilter.cpp | 671 +++++ image/test/gtest/TestCopyOnWrite.cpp | 235 ++ image/test/gtest/TestDecodeToSurface.cpp | 123 + image/test/gtest/TestDecoders.cpp | 669 +++++ image/test/gtest/TestDeinterlacingFilter.cpp | 672 +++++ image/test/gtest/TestDownscalingFilter.cpp | 231 ++ image/test/gtest/TestDownscalingFilterNoSkia.cpp | 57 + image/test/gtest/TestMetadata.cpp | 255 ++ image/test/gtest/TestRemoveFrameRectFilter.cpp | 327 +++ image/test/gtest/TestSourceBuffer.cpp | 810 ++++++ image/test/gtest/TestStreamingLexer.cpp | 973 +++++++ image/test/gtest/TestSurfacePipeIntegration.cpp | 508 ++++ image/test/gtest/TestSurfaceSink.cpp | 1491 ++++++++++ .../gtest/animated-with-extra-image-sub-blocks.gif | Bin 0 -> 434 bytes image/test/gtest/corrupt-with-bad-bmp-height.ico | Bin 0 -> 41663 bytes image/test/gtest/corrupt-with-bad-bmp-width.ico | Bin 0 -> 41663 bytes image/test/gtest/corrupt.jpg | Bin 0 -> 2477 bytes image/test/gtest/downscaled.bmp | Bin 0 -> 30138 bytes image/test/gtest/downscaled.gif | Bin 0 -> 223 bytes image/test/gtest/downscaled.ico | Bin 0 -> 41662 bytes image/test/gtest/downscaled.icon | Bin 0 -> 40003 bytes image/test/gtest/downscaled.jpg | Bin 0 -> 6035 bytes image/test/gtest/downscaled.png | Bin 0 -> 1015 bytes image/test/gtest/first-frame-green.gif | Bin 0 -> 317 bytes image/test/gtest/first-frame-green.png | Bin 0 -> 364 bytes image/test/gtest/first-frame-padding.gif | Bin 0 -> 49 bytes image/test/gtest/green-1x1-truncated.gif | Bin 0 -> 53 bytes image/test/gtest/green.bmp | Bin 0 -> 30138 bytes image/test/gtest/green.gif | Bin 0 -> 156 bytes image/test/gtest/green.ico | Bin 0 -> 41662 bytes image/test/gtest/green.icon | Bin 0 -> 40002 bytes image/test/gtest/green.jpg | Bin 0 -> 361 bytes image/test/gtest/green.png | Bin 0 -> 255 bytes image/test/gtest/invalid-truncated-metadata.bmp | Bin 0 -> 54 bytes image/test/gtest/moz.build | 78 + image/test/gtest/no-frame-delay.gif | Bin 0 -> 317 bytes image/test/gtest/rle4.bmp | Bin 0 -> 3686 bytes image/test/gtest/rle8.bmp | Bin 0 -> 1288 bytes image/test/gtest/transparent-ico-with-and-mask.ico | Bin 0 -> 3262 bytes image/test/gtest/transparent-if-within-ico.bmp | Bin 0 -> 4234 bytes image/test/gtest/transparent.gif | Bin 0 -> 355 bytes image/test/gtest/transparent.png | Bin 0 -> 419 bytes image/test/mochitest/12M-pixels-1.png | Bin 0 -> 22467 bytes image/test/mochitest/12M-pixels-2.png | Bin 0 -> 22467 bytes image/test/mochitest/6M-pixels.png | Bin 0 -> 10147 bytes image/test/mochitest/INT32_MIN.bmp | Bin 0 -> 60 bytes image/test/mochitest/animated-gif-finalframe.gif | Bin 0 -> 72 bytes image/test/mochitest/animated-gif.gif | Bin 0 -> 146 bytes image/test/mochitest/animated-gif2.gif | Bin 0 -> 165 bytes .../mochitest/animated-gif_trailing-garbage.gif | Bin 0 -> 4030 bytes image/test/mochitest/animated1.gif | Bin 0 -> 4558 bytes image/test/mochitest/animated2.gif | Bin 0 -> 4558 bytes image/test/mochitest/animation.svg | 5 + image/test/mochitest/animationPolling.js | 414 +++ image/test/mochitest/bad.jpg | Bin 0 -> 2477 bytes image/test/mochitest/big.png | Bin 0 -> 129497 bytes image/test/mochitest/blue.gif | Bin 0 -> 45 bytes image/test/mochitest/blue.png | Bin 0 -> 2745 bytes image/test/mochitest/bug1132427.gif | Bin 0 -> 634 bytes image/test/mochitest/bug1132427.html | 6 + image/test/mochitest/bug1180105-waiter.sjs | 24 + image/test/mochitest/bug1180105.sjs | 63 + image/test/mochitest/bug1217571-iframe.html | 17 + image/test/mochitest/bug1319025-ref.png | Bin 0 -> 347 bytes image/test/mochitest/bug1319025.png | Bin 0 -> 422 bytes image/test/mochitest/bug399925.gif | Bin 0 -> 1645 bytes image/test/mochitest/bug415761.ico | Bin 0 -> 766 bytes image/test/mochitest/bug468160.sjs | 6 + image/test/mochitest/bug478398_ONLY.png | Bin 0 -> 14139 bytes image/test/mochitest/bug490949-iframe.html | 7 + image/test/mochitest/bug490949.sjs | 33 + image/test/mochitest/bug496292-1.sjs | 32 + image/test/mochitest/bug496292-2.sjs | 32 + image/test/mochitest/bug496292-iframe-1.html | 7 + image/test/mochitest/bug496292-iframe-2.html | 7 + image/test/mochitest/bug496292-iframe-ref.html | 7 + image/test/mochitest/bug497665-iframe.html | 8 + image/test/mochitest/bug497665.sjs | 34 + image/test/mochitest/bug552605.sjs | 13 + image/test/mochitest/bug657191.sjs | 27 + image/test/mochitest/bug671906-iframe.html | 7 + image/test/mochitest/bug671906.sjs | 36 + image/test/mochitest/bug733553-informant.sjs | 15 + image/test/mochitest/bug733553.sjs | 104 + image/test/mochitest/bug767779.sjs | 56 + image/test/mochitest/bug89419-iframe.html | 7 + image/test/mochitest/bug89419.sjs | 13 + image/test/mochitest/bug900200-ref.png | Bin 0 -> 660 bytes image/test/mochitest/bug900200.png | Bin 0 -> 840 bytes image/test/mochitest/chrome.ini | 7 + image/test/mochitest/clear.gif | Bin 0 -> 321 bytes image/test/mochitest/clear.png | Bin 0 -> 622 bytes image/test/mochitest/clear2-results.gif | Bin 0 -> 177 bytes image/test/mochitest/clear2.gif | Bin 0 -> 219 bytes image/test/mochitest/damon.jpg | Bin 0 -> 2679 bytes image/test/mochitest/error-early.png | 1 + image/test/mochitest/filter-final.svg | 9 + image/test/mochitest/filter.svg | 9 + image/test/mochitest/first-frame-padding.gif | Bin 0 -> 49 bytes image/test/mochitest/green-background.html | 28 + image/test/mochitest/green.png | Bin 0 -> 255 bytes image/test/mochitest/grey.png | Bin 0 -> 256 bytes image/test/mochitest/ico-bmp-opaque.ico | Bin 0 -> 1094 bytes image/test/mochitest/ico-bmp-transparent.ico | Bin 0 -> 4286 bytes image/test/mochitest/iframe.html | 5 + image/test/mochitest/imgutils.js | 138 + image/test/mochitest/invalid.jpg | 1 + image/test/mochitest/keep.gif | Bin 0 -> 321 bytes image/test/mochitest/keep.png | Bin 0 -> 622 bytes image/test/mochitest/lime-anim-100x100-2.svg | 6 + image/test/mochitest/lime-anim-100x100.svg | 7 + image/test/mochitest/lime-css-anim-100x100.svg | 19 + image/test/mochitest/lime100x100.svg | 4 + image/test/mochitest/mochitest.ini | 158 ++ image/test/mochitest/opaque.bmp | Bin 0 -> 1086 bytes image/test/mochitest/over.png | Bin 0 -> 525 bytes image/test/mochitest/purple.gif | Bin 0 -> 86 bytes image/test/mochitest/red.gif | Bin 0 -> 43 bytes image/test/mochitest/red.png | Bin 0 -> 82 bytes image/test/mochitest/ref-iframe.html | 6 + image/test/mochitest/restore-previous.gif | Bin 0 -> 457 bytes image/test/mochitest/restore-previous.png | Bin 0 -> 622 bytes image/test/mochitest/rillybad.jpg | Bin 0 -> 11142 bytes image/test/mochitest/schrep.png | Bin 0 -> 38767 bytes image/test/mochitest/shaver.png | Bin 0 -> 52045 bytes image/test/mochitest/short_header.gif | Bin 0 -> 1488 bytes image/test/mochitest/source.png | Bin 0 -> 525 bytes image/test/mochitest/test_ImageContentLoaded.html | 28 + image/test/mochitest/test_animSVGImage.html | 122 + image/test/mochitest/test_animSVGImage2.html | 124 + image/test/mochitest/test_animation.html | 45 + image/test/mochitest/test_animation2.html | 49 + image/test/mochitest/test_animation_operators.html | 159 ++ .../test/mochitest/test_background_image_anim.html | 44 + image/test/mochitest/test_bug1132427.html | 96 + image/test/mochitest/test_bug1180105.html | 46 + image/test/mochitest/test_bug1217571.html | 44 + image/test/mochitest/test_bug399925.html | 105 + image/test/mochitest/test_bug415761.html | 98 + image/test/mochitest/test_bug435296.html | 81 + image/test/mochitest/test_bug466586.html | 58 + image/test/mochitest/test_bug468160.html | 29 + image/test/mochitest/test_bug478398.html | 85 + image/test/mochitest/test_bug490949.html | 112 + image/test/mochitest/test_bug496292.html | 130 + image/test/mochitest/test_bug497665.html | 92 + image/test/mochitest/test_bug552605-1.html | 56 + image/test/mochitest/test_bug552605-2.html | 53 + image/test/mochitest/test_bug553982.html | 39 + image/test/mochitest/test_bug601470.html | 45 + image/test/mochitest/test_bug614392.html | 43 + image/test/mochitest/test_bug657191.html | 34 + image/test/mochitest/test_bug671906.html | 71 + image/test/mochitest/test_bug733553.html | 92 + image/test/mochitest/test_bug767779.html | 44 + image/test/mochitest/test_bug865919.html | 53 + image/test/mochitest/test_bug89419-1.html | 68 + image/test/mochitest/test_bug89419-2.html | 68 + image/test/mochitest/test_bullet_animation.html | 56 + image/test/mochitest/test_changeOfSource.html | 62 + image/test/mochitest/test_changeOfSource2.html | 47 + image/test/mochitest/test_drawDiscardedImage.html | 85 + image/test/mochitest/test_error_events.html | 67 + image/test/mochitest/test_has_transparency.html | 168 ++ .../mochitest/test_image_crossorigin_data_url.html | 27 + image/test/mochitest/test_net_failedtoprocess.html | 51 + image/test/mochitest/test_removal_ondecode.html | 128 + image/test/mochitest/test_removal_onload.html | 128 + image/test/mochitest/test_short_gif_header.html | 35 + image/test/mochitest/test_staticClone.html | 41 + image/test/mochitest/test_svg_animatedGIF.html | 53 + .../test/mochitest/test_svg_filter_animation.html | 42 + .../mochitest/test_synchronized_animation.html | 128 + image/test/mochitest/test_undisplayed_iframe.html | 47 + image/test/mochitest/test_webcam.html | 68 + image/test/mochitest/test_xultree_animation.xhtml | 67 + image/test/mochitest/transparent.gif | Bin 0 -> 355 bytes image/test/mochitest/transparent.png | Bin 0 -> 419 bytes image/test/mochitest/webcam-simulacrum.sjs | 51 + image/test/reftest/ImageDocument.css | 16 + image/test/reftest/apng/bug411852-1-ref.png | Bin 0 -> 164 bytes image/test/reftest/apng/bug411852-1.png | Bin 0 -> 606 bytes image/test/reftest/apng/bug546272-ref.png | Bin 0 -> 712 bytes image/test/reftest/apng/bug546272.png | Bin 0 -> 1391 bytes image/test/reftest/apng/delaytest.html | 41 + image/test/reftest/apng/reftest-stylo.list | 7 + image/test/reftest/apng/reftest.list | 6 + .../blob/blob-uri-with-ref-param-notref.html | 41 + .../test/reftest/blob/blob-uri-with-ref-param.html | 40 + image/test/reftest/blob/image.png | Bin 0 -> 840 bytes image/test/reftest/blob/reftest-stylo.list | 8 + image/test/reftest/blob/reftest.list | 7 + image/test/reftest/bmp/1240629-1.bmp | Bin 0 -> 68 bytes image/test/reftest/bmp/1240629-2.bmp | Bin 0 -> 68 bytes .../reftest/bmp/bmp-1bpp/bmp-not-square-1bpp.bmp | Bin 0 -> 130 bytes .../reftest/bmp/bmp-1bpp/bmp-not-square-1bpp.png | Bin 0 -> 147 bytes .../reftest/bmp/bmp-1bpp/bmp-size-15x15-1bpp.bmp | Bin 0 -> 122 bytes .../reftest/bmp/bmp-1bpp/bmp-size-15x15-1bpp.png | Bin 0 -> 220 bytes .../reftest/bmp/bmp-1bpp/bmp-size-16x16-1bpp.bmp | Bin 0 -> 126 bytes .../reftest/bmp/bmp-1bpp/bmp-size-16x16-1bpp.png | Bin 0 -> 242 bytes .../reftest/bmp/bmp-1bpp/bmp-size-17x17-1bpp.bmp | Bin 0 -> 130 bytes .../reftest/bmp/bmp-1bpp/bmp-size-17x17-1bpp.png | Bin 0 -> 247 bytes .../reftest/bmp/bmp-1bpp/bmp-size-1x1-1bpp.bmp | Bin 0 -> 66 bytes .../reftest/bmp/bmp-1bpp/bmp-size-1x1-1bpp.ico | Bin 0 -> 78 bytes .../reftest/bmp/bmp-1bpp/bmp-size-1x1-1bpp.png | Bin 0 -> 120 bytes .../reftest/bmp/bmp-1bpp/bmp-size-2x2-1bpp.bmp | Bin 0 -> 70 bytes .../reftest/bmp/bmp-1bpp/bmp-size-2x2-1bpp.png | Bin 0 -> 126 bytes .../reftest/bmp/bmp-1bpp/bmp-size-31x31-1bpp.bmp | Bin 0 -> 186 bytes .../reftest/bmp/bmp-1bpp/bmp-size-31x31-1bpp.png | Bin 0 -> 447 bytes .../reftest/bmp/bmp-1bpp/bmp-size-32x32-1bpp.bmp | Bin 0 -> 190 bytes .../reftest/bmp/bmp-1bpp/bmp-size-32x32-1bpp.png | Bin 0 -> 455 bytes .../reftest/bmp/bmp-1bpp/bmp-size-33x33-1bpp.bmp | Bin 0 -> 326 bytes .../reftest/bmp/bmp-1bpp/bmp-size-33x33-1bpp.png | Bin 0 -> 489 bytes .../reftest/bmp/bmp-1bpp/bmp-size-3x3-1bpp.bmp | Bin 0 -> 74 bytes .../reftest/bmp/bmp-1bpp/bmp-size-3x3-1bpp.png | Bin 0 -> 132 bytes .../reftest/bmp/bmp-1bpp/bmp-size-4x4-1bpp.bmp | Bin 0 -> 78 bytes .../reftest/bmp/bmp-1bpp/bmp-size-4x4-1bpp.png | Bin 0 -> 135 bytes .../reftest/bmp/bmp-1bpp/bmp-size-5x5-1bpp.bmp | Bin 0 -> 82 bytes .../reftest/bmp/bmp-1bpp/bmp-size-5x5-1bpp.png | Bin 0 -> 146 bytes .../reftest/bmp/bmp-1bpp/bmp-size-6x6-1bpp.bmp | Bin 0 -> 86 bytes .../reftest/bmp/bmp-1bpp/bmp-size-6x6-1bpp.png | Bin 0 -> 149 bytes .../reftest/bmp/bmp-1bpp/bmp-size-7x7-1bpp.bmp | Bin 0 -> 90 bytes .../reftest/bmp/bmp-1bpp/bmp-size-7x7-1bpp.png | Bin 0 -> 156 bytes .../reftest/bmp/bmp-1bpp/bmp-size-8x8-1bpp.bmp | Bin 0 -> 94 bytes .../reftest/bmp/bmp-1bpp/bmp-size-8x8-1bpp.png | Bin 0 -> 161 bytes .../reftest/bmp/bmp-1bpp/bmp-size-9x9-1bpp.bmp | Bin 0 -> 98 bytes .../reftest/bmp/bmp-1bpp/bmp-size-9x9-1bpp.png | Bin 0 -> 171 bytes .../bmp/bmp-1bpp/os2bmp-size-32x32-1bpp.bmp | Bin 0 -> 160 bytes image/test/reftest/bmp/bmp-1bpp/reftest-stylo.list | 22 + image/test/reftest/bmp/bmp-1bpp/reftest.list | 21 + .../bmp/bmp-1bpp/top-to-bottom-16x16-1bpp.bmp | Bin 0 -> 126 bytes .../reftest/bmp/bmp-24bpp/bmp-not-square-24bpp.bmp | Bin 0 -> 802 bytes .../reftest/bmp/bmp-24bpp/bmp-not-square-24bpp.png | Bin 0 -> 490 bytes .../reftest/bmp/bmp-24bpp/bmp-size-15x15-24bpp.bmp | Bin 0 -> 774 bytes .../reftest/bmp/bmp-24bpp/bmp-size-15x15-24bpp.png | Bin 0 -> 809 bytes .../reftest/bmp/bmp-24bpp/bmp-size-16x16-24bpp.bmp | Bin 0 -> 822 bytes .../reftest/bmp/bmp-24bpp/bmp-size-16x16-24bpp.png | Bin 0 -> 879 bytes .../reftest/bmp/bmp-24bpp/bmp-size-17x17-24bpp.bmp | Bin 0 -> 938 bytes .../reftest/bmp/bmp-24bpp/bmp-size-17x17-24bpp.png | Bin 0 -> 1000 bytes .../reftest/bmp/bmp-24bpp/bmp-size-1x1-24bpp.bmp | Bin 0 -> 58 bytes .../reftest/bmp/bmp-24bpp/bmp-size-1x1-24bpp.png | Bin 0 -> 70 bytes .../reftest/bmp/bmp-24bpp/bmp-size-2x2-24bpp.bmp | Bin 0 -> 70 bytes .../reftest/bmp/bmp-24bpp/bmp-size-2x2-24bpp.png | Bin 0 -> 83 bytes .../reftest/bmp/bmp-24bpp/bmp-size-31x31-24bpp.bmp | Bin 0 -> 3030 bytes .../reftest/bmp/bmp-24bpp/bmp-size-31x31-24bpp.png | Bin 0 -> 2936 bytes .../reftest/bmp/bmp-24bpp/bmp-size-32x32-24bpp.bmp | Bin 0 -> 3126 bytes .../reftest/bmp/bmp-24bpp/bmp-size-32x32-24bpp.png | Bin 0 -> 3106 bytes .../reftest/bmp/bmp-24bpp/bmp-size-33x33-24bpp.bmp | Bin 0 -> 3354 bytes .../reftest/bmp/bmp-24bpp/bmp-size-33x33-24bpp.png | Bin 0 -> 3303 bytes .../reftest/bmp/bmp-24bpp/bmp-size-3x3-24bpp.bmp | Bin 0 -> 90 bytes .../reftest/bmp/bmp-24bpp/bmp-size-3x3-24bpp.png | Bin 0 -> 107 bytes .../reftest/bmp/bmp-24bpp/bmp-size-4x4-24bpp.bmp | Bin 0 -> 102 bytes .../reftest/bmp/bmp-24bpp/bmp-size-4x4-24bpp.png | Bin 0 -> 136 bytes .../reftest/bmp/bmp-24bpp/bmp-size-5x5-24bpp.bmp | Bin 0 -> 134 bytes .../reftest/bmp/bmp-24bpp/bmp-size-5x5-24bpp.png | Bin 0 -> 173 bytes .../reftest/bmp/bmp-24bpp/bmp-size-6x6-24bpp.bmp | Bin 0 -> 174 bytes .../reftest/bmp/bmp-24bpp/bmp-size-6x6-24bpp.png | Bin 0 -> 218 bytes .../reftest/bmp/bmp-24bpp/bmp-size-7x7-24bpp.bmp | Bin 0 -> 222 bytes .../reftest/bmp/bmp-24bpp/bmp-size-7x7-24bpp.png | Bin 0 -> 271 bytes .../reftest/bmp/bmp-24bpp/bmp-size-8x8-24bpp.bmp | Bin 0 -> 246 bytes .../reftest/bmp/bmp-24bpp/bmp-size-8x8-24bpp.png | Bin 0 -> 313 bytes .../reftest/bmp/bmp-24bpp/bmp-size-9x9-24bpp.bmp | Bin 0 -> 306 bytes .../reftest/bmp/bmp-24bpp/bmp-size-9x9-24bpp.png | Bin 0 -> 368 bytes .../bmp/bmp-24bpp/os2bmp-size-32x32-24bpp.bmp | Bin 0 -> 3098 bytes .../test/reftest/bmp/bmp-24bpp/reftest-stylo.list | 22 + image/test/reftest/bmp/bmp-24bpp/reftest.list | 21 + .../bmp/bmp-24bpp/top-to-bottom-16x16-24bpp.bmp | Bin 0 -> 822 bytes .../reftest/bmp/bmp-4bpp/bmp-not-square-4bpp.bmp | Bin 0 -> 254 bytes .../reftest/bmp/bmp-4bpp/bmp-not-square-4bpp.png | Bin 0 -> 229 bytes .../reftest/bmp/bmp-4bpp/bmp-size-15x15-4bpp.bmp | Bin 0 -> 238 bytes .../reftest/bmp/bmp-4bpp/bmp-size-15x15-4bpp.png | Bin 0 -> 304 bytes .../reftest/bmp/bmp-4bpp/bmp-size-16x16-4bpp.bmp | Bin 0 -> 246 bytes .../reftest/bmp/bmp-4bpp/bmp-size-16x16-4bpp.png | Bin 0 -> 323 bytes .../reftest/bmp/bmp-4bpp/bmp-size-17x17-4bpp.bmp | Bin 0 -> 322 bytes .../reftest/bmp/bmp-4bpp/bmp-size-17x17-4bpp.png | Bin 0 -> 337 bytes .../reftest/bmp/bmp-4bpp/bmp-size-1x1-4bpp.bmp | Bin 0 -> 122 bytes .../reftest/bmp/bmp-4bpp/bmp-size-1x1-4bpp.png | Bin 0 -> 120 bytes .../reftest/bmp/bmp-4bpp/bmp-size-2x2-4bpp.bmp | Bin 0 -> 126 bytes .../reftest/bmp/bmp-4bpp/bmp-size-2x2-4bpp.png | Bin 0 -> 128 bytes .../reftest/bmp/bmp-4bpp/bmp-size-31x31-4bpp.bmp | Bin 0 -> 614 bytes .../reftest/bmp/bmp-4bpp/bmp-size-31x31-4bpp.png | Bin 0 -> 700 bytes .../reftest/bmp/bmp-4bpp/bmp-size-32x32-4bpp.bmp | Bin 0 -> 630 bytes .../reftest/bmp/bmp-4bpp/bmp-size-32x32-4bpp.png | Bin 0 -> 763 bytes .../reftest/bmp/bmp-4bpp/bmp-size-33x33-4bpp.bmp | Bin 0 -> 778 bytes .../reftest/bmp/bmp-4bpp/bmp-size-33x33-4bpp.png | Bin 0 -> 778 bytes .../reftest/bmp/bmp-4bpp/bmp-size-3x3-4bpp.bmp | Bin 0 -> 130 bytes .../reftest/bmp/bmp-4bpp/bmp-size-3x3-4bpp.png | Bin 0 -> 139 bytes .../reftest/bmp/bmp-4bpp/bmp-size-4x4-4bpp.bmp | Bin 0 -> 134 bytes .../reftest/bmp/bmp-4bpp/bmp-size-4x4-4bpp.png | Bin 0 -> 147 bytes .../reftest/bmp/bmp-4bpp/bmp-size-5x5-4bpp.bmp | Bin 0 -> 138 bytes .../reftest/bmp/bmp-4bpp/bmp-size-5x5-4bpp.png | Bin 0 -> 156 bytes .../reftest/bmp/bmp-4bpp/bmp-size-6x6-4bpp.bmp | Bin 0 -> 142 bytes .../reftest/bmp/bmp-4bpp/bmp-size-6x6-4bpp.png | Bin 0 -> 163 bytes .../reftest/bmp/bmp-4bpp/bmp-size-7x7-4bpp.bmp | Bin 0 -> 146 bytes .../reftest/bmp/bmp-4bpp/bmp-size-7x7-4bpp.png | Bin 0 -> 172 bytes .../reftest/bmp/bmp-4bpp/bmp-size-8x8-4bpp.bmp | Bin 0 -> 150 bytes .../reftest/bmp/bmp-4bpp/bmp-size-8x8-4bpp.png | Bin 0 -> 188 bytes .../reftest/bmp/bmp-4bpp/bmp-size-9x9-4bpp.bmp | Bin 0 -> 190 bytes .../reftest/bmp/bmp-4bpp/bmp-size-9x9-4bpp.png | Bin 0 -> 198 bytes .../bmp/bmp-4bpp/os2bmp-size-32x32-4bpp.bmp | Bin 0 -> 586 bytes image/test/reftest/bmp/bmp-4bpp/reftest-stylo.list | 25 + image/test/reftest/bmp/bmp-4bpp/reftest.list | 24 + .../reftest/bmp/bmp-4bpp/rle4-delta-320x240.bmp | Bin 0 -> 3686 bytes .../reftest/bmp/bmp-4bpp/rle4-delta-320x240.png | Bin 0 -> 886 bytes .../bmp/bmp-4bpp/top-to-bottom-16x16-4bpp.bmp | Bin 0 -> 246 bytes .../reftest/bmp/bmp-8bpp/bmp-not-square-8bpp.bmp | Bin 0 -> 1350 bytes .../reftest/bmp/bmp-8bpp/bmp-not-square-8bpp.png | Bin 0 -> 324 bytes .../reftest/bmp/bmp-8bpp/bmp-size-15x15-8bpp.bmp | Bin 0 -> 1318 bytes .../reftest/bmp/bmp-8bpp/bmp-size-15x15-8bpp.png | Bin 0 -> 325 bytes .../reftest/bmp/bmp-8bpp/bmp-size-16x16-8bpp.bmp | Bin 0 -> 1334 bytes .../reftest/bmp/bmp-8bpp/bmp-size-16x16-8bpp.png | Bin 0 -> 338 bytes .../reftest/bmp/bmp-8bpp/bmp-size-17x17-8bpp.bmp | Bin 0 -> 1418 bytes .../reftest/bmp/bmp-8bpp/bmp-size-17x17-8bpp.png | Bin 0 -> 372 bytes .../reftest/bmp/bmp-8bpp/bmp-size-1x1-8bpp.bmp | Bin 0 -> 1082 bytes .../reftest/bmp/bmp-8bpp/bmp-size-1x1-8bpp.png | Bin 0 -> 120 bytes .../reftest/bmp/bmp-8bpp/bmp-size-2x2-8bpp.bmp | Bin 0 -> 1086 bytes .../reftest/bmp/bmp-8bpp/bmp-size-2x2-8bpp.png | Bin 0 -> 131 bytes .../reftest/bmp/bmp-8bpp/bmp-size-31x31-8bpp.bmp | Bin 0 -> 2102 bytes .../reftest/bmp/bmp-8bpp/bmp-size-31x31-8bpp.png | Bin 0 -> 772 bytes .../reftest/bmp/bmp-8bpp/bmp-size-32x32-8bpp.bmp | Bin 0 -> 2102 bytes .../reftest/bmp/bmp-8bpp/bmp-size-32x32-8bpp.png | Bin 0 -> 754 bytes .../reftest/bmp/bmp-8bpp/bmp-size-33x33-8bpp.bmp | Bin 0 -> 2266 bytes .../reftest/bmp/bmp-8bpp/bmp-size-33x33-8bpp.png | Bin 0 -> 833 bytes .../reftest/bmp/bmp-8bpp/bmp-size-3x3-8bpp.bmp | Bin 0 -> 1090 bytes .../reftest/bmp/bmp-8bpp/bmp-size-3x3-8bpp.png | Bin 0 -> 150 bytes .../reftest/bmp/bmp-8bpp/bmp-size-4x4-8bpp.bmp | Bin 0 -> 1094 bytes .../reftest/bmp/bmp-8bpp/bmp-size-4x4-8bpp.png | Bin 0 -> 165 bytes .../reftest/bmp/bmp-8bpp/bmp-size-5x5-8bpp.bmp | Bin 0 -> 1118 bytes .../reftest/bmp/bmp-8bpp/bmp-size-5x5-8bpp.png | Bin 0 -> 169 bytes .../reftest/bmp/bmp-8bpp/bmp-size-6x6-8bpp.bmp | Bin 0 -> 1126 bytes .../reftest/bmp/bmp-8bpp/bmp-size-6x6-8bpp.png | Bin 0 -> 180 bytes .../reftest/bmp/bmp-8bpp/bmp-size-7x7-8bpp.bmp | Bin 0 -> 1134 bytes .../reftest/bmp/bmp-8bpp/bmp-size-7x7-8bpp.png | Bin 0 -> 194 bytes .../reftest/bmp/bmp-8bpp/bmp-size-8x8-8bpp.bmp | Bin 0 -> 1142 bytes .../reftest/bmp/bmp-8bpp/bmp-size-8x8-8bpp.png | Bin 0 -> 217 bytes .../reftest/bmp/bmp-8bpp/bmp-size-9x9-8bpp.bmp | Bin 0 -> 1186 bytes .../reftest/bmp/bmp-8bpp/bmp-size-9x9-8bpp.png | Bin 0 -> 229 bytes .../bmp/bmp-8bpp/os2-bmp-size-32x32-8bpp.bmp | Bin 0 -> 1818 bytes image/test/reftest/bmp/bmp-8bpp/reftest-stylo.list | 25 + image/test/reftest/bmp/bmp-8bpp/reftest.list | 24 + .../bmp/bmp-8bpp/rle-bmp-not-square-8bpp.bmp | Bin 0 -> 1384 bytes .../bmp/bmp-8bpp/rle-bmp-size-32x32-8bpp.bmp | Bin 0 -> 1288 bytes .../bmp/bmp-8bpp/top-to-bottom-16x16-8bpp.bmp | Bin 0 -> 1334 bytes .../top-to-bottom-rle-bmp-size-32x32-8bpp.bmp | Bin 0 -> 1284 bytes .../test/reftest/bmp/bmp-corrupted/invalid-bpp.bmp | Bin 0 -> 58 bytes .../invalid-compression-BITFIELDS.bmp | Bin 0 -> 78 bytes .../bmp/bmp-corrupted/invalid-compression-RLE4.bmp | Bin 0 -> 246 bytes .../bmp/bmp-corrupted/invalid-compression-RLE8.bmp | Bin 0 -> 246 bytes .../bmp/bmp-corrupted/invalid-compression.bmp | Bin 0 -> 822 bytes .../bmp/bmp-corrupted/invalid-signature.bmp | Bin 0 -> 58 bytes .../bmp-corrupted/invalid-truncated-metadata.bmp | Bin 0 -> 54 bytes .../reftest/bmp/bmp-corrupted/os2-invalid-bpp.bmp | Bin 0 -> 30 bytes .../reftest/bmp/bmp-corrupted/reftest-stylo.list | 19 + image/test/reftest/bmp/bmp-corrupted/reftest.list | 18 + image/test/reftest/bmp/bmp-corrupted/wrapper.html | 28 + image/test/reftest/bmp/bmpsuite/COPYING.txt | 675 +++++ image/test/reftest/bmp/bmpsuite/README.mozilla | 39 + image/test/reftest/bmp/bmpsuite/b/badbitcount.bmp | Bin 0 -> 1086 bytes image/test/reftest/bmp/bmpsuite/b/badbitssize.bmp | Bin 0 -> 1086 bytes image/test/reftest/bmp/bmpsuite/b/baddens1.bmp | Bin 0 -> 1086 bytes image/test/reftest/bmp/bmpsuite/b/baddens2.bmp | Bin 0 -> 1086 bytes image/test/reftest/bmp/bmpsuite/b/badfilesize.bmp | Bin 0 -> 1086 bytes .../test/reftest/bmp/bmpsuite/b/badheadersize.bmp | Bin 0 -> 1112 bytes .../test/reftest/bmp/bmpsuite/b/badpalettesize.bmp | Bin 0 -> 9254 bytes image/test/reftest/bmp/bmpsuite/b/badplanes.bmp | Bin 0 -> 1086 bytes image/test/reftest/bmp/bmpsuite/b/badrle.bmp | Bin 0 -> 9212 bytes image/test/reftest/bmp/bmpsuite/b/badrle.png | Bin 0 -> 438 bytes image/test/reftest/bmp/bmpsuite/b/badwidth.bmp | Bin 0 -> 1086 bytes image/test/reftest/bmp/bmpsuite/b/pal1.png | Bin 0 -> 586 bytes image/test/reftest/bmp/bmpsuite/b/pal8.png | Bin 0 -> 3772 bytes image/test/reftest/bmp/bmpsuite/b/pal8badindex.bmp | Bin 0 -> 8650 bytes image/test/reftest/bmp/bmpsuite/b/pal8badindex.png | Bin 0 -> 1819 bytes image/test/reftest/bmp/bmpsuite/b/reallybig.bmp | Bin 0 -> 24630 bytes .../test/reftest/bmp/bmpsuite/b/reftest-stylo.list | 85 + image/test/reftest/bmp/bmpsuite/b/reftest.list | 84 + image/test/reftest/bmp/bmpsuite/b/rletopdown.bmp | Bin 0 -> 8788 bytes image/test/reftest/bmp/bmpsuite/b/shortfile.bmp | Bin 0 -> 273 bytes image/test/reftest/bmp/bmpsuite/b/shortfile.png | Bin 0 -> 399 bytes image/test/reftest/bmp/bmpsuite/b/wrapper.html | 28 + image/test/reftest/bmp/bmpsuite/g/pal1.bmp | Bin 0 -> 1086 bytes image/test/reftest/bmp/bmpsuite/g/pal1.png | Bin 0 -> 586 bytes image/test/reftest/bmp/bmpsuite/g/pal1bg.bmp | Bin 0 -> 1086 bytes image/test/reftest/bmp/bmpsuite/g/pal1bg.png | Bin 0 -> 604 bytes image/test/reftest/bmp/bmpsuite/g/pal1wb.bmp | Bin 0 -> 1086 bytes image/test/reftest/bmp/bmpsuite/g/pal4.bmp | Bin 0 -> 4198 bytes image/test/reftest/bmp/bmpsuite/g/pal4.png | Bin 0 -> 1428 bytes image/test/reftest/bmp/bmpsuite/g/pal4rle.bmp | Bin 0 -> 3836 bytes image/test/reftest/bmp/bmpsuite/g/pal8-0.bmp | Bin 0 -> 9270 bytes image/test/reftest/bmp/bmpsuite/g/pal8.bmp | Bin 0 -> 9254 bytes image/test/reftest/bmp/bmpsuite/g/pal8.png | Bin 0 -> 3772 bytes .../reftest/bmp/bmpsuite/g/pal8nonsquare-e.png | Bin 0 -> 2513 bytes .../test/reftest/bmp/bmpsuite/g/pal8nonsquare.bmp | Bin 0 -> 5158 bytes .../test/reftest/bmp/bmpsuite/g/pal8nonsquare.png | Bin 0 -> 2714 bytes image/test/reftest/bmp/bmpsuite/g/pal8os2.bmp | Bin 0 -> 8986 bytes image/test/reftest/bmp/bmpsuite/g/pal8rle.bmp | Bin 0 -> 8788 bytes image/test/reftest/bmp/bmpsuite/g/pal8topdown.bmp | Bin 0 -> 9254 bytes image/test/reftest/bmp/bmpsuite/g/pal8v4.bmp | Bin 0 -> 9322 bytes image/test/reftest/bmp/bmpsuite/g/pal8v5.bmp | Bin 0 -> 9338 bytes image/test/reftest/bmp/bmpsuite/g/pal8w124.bmp | Bin 0 -> 8626 bytes image/test/reftest/bmp/bmpsuite/g/pal8w124.png | Bin 0 -> 3585 bytes image/test/reftest/bmp/bmpsuite/g/pal8w125.bmp | Bin 0 -> 8998 bytes image/test/reftest/bmp/bmpsuite/g/pal8w125.png | Bin 0 -> 3628 bytes image/test/reftest/bmp/bmpsuite/g/pal8w126.bmp | Bin 0 -> 9126 bytes image/test/reftest/bmp/bmpsuite/g/pal8w126.png | Bin 0 -> 3714 bytes .../test/reftest/bmp/bmpsuite/g/reftest-stylo.list | 113 + image/test/reftest/bmp/bmpsuite/g/reftest.list | 112 + image/test/reftest/bmp/bmpsuite/g/rgb16-565.bmp | Bin 0 -> 16450 bytes image/test/reftest/bmp/bmpsuite/g/rgb16-565.png | Bin 0 -> 1297 bytes image/test/reftest/bmp/bmpsuite/g/rgb16-565pal.bmp | Bin 0 -> 17474 bytes image/test/reftest/bmp/bmpsuite/g/rgb16.bmp | Bin 0 -> 16438 bytes image/test/reftest/bmp/bmpsuite/g/rgb16.png | Bin 0 -> 1177 bytes image/test/reftest/bmp/bmpsuite/g/rgb24.bmp | Bin 0 -> 24630 bytes image/test/reftest/bmp/bmpsuite/g/rgb24.png | Bin 0 -> 1072 bytes image/test/reftest/bmp/bmpsuite/g/rgb24pal.bmp | Bin 0 -> 25654 bytes image/test/reftest/bmp/bmpsuite/g/rgb32.bmp | Bin 0 -> 32566 bytes image/test/reftest/bmp/bmpsuite/g/rgb32bf.bmp | Bin 0 -> 32578 bytes image/test/reftest/bmp/bmpsuite/q/pal1p1.bmp | Bin 0 -> 1082 bytes image/test/reftest/bmp/bmpsuite/q/pal1p1.png | Bin 0 -> 124 bytes image/test/reftest/bmp/bmpsuite/q/pal2.bmp | Bin 0 -> 2118 bytes image/test/reftest/bmp/bmpsuite/q/pal4rletrns.bmp | Bin 0 -> 4326 bytes image/test/reftest/bmp/bmpsuite/q/pal4rletrns.png | Bin 0 -> 1465 bytes image/test/reftest/bmp/bmpsuite/q/pal8.png | Bin 0 -> 3772 bytes image/test/reftest/bmp/bmpsuite/q/pal8offs.bmp | Bin 0 -> 9354 bytes image/test/reftest/bmp/bmpsuite/q/pal8os2sp.bmp | Bin 0 -> 8974 bytes image/test/reftest/bmp/bmpsuite/q/pal8os2v2-16.bmp | Bin 0 -> 9246 bytes image/test/reftest/bmp/bmpsuite/q/pal8os2v2.bmp | Bin 0 -> 9278 bytes .../reftest/bmp/bmpsuite/q/pal8oversizepal.bmp | Bin 0 -> 9446 bytes image/test/reftest/bmp/bmpsuite/q/pal8rletrns.bmp | Bin 0 -> 9212 bytes image/test/reftest/bmp/bmpsuite/q/pal8rletrns.png | Bin 0 -> 3793 bytes .../test/reftest/bmp/bmpsuite/q/reftest-stylo.list | 131 + image/test/reftest/bmp/bmpsuite/q/reftest.list | 130 + image/test/reftest/bmp/bmpsuite/q/rgb16-231.bmp | Bin 0 -> 16450 bytes image/test/reftest/bmp/bmpsuite/q/rgb16-231.png | Bin 0 -> 2643 bytes image/test/reftest/bmp/bmpsuite/q/rgb24.png | Bin 0 -> 1072 bytes image/test/reftest/bmp/bmpsuite/q/rgb24jpeg.bmp | Bin 0 -> 2457 bytes .../test/reftest/bmp/bmpsuite/q/rgb24largepal.bmp | Bin 0 -> 25830 bytes image/test/reftest/bmp/bmpsuite/q/rgb24lprof.bmp | Bin 0 -> 24743 bytes image/test/reftest/bmp/bmpsuite/q/rgb24png.bmp | Bin 0 -> 1210 bytes image/test/reftest/bmp/bmpsuite/q/rgb24prof.bmp | Bin 0 -> 27782 bytes image/test/reftest/bmp/bmpsuite/q/rgb32-111110.bmp | Bin 0 -> 32578 bytes .../test/reftest/bmp/bmpsuite/q/rgb32fakealpha.bmp | Bin 0 -> 32566 bytes image/test/reftest/bmp/bmpsuite/q/rgba16-4444.bmp | Bin 0 -> 16522 bytes image/test/reftest/bmp/bmpsuite/q/rgba16-4444.png | Bin 0 -> 1093 bytes image/test/reftest/bmp/bmpsuite/q/rgba32.bmp | Bin 0 -> 32650 bytes image/test/reftest/bmp/bmpsuite/q/rgba32.png | Bin 0 -> 1229 bytes image/test/reftest/bmp/bmpsuite/q/rgba32abf.bmp | Bin 0 -> 32582 bytes image/test/reftest/bmp/bmpsuite/q/wrapper.html | 28 + image/test/reftest/bmp/bmpsuite/reftest-stylo.list | 8 + image/test/reftest/bmp/bmpsuite/reftest.list | 7 + image/test/reftest/bmp/reftest-stylo.list | 17 + image/test/reftest/bmp/reftest.list | 16 + image/test/reftest/color-management/color-curv.png | Bin 0 -> 1753 bytes image/test/reftest/color-management/color-lin.png | Bin 0 -> 1749 bytes .../test/reftest/color-management/color-table.png | Bin 0 -> 1754 bytes .../reftest/color-management/invalid-chrm-ref.png | Bin 0 -> 1460 bytes .../test/reftest/color-management/invalid-chrm.png | Bin 0 -> 1504 bytes .../color-management/invalid-whitepoint.png | Bin 0 -> 1504 bytes .../reftest/color-management/reftest-stylo.list | 8 + image/test/reftest/color-management/reftest.list | 7 + .../reftest/color-management/trc-type-ref.html | 8 + image/test/reftest/color-management/trc-type.html | 53 + image/test/reftest/colordepth.html | 16 + .../reftest/downscaling/black-border-bottom.png | Bin 0 -> 4094 bytes .../test/reftest/downscaling/black-border-left.png | Bin 0 -> 4176 bytes .../test/reftest/downscaling/black-border-rect.svg | 3 + .../reftest/downscaling/black-border-right.png | Bin 0 -> 4097 bytes .../test/reftest/downscaling/black-border-top.png | Bin 0 -> 4144 bytes .../reftest/downscaling/bmp-size-16x16-24bpp.png | Bin 0 -> 879 bytes .../reftest/downscaling/downscale-1-bigimage.png | Bin 0 -> 195 bytes .../test/reftest/downscaling/downscale-1-ref.html | 8 + .../reftest/downscaling/downscale-1-smallimage.png | Bin 0 -> 88 bytes image/test/reftest/downscaling/downscale-1.html | 24 + image/test/reftest/downscaling/downscale-16px.html | 28 + image/test/reftest/downscaling/downscale-2a.html | 31 + image/test/reftest/downscaling/downscale-2b.html | 31 + image/test/reftest/downscaling/downscale-2c.html | 31 + image/test/reftest/downscaling/downscale-2d.html | 31 + image/test/reftest/downscaling/downscale-2e.html | 31 + image/test/reftest/downscaling/downscale-2f.html | 31 + .../reftest/downscaling/downscale-32px-ref.html | 8 + image/test/reftest/downscaling/downscale-32px.html | 31 + image/test/reftest/downscaling/downscale-8px.html | 27 + .../downscaling/downscale-moz-icon-1-ref.html | 37 + .../reftest/downscaling/downscale-moz-icon-1.html | 19 + image/test/reftest/downscaling/downscale-png.html | 31 + .../reftest/downscaling/downscale-svg-1-ref.html | 13 + .../test/reftest/downscaling/downscale-svg-1a.html | 8 + .../test/reftest/downscaling/downscale-svg-1b.html | 8 + .../test/reftest/downscaling/downscale-svg-1c.html | 8 + .../test/reftest/downscaling/downscale-svg-1d.html | 8 + .../test/reftest/downscaling/downscale-svg-1e.html | 8 + .../test/reftest/downscaling/downscale-svg-1f.html | 8 + image/test/reftest/downscaling/ff-0RGB.ico | Bin 0 -> 4286 bytes image/test/reftest/downscaling/ff-0RGB.png | Bin 0 -> 2515 bytes image/test/reftest/downscaling/ff-ARGB.ico | Bin 0 -> 4286 bytes image/test/reftest/downscaling/ff-ARGB.png | Bin 0 -> 115 bytes .../reftest/downscaling/lime-red-256px-bmp-in.ico | Bin 0 -> 74814 bytes .../reftest/downscaling/lime-red-256px-png-in.ico | Bin 0 -> 881 bytes image/test/reftest/downscaling/lime-red-256px.bmp | Bin 0 -> 196730 bytes image/test/reftest/downscaling/lime-red-256px.gif | Bin 0 -> 873 bytes image/test/reftest/downscaling/lime-red-256px.jpg | Bin 0 -> 2865 bytes image/test/reftest/downscaling/lime-red-256px.png | Bin 0 -> 568 bytes image/test/reftest/downscaling/lime-red-256px.svg | 5 + image/test/reftest/downscaling/lime-red-32px.png | Bin 0 -> 103 bytes image/test/reftest/downscaling/png-interlaced.png | Bin 0 -> 806 bytes image/test/reftest/downscaling/png-normal.png | Bin 0 -> 421 bytes image/test/reftest/downscaling/reftest-stylo.list | 195 ++ image/test/reftest/downscaling/reftest.list | 193 ++ .../downscaling/top-to-bottom-16x16-24bpp.bmp | Bin 0 -> 822 bytes .../reftest/encoders-lossless/ImageDocument.css | 16 + image/test/reftest/encoders-lossless/encoder.html | 113 + .../reftest/encoders-lossless/reftest-stylo.list | 160 ++ image/test/reftest/encoders-lossless/reftest.list | 159 ++ .../test/reftest/encoders-lossless/size-15x15.png | Bin 0 -> 809 bytes .../test/reftest/encoders-lossless/size-16x16.png | Bin 0 -> 879 bytes .../test/reftest/encoders-lossless/size-17x17.png | Bin 0 -> 1000 bytes image/test/reftest/encoders-lossless/size-1x1.png | Bin 0 -> 70 bytes .../reftest/encoders-lossless/size-256x256.png | Bin 0 -> 5480 bytes image/test/reftest/encoders-lossless/size-2x2.png | Bin 0 -> 83 bytes .../test/reftest/encoders-lossless/size-31x31.png | Bin 0 -> 2936 bytes .../test/reftest/encoders-lossless/size-32x32.png | Bin 0 -> 3106 bytes .../test/reftest/encoders-lossless/size-33x33.png | Bin 0 -> 3303 bytes image/test/reftest/encoders-lossless/size-3x3.png | Bin 0 -> 107 bytes image/test/reftest/encoders-lossless/size-4x4.png | Bin 0 -> 136 bytes image/test/reftest/encoders-lossless/size-5x5.png | Bin 0 -> 173 bytes image/test/reftest/encoders-lossless/size-6x6.png | Bin 0 -> 218 bytes image/test/reftest/encoders-lossless/size-7x7.png | Bin 0 -> 271 bytes image/test/reftest/encoders-lossless/size-8x8.png | Bin 0 -> 313 bytes image/test/reftest/encoders-lossless/size-9x9.png | Bin 0 -> 368 bytes image/test/reftest/encoders-lossless/test.png | Bin 0 -> 3106 bytes .../reftest/generic/accept-image-catchall-ref.html | 12 + .../reftest/generic/accept-image-catchall.html | 13 + image/test/reftest/generic/check-header.sjs | 72 + image/test/reftest/generic/green.png | Bin 0 -> 201 bytes image/test/reftest/generic/reftest-stylo.list | 2 + image/test/reftest/generic/reftest.list | 1 + image/test/reftest/gif/1bit-255-trans.gif | Bin 0 -> 337 bytes image/test/reftest/gif/1bit-255-trans.png | Bin 0 -> 1214 bytes image/test/reftest/gif/ImageDocument.css | 16 + image/test/reftest/gif/animation1a.gif | Bin 0 -> 167 bytes image/test/reftest/gif/animation2a-finalframe.gif | Bin 0 -> 107 bytes image/test/reftest/gif/animation2a.gif | Bin 0 -> 167 bytes image/test/reftest/gif/blue.gif | Bin 0 -> 43 bytes image/test/reftest/gif/comment.gif | Bin 0 -> 68 bytes image/test/reftest/gif/comment.png | Bin 0 -> 167 bytes image/test/reftest/gif/delaytest.html | 41 + image/test/reftest/gif/in-colormap-trans.gif | Bin 0 -> 355 bytes image/test/reftest/gif/in-colormap-trans.png | Bin 0 -> 237 bytes image/test/reftest/gif/one-color-offset-ref.gif | Bin 0 -> 69 bytes image/test/reftest/gif/one-color-offset.gif | Bin 0 -> 49 bytes image/test/reftest/gif/out-of-colormap-trans.gif | Bin 0 -> 355 bytes image/test/reftest/gif/out-of-colormap-trans.png | Bin 0 -> 241 bytes image/test/reftest/gif/red.gif | Bin 0 -> 43 bytes image/test/reftest/gif/reftest-stylo.list | 57 + image/test/reftest/gif/reftest.list | 29 + .../reftest/gif/small-background-size-2-ref.gif | Bin 0 -> 807 bytes image/test/reftest/gif/small-background-size-2.gif | Bin 0 -> 572 bytes .../test/reftest/gif/small-background-size-ref.gif | Bin 0 -> 1076 bytes image/test/reftest/gif/small-background-size.gif | Bin 0 -> 991 bytes image/test/reftest/gif/test_bug641198.html | 53 + image/test/reftest/gif/tile-transform-ref.html | 12 + image/test/reftest/gif/tile-transform.html | 12 + image/test/reftest/gif/tiletest-ref.png | Bin 0 -> 282 bytes image/test/reftest/gif/tiletest.gif | Bin 0 -> 156 bytes .../gif/transparent-animation-finalframe.gif | Bin 0 -> 121 bytes image/test/reftest/gif/transparent-animation.gif | Bin 0 -> 527 bytes .../gif/truncated-framerect-interlaced-ref.gif | Bin 0 -> 927 bytes .../reftest/gif/truncated-framerect-interlaced.gif | Bin 0 -> 927 bytes image/test/reftest/gif/truncated-framerect-ref.gif | Bin 0 -> 929 bytes .../test/reftest/gif/truncated-framerect-ref.html | 33 + image/test/reftest/gif/truncated-framerect.gif | Bin 0 -> 929 bytes image/test/reftest/gif/truncated-framerect.html | 28 + image/test/reftest/ico/cur/pointer.cur | Bin 0 -> 4286 bytes image/test/reftest/ico/cur/pointer.png | Bin 0 -> 453 bytes image/test/reftest/ico/cur/reftest-stylo.list | 5 + image/test/reftest/ico/cur/reftest.list | 4 + image/test/reftest/ico/cur/wrapper.html | 27 + .../ico-not-square-transparent-1bpp.ico | Bin 0 -> 182 bytes .../ico-not-square-transparent-1bpp.png | Bin 0 -> 241 bytes .../ico-bmp-1bpp/ico-partial-transparent-1bpp.ico | Bin 0 -> 326 bytes .../ico-bmp-1bpp/ico-partial-transparent-1bpp.png | Bin 0 -> 410 bytes .../ico/ico-bmp-1bpp/ico-size-15x15-1bpp.ico | Bin 0 -> 190 bytes .../ico/ico-bmp-1bpp/ico-size-15x15-1bpp.png | Bin 0 -> 220 bytes .../ico/ico-bmp-1bpp/ico-size-16x16-1bpp.ico | Bin 0 -> 198 bytes .../ico/ico-bmp-1bpp/ico-size-16x16-1bpp.png | Bin 0 -> 242 bytes .../ico/ico-bmp-1bpp/ico-size-17x17-1bpp.ico | Bin 0 -> 206 bytes .../ico/ico-bmp-1bpp/ico-size-17x17-1bpp.png | Bin 0 -> 247 bytes .../reftest/ico/ico-bmp-1bpp/ico-size-1x1-1bpp.ico | Bin 0 -> 78 bytes .../reftest/ico/ico-bmp-1bpp/ico-size-1x1-1bpp.png | Bin 0 -> 120 bytes .../ico/ico-bmp-1bpp/ico-size-256x256-1bpp.ico | Bin 0 -> 16454 bytes .../ico/ico-bmp-1bpp/ico-size-256x256-1bpp.png | Bin 0 -> 7673 bytes .../reftest/ico/ico-bmp-1bpp/ico-size-2x2-1bpp.ico | Bin 0 -> 86 bytes .../reftest/ico/ico-bmp-1bpp/ico-size-2x2-1bpp.png | Bin 0 -> 126 bytes .../ico/ico-bmp-1bpp/ico-size-31x31-1bpp.ico | Bin 0 -> 318 bytes .../ico/ico-bmp-1bpp/ico-size-31x31-1bpp.png | Bin 0 -> 447 bytes .../ico/ico-bmp-1bpp/ico-size-32x32-1bpp.ico | Bin 0 -> 326 bytes .../ico/ico-bmp-1bpp/ico-size-32x32-1bpp.png | Bin 0 -> 455 bytes .../ico/ico-bmp-1bpp/ico-size-33x33-1bpp.ico | Bin 0 -> 598 bytes .../ico/ico-bmp-1bpp/ico-size-33x33-1bpp.png | Bin 0 -> 489 bytes .../reftest/ico/ico-bmp-1bpp/ico-size-3x3-1bpp.ico | Bin 0 -> 94 bytes .../reftest/ico/ico-bmp-1bpp/ico-size-3x3-1bpp.png | Bin 0 -> 132 bytes .../reftest/ico/ico-bmp-1bpp/ico-size-4x4-1bpp.ico | Bin 0 -> 102 bytes .../reftest/ico/ico-bmp-1bpp/ico-size-4x4-1bpp.png | Bin 0 -> 135 bytes .../reftest/ico/ico-bmp-1bpp/ico-size-5x5-1bpp.ico | Bin 0 -> 110 bytes .../reftest/ico/ico-bmp-1bpp/ico-size-5x5-1bpp.png | Bin 0 -> 146 bytes .../reftest/ico/ico-bmp-1bpp/ico-size-6x6-1bpp.ico | Bin 0 -> 118 bytes .../reftest/ico/ico-bmp-1bpp/ico-size-6x6-1bpp.png | Bin 0 -> 149 bytes .../reftest/ico/ico-bmp-1bpp/ico-size-7x7-1bpp.ico | Bin 0 -> 126 bytes .../reftest/ico/ico-bmp-1bpp/ico-size-7x7-1bpp.png | Bin 0 -> 156 bytes .../reftest/ico/ico-bmp-1bpp/ico-size-8x8-1bpp.ico | Bin 0 -> 134 bytes .../reftest/ico/ico-bmp-1bpp/ico-size-8x8-1bpp.png | Bin 0 -> 161 bytes .../reftest/ico/ico-bmp-1bpp/ico-size-9x9-1bpp.ico | Bin 0 -> 142 bytes .../reftest/ico/ico-bmp-1bpp/ico-size-9x9-1bpp.png | Bin 0 -> 171 bytes .../ico/ico-bmp-1bpp/ico-transparent-1bpp.ico | Bin 0 -> 3262 bytes .../ico/ico-bmp-1bpp/ico-transparent-1bpp.png | Bin 0 -> 195 bytes .../reftest/ico/ico-bmp-1bpp/reftest-stylo.list | 25 + image/test/reftest/ico/ico-bmp-1bpp/reftest.list | 23 + .../ico-not-square-transparent-24bpp.ico | Bin 0 -> 1126 bytes .../ico-not-square-transparent-24bpp.png | Bin 0 -> 514 bytes .../ico-partial-transparent-24bpp.ico | Bin 0 -> 3262 bytes .../ico-partial-transparent-24bpp.png | Bin 0 -> 1028 bytes .../ico/ico-bmp-24bpp/ico-size-15x15-24bpp.ico | Bin 0 -> 842 bytes .../ico/ico-bmp-24bpp/ico-size-15x15-24bpp.png | Bin 0 -> 809 bytes .../ico/ico-bmp-24bpp/ico-size-16x16-24bpp.ico | Bin 0 -> 894 bytes .../ico/ico-bmp-24bpp/ico-size-16x16-24bpp.png | Bin 0 -> 879 bytes .../ico/ico-bmp-24bpp/ico-size-17x17-24bpp.ico | Bin 0 -> 1014 bytes .../ico/ico-bmp-24bpp/ico-size-17x17-24bpp.png | Bin 0 -> 1000 bytes .../ico/ico-bmp-24bpp/ico-size-1x1-24bpp.ico | Bin 0 -> 70 bytes .../ico/ico-bmp-24bpp/ico-size-1x1-24bpp.png | Bin 0 -> 70 bytes .../ico/ico-bmp-24bpp/ico-size-256x256-24bpp.ico | Bin 0 -> 204862 bytes .../ico/ico-bmp-24bpp/ico-size-256x256-24bpp.png | Bin 0 -> 5480 bytes .../ico/ico-bmp-24bpp/ico-size-2x2-24bpp.ico | Bin 0 -> 86 bytes .../ico/ico-bmp-24bpp/ico-size-2x2-24bpp.png | Bin 0 -> 83 bytes .../ico/ico-bmp-24bpp/ico-size-31x31-24bpp.ico | Bin 0 -> 3162 bytes .../ico/ico-bmp-24bpp/ico-size-31x31-24bpp.png | Bin 0 -> 2936 bytes .../ico/ico-bmp-24bpp/ico-size-32x32-24bpp.ico | Bin 0 -> 3262 bytes .../ico/ico-bmp-24bpp/ico-size-32x32-24bpp.png | Bin 0 -> 3106 bytes .../ico/ico-bmp-24bpp/ico-size-33x33-24bpp.ico | Bin 0 -> 3626 bytes .../ico/ico-bmp-24bpp/ico-size-33x33-24bpp.png | Bin 0 -> 3303 bytes .../ico/ico-bmp-24bpp/ico-size-3x3-24bpp.ico | Bin 0 -> 110 bytes .../ico/ico-bmp-24bpp/ico-size-3x3-24bpp.png | Bin 0 -> 107 bytes .../ico/ico-bmp-24bpp/ico-size-4x4-24bpp.ico | Bin 0 -> 126 bytes .../ico/ico-bmp-24bpp/ico-size-4x4-24bpp.png | Bin 0 -> 136 bytes .../ico/ico-bmp-24bpp/ico-size-5x5-24bpp.ico | Bin 0 -> 162 bytes .../ico/ico-bmp-24bpp/ico-size-5x5-24bpp.png | Bin 0 -> 173 bytes .../ico/ico-bmp-24bpp/ico-size-6x6-24bpp.ico | Bin 0 -> 206 bytes .../ico/ico-bmp-24bpp/ico-size-6x6-24bpp.png | Bin 0 -> 218 bytes .../ico/ico-bmp-24bpp/ico-size-7x7-24bpp.ico | Bin 0 -> 258 bytes .../ico/ico-bmp-24bpp/ico-size-7x7-24bpp.png | Bin 0 -> 271 bytes .../ico/ico-bmp-24bpp/ico-size-8x8-24bpp.ico | Bin 0 -> 286 bytes .../ico/ico-bmp-24bpp/ico-size-8x8-24bpp.png | Bin 0 -> 313 bytes .../ico/ico-bmp-24bpp/ico-size-9x9-24bpp.ico | Bin 0 -> 350 bytes .../ico/ico-bmp-24bpp/ico-size-9x9-24bpp.png | Bin 0 -> 368 bytes .../ico/ico-bmp-24bpp/ico-transparent-24bpp.ico | Bin 0 -> 3262 bytes .../ico/ico-bmp-24bpp/ico-transparent-24bpp.png | Bin 0 -> 195 bytes .../reftest/ico/ico-bmp-24bpp/reftest-stylo.list | 24 + image/test/reftest/ico/ico-bmp-24bpp/reftest.list | 23 + .../ico-not-square-transparent-32bpp.ico | Bin 0 -> 1462 bytes .../ico-not-square-transparent-32bpp.png | Bin 0 -> 533 bytes .../ico-partial-transparent-32bpp.ico | Bin 0 -> 4286 bytes .../ico-partial-transparent-32bpp.png | Bin 0 -> 1028 bytes .../ico/ico-bmp-32bpp/ico-size-15x15-32bpp.ico | Bin 0 -> 1022 bytes .../ico/ico-bmp-32bpp/ico-size-15x15-32bpp.png | Bin 0 -> 809 bytes .../ico/ico-bmp-32bpp/ico-size-16x16-32bpp.ico | Bin 0 -> 1150 bytes .../ico/ico-bmp-32bpp/ico-size-16x16-32bpp.png | Bin 0 -> 879 bytes .../ico/ico-bmp-32bpp/ico-size-17x17-32bpp.ico | Bin 0 -> 1286 bytes .../ico/ico-bmp-32bpp/ico-size-17x17-32bpp.png | Bin 0 -> 1000 bytes .../ico/ico-bmp-32bpp/ico-size-1x1-32bpp.ico | Bin 0 -> 70 bytes .../ico/ico-bmp-32bpp/ico-size-1x1-32bpp.png | Bin 0 -> 70 bytes .../ico/ico-bmp-32bpp/ico-size-256x256-32bpp.ico | Bin 0 -> 270398 bytes .../ico/ico-bmp-32bpp/ico-size-256x256-32bpp.png | Bin 0 -> 5480 bytes .../ico/ico-bmp-32bpp/ico-size-2x2-32bpp.ico | Bin 0 -> 86 bytes .../ico/ico-bmp-32bpp/ico-size-2x2-32bpp.png | Bin 0 -> 83 bytes .../ico/ico-bmp-32bpp/ico-size-31x31-32bpp.ico | Bin 0 -> 4030 bytes .../ico/ico-bmp-32bpp/ico-size-31x31-32bpp.png | Bin 0 -> 2936 bytes .../ico/ico-bmp-32bpp/ico-size-32x32-32bpp.ico | Bin 0 -> 4286 bytes .../ico/ico-bmp-32bpp/ico-size-32x32-32bpp.png | Bin 0 -> 3106 bytes .../ico/ico-bmp-32bpp/ico-size-33x33-32bpp.ico | Bin 0 -> 4682 bytes .../ico/ico-bmp-32bpp/ico-size-33x33-32bpp.png | Bin 0 -> 3303 bytes .../ico/ico-bmp-32bpp/ico-size-3x3-32bpp.ico | Bin 0 -> 110 bytes .../ico/ico-bmp-32bpp/ico-size-3x3-32bpp.png | Bin 0 -> 107 bytes .../ico/ico-bmp-32bpp/ico-size-4x4-32bpp.ico | Bin 0 -> 142 bytes .../ico/ico-bmp-32bpp/ico-size-4x4-32bpp.png | Bin 0 -> 136 bytes .../ico/ico-bmp-32bpp/ico-size-5x5-32bpp.ico | Bin 0 -> 182 bytes .../ico/ico-bmp-32bpp/ico-size-5x5-32bpp.png | Bin 0 -> 173 bytes .../ico/ico-bmp-32bpp/ico-size-6x6-32bpp.ico | Bin 0 -> 230 bytes .../ico/ico-bmp-32bpp/ico-size-6x6-32bpp.png | Bin 0 -> 218 bytes .../ico/ico-bmp-32bpp/ico-size-7x7-32bpp.ico | Bin 0 -> 286 bytes .../ico/ico-bmp-32bpp/ico-size-7x7-32bpp.png | Bin 0 -> 271 bytes .../ico/ico-bmp-32bpp/ico-size-8x8-32bpp.ico | Bin 0 -> 350 bytes .../ico/ico-bmp-32bpp/ico-size-8x8-32bpp.png | Bin 0 -> 313 bytes .../ico/ico-bmp-32bpp/ico-size-9x9-32bpp.ico | Bin 0 -> 422 bytes .../ico/ico-bmp-32bpp/ico-size-9x9-32bpp.png | Bin 0 -> 368 bytes .../ico/ico-bmp-32bpp/ico-transparent-32bpp.ico | Bin 0 -> 4286 bytes .../ico/ico-bmp-32bpp/ico-transparent-32bpp.png | Bin 0 -> 195 bytes .../reftest/ico/ico-bmp-32bpp/reftest-stylo.list | 23 + image/test/reftest/ico/ico-bmp-32bpp/reftest.list | 22 + .../ico-not-square-transparent-4bpp.ico | Bin 0 -> 350 bytes .../ico-not-square-transparent-4bpp.png | Bin 0 -> 315 bytes .../ico-bmp-4bpp/ico-partial-transparent-4bpp.ico | Bin 0 -> 766 bytes .../ico-bmp-4bpp/ico-partial-transparent-4bpp.png | Bin 0 -> 556 bytes .../ico/ico-bmp-4bpp/ico-size-15x15-4bpp.ico | Bin 0 -> 306 bytes .../ico/ico-bmp-4bpp/ico-size-15x15-4bpp.png | Bin 0 -> 304 bytes .../ico/ico-bmp-4bpp/ico-size-16x16-4bpp.ico | Bin 0 -> 318 bytes .../ico/ico-bmp-4bpp/ico-size-16x16-4bpp.png | Bin 0 -> 323 bytes .../ico/ico-bmp-4bpp/ico-size-17x17-4bpp.ico | Bin 0 -> 398 bytes .../ico/ico-bmp-4bpp/ico-size-17x17-4bpp.png | Bin 0 -> 337 bytes .../reftest/ico/ico-bmp-4bpp/ico-size-1x1-4bpp.ico | Bin 0 -> 134 bytes .../reftest/ico/ico-bmp-4bpp/ico-size-1x1-4bpp.png | Bin 0 -> 120 bytes .../ico/ico-bmp-4bpp/ico-size-256x256-4bpp.ico | Bin 0 -> 41086 bytes .../ico/ico-bmp-4bpp/ico-size-256x256-4bpp.png | Bin 0 -> 16944 bytes .../reftest/ico/ico-bmp-4bpp/ico-size-2x2-4bpp.ico | Bin 0 -> 142 bytes .../reftest/ico/ico-bmp-4bpp/ico-size-2x2-4bpp.png | Bin 0 -> 128 bytes .../ico/ico-bmp-4bpp/ico-size-31x31-4bpp.ico | Bin 0 -> 746 bytes .../ico/ico-bmp-4bpp/ico-size-31x31-4bpp.png | Bin 0 -> 700 bytes .../ico/ico-bmp-4bpp/ico-size-32x32-4bpp.ico | Bin 0 -> 766 bytes .../ico/ico-bmp-4bpp/ico-size-32x32-4bpp.png | Bin 0 -> 763 bytes .../ico/ico-bmp-4bpp/ico-size-33x33-4bpp.ico | Bin 0 -> 1050 bytes .../ico/ico-bmp-4bpp/ico-size-33x33-4bpp.png | Bin 0 -> 778 bytes .../reftest/ico/ico-bmp-4bpp/ico-size-3x3-4bpp.ico | Bin 0 -> 150 bytes .../reftest/ico/ico-bmp-4bpp/ico-size-3x3-4bpp.png | Bin 0 -> 139 bytes .../reftest/ico/ico-bmp-4bpp/ico-size-4x4-4bpp.ico | Bin 0 -> 158 bytes .../reftest/ico/ico-bmp-4bpp/ico-size-4x4-4bpp.png | Bin 0 -> 147 bytes .../reftest/ico/ico-bmp-4bpp/ico-size-5x5-4bpp.ico | Bin 0 -> 166 bytes .../reftest/ico/ico-bmp-4bpp/ico-size-5x5-4bpp.png | Bin 0 -> 156 bytes .../reftest/ico/ico-bmp-4bpp/ico-size-6x6-4bpp.ico | Bin 0 -> 174 bytes .../reftest/ico/ico-bmp-4bpp/ico-size-6x6-4bpp.png | Bin 0 -> 163 bytes .../reftest/ico/ico-bmp-4bpp/ico-size-7x7-4bpp.ico | Bin 0 -> 182 bytes .../reftest/ico/ico-bmp-4bpp/ico-size-7x7-4bpp.png | Bin 0 -> 172 bytes .../reftest/ico/ico-bmp-4bpp/ico-size-8x8-4bpp.ico | Bin 0 -> 190 bytes .../reftest/ico/ico-bmp-4bpp/ico-size-8x8-4bpp.png | Bin 0 -> 188 bytes .../reftest/ico/ico-bmp-4bpp/ico-size-9x9-4bpp.ico | Bin 0 -> 234 bytes .../reftest/ico/ico-bmp-4bpp/ico-size-9x9-4bpp.png | Bin 0 -> 198 bytes .../ico/ico-bmp-4bpp/ico-transparent-4bpp.ico | Bin 0 -> 3262 bytes .../ico/ico-bmp-4bpp/ico-transparent-4bpp.png | Bin 0 -> 195 bytes .../reftest/ico/ico-bmp-4bpp/reftest-stylo.list | 24 + image/test/reftest/ico/ico-bmp-4bpp/reftest.list | 23 + .../ico-not-square-transparent-8bpp.ico | Bin 0 -> 1478 bytes .../ico-not-square-transparent-8bpp.png | Bin 0 -> 514 bytes .../ico-bmp-8bpp/ico-partial-transparent-8bpp.ico | Bin 0 -> 2238 bytes .../ico-bmp-8bpp/ico-partial-transparent-8bpp.png | Bin 0 -> 983 bytes .../ico/ico-bmp-8bpp/ico-size-15x15-8bpp.ico | Bin 0 -> 1386 bytes .../ico/ico-bmp-8bpp/ico-size-15x15-8bpp.png | Bin 0 -> 809 bytes .../ico/ico-bmp-8bpp/ico-size-16x16-8bpp.ico | Bin 0 -> 1406 bytes .../ico/ico-bmp-8bpp/ico-size-16x16-8bpp.png | Bin 0 -> 903 bytes .../ico/ico-bmp-8bpp/ico-size-17x17-8bpp.ico | Bin 0 -> 1494 bytes .../ico/ico-bmp-8bpp/ico-size-17x17-8bpp.png | Bin 0 -> 964 bytes .../reftest/ico/ico-bmp-8bpp/ico-size-1x1-8bpp.ico | Bin 0 -> 1094 bytes .../reftest/ico/ico-bmp-8bpp/ico-size-1x1-8bpp.png | Bin 0 -> 70 bytes .../ico/ico-bmp-8bpp/ico-size-256x256-8bpp.ico | Bin 0 -> 74814 bytes .../ico/ico-bmp-8bpp/ico-size-256x256-8bpp.png | Bin 0 -> 22443 bytes .../reftest/ico/ico-bmp-8bpp/ico-size-2x2-8bpp.ico | Bin 0 -> 1102 bytes .../reftest/ico/ico-bmp-8bpp/ico-size-2x2-8bpp.png | Bin 0 -> 83 bytes .../ico/ico-bmp-8bpp/ico-size-31x31-8bpp.ico | Bin 0 -> 2238 bytes .../ico/ico-bmp-8bpp/ico-size-31x31-8bpp.png | Bin 0 -> 1546 bytes .../ico/ico-bmp-8bpp/ico-size-32x32-8bpp.ico | Bin 0 -> 2238 bytes .../ico/ico-bmp-8bpp/ico-size-32x32-8bpp.png | Bin 0 -> 1530 bytes .../ico/ico-bmp-8bpp/ico-size-33x33-8bpp.ico | Bin 0 -> 2538 bytes .../ico/ico-bmp-8bpp/ico-size-33x33-8bpp.png | Bin 0 -> 1632 bytes .../reftest/ico/ico-bmp-8bpp/ico-size-3x3-8bpp.ico | Bin 0 -> 1110 bytes .../reftest/ico/ico-bmp-8bpp/ico-size-3x3-8bpp.png | Bin 0 -> 107 bytes .../reftest/ico/ico-bmp-8bpp/ico-size-4x4-8bpp.ico | Bin 0 -> 1118 bytes .../reftest/ico/ico-bmp-8bpp/ico-size-4x4-8bpp.png | Bin 0 -> 136 bytes .../reftest/ico/ico-bmp-8bpp/ico-size-5x5-8bpp.ico | Bin 0 -> 1146 bytes .../reftest/ico/ico-bmp-8bpp/ico-size-5x5-8bpp.png | Bin 0 -> 173 bytes .../reftest/ico/ico-bmp-8bpp/ico-size-6x6-8bpp.ico | Bin 0 -> 1158 bytes .../reftest/ico/ico-bmp-8bpp/ico-size-6x6-8bpp.png | Bin 0 -> 218 bytes .../reftest/ico/ico-bmp-8bpp/ico-size-7x7-8bpp.ico | Bin 0 -> 1170 bytes .../reftest/ico/ico-bmp-8bpp/ico-size-7x7-8bpp.png | Bin 0 -> 271 bytes .../reftest/ico/ico-bmp-8bpp/ico-size-8x8-8bpp.ico | Bin 0 -> 286 bytes .../reftest/ico/ico-bmp-8bpp/ico-size-8x8-8bpp.png | Bin 0 -> 313 bytes .../reftest/ico/ico-bmp-8bpp/ico-size-9x9-8bpp.ico | Bin 0 -> 1230 bytes .../reftest/ico/ico-bmp-8bpp/ico-size-9x9-8bpp.png | Bin 0 -> 368 bytes .../ico/ico-bmp-8bpp/ico-transparent-8bpp.ico | Bin 0 -> 3262 bytes .../ico/ico-bmp-8bpp/ico-transparent-8bpp.png | Bin 0 -> 195 bytes .../reftest/ico/ico-bmp-8bpp/reftest-stylo.list | 25 + image/test/reftest/ico/ico-bmp-8bpp/reftest.list | 23 + image/test/reftest/ico/ico-bmp-corrupted/16x16.png | Bin 0 -> 879 bytes .../reftest/ico/ico-bmp-corrupted/invalid-bpp.ico | Bin 0 -> 86 bytes .../ico-bmp-corrupted/invalid-compression-RLE4.ico | Bin 0 -> 86 bytes .../ico-bmp-corrupted/invalid-compression-RLE8.ico | Bin 0 -> 86 bytes .../ico/ico-bmp-corrupted/invalid-compression.ico | Bin 0 -> 830 bytes .../ico/ico-bmp-corrupted/reftest-stylo.list | 11 + .../reftest/ico/ico-bmp-corrupted/reftest.list | 10 + .../reftest/ico/ico-bmp-corrupted/wrapper.html | 80 + image/test/reftest/ico/ico-mixed/mixed-bmp-png.ico | Bin 0 -> 17542 bytes image/test/reftest/ico/ico-mixed/mixed-bmp-png.png | Bin 0 -> 629 bytes .../test/reftest/ico/ico-mixed/mixed-bmp-png32.png | Bin 0 -> 940 bytes .../test/reftest/ico/ico-mixed/mixed-bmp-png48.png | Bin 0 -> 1332 bytes .../test/reftest/ico/ico-mixed/reftest-stylo.list | 4 + image/test/reftest/ico/ico-mixed/reftest.list | 3 + .../reftest/ico/ico-png/corrupted_x00n0g01.ico | Bin 0 -> 71 bytes .../reftest/ico/ico-png/corrupted_xxcrn0g04.ico | Bin 0 -> 283 bytes .../reftest/ico/ico-png/ico-size-15x15-png.ico | Bin 0 -> 831 bytes .../reftest/ico/ico-png/ico-size-15x15-png.png | Bin 0 -> 809 bytes .../reftest/ico/ico-png/ico-size-16x16-png.ico | Bin 0 -> 901 bytes .../reftest/ico/ico-png/ico-size-16x16-png.png | Bin 0 -> 879 bytes .../reftest/ico/ico-png/ico-size-17x17-png.ico | Bin 0 -> 1022 bytes .../reftest/ico/ico-png/ico-size-17x17-png.png | Bin 0 -> 1000 bytes .../test/reftest/ico/ico-png/ico-size-1x1-png.ico | Bin 0 -> 92 bytes .../test/reftest/ico/ico-png/ico-size-1x1-png.png | Bin 0 -> 70 bytes .../reftest/ico/ico-png/ico-size-256x256-png.ico | Bin 0 -> 5934 bytes .../reftest/ico/ico-png/ico-size-256x256-png.png | Bin 0 -> 5912 bytes .../test/reftest/ico/ico-png/ico-size-2x2-png.ico | Bin 0 -> 105 bytes .../test/reftest/ico/ico-png/ico-size-2x2-png.png | Bin 0 -> 83 bytes .../reftest/ico/ico-png/ico-size-31x31-png.ico | Bin 0 -> 2958 bytes .../reftest/ico/ico-png/ico-size-31x31-png.png | Bin 0 -> 2936 bytes .../reftest/ico/ico-png/ico-size-32x32-png.ico | Bin 0 -> 3128 bytes .../reftest/ico/ico-png/ico-size-32x32-png.png | Bin 0 -> 3106 bytes .../reftest/ico/ico-png/ico-size-33x33-png.ico | Bin 0 -> 3325 bytes .../reftest/ico/ico-png/ico-size-33x33-png.png | Bin 0 -> 3303 bytes .../test/reftest/ico/ico-png/ico-size-3x3-png.ico | Bin 0 -> 129 bytes .../test/reftest/ico/ico-png/ico-size-3x3-png.png | Bin 0 -> 107 bytes .../test/reftest/ico/ico-png/ico-size-4x4-png.ico | Bin 0 -> 158 bytes .../test/reftest/ico/ico-png/ico-size-4x4-png.png | Bin 0 -> 136 bytes .../test/reftest/ico/ico-png/ico-size-5x5-png.ico | Bin 0 -> 195 bytes .../test/reftest/ico/ico-png/ico-size-5x5-png.png | Bin 0 -> 173 bytes .../test/reftest/ico/ico-png/ico-size-6x6-png.ico | Bin 0 -> 240 bytes .../test/reftest/ico/ico-png/ico-size-6x6-png.png | Bin 0 -> 218 bytes .../test/reftest/ico/ico-png/ico-size-7x7-png.ico | Bin 0 -> 293 bytes .../test/reftest/ico/ico-png/ico-size-7x7-png.png | Bin 0 -> 271 bytes .../test/reftest/ico/ico-png/ico-size-8x8-png.ico | Bin 0 -> 335 bytes .../test/reftest/ico/ico-png/ico-size-8x8-png.png | Bin 0 -> 313 bytes .../test/reftest/ico/ico-png/ico-size-9x9-png.ico | Bin 0 -> 390 bytes .../test/reftest/ico/ico-png/ico-size-9x9-png.png | Bin 0 -> 368 bytes image/test/reftest/ico/ico-png/reftest-stylo.list | 30 + image/test/reftest/ico/ico-png/reftest.list | 29 + image/test/reftest/ico/ico-png/tmp.ico | Bin 0 -> 1150 bytes image/test/reftest/ico/ico-png/transparent-png.ico | Bin 0 -> 1150 bytes image/test/reftest/ico/ico-png/transparent-png.png | Bin 0 -> 699 bytes image/test/reftest/ico/ico-png/wrapper.html | 28 + image/test/reftest/ico/ico-png/x00n0g01.png | Bin 0 -> 49 bytes image/test/reftest/ico/ico-png/xcrn0g04.png | Bin 0 -> 261 bytes image/test/reftest/ico/reftest-stylo.list | 13 + image/test/reftest/ico/reftest.list | 11 + image/test/reftest/img2html.html | 122 + image/test/reftest/jpeg/blue.jpg | Bin 0 -> 3937 bytes image/test/reftest/jpeg/jpg-cmyk-1.jpg | Bin 0 -> 1498 bytes image/test/reftest/jpeg/jpg-cmyk-1.png | Bin 0 -> 2523 bytes image/test/reftest/jpeg/jpg-cmyk-2.jpg | Bin 0 -> 5174 bytes image/test/reftest/jpeg/jpg-cmyk-2.png | Bin 0 -> 21147 bytes image/test/reftest/jpeg/jpg-gray.jpg | Bin 0 -> 396 bytes image/test/reftest/jpeg/jpg-gray.png | Bin 0 -> 498 bytes image/test/reftest/jpeg/jpg-progressive.jpg | Bin 0 -> 979 bytes image/test/reftest/jpeg/jpg-progressive.png | Bin 0 -> 3106 bytes image/test/reftest/jpeg/jpg-size-15x15.jpg | Bin 0 -> 465 bytes image/test/reftest/jpeg/jpg-size-15x15.png | Bin 0 -> 809 bytes image/test/reftest/jpeg/jpg-size-16x16.jpg | Bin 0 -> 443 bytes image/test/reftest/jpeg/jpg-size-16x16.png | Bin 0 -> 879 bytes image/test/reftest/jpeg/jpg-size-17x17.jpg | Bin 0 -> 582 bytes image/test/reftest/jpeg/jpg-size-17x17.png | Bin 0 -> 1000 bytes image/test/reftest/jpeg/jpg-size-1x1.jpg | Bin 0 -> 288 bytes image/test/reftest/jpeg/jpg-size-1x1.png | Bin 0 -> 70 bytes image/test/reftest/jpeg/jpg-size-2x2.jpg | Bin 0 -> 353 bytes image/test/reftest/jpeg/jpg-size-2x2.png | Bin 0 -> 83 bytes image/test/reftest/jpeg/jpg-size-31x31.jpg | Bin 0 -> 773 bytes image/test/reftest/jpeg/jpg-size-31x31.png | Bin 0 -> 2936 bytes image/test/reftest/jpeg/jpg-size-32x32.jpg | Bin 0 -> 759 bytes image/test/reftest/jpeg/jpg-size-32x32.png | Bin 0 -> 3106 bytes image/test/reftest/jpeg/jpg-size-33x33.jpg | Bin 0 -> 941 bytes image/test/reftest/jpeg/jpg-size-33x33.png | Bin 0 -> 3303 bytes image/test/reftest/jpeg/jpg-size-3x3.jpg | Bin 0 -> 429 bytes image/test/reftest/jpeg/jpg-size-3x3.png | Bin 0 -> 107 bytes image/test/reftest/jpeg/jpg-size-4x4.jpg | Bin 0 -> 427 bytes image/test/reftest/jpeg/jpg-size-4x4.png | Bin 0 -> 136 bytes image/test/reftest/jpeg/jpg-size-5x5.jpg | Bin 0 -> 421 bytes image/test/reftest/jpeg/jpg-size-5x5.png | Bin 0 -> 173 bytes image/test/reftest/jpeg/jpg-size-6x6.jpg | Bin 0 -> 218 bytes image/test/reftest/jpeg/jpg-size-6x6.png | Bin 0 -> 218 bytes image/test/reftest/jpeg/jpg-size-7x7.jpg | Bin 0 -> 420 bytes image/test/reftest/jpeg/jpg-size-7x7.png | Bin 0 -> 271 bytes image/test/reftest/jpeg/jpg-size-8x8.jpg | Bin 0 -> 412 bytes image/test/reftest/jpeg/jpg-size-8x8.png | Bin 0 -> 313 bytes image/test/reftest/jpeg/jpg-size-9x9.jpg | Bin 0 -> 438 bytes image/test/reftest/jpeg/jpg-size-9x9.png | Bin 0 -> 368 bytes image/test/reftest/jpeg/jpg-srgb-icc.jpg | Bin 0 -> 3226 bytes image/test/reftest/jpeg/jpg-srgb-icc.png | Bin 0 -> 2738 bytes image/test/reftest/jpeg/red.jpg | Bin 0 -> 3938 bytes image/test/reftest/jpeg/reftest-stylo.list | 57 + image/test/reftest/jpeg/reftest.list | 56 + image/test/reftest/jpeg/webcam-simulacrum.mjpg | Bin 0 -> 7978 bytes .../reftest/jpeg/webcam-simulacrum.mjpg^headers^ | 3 + .../test/reftest/pngsuite-ancillary/ccwn2c08.html | 1242 ++++++++ image/test/reftest/pngsuite-ancillary/ccwn2c08.png | Bin 0 -> 1514 bytes .../test/reftest/pngsuite-ancillary/ccwn3p08.html | 1272 +++++++++ image/test/reftest/pngsuite-ancillary/ccwn3p08.png | Bin 0 -> 1554 bytes .../test/reftest/pngsuite-ancillary/cdfn2c08.html | 326 +++ image/test/reftest/pngsuite-ancillary/cdfn2c08.png | Bin 0 -> 404 bytes .../test/reftest/pngsuite-ancillary/cdhn2c08.html | 278 ++ image/test/reftest/pngsuite-ancillary/cdhn2c08.png | Bin 0 -> 344 bytes .../test/reftest/pngsuite-ancillary/cdsn2c08.html | 86 + image/test/reftest/pngsuite-ancillary/cdsn2c08.png | Bin 0 -> 232 bytes .../test/reftest/pngsuite-ancillary/cdun2c08.html | 1094 +++++++ image/test/reftest/pngsuite-ancillary/cdun2c08.png | Bin 0 -> 724 bytes .../test/reftest/pngsuite-ancillary/ch1n3p04.html | 1094 +++++++ image/test/reftest/pngsuite-ancillary/ch1n3p04.png | Bin 0 -> 258 bytes .../test/reftest/pngsuite-ancillary/ch2n3p08.html | 1094 +++++++ image/test/reftest/pngsuite-ancillary/ch2n3p08.png | Bin 0 -> 1810 bytes .../test/reftest/pngsuite-ancillary/cm0n0g04.html | 1094 +++++++ image/test/reftest/pngsuite-ancillary/cm0n0g04.png | Bin 0 -> 292 bytes .../test/reftest/pngsuite-ancillary/cm7n0g04.html | 1094 +++++++ image/test/reftest/pngsuite-ancillary/cm7n0g04.png | Bin 0 -> 292 bytes .../test/reftest/pngsuite-ancillary/cm9n0g04.html | 1094 +++++++ image/test/reftest/pngsuite-ancillary/cm9n0g04.png | Bin 0 -> 292 bytes .../test/reftest/pngsuite-ancillary/cs3n2c16.html | 1094 +++++++ image/test/reftest/pngsuite-ancillary/cs3n2c16.png | Bin 0 -> 214 bytes .../test/reftest/pngsuite-ancillary/cs3n3p08.html | 1094 +++++++ image/test/reftest/pngsuite-ancillary/cs3n3p08.png | Bin 0 -> 259 bytes .../test/reftest/pngsuite-ancillary/cs5n2c08.html | 1094 +++++++ image/test/reftest/pngsuite-ancillary/cs5n2c08.png | Bin 0 -> 186 bytes .../test/reftest/pngsuite-ancillary/cs5n3p08.html | 1094 +++++++ image/test/reftest/pngsuite-ancillary/cs5n3p08.png | Bin 0 -> 271 bytes .../test/reftest/pngsuite-ancillary/cs8n2c08.html | 1094 +++++++ image/test/reftest/pngsuite-ancillary/cs8n2c08.png | Bin 0 -> 149 bytes .../test/reftest/pngsuite-ancillary/cs8n3p08.html | 1094 +++++++ image/test/reftest/pngsuite-ancillary/cs8n3p08.png | Bin 0 -> 256 bytes .../test/reftest/pngsuite-ancillary/ct0n0g04.html | 1094 +++++++ image/test/reftest/pngsuite-ancillary/ct0n0g04.png | Bin 0 -> 273 bytes .../test/reftest/pngsuite-ancillary/ct1n0g04.html | 1094 +++++++ image/test/reftest/pngsuite-ancillary/ct1n0g04.png | Bin 0 -> 792 bytes .../test/reftest/pngsuite-ancillary/ctzn0g04.html | 1094 +++++++ image/test/reftest/pngsuite-ancillary/ctzn0g04.png | Bin 0 -> 753 bytes .../reftest/pngsuite-ancillary/qcms-asm-check.js | 28 + .../reftest/pngsuite-ancillary/reftest-stylo.list | 63 + image/test/reftest/pngsuite-ancillary/reftest.list | 62 + .../test/reftest/pngsuite-background/bg__4a08.html | 1092 +++++++ .../test/reftest/pngsuite-background/bg__4a16.html | 1092 +++++++ .../test/reftest/pngsuite-background/bg__6a08.html | 1092 +++++++ .../test/reftest/pngsuite-background/bg__6a16.html | 1092 +++++++ .../test/reftest/pngsuite-background/bgai4a08.png | Bin 0 -> 214 bytes .../test/reftest/pngsuite-background/bgai4a16.png | Bin 0 -> 2855 bytes .../test/reftest/pngsuite-background/bgan6a08.png | Bin 0 -> 184 bytes .../test/reftest/pngsuite-background/bgan6a16.png | Bin 0 -> 3435 bytes .../test/reftest/pngsuite-background/bgbn4a08.png | Bin 0 -> 140 bytes .../test/reftest/pngsuite-background/bggn4a16.png | Bin 0 -> 2220 bytes .../test/reftest/pngsuite-background/bgwn6a08.png | Bin 0 -> 202 bytes .../test/reftest/pngsuite-background/bgyn6a16.png | Bin 0 -> 3453 bytes .../reftest/pngsuite-background/reftest-stylo.list | 23 + .../test/reftest/pngsuite-background/reftest.list | 22 + .../test/reftest/pngsuite-background/wrapper.html | 27 + image/test/reftest/pngsuite-basic-i/basi0g01.html | 1094 +++++++ image/test/reftest/pngsuite-basic-i/basi0g01.png | Bin 0 -> 217 bytes image/test/reftest/pngsuite-basic-i/basi0g02.html | 1094 +++++++ image/test/reftest/pngsuite-basic-i/basi0g02.png | Bin 0 -> 154 bytes image/test/reftest/pngsuite-basic-i/basi0g04.html | 1094 +++++++ image/test/reftest/pngsuite-basic-i/basi0g04.png | Bin 0 -> 247 bytes image/test/reftest/pngsuite-basic-i/basi0g08.html | 1094 +++++++ image/test/reftest/pngsuite-basic-i/basi0g08.png | Bin 0 -> 254 bytes image/test/reftest/pngsuite-basic-i/basi0g16.html | 1094 +++++++ image/test/reftest/pngsuite-basic-i/basi0g16.png | Bin 0 -> 299 bytes image/test/reftest/pngsuite-basic-i/basi2c08.html | 1094 +++++++ image/test/reftest/pngsuite-basic-i/basi2c08.png | Bin 0 -> 315 bytes image/test/reftest/pngsuite-basic-i/basi2c16.html | 1094 +++++++ image/test/reftest/pngsuite-basic-i/basi2c16.png | Bin 0 -> 595 bytes image/test/reftest/pngsuite-basic-i/basi3p01.html | 1094 +++++++ image/test/reftest/pngsuite-basic-i/basi3p01.png | Bin 0 -> 132 bytes image/test/reftest/pngsuite-basic-i/basi3p02.html | 1094 +++++++ image/test/reftest/pngsuite-basic-i/basi3p02.png | Bin 0 -> 193 bytes image/test/reftest/pngsuite-basic-i/basi3p04.html | 1094 +++++++ image/test/reftest/pngsuite-basic-i/basi3p04.png | Bin 0 -> 327 bytes image/test/reftest/pngsuite-basic-i/basi3p08.html | 1094 +++++++ image/test/reftest/pngsuite-basic-i/basi3p08.png | Bin 0 -> 1527 bytes image/test/reftest/pngsuite-basic-i/basi4a08.png | Bin 0 -> 214 bytes image/test/reftest/pngsuite-basic-i/basi4a16.png | Bin 0 -> 2855 bytes image/test/reftest/pngsuite-basic-i/basi6a08.png | Bin 0 -> 361 bytes image/test/reftest/pngsuite-basic-i/basi6a16.png | Bin 0 -> 4180 bytes .../reftest/pngsuite-basic-i/reftest-stylo.list | 34 + image/test/reftest/pngsuite-basic-i/reftest.list | 33 + image/test/reftest/pngsuite-basic-n/basn0g01.html | 1094 +++++++ image/test/reftest/pngsuite-basic-n/basn0g01.png | Bin 0 -> 164 bytes image/test/reftest/pngsuite-basic-n/basn0g02.html | 1094 +++++++ image/test/reftest/pngsuite-basic-n/basn0g02.png | Bin 0 -> 104 bytes image/test/reftest/pngsuite-basic-n/basn0g04.html | 1094 +++++++ image/test/reftest/pngsuite-basic-n/basn0g04.png | Bin 0 -> 145 bytes image/test/reftest/pngsuite-basic-n/basn0g08.html | 1094 +++++++ image/test/reftest/pngsuite-basic-n/basn0g08.png | Bin 0 -> 138 bytes image/test/reftest/pngsuite-basic-n/basn0g16.html | 1094 +++++++ image/test/reftest/pngsuite-basic-n/basn0g16.png | Bin 0 -> 167 bytes image/test/reftest/pngsuite-basic-n/basn2c08.html | 1094 +++++++ image/test/reftest/pngsuite-basic-n/basn2c08.png | Bin 0 -> 145 bytes image/test/reftest/pngsuite-basic-n/basn2c16.html | 1094 +++++++ image/test/reftest/pngsuite-basic-n/basn2c16.png | Bin 0 -> 302 bytes image/test/reftest/pngsuite-basic-n/basn3p01.html | 1094 +++++++ image/test/reftest/pngsuite-basic-n/basn3p01.png | Bin 0 -> 112 bytes image/test/reftest/pngsuite-basic-n/basn3p02.html | 1094 +++++++ image/test/reftest/pngsuite-basic-n/basn3p02.png | Bin 0 -> 146 bytes image/test/reftest/pngsuite-basic-n/basn3p04.html | 1094 +++++++ image/test/reftest/pngsuite-basic-n/basn3p04.png | Bin 0 -> 216 bytes image/test/reftest/pngsuite-basic-n/basn3p08.html | 1094 +++++++ image/test/reftest/pngsuite-basic-n/basn3p08.png | Bin 0 -> 1286 bytes image/test/reftest/pngsuite-basic-n/basn4a08.png | Bin 0 -> 126 bytes image/test/reftest/pngsuite-basic-n/basn4a16.png | Bin 0 -> 2206 bytes image/test/reftest/pngsuite-basic-n/basn6a08.png | Bin 0 -> 184 bytes image/test/reftest/pngsuite-basic-n/basn6a16.png | Bin 0 -> 3435 bytes .../reftest/pngsuite-basic-n/reftest-stylo.list | 34 + image/test/reftest/pngsuite-basic-n/reftest.list | 33 + image/test/reftest/pngsuite-chunkorder/color.html | 1094 +++++++ .../reftest/pngsuite-chunkorder/grayscale.html | 1094 +++++++ .../test/reftest/pngsuite-chunkorder/oi1n0g16.png | Bin 0 -> 167 bytes .../test/reftest/pngsuite-chunkorder/oi1n2c16.png | Bin 0 -> 302 bytes .../test/reftest/pngsuite-chunkorder/oi2n0g16.png | Bin 0 -> 179 bytes .../test/reftest/pngsuite-chunkorder/oi2n2c16.png | Bin 0 -> 314 bytes .../test/reftest/pngsuite-chunkorder/oi4n0g16.png | Bin 0 -> 203 bytes .../test/reftest/pngsuite-chunkorder/oi4n2c16.png | Bin 0 -> 338 bytes .../test/reftest/pngsuite-chunkorder/oi9n0g16.png | Bin 0 -> 1283 bytes .../test/reftest/pngsuite-chunkorder/oi9n2c16.png | Bin 0 -> 3038 bytes .../reftest/pngsuite-chunkorder/reftest-stylo.list | 22 + .../test/reftest/pngsuite-chunkorder/reftest.list | 21 + .../reftest/pngsuite-corrupted/reftest-stylo.list | 11 + image/test/reftest/pngsuite-corrupted/reftest.list | 10 + image/test/reftest/pngsuite-corrupted/wrapper.html | 28 + image/test/reftest/pngsuite-corrupted/x00n0g01.png | Bin 0 -> 49 bytes image/test/reftest/pngsuite-corrupted/xcrn0g04.png | Bin 0 -> 261 bytes image/test/reftest/pngsuite-corrupted/xlfn0g04.png | 13 + .../test/reftest/pngsuite-filtering/f00n0g08.html | 1094 +++++++ image/test/reftest/pngsuite-filtering/f00n0g08.png | Bin 0 -> 319 bytes .../test/reftest/pngsuite-filtering/f00n2c08.html | 1094 +++++++ image/test/reftest/pngsuite-filtering/f00n2c08.png | Bin 0 -> 2475 bytes .../test/reftest/pngsuite-filtering/f01n0g08.html | 1094 +++++++ image/test/reftest/pngsuite-filtering/f01n0g08.png | Bin 0 -> 321 bytes .../test/reftest/pngsuite-filtering/f01n2c08.html | 1094 +++++++ image/test/reftest/pngsuite-filtering/f01n2c08.png | Bin 0 -> 1180 bytes .../test/reftest/pngsuite-filtering/f02n0g08.html | 1094 +++++++ image/test/reftest/pngsuite-filtering/f02n0g08.png | Bin 0 -> 355 bytes .../test/reftest/pngsuite-filtering/f02n2c08.html | 1094 +++++++ image/test/reftest/pngsuite-filtering/f02n2c08.png | Bin 0 -> 1729 bytes .../test/reftest/pngsuite-filtering/f03n0g08.html | 1094 +++++++ image/test/reftest/pngsuite-filtering/f03n0g08.png | Bin 0 -> 389 bytes .../test/reftest/pngsuite-filtering/f03n2c08.html | 1094 +++++++ image/test/reftest/pngsuite-filtering/f03n2c08.png | Bin 0 -> 1291 bytes .../test/reftest/pngsuite-filtering/f04n0g08.html | 1094 +++++++ image/test/reftest/pngsuite-filtering/f04n0g08.png | Bin 0 -> 269 bytes .../test/reftest/pngsuite-filtering/f04n2c08.html | 1094 +++++++ image/test/reftest/pngsuite-filtering/f04n2c08.png | Bin 0 -> 985 bytes .../reftest/pngsuite-filtering/reftest-stylo.list | 23 + image/test/reftest/pngsuite-filtering/reftest.list | 22 + image/test/reftest/pngsuite-gamma/g03n0g16.html | 1094 +++++++ image/test/reftest/pngsuite-gamma/g03n0g16.png | Bin 0 -> 345 bytes image/test/reftest/pngsuite-gamma/g03n2c08.html | 1094 +++++++ image/test/reftest/pngsuite-gamma/g03n2c08.png | Bin 0 -> 370 bytes image/test/reftest/pngsuite-gamma/g03n3p04.html | 1094 +++++++ image/test/reftest/pngsuite-gamma/g03n3p04.png | Bin 0 -> 214 bytes image/test/reftest/pngsuite-gamma/g04n0g16.html | 1094 +++++++ image/test/reftest/pngsuite-gamma/g04n0g16.png | Bin 0 -> 363 bytes image/test/reftest/pngsuite-gamma/g04n2c08.html | 1094 +++++++ image/test/reftest/pngsuite-gamma/g04n2c08.png | Bin 0 -> 377 bytes image/test/reftest/pngsuite-gamma/g04n3p04.html | 1094 +++++++ image/test/reftest/pngsuite-gamma/g04n3p04.png | Bin 0 -> 219 bytes image/test/reftest/pngsuite-gamma/g05n0g16.html | 1094 +++++++ image/test/reftest/pngsuite-gamma/g05n0g16.png | Bin 0 -> 339 bytes image/test/reftest/pngsuite-gamma/g05n2c08.html | 1094 +++++++ image/test/reftest/pngsuite-gamma/g05n2c08.png | Bin 0 -> 350 bytes image/test/reftest/pngsuite-gamma/g05n3p04.html | 1094 +++++++ image/test/reftest/pngsuite-gamma/g05n3p04.png | Bin 0 -> 206 bytes image/test/reftest/pngsuite-gamma/g07n0g16.html | 1094 +++++++ image/test/reftest/pngsuite-gamma/g07n0g16.png | Bin 0 -> 321 bytes image/test/reftest/pngsuite-gamma/g07n2c08.html | 1094 +++++++ image/test/reftest/pngsuite-gamma/g07n2c08.png | Bin 0 -> 340 bytes image/test/reftest/pngsuite-gamma/g07n3p04.html | 1094 +++++++ image/test/reftest/pngsuite-gamma/g07n3p04.png | Bin 0 -> 207 bytes image/test/reftest/pngsuite-gamma/g10n0g16.html | 1094 +++++++ image/test/reftest/pngsuite-gamma/g10n0g16.png | Bin 0 -> 262 bytes image/test/reftest/pngsuite-gamma/g10n2c08.html | 1094 +++++++ image/test/reftest/pngsuite-gamma/g10n2c08.png | Bin 0 -> 285 bytes image/test/reftest/pngsuite-gamma/g10n3p04.html | 1094 +++++++ image/test/reftest/pngsuite-gamma/g10n3p04.png | Bin 0 -> 214 bytes image/test/reftest/pngsuite-gamma/g25n0g16.html | 1094 +++++++ image/test/reftest/pngsuite-gamma/g25n0g16.png | Bin 0 -> 383 bytes image/test/reftest/pngsuite-gamma/g25n2c08.html | 1094 +++++++ image/test/reftest/pngsuite-gamma/g25n2c08.png | Bin 0 -> 405 bytes image/test/reftest/pngsuite-gamma/g25n3p04.html | 1094 +++++++ image/test/reftest/pngsuite-gamma/g25n3p04.png | Bin 0 -> 215 bytes .../test/reftest/pngsuite-gamma/reftest-stylo.list | 39 + image/test/reftest/pngsuite-gamma/reftest.list | 38 + .../reftest/pngsuite-oddsizes/reftest-stylo.list | 78 + image/test/reftest/pngsuite-oddsizes/reftest.list | 77 + image/test/reftest/pngsuite-oddsizes/s01_3p01.html | 9 + image/test/reftest/pngsuite-oddsizes/s01i3p01.png | Bin 0 -> 113 bytes image/test/reftest/pngsuite-oddsizes/s01n3p01.png | Bin 0 -> 113 bytes image/test/reftest/pngsuite-oddsizes/s02_3p01.html | 14 + image/test/reftest/pngsuite-oddsizes/s02i3p01.png | Bin 0 -> 114 bytes image/test/reftest/pngsuite-oddsizes/s02n3p01.png | Bin 0 -> 115 bytes image/test/reftest/pngsuite-oddsizes/s03_3p01.html | 21 + image/test/reftest/pngsuite-oddsizes/s03i3p01.png | Bin 0 -> 118 bytes image/test/reftest/pngsuite-oddsizes/s03n3p01.png | Bin 0 -> 120 bytes image/test/reftest/pngsuite-oddsizes/s04_3p01.html | 30 + image/test/reftest/pngsuite-oddsizes/s04i3p01.png | Bin 0 -> 126 bytes image/test/reftest/pngsuite-oddsizes/s04n3p01.png | Bin 0 -> 121 bytes image/test/reftest/pngsuite-oddsizes/s05_3p02.html | 41 + image/test/reftest/pngsuite-oddsizes/s05i3p02.png | Bin 0 -> 134 bytes image/test/reftest/pngsuite-oddsizes/s05n3p02.png | Bin 0 -> 129 bytes image/test/reftest/pngsuite-oddsizes/s06_3p02.html | 54 + image/test/reftest/pngsuite-oddsizes/s06i3p02.png | Bin 0 -> 143 bytes image/test/reftest/pngsuite-oddsizes/s06n3p02.png | Bin 0 -> 131 bytes image/test/reftest/pngsuite-oddsizes/s07_3p02.html | 69 + image/test/reftest/pngsuite-oddsizes/s07i3p02.png | Bin 0 -> 149 bytes image/test/reftest/pngsuite-oddsizes/s07n3p02.png | Bin 0 -> 138 bytes image/test/reftest/pngsuite-oddsizes/s08_3p02.html | 86 + image/test/reftest/pngsuite-oddsizes/s08i3p02.png | Bin 0 -> 149 bytes image/test/reftest/pngsuite-oddsizes/s08n3p02.png | Bin 0 -> 139 bytes image/test/reftest/pngsuite-oddsizes/s09_3p02.html | 105 + image/test/reftest/pngsuite-oddsizes/s09i3p02.png | Bin 0 -> 147 bytes image/test/reftest/pngsuite-oddsizes/s09n3p02.png | Bin 0 -> 143 bytes image/test/reftest/pngsuite-oddsizes/s32_3p04.html | 1094 +++++++ image/test/reftest/pngsuite-oddsizes/s32i3p04.png | Bin 0 -> 355 bytes image/test/reftest/pngsuite-oddsizes/s32n3p04.png | Bin 0 -> 263 bytes image/test/reftest/pngsuite-oddsizes/s33_3p04.html | 1161 ++++++++ image/test/reftest/pngsuite-oddsizes/s33i3p04.png | Bin 0 -> 385 bytes image/test/reftest/pngsuite-oddsizes/s33n3p04.png | Bin 0 -> 329 bytes image/test/reftest/pngsuite-oddsizes/s34_3p04.html | 1230 ++++++++ image/test/reftest/pngsuite-oddsizes/s34i3p04.png | Bin 0 -> 349 bytes image/test/reftest/pngsuite-oddsizes/s34n3p04.png | Bin 0 -> 248 bytes image/test/reftest/pngsuite-oddsizes/s35_3p04.html | 1301 +++++++++ image/test/reftest/pngsuite-oddsizes/s35i3p04.png | Bin 0 -> 399 bytes image/test/reftest/pngsuite-oddsizes/s35n3p04.png | Bin 0 -> 338 bytes image/test/reftest/pngsuite-oddsizes/s36_3p04.html | 1374 +++++++++ image/test/reftest/pngsuite-oddsizes/s36i3p04.png | Bin 0 -> 356 bytes image/test/reftest/pngsuite-oddsizes/s36n3p04.png | Bin 0 -> 258 bytes image/test/reftest/pngsuite-oddsizes/s37_3p04.html | 1449 ++++++++++ image/test/reftest/pngsuite-oddsizes/s37i3p04.png | Bin 0 -> 393 bytes image/test/reftest/pngsuite-oddsizes/s37n3p04.png | Bin 0 -> 336 bytes image/test/reftest/pngsuite-oddsizes/s38_3p04.html | 1526 ++++++++++ image/test/reftest/pngsuite-oddsizes/s38i3p04.png | Bin 0 -> 357 bytes image/test/reftest/pngsuite-oddsizes/s38n3p04.png | Bin 0 -> 245 bytes image/test/reftest/pngsuite-oddsizes/s39_3p04.html | 1605 +++++++++++ image/test/reftest/pngsuite-oddsizes/s39i3p04.png | Bin 0 -> 420 bytes image/test/reftest/pngsuite-oddsizes/s39n3p04.png | Bin 0 -> 352 bytes image/test/reftest/pngsuite-oddsizes/s40_3p04.html | 1686 +++++++++++ image/test/reftest/pngsuite-oddsizes/s40i3p04.png | Bin 0 -> 357 bytes image/test/reftest/pngsuite-oddsizes/s40n3p04.png | Bin 0 -> 256 bytes image/test/reftest/pngsuite-palettes/pp0n2c16.html | 1094 +++++++ image/test/reftest/pngsuite-palettes/pp0n2c16.png | Bin 0 -> 962 bytes image/test/reftest/pngsuite-palettes/pp0n6a08.png | Bin 0 -> 818 bytes image/test/reftest/pngsuite-palettes/ps1n0g08.html | 1094 +++++++ image/test/reftest/pngsuite-palettes/ps1n0g08.png | Bin 0 -> 1477 bytes image/test/reftest/pngsuite-palettes/ps1n2c16.html | 1094 +++++++ image/test/reftest/pngsuite-palettes/ps1n2c16.png | Bin 0 -> 1641 bytes image/test/reftest/pngsuite-palettes/ps2n0g08.html | 1094 +++++++ image/test/reftest/pngsuite-palettes/ps2n0g08.png | Bin 0 -> 2341 bytes image/test/reftest/pngsuite-palettes/ps2n2c16.html | 1094 +++++++ image/test/reftest/pngsuite-palettes/ps2n2c16.png | Bin 0 -> 2505 bytes .../reftest/pngsuite-palettes/reftest-stylo.list | 15 + image/test/reftest/pngsuite-palettes/reftest.list | 14 + .../pngsuite-transparency/reftest-stylo.list | 27 + .../reftest/pngsuite-transparency/reftest.list | 26 + .../reftest/pngsuite-transparency/tbbn1g04.html | 1092 +++++++ .../reftest/pngsuite-transparency/tbbn1g04.png | Bin 0 -> 419 bytes .../reftest/pngsuite-transparency/tbbn2c16.html | 1092 +++++++ .../reftest/pngsuite-transparency/tbbn2c16.png | Bin 0 -> 1994 bytes .../reftest/pngsuite-transparency/tbbn3p08.html | 1092 +++++++ .../reftest/pngsuite-transparency/tbbn3p08.png | Bin 0 -> 1128 bytes .../reftest/pngsuite-transparency/tbgn2c16.html | 1092 +++++++ .../reftest/pngsuite-transparency/tbgn2c16.png | Bin 0 -> 1994 bytes .../reftest/pngsuite-transparency/tbgn3p08.html | 1092 +++++++ .../reftest/pngsuite-transparency/tbgn3p08.png | Bin 0 -> 1128 bytes .../reftest/pngsuite-transparency/tbrn2c08.html | 1092 +++++++ .../reftest/pngsuite-transparency/tbrn2c08.png | Bin 0 -> 1347 bytes .../reftest/pngsuite-transparency/tbwn1g16.html | 1092 +++++++ .../reftest/pngsuite-transparency/tbwn1g16.png | Bin 0 -> 1146 bytes .../reftest/pngsuite-transparency/tbwn3p08.html | 1092 +++++++ .../reftest/pngsuite-transparency/tbwn3p08.png | Bin 0 -> 1131 bytes .../reftest/pngsuite-transparency/tbyn3p08.html | 1092 +++++++ .../reftest/pngsuite-transparency/tbyn3p08.png | Bin 0 -> 1131 bytes .../reftest/pngsuite-transparency/tp1n3p08.html | 1092 +++++++ .../reftest/pngsuite-transparency/tp1n3p08.png | Bin 0 -> 1115 bytes .../reftest/pngsuite-transparency/wrapper.html | 27 + .../test/reftest/pngsuite-zlib/reftest-stylo.list | 9 + image/test/reftest/pngsuite-zlib/reftest.list | 8 + image/test/reftest/pngsuite-zlib/z00n2c08.html | 1094 +++++++ image/test/reftest/pngsuite-zlib/z00n2c08.png | Bin 0 -> 3172 bytes image/test/reftest/pngsuite-zlib/z03n2c08.html | 1094 +++++++ image/test/reftest/pngsuite-zlib/z03n2c08.png | Bin 0 -> 232 bytes image/test/reftest/pngsuite-zlib/z06n2c08.html | 1094 +++++++ image/test/reftest/pngsuite-zlib/z06n2c08.png | Bin 0 -> 224 bytes image/test/reftest/pngsuite-zlib/z09n2c08.html | 1094 +++++++ image/test/reftest/pngsuite-zlib/z09n2c08.png | Bin 0 -> 224 bytes image/test/reftest/reftest-stylo.list | 65 + image/test/reftest/reftest.list | 50 + image/test/unit/async_load_tests.js | 214 ++ image/test/unit/bug413512.ico | Bin 0 -> 17759 bytes image/test/unit/bug815359.ico | Bin 0 -> 4286 bytes image/test/unit/image1.png | Bin 0 -> 8415 bytes image/test/unit/image1png16x16.jpg | Bin 0 -> 1051 bytes image/test/unit/image1png64x64.jpg | Bin 0 -> 4503 bytes image/test/unit/image2.jpg | Bin 0 -> 3494 bytes image/test/unit/image2jpg16x16-win.png | Bin 0 -> 948 bytes image/test/unit/image2jpg16x16.png | Bin 0 -> 950 bytes image/test/unit/image2jpg16x16cropped.jpg | Bin 0 -> 879 bytes image/test/unit/image2jpg16x16cropped2.jpg | Bin 0 -> 878 bytes image/test/unit/image2jpg16x32cropped3.jpg | Bin 0 -> 1127 bytes image/test/unit/image2jpg16x32scaled.jpg | Bin 0 -> 1219 bytes image/test/unit/image2jpg32x16cropped4.jpg | Bin 0 -> 1135 bytes image/test/unit/image2jpg32x16scaled.jpg | Bin 0 -> 1227 bytes image/test/unit/image2jpg32x32-win.png | Bin 0 -> 3104 bytes image/test/unit/image2jpg32x32.jpg | Bin 0 -> 1634 bytes image/test/unit/image2jpg32x32.png | Bin 0 -> 3105 bytes image/test/unit/image3.ico | Bin 0 -> 1406 bytes image/test/unit/image3ico16x16.png | Bin 0 -> 330 bytes image/test/unit/image3ico32x32.png | Bin 0 -> 2285 bytes image/test/unit/image4.gif | Bin 0 -> 1809 bytes image/test/unit/image4gif16x16bmp24bpp.ico | Bin 0 -> 894 bytes image/test/unit/image4gif16x16bmp32bpp.ico | Bin 0 -> 1150 bytes image/test/unit/image4gif32x32bmp24bpp.ico | Bin 0 -> 3262 bytes image/test/unit/image4gif32x32bmp32bpp.ico | Bin 0 -> 4286 bytes image/test/unit/image_load_helpers.js | 122 + image/test/unit/test_async_notification.js | 10 + image/test/unit/test_async_notification_404.js | 16 + .../test/unit/test_async_notification_animated.js | 14 + image/test/unit/test_encoder_apng.js | 470 +++ image/test/unit/test_encoder_png.js | 256 ++ image/test/unit/test_imgtools.js | 737 +++++ image/test/unit/test_moz_icon_uri.js | 157 + image/test/unit/test_private_channel.js | 143 + image/test/unit/xpcshell.ini | 45 + 1422 files changed, 181639 insertions(+) create mode 100644 image/AnimationSurfaceProvider.cpp create mode 100644 image/AnimationSurfaceProvider.h create mode 100644 image/BMPHeaders.h create mode 100644 image/ClippedImage.cpp create mode 100644 image/ClippedImage.h create mode 100644 image/CopyOnWrite.h create mode 100644 image/DecodePool.cpp create mode 100644 image/DecodePool.h create mode 100644 image/DecodedSurfaceProvider.cpp create mode 100644 image/DecodedSurfaceProvider.h create mode 100644 image/Decoder.cpp create mode 100644 image/Decoder.h create mode 100644 image/DecoderFactory.cpp create mode 100644 image/DecoderFactory.h create mode 100644 image/DecoderFlags.h create mode 100644 image/Downscaler.cpp create mode 100644 image/Downscaler.h create mode 100644 image/DownscalingFilter.h create mode 100644 image/DrawResult.h create mode 100644 image/DynamicImage.cpp create mode 100644 image/DynamicImage.h create mode 100644 image/FrameAnimator.cpp create mode 100644 image/FrameAnimator.h create mode 100644 image/FrozenImage.cpp create mode 100644 image/FrozenImage.h create mode 100644 image/ICOFileHeaders.h create mode 100644 image/IDecodingTask.cpp create mode 100644 image/IDecodingTask.h create mode 100644 image/IProgressObserver.h create mode 100644 image/ISurfaceProvider.h create mode 100644 image/Image.cpp create mode 100644 image/Image.h create mode 100644 image/ImageCacheKey.cpp create mode 100644 image/ImageCacheKey.h create mode 100644 image/ImageFactory.cpp create mode 100644 image/ImageFactory.h create mode 100644 image/ImageLogging.h create mode 100644 image/ImageMetadata.h create mode 100644 image/ImageOps.cpp create mode 100644 image/ImageOps.h create mode 100644 image/ImageRegion.h create mode 100644 image/ImageURL.h create mode 100644 image/ImageWrapper.cpp create mode 100644 image/ImageWrapper.h create mode 100644 image/LookupResult.h create mode 100644 image/MultipartImage.cpp create mode 100644 image/MultipartImage.h create mode 100644 image/Orientation.h create mode 100644 image/OrientedImage.cpp create mode 100644 image/OrientedImage.h create mode 100644 image/PlaybackType.h create mode 100644 image/ProgressTracker.cpp create mode 100644 image/ProgressTracker.h create mode 100644 image/RasterImage.cpp create mode 100644 image/RasterImage.h create mode 100644 image/SVGDocumentWrapper.cpp create mode 100644 image/SVGDocumentWrapper.h create mode 100644 image/ScriptedNotificationObserver.cpp create mode 100644 image/ScriptedNotificationObserver.h create mode 100644 image/ShutdownTracker.cpp create mode 100644 image/ShutdownTracker.h create mode 100644 image/SourceBuffer.cpp create mode 100644 image/SourceBuffer.h create mode 100644 image/StreamingLexer.h create mode 100644 image/SurfaceCache.cpp create mode 100644 image/SurfaceCache.h create mode 100644 image/SurfaceCacheUtils.cpp create mode 100644 image/SurfaceCacheUtils.h create mode 100644 image/SurfaceFilters.h create mode 100644 image/SurfaceFlags.h create mode 100644 image/SurfacePipe.cpp create mode 100644 image/SurfacePipe.h create mode 100644 image/SurfacePipeFactory.h create mode 100644 image/VectorImage.cpp create mode 100644 image/VectorImage.h create mode 100644 image/build/moz.build create mode 100644 image/build/nsImageModule.cpp create mode 100644 image/build/nsImageModule.h create mode 100644 image/decoders/EXIF.cpp create mode 100644 image/decoders/EXIF.h create mode 100644 image/decoders/GIF2.h create mode 100644 image/decoders/iccjpeg.c create mode 100644 image/decoders/iccjpeg.h create mode 100644 image/decoders/icon/android/moz.build create mode 100644 image/decoders/icon/android/nsIconChannel.cpp create mode 100644 image/decoders/icon/android/nsIconChannel.h create mode 100644 image/decoders/icon/gtk/moz.build create mode 100644 image/decoders/icon/gtk/nsIconChannel.cpp create mode 100644 image/decoders/icon/gtk/nsIconChannel.h create mode 100644 image/decoders/icon/mac/moz.build create mode 100644 image/decoders/icon/mac/nsIconChannel.h create mode 100644 image/decoders/icon/mac/nsIconChannelCocoa.mm create mode 100644 image/decoders/icon/moz.build create mode 100644 image/decoders/icon/nsIconModule.cpp create mode 100644 image/decoders/icon/nsIconProtocolHandler.cpp create mode 100644 image/decoders/icon/nsIconProtocolHandler.h create mode 100644 image/decoders/icon/nsIconURI.cpp create mode 100644 image/decoders/icon/nsIconURI.h create mode 100644 image/decoders/icon/win/moz.build create mode 100644 image/decoders/icon/win/nsIconChannel.cpp create mode 100644 image/decoders/icon/win/nsIconChannel.h create mode 100644 image/decoders/moz.build create mode 100644 image/decoders/nsBMPDecoder.cpp create mode 100644 image/decoders/nsBMPDecoder.h create mode 100644 image/decoders/nsGIFDecoder2.cpp create mode 100644 image/decoders/nsGIFDecoder2.h create mode 100644 image/decoders/nsICODecoder.cpp create mode 100644 image/decoders/nsICODecoder.h create mode 100644 image/decoders/nsIconDecoder.cpp create mode 100644 image/decoders/nsIconDecoder.h create mode 100644 image/decoders/nsJPEGDecoder.cpp create mode 100644 image/decoders/nsJPEGDecoder.h create mode 100644 image/decoders/nsPNGDecoder.cpp create mode 100644 image/decoders/nsPNGDecoder.h create mode 100644 image/encoders/bmp/moz.build create mode 100644 image/encoders/bmp/nsBMPEncoder.cpp create mode 100644 image/encoders/bmp/nsBMPEncoder.h create mode 100644 image/encoders/ico/moz.build create mode 100644 image/encoders/ico/nsICOEncoder.cpp create mode 100644 image/encoders/ico/nsICOEncoder.h create mode 100644 image/encoders/jpeg/moz.build create mode 100644 image/encoders/jpeg/nsJPEGEncoder.cpp create mode 100644 image/encoders/jpeg/nsJPEGEncoder.h create mode 100644 image/encoders/moz.build create mode 100644 image/encoders/png/moz.build create mode 100644 image/encoders/png/nsPNGEncoder.cpp create mode 100644 image/encoders/png/nsPNGEncoder.h create mode 100644 image/imgFrame.cpp create mode 100644 image/imgFrame.h create mode 100644 image/imgICache.idl create mode 100644 image/imgIContainer.idl create mode 100644 image/imgIContainerDebug.idl create mode 100644 image/imgIEncoder.idl create mode 100644 image/imgILoader.idl create mode 100644 image/imgINotificationObserver.idl create mode 100644 image/imgIOnloadBlocker.idl create mode 100644 image/imgIRequest.idl create mode 100644 image/imgIScriptedNotificationObserver.idl create mode 100644 image/imgITools.idl create mode 100644 image/imgLoader.cpp create mode 100644 image/imgLoader.h create mode 100644 image/imgRequest.cpp create mode 100644 image/imgRequest.h create mode 100644 image/imgRequestProxy.cpp create mode 100644 image/imgRequestProxy.h create mode 100644 image/imgTools.cpp create mode 100644 image/imgTools.h create mode 100644 image/moz.build create mode 100644 image/nsIIconURI.idl create mode 100644 image/test/browser/animated.gif create mode 100644 image/test/browser/animated2.gif create mode 100644 image/test/browser/big.png create mode 100644 image/test/browser/browser.ini create mode 100644 image/test/browser/browser_bug666317.js create mode 100644 image/test/browser/browser_docshell_type_editor.js create mode 100644 image/test/browser/browser_image.js create mode 100644 image/test/browser/head.js create mode 100644 image/test/browser/image.html create mode 100644 image/test/browser/imageX2.html create mode 100644 image/test/crashtests/1205923-1.html create mode 100644 image/test/crashtests/1210745-1.gif create mode 100644 image/test/crashtests/1212954-1.svg create mode 100644 image/test/crashtests/1235605.gif create mode 100644 image/test/crashtests/1241728-1.html create mode 100644 image/test/crashtests/1241729-1.bmp create mode 100644 image/test/crashtests/1241729-1.html create mode 100644 image/test/crashtests/1242093-1.html create mode 100644 image/test/crashtests/1242778-1.png create mode 100644 image/test/crashtests/1249576-1.png create mode 100644 image/test/crashtests/1251091-1.html create mode 100644 image/test/crashtests/1251091-1.png create mode 100644 image/test/crashtests/1253362-1.html create mode 100644 image/test/crashtests/256-height.ico create mode 100644 image/test/crashtests/256-width.ico create mode 100644 image/test/crashtests/463696.bmp create mode 100644 image/test/crashtests/523528-1.gif create mode 100644 image/test/crashtests/523528-2.gif create mode 100644 image/test/crashtests/570451.png create mode 100644 image/test/crashtests/681190.html create mode 100644 image/test/crashtests/694165-1.xhtml create mode 100644 image/test/crashtests/732319-1.html create mode 100644 image/test/crashtests/83804-1.gif create mode 100644 image/test/crashtests/844403-1.html create mode 100644 image/test/crashtests/856616.gif create mode 100644 image/test/crashtests/89341-1.gif create mode 100644 image/test/crashtests/944353.jpg create mode 100644 image/test/crashtests/colormap-range.gif create mode 100644 image/test/crashtests/crashtests.list create mode 100644 image/test/crashtests/delayedframe.sjs create mode 100644 image/test/crashtests/delaytest.html create mode 100644 image/test/crashtests/discardframe.htm create mode 100644 image/test/crashtests/ie.png create mode 100644 image/test/crashtests/invalid-disposal-method-1.gif create mode 100644 image/test/crashtests/invalid-disposal-method-2.gif create mode 100644 image/test/crashtests/invalid-disposal-method-3.gif create mode 100644 image/test/crashtests/invalid-icc-profile.jpg create mode 100644 image/test/crashtests/invalid-size-second-frame.gif create mode 100644 image/test/crashtests/invalid-size.gif create mode 100644 image/test/crashtests/invalid_ico_height.ico create mode 100644 image/test/crashtests/invalid_ico_width.ico create mode 100644 image/test/crashtests/multiple-png-hassize.ico create mode 100644 image/test/crashtests/ownerdiscard.html create mode 100644 image/test/crashtests/threeframes-end.gif create mode 100644 image/test/crashtests/threeframes-start.gif create mode 100644 image/test/crashtests/truncated-second-frame.png create mode 100644 image/test/crashtests/unsized-svg.svg create mode 100644 image/test/gtest/Common.cpp create mode 100644 image/test/gtest/Common.h create mode 100644 image/test/gtest/TestADAM7InterpolatingFilter.cpp create mode 100644 image/test/gtest/TestCopyOnWrite.cpp create mode 100644 image/test/gtest/TestDecodeToSurface.cpp create mode 100644 image/test/gtest/TestDecoders.cpp create mode 100644 image/test/gtest/TestDeinterlacingFilter.cpp create mode 100644 image/test/gtest/TestDownscalingFilter.cpp create mode 100644 image/test/gtest/TestDownscalingFilterNoSkia.cpp create mode 100644 image/test/gtest/TestMetadata.cpp create mode 100644 image/test/gtest/TestRemoveFrameRectFilter.cpp create mode 100644 image/test/gtest/TestSourceBuffer.cpp create mode 100644 image/test/gtest/TestStreamingLexer.cpp create mode 100644 image/test/gtest/TestSurfacePipeIntegration.cpp create mode 100644 image/test/gtest/TestSurfaceSink.cpp create mode 100644 image/test/gtest/animated-with-extra-image-sub-blocks.gif create mode 100644 image/test/gtest/corrupt-with-bad-bmp-height.ico create mode 100644 image/test/gtest/corrupt-with-bad-bmp-width.ico create mode 100644 image/test/gtest/corrupt.jpg create mode 100644 image/test/gtest/downscaled.bmp create mode 100644 image/test/gtest/downscaled.gif create mode 100644 image/test/gtest/downscaled.ico create mode 100644 image/test/gtest/downscaled.icon create mode 100644 image/test/gtest/downscaled.jpg create mode 100644 image/test/gtest/downscaled.png create mode 100644 image/test/gtest/first-frame-green.gif create mode 100644 image/test/gtest/first-frame-green.png create mode 100644 image/test/gtest/first-frame-padding.gif create mode 100644 image/test/gtest/green-1x1-truncated.gif create mode 100644 image/test/gtest/green.bmp create mode 100644 image/test/gtest/green.gif create mode 100644 image/test/gtest/green.ico create mode 100644 image/test/gtest/green.icon create mode 100644 image/test/gtest/green.jpg create mode 100644 image/test/gtest/green.png create mode 100644 image/test/gtest/invalid-truncated-metadata.bmp create mode 100644 image/test/gtest/moz.build create mode 100644 image/test/gtest/no-frame-delay.gif create mode 100644 image/test/gtest/rle4.bmp create mode 100644 image/test/gtest/rle8.bmp create mode 100644 image/test/gtest/transparent-ico-with-and-mask.ico create mode 100644 image/test/gtest/transparent-if-within-ico.bmp create mode 100644 image/test/gtest/transparent.gif create mode 100644 image/test/gtest/transparent.png create mode 100644 image/test/mochitest/12M-pixels-1.png create mode 100644 image/test/mochitest/12M-pixels-2.png create mode 100644 image/test/mochitest/6M-pixels.png create mode 100644 image/test/mochitest/INT32_MIN.bmp create mode 100644 image/test/mochitest/animated-gif-finalframe.gif create mode 100644 image/test/mochitest/animated-gif.gif create mode 100644 image/test/mochitest/animated-gif2.gif create mode 100644 image/test/mochitest/animated-gif_trailing-garbage.gif create mode 100644 image/test/mochitest/animated1.gif create mode 100644 image/test/mochitest/animated2.gif create mode 100644 image/test/mochitest/animation.svg create mode 100644 image/test/mochitest/animationPolling.js create mode 100644 image/test/mochitest/bad.jpg create mode 100644 image/test/mochitest/big.png create mode 100644 image/test/mochitest/blue.gif create mode 100644 image/test/mochitest/blue.png create mode 100644 image/test/mochitest/bug1132427.gif create mode 100644 image/test/mochitest/bug1132427.html create mode 100644 image/test/mochitest/bug1180105-waiter.sjs create mode 100644 image/test/mochitest/bug1180105.sjs create mode 100644 image/test/mochitest/bug1217571-iframe.html create mode 100644 image/test/mochitest/bug1319025-ref.png create mode 100644 image/test/mochitest/bug1319025.png create mode 100644 image/test/mochitest/bug399925.gif create mode 100644 image/test/mochitest/bug415761.ico create mode 100644 image/test/mochitest/bug468160.sjs create mode 100644 image/test/mochitest/bug478398_ONLY.png create mode 100644 image/test/mochitest/bug490949-iframe.html create mode 100644 image/test/mochitest/bug490949.sjs create mode 100644 image/test/mochitest/bug496292-1.sjs create mode 100644 image/test/mochitest/bug496292-2.sjs create mode 100644 image/test/mochitest/bug496292-iframe-1.html create mode 100644 image/test/mochitest/bug496292-iframe-2.html create mode 100644 image/test/mochitest/bug496292-iframe-ref.html create mode 100644 image/test/mochitest/bug497665-iframe.html create mode 100644 image/test/mochitest/bug497665.sjs create mode 100644 image/test/mochitest/bug552605.sjs create mode 100644 image/test/mochitest/bug657191.sjs create mode 100644 image/test/mochitest/bug671906-iframe.html create mode 100644 image/test/mochitest/bug671906.sjs create mode 100644 image/test/mochitest/bug733553-informant.sjs create mode 100644 image/test/mochitest/bug733553.sjs create mode 100644 image/test/mochitest/bug767779.sjs create mode 100644 image/test/mochitest/bug89419-iframe.html create mode 100644 image/test/mochitest/bug89419.sjs create mode 100644 image/test/mochitest/bug900200-ref.png create mode 100644 image/test/mochitest/bug900200.png create mode 100644 image/test/mochitest/chrome.ini create mode 100644 image/test/mochitest/clear.gif create mode 100644 image/test/mochitest/clear.png create mode 100644 image/test/mochitest/clear2-results.gif create mode 100644 image/test/mochitest/clear2.gif create mode 100644 image/test/mochitest/damon.jpg create mode 100644 image/test/mochitest/error-early.png create mode 100644 image/test/mochitest/filter-final.svg create mode 100644 image/test/mochitest/filter.svg create mode 100644 image/test/mochitest/first-frame-padding.gif create mode 100644 image/test/mochitest/green-background.html create mode 100644 image/test/mochitest/green.png create mode 100644 image/test/mochitest/grey.png create mode 100644 image/test/mochitest/ico-bmp-opaque.ico create mode 100644 image/test/mochitest/ico-bmp-transparent.ico create mode 100644 image/test/mochitest/iframe.html create mode 100644 image/test/mochitest/imgutils.js create mode 100644 image/test/mochitest/invalid.jpg create mode 100644 image/test/mochitest/keep.gif create mode 100644 image/test/mochitest/keep.png create mode 100644 image/test/mochitest/lime-anim-100x100-2.svg create mode 100644 image/test/mochitest/lime-anim-100x100.svg create mode 100644 image/test/mochitest/lime-css-anim-100x100.svg create mode 100644 image/test/mochitest/lime100x100.svg create mode 100644 image/test/mochitest/mochitest.ini create mode 100644 image/test/mochitest/opaque.bmp create mode 100644 image/test/mochitest/over.png create mode 100644 image/test/mochitest/purple.gif create mode 100644 image/test/mochitest/red.gif create mode 100644 image/test/mochitest/red.png create mode 100644 image/test/mochitest/ref-iframe.html create mode 100644 image/test/mochitest/restore-previous.gif create mode 100644 image/test/mochitest/restore-previous.png create mode 100644 image/test/mochitest/rillybad.jpg create mode 100644 image/test/mochitest/schrep.png create mode 100644 image/test/mochitest/shaver.png create mode 100644 image/test/mochitest/short_header.gif create mode 100644 image/test/mochitest/source.png create mode 100644 image/test/mochitest/test_ImageContentLoaded.html create mode 100644 image/test/mochitest/test_animSVGImage.html create mode 100644 image/test/mochitest/test_animSVGImage2.html create mode 100644 image/test/mochitest/test_animation.html create mode 100644 image/test/mochitest/test_animation2.html create mode 100644 image/test/mochitest/test_animation_operators.html create mode 100644 image/test/mochitest/test_background_image_anim.html create mode 100644 image/test/mochitest/test_bug1132427.html create mode 100644 image/test/mochitest/test_bug1180105.html create mode 100644 image/test/mochitest/test_bug1217571.html create mode 100644 image/test/mochitest/test_bug399925.html create mode 100644 image/test/mochitest/test_bug415761.html create mode 100644 image/test/mochitest/test_bug435296.html create mode 100644 image/test/mochitest/test_bug466586.html create mode 100644 image/test/mochitest/test_bug468160.html create mode 100644 image/test/mochitest/test_bug478398.html create mode 100644 image/test/mochitest/test_bug490949.html create mode 100644 image/test/mochitest/test_bug496292.html create mode 100644 image/test/mochitest/test_bug497665.html create mode 100644 image/test/mochitest/test_bug552605-1.html create mode 100644 image/test/mochitest/test_bug552605-2.html create mode 100644 image/test/mochitest/test_bug553982.html create mode 100644 image/test/mochitest/test_bug601470.html create mode 100644 image/test/mochitest/test_bug614392.html create mode 100644 image/test/mochitest/test_bug657191.html create mode 100644 image/test/mochitest/test_bug671906.html create mode 100644 image/test/mochitest/test_bug733553.html create mode 100644 image/test/mochitest/test_bug767779.html create mode 100644 image/test/mochitest/test_bug865919.html create mode 100644 image/test/mochitest/test_bug89419-1.html create mode 100644 image/test/mochitest/test_bug89419-2.html create mode 100644 image/test/mochitest/test_bullet_animation.html create mode 100644 image/test/mochitest/test_changeOfSource.html create mode 100644 image/test/mochitest/test_changeOfSource2.html create mode 100644 image/test/mochitest/test_drawDiscardedImage.html create mode 100644 image/test/mochitest/test_error_events.html create mode 100644 image/test/mochitest/test_has_transparency.html create mode 100644 image/test/mochitest/test_image_crossorigin_data_url.html create mode 100644 image/test/mochitest/test_net_failedtoprocess.html create mode 100644 image/test/mochitest/test_removal_ondecode.html create mode 100644 image/test/mochitest/test_removal_onload.html create mode 100644 image/test/mochitest/test_short_gif_header.html create mode 100644 image/test/mochitest/test_staticClone.html create mode 100644 image/test/mochitest/test_svg_animatedGIF.html create mode 100644 image/test/mochitest/test_svg_filter_animation.html create mode 100644 image/test/mochitest/test_synchronized_animation.html create mode 100644 image/test/mochitest/test_undisplayed_iframe.html create mode 100644 image/test/mochitest/test_webcam.html create mode 100644 image/test/mochitest/test_xultree_animation.xhtml create mode 100644 image/test/mochitest/transparent.gif create mode 100644 image/test/mochitest/transparent.png create mode 100644 image/test/mochitest/webcam-simulacrum.sjs create mode 100644 image/test/reftest/ImageDocument.css create mode 100644 image/test/reftest/apng/bug411852-1-ref.png create mode 100644 image/test/reftest/apng/bug411852-1.png create mode 100644 image/test/reftest/apng/bug546272-ref.png create mode 100644 image/test/reftest/apng/bug546272.png create mode 100644 image/test/reftest/apng/delaytest.html create mode 100644 image/test/reftest/apng/reftest-stylo.list create mode 100644 image/test/reftest/apng/reftest.list create mode 100644 image/test/reftest/blob/blob-uri-with-ref-param-notref.html create mode 100644 image/test/reftest/blob/blob-uri-with-ref-param.html create mode 100644 image/test/reftest/blob/image.png create mode 100644 image/test/reftest/blob/reftest-stylo.list create mode 100644 image/test/reftest/blob/reftest.list create mode 100644 image/test/reftest/bmp/1240629-1.bmp create mode 100644 image/test/reftest/bmp/1240629-2.bmp create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-not-square-1bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-not-square-1bpp.png create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-15x15-1bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-15x15-1bpp.png create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-16x16-1bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-16x16-1bpp.png create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-17x17-1bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-17x17-1bpp.png create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-1x1-1bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-1x1-1bpp.ico create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-1x1-1bpp.png create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-2x2-1bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-2x2-1bpp.png create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-31x31-1bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-31x31-1bpp.png create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-32x32-1bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-32x32-1bpp.png create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-33x33-1bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-33x33-1bpp.png create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-3x3-1bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-3x3-1bpp.png create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-4x4-1bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-4x4-1bpp.png create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-5x5-1bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-5x5-1bpp.png create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-6x6-1bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-6x6-1bpp.png create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-7x7-1bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-7x7-1bpp.png create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-8x8-1bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-8x8-1bpp.png create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-9x9-1bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-1bpp/bmp-size-9x9-1bpp.png create mode 100644 image/test/reftest/bmp/bmp-1bpp/os2bmp-size-32x32-1bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-1bpp/reftest-stylo.list create mode 100644 image/test/reftest/bmp/bmp-1bpp/reftest.list create mode 100644 image/test/reftest/bmp/bmp-1bpp/top-to-bottom-16x16-1bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-not-square-24bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-not-square-24bpp.png create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-15x15-24bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-15x15-24bpp.png create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-16x16-24bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-16x16-24bpp.png create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-17x17-24bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-17x17-24bpp.png create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-1x1-24bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-1x1-24bpp.png create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-2x2-24bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-2x2-24bpp.png create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-31x31-24bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-31x31-24bpp.png create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-32x32-24bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-32x32-24bpp.png create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-33x33-24bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-33x33-24bpp.png create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-3x3-24bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-3x3-24bpp.png create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-4x4-24bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-4x4-24bpp.png create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-5x5-24bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-5x5-24bpp.png create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-6x6-24bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-6x6-24bpp.png create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-7x7-24bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-7x7-24bpp.png create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-8x8-24bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-8x8-24bpp.png create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-9x9-24bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-24bpp/bmp-size-9x9-24bpp.png create mode 100644 image/test/reftest/bmp/bmp-24bpp/os2bmp-size-32x32-24bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-24bpp/reftest-stylo.list create mode 100644 image/test/reftest/bmp/bmp-24bpp/reftest.list create mode 100644 image/test/reftest/bmp/bmp-24bpp/top-to-bottom-16x16-24bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-not-square-4bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-not-square-4bpp.png create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-15x15-4bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-15x15-4bpp.png create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-16x16-4bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-16x16-4bpp.png create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-17x17-4bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-17x17-4bpp.png create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-1x1-4bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-1x1-4bpp.png create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-2x2-4bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-2x2-4bpp.png create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-31x31-4bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-31x31-4bpp.png create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-32x32-4bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-32x32-4bpp.png create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-33x33-4bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-33x33-4bpp.png create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-3x3-4bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-3x3-4bpp.png create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-4x4-4bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-4x4-4bpp.png create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-5x5-4bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-5x5-4bpp.png create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-6x6-4bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-6x6-4bpp.png create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-7x7-4bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-7x7-4bpp.png create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-8x8-4bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-8x8-4bpp.png create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-9x9-4bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-4bpp/bmp-size-9x9-4bpp.png create mode 100644 image/test/reftest/bmp/bmp-4bpp/os2bmp-size-32x32-4bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-4bpp/reftest-stylo.list create mode 100644 image/test/reftest/bmp/bmp-4bpp/reftest.list create mode 100644 image/test/reftest/bmp/bmp-4bpp/rle4-delta-320x240.bmp create mode 100644 image/test/reftest/bmp/bmp-4bpp/rle4-delta-320x240.png create mode 100644 image/test/reftest/bmp/bmp-4bpp/top-to-bottom-16x16-4bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-not-square-8bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-not-square-8bpp.png create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-15x15-8bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-15x15-8bpp.png create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-16x16-8bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-16x16-8bpp.png create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-17x17-8bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-17x17-8bpp.png create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-1x1-8bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-1x1-8bpp.png create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-2x2-8bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-2x2-8bpp.png create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-31x31-8bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-31x31-8bpp.png create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-32x32-8bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-32x32-8bpp.png create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-33x33-8bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-33x33-8bpp.png create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-3x3-8bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-3x3-8bpp.png create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-4x4-8bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-4x4-8bpp.png create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-5x5-8bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-5x5-8bpp.png create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-6x6-8bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-6x6-8bpp.png create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-7x7-8bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-7x7-8bpp.png create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-8x8-8bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-8x8-8bpp.png create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-9x9-8bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-8bpp/bmp-size-9x9-8bpp.png create mode 100644 image/test/reftest/bmp/bmp-8bpp/os2-bmp-size-32x32-8bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-8bpp/reftest-stylo.list create mode 100644 image/test/reftest/bmp/bmp-8bpp/reftest.list create mode 100644 image/test/reftest/bmp/bmp-8bpp/rle-bmp-not-square-8bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-8bpp/rle-bmp-size-32x32-8bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-8bpp/top-to-bottom-16x16-8bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-8bpp/top-to-bottom-rle-bmp-size-32x32-8bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-corrupted/invalid-bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-corrupted/invalid-compression-BITFIELDS.bmp create mode 100644 image/test/reftest/bmp/bmp-corrupted/invalid-compression-RLE4.bmp create mode 100644 image/test/reftest/bmp/bmp-corrupted/invalid-compression-RLE8.bmp create mode 100644 image/test/reftest/bmp/bmp-corrupted/invalid-compression.bmp create mode 100644 image/test/reftest/bmp/bmp-corrupted/invalid-signature.bmp create mode 100644 image/test/reftest/bmp/bmp-corrupted/invalid-truncated-metadata.bmp create mode 100644 image/test/reftest/bmp/bmp-corrupted/os2-invalid-bpp.bmp create mode 100644 image/test/reftest/bmp/bmp-corrupted/reftest-stylo.list create mode 100644 image/test/reftest/bmp/bmp-corrupted/reftest.list create mode 100644 image/test/reftest/bmp/bmp-corrupted/wrapper.html create mode 100644 image/test/reftest/bmp/bmpsuite/COPYING.txt create mode 100644 image/test/reftest/bmp/bmpsuite/README.mozilla create mode 100644 image/test/reftest/bmp/bmpsuite/b/badbitcount.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/b/badbitssize.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/b/baddens1.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/b/baddens2.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/b/badfilesize.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/b/badheadersize.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/b/badpalettesize.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/b/badplanes.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/b/badrle.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/b/badrle.png create mode 100644 image/test/reftest/bmp/bmpsuite/b/badwidth.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/b/pal1.png create mode 100644 image/test/reftest/bmp/bmpsuite/b/pal8.png create mode 100644 image/test/reftest/bmp/bmpsuite/b/pal8badindex.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/b/pal8badindex.png create mode 100644 image/test/reftest/bmp/bmpsuite/b/reallybig.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/b/reftest-stylo.list create mode 100644 image/test/reftest/bmp/bmpsuite/b/reftest.list create mode 100644 image/test/reftest/bmp/bmpsuite/b/rletopdown.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/b/shortfile.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/b/shortfile.png create mode 100644 image/test/reftest/bmp/bmpsuite/b/wrapper.html create mode 100644 image/test/reftest/bmp/bmpsuite/g/pal1.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/g/pal1.png create mode 100644 image/test/reftest/bmp/bmpsuite/g/pal1bg.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/g/pal1bg.png create mode 100644 image/test/reftest/bmp/bmpsuite/g/pal1wb.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/g/pal4.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/g/pal4.png create mode 100644 image/test/reftest/bmp/bmpsuite/g/pal4rle.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/g/pal8-0.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/g/pal8.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/g/pal8.png create mode 100644 image/test/reftest/bmp/bmpsuite/g/pal8nonsquare-e.png create mode 100644 image/test/reftest/bmp/bmpsuite/g/pal8nonsquare.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/g/pal8nonsquare.png create mode 100644 image/test/reftest/bmp/bmpsuite/g/pal8os2.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/g/pal8rle.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/g/pal8topdown.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/g/pal8v4.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/g/pal8v5.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/g/pal8w124.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/g/pal8w124.png create mode 100644 image/test/reftest/bmp/bmpsuite/g/pal8w125.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/g/pal8w125.png create mode 100644 image/test/reftest/bmp/bmpsuite/g/pal8w126.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/g/pal8w126.png create mode 100644 image/test/reftest/bmp/bmpsuite/g/reftest-stylo.list create mode 100644 image/test/reftest/bmp/bmpsuite/g/reftest.list create mode 100644 image/test/reftest/bmp/bmpsuite/g/rgb16-565.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/g/rgb16-565.png create mode 100644 image/test/reftest/bmp/bmpsuite/g/rgb16-565pal.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/g/rgb16.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/g/rgb16.png create mode 100644 image/test/reftest/bmp/bmpsuite/g/rgb24.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/g/rgb24.png create mode 100644 image/test/reftest/bmp/bmpsuite/g/rgb24pal.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/g/rgb32.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/g/rgb32bf.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/q/pal1p1.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/q/pal1p1.png create mode 100644 image/test/reftest/bmp/bmpsuite/q/pal2.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/q/pal4rletrns.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/q/pal4rletrns.png create mode 100644 image/test/reftest/bmp/bmpsuite/q/pal8.png create mode 100644 image/test/reftest/bmp/bmpsuite/q/pal8offs.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/q/pal8os2sp.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/q/pal8os2v2-16.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/q/pal8os2v2.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/q/pal8oversizepal.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/q/pal8rletrns.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/q/pal8rletrns.png create mode 100644 image/test/reftest/bmp/bmpsuite/q/reftest-stylo.list create mode 100644 image/test/reftest/bmp/bmpsuite/q/reftest.list create mode 100644 image/test/reftest/bmp/bmpsuite/q/rgb16-231.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/q/rgb16-231.png create mode 100644 image/test/reftest/bmp/bmpsuite/q/rgb24.png create mode 100644 image/test/reftest/bmp/bmpsuite/q/rgb24jpeg.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/q/rgb24largepal.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/q/rgb24lprof.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/q/rgb24png.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/q/rgb24prof.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/q/rgb32-111110.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/q/rgb32fakealpha.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/q/rgba16-4444.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/q/rgba16-4444.png create mode 100644 image/test/reftest/bmp/bmpsuite/q/rgba32.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/q/rgba32.png create mode 100644 image/test/reftest/bmp/bmpsuite/q/rgba32abf.bmp create mode 100644 image/test/reftest/bmp/bmpsuite/q/wrapper.html create mode 100644 image/test/reftest/bmp/bmpsuite/reftest-stylo.list create mode 100644 image/test/reftest/bmp/bmpsuite/reftest.list create mode 100644 image/test/reftest/bmp/reftest-stylo.list create mode 100644 image/test/reftest/bmp/reftest.list create mode 100644 image/test/reftest/color-management/color-curv.png create mode 100644 image/test/reftest/color-management/color-lin.png create mode 100644 image/test/reftest/color-management/color-table.png create mode 100644 image/test/reftest/color-management/invalid-chrm-ref.png create mode 100644 image/test/reftest/color-management/invalid-chrm.png create mode 100644 image/test/reftest/color-management/invalid-whitepoint.png create mode 100644 image/test/reftest/color-management/reftest-stylo.list create mode 100644 image/test/reftest/color-management/reftest.list create mode 100644 image/test/reftest/color-management/trc-type-ref.html create mode 100644 image/test/reftest/color-management/trc-type.html create mode 100644 image/test/reftest/colordepth.html create mode 100644 image/test/reftest/downscaling/black-border-bottom.png create mode 100644 image/test/reftest/downscaling/black-border-left.png create mode 100644 image/test/reftest/downscaling/black-border-rect.svg create mode 100644 image/test/reftest/downscaling/black-border-right.png create mode 100644 image/test/reftest/downscaling/black-border-top.png create mode 100644 image/test/reftest/downscaling/bmp-size-16x16-24bpp.png create mode 100644 image/test/reftest/downscaling/downscale-1-bigimage.png create mode 100644 image/test/reftest/downscaling/downscale-1-ref.html create mode 100644 image/test/reftest/downscaling/downscale-1-smallimage.png create mode 100644 image/test/reftest/downscaling/downscale-1.html create mode 100644 image/test/reftest/downscaling/downscale-16px.html create mode 100644 image/test/reftest/downscaling/downscale-2a.html create mode 100644 image/test/reftest/downscaling/downscale-2b.html create mode 100644 image/test/reftest/downscaling/downscale-2c.html create mode 100644 image/test/reftest/downscaling/downscale-2d.html create mode 100644 image/test/reftest/downscaling/downscale-2e.html create mode 100644 image/test/reftest/downscaling/downscale-2f.html create mode 100644 image/test/reftest/downscaling/downscale-32px-ref.html create mode 100644 image/test/reftest/downscaling/downscale-32px.html create mode 100644 image/test/reftest/downscaling/downscale-8px.html create mode 100644 image/test/reftest/downscaling/downscale-moz-icon-1-ref.html create mode 100644 image/test/reftest/downscaling/downscale-moz-icon-1.html create mode 100644 image/test/reftest/downscaling/downscale-png.html create mode 100644 image/test/reftest/downscaling/downscale-svg-1-ref.html create mode 100644 image/test/reftest/downscaling/downscale-svg-1a.html create mode 100644 image/test/reftest/downscaling/downscale-svg-1b.html create mode 100644 image/test/reftest/downscaling/downscale-svg-1c.html create mode 100644 image/test/reftest/downscaling/downscale-svg-1d.html create mode 100644 image/test/reftest/downscaling/downscale-svg-1e.html create mode 100644 image/test/reftest/downscaling/downscale-svg-1f.html create mode 100644 image/test/reftest/downscaling/ff-0RGB.ico create mode 100644 image/test/reftest/downscaling/ff-0RGB.png create mode 100644 image/test/reftest/downscaling/ff-ARGB.ico create mode 100644 image/test/reftest/downscaling/ff-ARGB.png create mode 100644 image/test/reftest/downscaling/lime-red-256px-bmp-in.ico create mode 100644 image/test/reftest/downscaling/lime-red-256px-png-in.ico create mode 100644 image/test/reftest/downscaling/lime-red-256px.bmp create mode 100644 image/test/reftest/downscaling/lime-red-256px.gif create mode 100644 image/test/reftest/downscaling/lime-red-256px.jpg create mode 100644 image/test/reftest/downscaling/lime-red-256px.png create mode 100644 image/test/reftest/downscaling/lime-red-256px.svg create mode 100644 image/test/reftest/downscaling/lime-red-32px.png create mode 100644 image/test/reftest/downscaling/png-interlaced.png create mode 100644 image/test/reftest/downscaling/png-normal.png create mode 100644 image/test/reftest/downscaling/reftest-stylo.list create mode 100644 image/test/reftest/downscaling/reftest.list create mode 100644 image/test/reftest/downscaling/top-to-bottom-16x16-24bpp.bmp create mode 100644 image/test/reftest/encoders-lossless/ImageDocument.css create mode 100644 image/test/reftest/encoders-lossless/encoder.html create mode 100644 image/test/reftest/encoders-lossless/reftest-stylo.list create mode 100644 image/test/reftest/encoders-lossless/reftest.list create mode 100644 image/test/reftest/encoders-lossless/size-15x15.png create mode 100644 image/test/reftest/encoders-lossless/size-16x16.png create mode 100644 image/test/reftest/encoders-lossless/size-17x17.png create mode 100644 image/test/reftest/encoders-lossless/size-1x1.png create mode 100644 image/test/reftest/encoders-lossless/size-256x256.png create mode 100644 image/test/reftest/encoders-lossless/size-2x2.png create mode 100644 image/test/reftest/encoders-lossless/size-31x31.png create mode 100644 image/test/reftest/encoders-lossless/size-32x32.png create mode 100644 image/test/reftest/encoders-lossless/size-33x33.png create mode 100644 image/test/reftest/encoders-lossless/size-3x3.png create mode 100644 image/test/reftest/encoders-lossless/size-4x4.png create mode 100644 image/test/reftest/encoders-lossless/size-5x5.png create mode 100644 image/test/reftest/encoders-lossless/size-6x6.png create mode 100644 image/test/reftest/encoders-lossless/size-7x7.png create mode 100644 image/test/reftest/encoders-lossless/size-8x8.png create mode 100644 image/test/reftest/encoders-lossless/size-9x9.png create mode 100644 image/test/reftest/encoders-lossless/test.png create mode 100644 image/test/reftest/generic/accept-image-catchall-ref.html create mode 100644 image/test/reftest/generic/accept-image-catchall.html create mode 100644 image/test/reftest/generic/check-header.sjs create mode 100644 image/test/reftest/generic/green.png create mode 100644 image/test/reftest/generic/reftest-stylo.list create mode 100644 image/test/reftest/generic/reftest.list create mode 100644 image/test/reftest/gif/1bit-255-trans.gif create mode 100644 image/test/reftest/gif/1bit-255-trans.png create mode 100644 image/test/reftest/gif/ImageDocument.css create mode 100644 image/test/reftest/gif/animation1a.gif create mode 100644 image/test/reftest/gif/animation2a-finalframe.gif create mode 100644 image/test/reftest/gif/animation2a.gif create mode 100644 image/test/reftest/gif/blue.gif create mode 100644 image/test/reftest/gif/comment.gif create mode 100644 image/test/reftest/gif/comment.png create mode 100644 image/test/reftest/gif/delaytest.html create mode 100644 image/test/reftest/gif/in-colormap-trans.gif create mode 100644 image/test/reftest/gif/in-colormap-trans.png create mode 100644 image/test/reftest/gif/one-color-offset-ref.gif create mode 100644 image/test/reftest/gif/one-color-offset.gif create mode 100644 image/test/reftest/gif/out-of-colormap-trans.gif create mode 100644 image/test/reftest/gif/out-of-colormap-trans.png create mode 100644 image/test/reftest/gif/red.gif create mode 100644 image/test/reftest/gif/reftest-stylo.list create mode 100644 image/test/reftest/gif/reftest.list create mode 100644 image/test/reftest/gif/small-background-size-2-ref.gif create mode 100644 image/test/reftest/gif/small-background-size-2.gif create mode 100644 image/test/reftest/gif/small-background-size-ref.gif create mode 100644 image/test/reftest/gif/small-background-size.gif create mode 100644 image/test/reftest/gif/test_bug641198.html create mode 100644 image/test/reftest/gif/tile-transform-ref.html create mode 100644 image/test/reftest/gif/tile-transform.html create mode 100644 image/test/reftest/gif/tiletest-ref.png create mode 100644 image/test/reftest/gif/tiletest.gif create mode 100644 image/test/reftest/gif/transparent-animation-finalframe.gif create mode 100644 image/test/reftest/gif/transparent-animation.gif create mode 100644 image/test/reftest/gif/truncated-framerect-interlaced-ref.gif create mode 100644 image/test/reftest/gif/truncated-framerect-interlaced.gif create mode 100644 image/test/reftest/gif/truncated-framerect-ref.gif create mode 100644 image/test/reftest/gif/truncated-framerect-ref.html create mode 100644 image/test/reftest/gif/truncated-framerect.gif create mode 100644 image/test/reftest/gif/truncated-framerect.html create mode 100644 image/test/reftest/ico/cur/pointer.cur create mode 100644 image/test/reftest/ico/cur/pointer.png create mode 100644 image/test/reftest/ico/cur/reftest-stylo.list create mode 100644 image/test/reftest/ico/cur/reftest.list create mode 100644 image/test/reftest/ico/cur/wrapper.html create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-not-square-transparent-1bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-not-square-transparent-1bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-partial-transparent-1bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-partial-transparent-1bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-15x15-1bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-15x15-1bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-16x16-1bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-16x16-1bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-17x17-1bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-17x17-1bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-1x1-1bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-1x1-1bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-256x256-1bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-256x256-1bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-2x2-1bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-2x2-1bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-31x31-1bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-31x31-1bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-32x32-1bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-32x32-1bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-33x33-1bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-33x33-1bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-3x3-1bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-3x3-1bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-4x4-1bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-4x4-1bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-5x5-1bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-5x5-1bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-6x6-1bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-6x6-1bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-7x7-1bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-7x7-1bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-8x8-1bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-8x8-1bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-9x9-1bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-size-9x9-1bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-transparent-1bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/ico-transparent-1bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/reftest-stylo.list create mode 100644 image/test/reftest/ico/ico-bmp-1bpp/reftest.list create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-not-square-transparent-24bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-not-square-transparent-24bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-partial-transparent-24bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-partial-transparent-24bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-15x15-24bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-15x15-24bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-16x16-24bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-16x16-24bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-17x17-24bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-17x17-24bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-1x1-24bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-1x1-24bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-256x256-24bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-256x256-24bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-2x2-24bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-2x2-24bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-31x31-24bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-31x31-24bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-32x32-24bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-32x32-24bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-33x33-24bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-33x33-24bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-3x3-24bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-3x3-24bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-4x4-24bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-4x4-24bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-5x5-24bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-5x5-24bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-6x6-24bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-6x6-24bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-7x7-24bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-7x7-24bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-8x8-24bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-8x8-24bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-9x9-24bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-size-9x9-24bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-transparent-24bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/ico-transparent-24bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/reftest-stylo.list create mode 100644 image/test/reftest/ico/ico-bmp-24bpp/reftest.list create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-not-square-transparent-32bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-not-square-transparent-32bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-partial-transparent-32bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-partial-transparent-32bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-15x15-32bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-15x15-32bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-16x16-32bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-16x16-32bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-17x17-32bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-17x17-32bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-1x1-32bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-1x1-32bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-256x256-32bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-256x256-32bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-2x2-32bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-2x2-32bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-31x31-32bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-31x31-32bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-32x32-32bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-32x32-32bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-33x33-32bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-33x33-32bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-3x3-32bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-3x3-32bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-4x4-32bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-4x4-32bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-5x5-32bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-5x5-32bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-6x6-32bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-6x6-32bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-7x7-32bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-7x7-32bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-8x8-32bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-8x8-32bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-9x9-32bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-size-9x9-32bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-transparent-32bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/ico-transparent-32bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/reftest-stylo.list create mode 100644 image/test/reftest/ico/ico-bmp-32bpp/reftest.list create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-not-square-transparent-4bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-not-square-transparent-4bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-partial-transparent-4bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-partial-transparent-4bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-15x15-4bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-15x15-4bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-16x16-4bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-16x16-4bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-17x17-4bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-17x17-4bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-1x1-4bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-1x1-4bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-256x256-4bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-256x256-4bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-2x2-4bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-2x2-4bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-31x31-4bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-31x31-4bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-32x32-4bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-32x32-4bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-33x33-4bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-33x33-4bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-3x3-4bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-3x3-4bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-4x4-4bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-4x4-4bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-5x5-4bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-5x5-4bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-6x6-4bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-6x6-4bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-7x7-4bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-7x7-4bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-8x8-4bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-8x8-4bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-9x9-4bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-size-9x9-4bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-transparent-4bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/ico-transparent-4bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/reftest-stylo.list create mode 100644 image/test/reftest/ico/ico-bmp-4bpp/reftest.list create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-not-square-transparent-8bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-not-square-transparent-8bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-partial-transparent-8bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-partial-transparent-8bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-15x15-8bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-15x15-8bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-16x16-8bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-16x16-8bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-17x17-8bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-17x17-8bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-1x1-8bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-1x1-8bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-256x256-8bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-256x256-8bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-2x2-8bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-2x2-8bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-31x31-8bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-31x31-8bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-32x32-8bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-32x32-8bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-33x33-8bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-33x33-8bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-3x3-8bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-3x3-8bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-4x4-8bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-4x4-8bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-5x5-8bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-5x5-8bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-6x6-8bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-6x6-8bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-7x7-8bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-7x7-8bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-8x8-8bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-8x8-8bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-9x9-8bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-size-9x9-8bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-transparent-8bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/ico-transparent-8bpp.png create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/reftest-stylo.list create mode 100644 image/test/reftest/ico/ico-bmp-8bpp/reftest.list create mode 100644 image/test/reftest/ico/ico-bmp-corrupted/16x16.png create mode 100644 image/test/reftest/ico/ico-bmp-corrupted/invalid-bpp.ico create mode 100644 image/test/reftest/ico/ico-bmp-corrupted/invalid-compression-RLE4.ico create mode 100644 image/test/reftest/ico/ico-bmp-corrupted/invalid-compression-RLE8.ico create mode 100644 image/test/reftest/ico/ico-bmp-corrupted/invalid-compression.ico create mode 100644 image/test/reftest/ico/ico-bmp-corrupted/reftest-stylo.list create mode 100644 image/test/reftest/ico/ico-bmp-corrupted/reftest.list create mode 100644 image/test/reftest/ico/ico-bmp-corrupted/wrapper.html create mode 100644 image/test/reftest/ico/ico-mixed/mixed-bmp-png.ico create mode 100644 image/test/reftest/ico/ico-mixed/mixed-bmp-png.png create mode 100644 image/test/reftest/ico/ico-mixed/mixed-bmp-png32.png create mode 100644 image/test/reftest/ico/ico-mixed/mixed-bmp-png48.png create mode 100644 image/test/reftest/ico/ico-mixed/reftest-stylo.list create mode 100644 image/test/reftest/ico/ico-mixed/reftest.list create mode 100644 image/test/reftest/ico/ico-png/corrupted_x00n0g01.ico create mode 100644 image/test/reftest/ico/ico-png/corrupted_xxcrn0g04.ico create mode 100644 image/test/reftest/ico/ico-png/ico-size-15x15-png.ico create mode 100644 image/test/reftest/ico/ico-png/ico-size-15x15-png.png create mode 100644 image/test/reftest/ico/ico-png/ico-size-16x16-png.ico create mode 100644 image/test/reftest/ico/ico-png/ico-size-16x16-png.png create mode 100644 image/test/reftest/ico/ico-png/ico-size-17x17-png.ico create mode 100644 image/test/reftest/ico/ico-png/ico-size-17x17-png.png create mode 100644 image/test/reftest/ico/ico-png/ico-size-1x1-png.ico create mode 100644 image/test/reftest/ico/ico-png/ico-size-1x1-png.png create mode 100644 image/test/reftest/ico/ico-png/ico-size-256x256-png.ico create mode 100644 image/test/reftest/ico/ico-png/ico-size-256x256-png.png create mode 100644 image/test/reftest/ico/ico-png/ico-size-2x2-png.ico create mode 100644 image/test/reftest/ico/ico-png/ico-size-2x2-png.png create mode 100644 image/test/reftest/ico/ico-png/ico-size-31x31-png.ico create mode 100644 image/test/reftest/ico/ico-png/ico-size-31x31-png.png create mode 100644 image/test/reftest/ico/ico-png/ico-size-32x32-png.ico create mode 100644 image/test/reftest/ico/ico-png/ico-size-32x32-png.png create mode 100644 image/test/reftest/ico/ico-png/ico-size-33x33-png.ico create mode 100644 image/test/reftest/ico/ico-png/ico-size-33x33-png.png create mode 100644 image/test/reftest/ico/ico-png/ico-size-3x3-png.ico create mode 100644 image/test/reftest/ico/ico-png/ico-size-3x3-png.png create mode 100644 image/test/reftest/ico/ico-png/ico-size-4x4-png.ico create mode 100644 image/test/reftest/ico/ico-png/ico-size-4x4-png.png create mode 100644 image/test/reftest/ico/ico-png/ico-size-5x5-png.ico create mode 100644 image/test/reftest/ico/ico-png/ico-size-5x5-png.png create mode 100644 image/test/reftest/ico/ico-png/ico-size-6x6-png.ico create mode 100644 image/test/reftest/ico/ico-png/ico-size-6x6-png.png create mode 100644 image/test/reftest/ico/ico-png/ico-size-7x7-png.ico create mode 100644 image/test/reftest/ico/ico-png/ico-size-7x7-png.png create mode 100644 image/test/reftest/ico/ico-png/ico-size-8x8-png.ico create mode 100644 image/test/reftest/ico/ico-png/ico-size-8x8-png.png create mode 100644 image/test/reftest/ico/ico-png/ico-size-9x9-png.ico create mode 100644 image/test/reftest/ico/ico-png/ico-size-9x9-png.png create mode 100644 image/test/reftest/ico/ico-png/reftest-stylo.list create mode 100644 image/test/reftest/ico/ico-png/reftest.list create mode 100644 image/test/reftest/ico/ico-png/tmp.ico create mode 100644 image/test/reftest/ico/ico-png/transparent-png.ico create mode 100644 image/test/reftest/ico/ico-png/transparent-png.png create mode 100644 image/test/reftest/ico/ico-png/wrapper.html create mode 100644 image/test/reftest/ico/ico-png/x00n0g01.png create mode 100644 image/test/reftest/ico/ico-png/xcrn0g04.png create mode 100644 image/test/reftest/ico/reftest-stylo.list create mode 100644 image/test/reftest/ico/reftest.list create mode 100644 image/test/reftest/img2html.html create mode 100644 image/test/reftest/jpeg/blue.jpg create mode 100644 image/test/reftest/jpeg/jpg-cmyk-1.jpg create mode 100644 image/test/reftest/jpeg/jpg-cmyk-1.png create mode 100644 image/test/reftest/jpeg/jpg-cmyk-2.jpg create mode 100644 image/test/reftest/jpeg/jpg-cmyk-2.png create mode 100644 image/test/reftest/jpeg/jpg-gray.jpg create mode 100644 image/test/reftest/jpeg/jpg-gray.png create mode 100644 image/test/reftest/jpeg/jpg-progressive.jpg create mode 100644 image/test/reftest/jpeg/jpg-progressive.png create mode 100644 image/test/reftest/jpeg/jpg-size-15x15.jpg create mode 100644 image/test/reftest/jpeg/jpg-size-15x15.png create mode 100644 image/test/reftest/jpeg/jpg-size-16x16.jpg create mode 100644 image/test/reftest/jpeg/jpg-size-16x16.png create mode 100644 image/test/reftest/jpeg/jpg-size-17x17.jpg create mode 100644 image/test/reftest/jpeg/jpg-size-17x17.png create mode 100644 image/test/reftest/jpeg/jpg-size-1x1.jpg create mode 100644 image/test/reftest/jpeg/jpg-size-1x1.png create mode 100644 image/test/reftest/jpeg/jpg-size-2x2.jpg create mode 100644 image/test/reftest/jpeg/jpg-size-2x2.png create mode 100644 image/test/reftest/jpeg/jpg-size-31x31.jpg create mode 100644 image/test/reftest/jpeg/jpg-size-31x31.png create mode 100644 image/test/reftest/jpeg/jpg-size-32x32.jpg create mode 100644 image/test/reftest/jpeg/jpg-size-32x32.png create mode 100644 image/test/reftest/jpeg/jpg-size-33x33.jpg create mode 100644 image/test/reftest/jpeg/jpg-size-33x33.png create mode 100644 image/test/reftest/jpeg/jpg-size-3x3.jpg create mode 100644 image/test/reftest/jpeg/jpg-size-3x3.png create mode 100644 image/test/reftest/jpeg/jpg-size-4x4.jpg create mode 100644 image/test/reftest/jpeg/jpg-size-4x4.png create mode 100644 image/test/reftest/jpeg/jpg-size-5x5.jpg create mode 100644 image/test/reftest/jpeg/jpg-size-5x5.png create mode 100644 image/test/reftest/jpeg/jpg-size-6x6.jpg create mode 100644 image/test/reftest/jpeg/jpg-size-6x6.png create mode 100644 image/test/reftest/jpeg/jpg-size-7x7.jpg create mode 100644 image/test/reftest/jpeg/jpg-size-7x7.png create mode 100644 image/test/reftest/jpeg/jpg-size-8x8.jpg create mode 100644 image/test/reftest/jpeg/jpg-size-8x8.png create mode 100644 image/test/reftest/jpeg/jpg-size-9x9.jpg create mode 100644 image/test/reftest/jpeg/jpg-size-9x9.png create mode 100644 image/test/reftest/jpeg/jpg-srgb-icc.jpg create mode 100644 image/test/reftest/jpeg/jpg-srgb-icc.png create mode 100644 image/test/reftest/jpeg/red.jpg create mode 100644 image/test/reftest/jpeg/reftest-stylo.list create mode 100644 image/test/reftest/jpeg/reftest.list create mode 100644 image/test/reftest/jpeg/webcam-simulacrum.mjpg create mode 100644 image/test/reftest/jpeg/webcam-simulacrum.mjpg^headers^ create mode 100644 image/test/reftest/pngsuite-ancillary/ccwn2c08.html create mode 100644 image/test/reftest/pngsuite-ancillary/ccwn2c08.png create mode 100644 image/test/reftest/pngsuite-ancillary/ccwn3p08.html create mode 100644 image/test/reftest/pngsuite-ancillary/ccwn3p08.png create mode 100644 image/test/reftest/pngsuite-ancillary/cdfn2c08.html create mode 100644 image/test/reftest/pngsuite-ancillary/cdfn2c08.png create mode 100644 image/test/reftest/pngsuite-ancillary/cdhn2c08.html create mode 100644 image/test/reftest/pngsuite-ancillary/cdhn2c08.png create mode 100644 image/test/reftest/pngsuite-ancillary/cdsn2c08.html create mode 100644 image/test/reftest/pngsuite-ancillary/cdsn2c08.png create mode 100644 image/test/reftest/pngsuite-ancillary/cdun2c08.html create mode 100644 image/test/reftest/pngsuite-ancillary/cdun2c08.png create mode 100644 image/test/reftest/pngsuite-ancillary/ch1n3p04.html create mode 100644 image/test/reftest/pngsuite-ancillary/ch1n3p04.png create mode 100644 image/test/reftest/pngsuite-ancillary/ch2n3p08.html create mode 100644 image/test/reftest/pngsuite-ancillary/ch2n3p08.png create mode 100644 image/test/reftest/pngsuite-ancillary/cm0n0g04.html create mode 100644 image/test/reftest/pngsuite-ancillary/cm0n0g04.png create mode 100644 image/test/reftest/pngsuite-ancillary/cm7n0g04.html create mode 100644 image/test/reftest/pngsuite-ancillary/cm7n0g04.png create mode 100644 image/test/reftest/pngsuite-ancillary/cm9n0g04.html create mode 100644 image/test/reftest/pngsuite-ancillary/cm9n0g04.png create mode 100644 image/test/reftest/pngsuite-ancillary/cs3n2c16.html create mode 100644 image/test/reftest/pngsuite-ancillary/cs3n2c16.png create mode 100644 image/test/reftest/pngsuite-ancillary/cs3n3p08.html create mode 100644 image/test/reftest/pngsuite-ancillary/cs3n3p08.png create mode 100644 image/test/reftest/pngsuite-ancillary/cs5n2c08.html create mode 100644 image/test/reftest/pngsuite-ancillary/cs5n2c08.png create mode 100644 image/test/reftest/pngsuite-ancillary/cs5n3p08.html create mode 100644 image/test/reftest/pngsuite-ancillary/cs5n3p08.png create mode 100644 image/test/reftest/pngsuite-ancillary/cs8n2c08.html create mode 100644 image/test/reftest/pngsuite-ancillary/cs8n2c08.png create mode 100644 image/test/reftest/pngsuite-ancillary/cs8n3p08.html create mode 100644 image/test/reftest/pngsuite-ancillary/cs8n3p08.png create mode 100644 image/test/reftest/pngsuite-ancillary/ct0n0g04.html create mode 100644 image/test/reftest/pngsuite-ancillary/ct0n0g04.png create mode 100644 image/test/reftest/pngsuite-ancillary/ct1n0g04.html create mode 100644 image/test/reftest/pngsuite-ancillary/ct1n0g04.png create mode 100644 image/test/reftest/pngsuite-ancillary/ctzn0g04.html create mode 100644 image/test/reftest/pngsuite-ancillary/ctzn0g04.png create mode 100644 image/test/reftest/pngsuite-ancillary/qcms-asm-check.js create mode 100644 image/test/reftest/pngsuite-ancillary/reftest-stylo.list create mode 100644 image/test/reftest/pngsuite-ancillary/reftest.list create mode 100644 image/test/reftest/pngsuite-background/bg__4a08.html create mode 100644 image/test/reftest/pngsuite-background/bg__4a16.html create mode 100644 image/test/reftest/pngsuite-background/bg__6a08.html create mode 100644 image/test/reftest/pngsuite-background/bg__6a16.html create mode 100644 image/test/reftest/pngsuite-background/bgai4a08.png create mode 100644 image/test/reftest/pngsuite-background/bgai4a16.png create mode 100644 image/test/reftest/pngsuite-background/bgan6a08.png create mode 100644 image/test/reftest/pngsuite-background/bgan6a16.png create mode 100644 image/test/reftest/pngsuite-background/bgbn4a08.png create mode 100644 image/test/reftest/pngsuite-background/bggn4a16.png create mode 100644 image/test/reftest/pngsuite-background/bgwn6a08.png create mode 100644 image/test/reftest/pngsuite-background/bgyn6a16.png create mode 100644 image/test/reftest/pngsuite-background/reftest-stylo.list create mode 100644 image/test/reftest/pngsuite-background/reftest.list create mode 100644 image/test/reftest/pngsuite-background/wrapper.html create mode 100644 image/test/reftest/pngsuite-basic-i/basi0g01.html create mode 100644 image/test/reftest/pngsuite-basic-i/basi0g01.png create mode 100644 image/test/reftest/pngsuite-basic-i/basi0g02.html create mode 100644 image/test/reftest/pngsuite-basic-i/basi0g02.png create mode 100644 image/test/reftest/pngsuite-basic-i/basi0g04.html create mode 100644 image/test/reftest/pngsuite-basic-i/basi0g04.png create mode 100644 image/test/reftest/pngsuite-basic-i/basi0g08.html create mode 100644 image/test/reftest/pngsuite-basic-i/basi0g08.png create mode 100644 image/test/reftest/pngsuite-basic-i/basi0g16.html create mode 100644 image/test/reftest/pngsuite-basic-i/basi0g16.png create mode 100644 image/test/reftest/pngsuite-basic-i/basi2c08.html create mode 100644 image/test/reftest/pngsuite-basic-i/basi2c08.png create mode 100644 image/test/reftest/pngsuite-basic-i/basi2c16.html create mode 100644 image/test/reftest/pngsuite-basic-i/basi2c16.png create mode 100644 image/test/reftest/pngsuite-basic-i/basi3p01.html create mode 100644 image/test/reftest/pngsuite-basic-i/basi3p01.png create mode 100644 image/test/reftest/pngsuite-basic-i/basi3p02.html create mode 100644 image/test/reftest/pngsuite-basic-i/basi3p02.png create mode 100644 image/test/reftest/pngsuite-basic-i/basi3p04.html create mode 100644 image/test/reftest/pngsuite-basic-i/basi3p04.png create mode 100644 image/test/reftest/pngsuite-basic-i/basi3p08.html create mode 100644 image/test/reftest/pngsuite-basic-i/basi3p08.png create mode 100644 image/test/reftest/pngsuite-basic-i/basi4a08.png create mode 100644 image/test/reftest/pngsuite-basic-i/basi4a16.png create mode 100644 image/test/reftest/pngsuite-basic-i/basi6a08.png create mode 100644 image/test/reftest/pngsuite-basic-i/basi6a16.png create mode 100644 image/test/reftest/pngsuite-basic-i/reftest-stylo.list create mode 100644 image/test/reftest/pngsuite-basic-i/reftest.list create mode 100644 image/test/reftest/pngsuite-basic-n/basn0g01.html create mode 100644 image/test/reftest/pngsuite-basic-n/basn0g01.png create mode 100644 image/test/reftest/pngsuite-basic-n/basn0g02.html create mode 100644 image/test/reftest/pngsuite-basic-n/basn0g02.png create mode 100644 image/test/reftest/pngsuite-basic-n/basn0g04.html create mode 100644 image/test/reftest/pngsuite-basic-n/basn0g04.png create mode 100644 image/test/reftest/pngsuite-basic-n/basn0g08.html create mode 100644 image/test/reftest/pngsuite-basic-n/basn0g08.png create mode 100644 image/test/reftest/pngsuite-basic-n/basn0g16.html create mode 100644 image/test/reftest/pngsuite-basic-n/basn0g16.png create mode 100644 image/test/reftest/pngsuite-basic-n/basn2c08.html create mode 100644 image/test/reftest/pngsuite-basic-n/basn2c08.png create mode 100644 image/test/reftest/pngsuite-basic-n/basn2c16.html create mode 100644 image/test/reftest/pngsuite-basic-n/basn2c16.png create mode 100644 image/test/reftest/pngsuite-basic-n/basn3p01.html create mode 100644 image/test/reftest/pngsuite-basic-n/basn3p01.png create mode 100644 image/test/reftest/pngsuite-basic-n/basn3p02.html create mode 100644 image/test/reftest/pngsuite-basic-n/basn3p02.png create mode 100644 image/test/reftest/pngsuite-basic-n/basn3p04.html create mode 100644 image/test/reftest/pngsuite-basic-n/basn3p04.png create mode 100644 image/test/reftest/pngsuite-basic-n/basn3p08.html create mode 100644 image/test/reftest/pngsuite-basic-n/basn3p08.png create mode 100644 image/test/reftest/pngsuite-basic-n/basn4a08.png create mode 100644 image/test/reftest/pngsuite-basic-n/basn4a16.png create mode 100644 image/test/reftest/pngsuite-basic-n/basn6a08.png create mode 100644 image/test/reftest/pngsuite-basic-n/basn6a16.png create mode 100644 image/test/reftest/pngsuite-basic-n/reftest-stylo.list create mode 100644 image/test/reftest/pngsuite-basic-n/reftest.list create mode 100644 image/test/reftest/pngsuite-chunkorder/color.html create mode 100644 image/test/reftest/pngsuite-chunkorder/grayscale.html create mode 100644 image/test/reftest/pngsuite-chunkorder/oi1n0g16.png create mode 100644 image/test/reftest/pngsuite-chunkorder/oi1n2c16.png create mode 100644 image/test/reftest/pngsuite-chunkorder/oi2n0g16.png create mode 100644 image/test/reftest/pngsuite-chunkorder/oi2n2c16.png create mode 100644 image/test/reftest/pngsuite-chunkorder/oi4n0g16.png create mode 100644 image/test/reftest/pngsuite-chunkorder/oi4n2c16.png create mode 100644 image/test/reftest/pngsuite-chunkorder/oi9n0g16.png create mode 100644 image/test/reftest/pngsuite-chunkorder/oi9n2c16.png create mode 100644 image/test/reftest/pngsuite-chunkorder/reftest-stylo.list create mode 100644 image/test/reftest/pngsuite-chunkorder/reftest.list create mode 100644 image/test/reftest/pngsuite-corrupted/reftest-stylo.list create mode 100644 image/test/reftest/pngsuite-corrupted/reftest.list create mode 100644 image/test/reftest/pngsuite-corrupted/wrapper.html create mode 100644 image/test/reftest/pngsuite-corrupted/x00n0g01.png create mode 100644 image/test/reftest/pngsuite-corrupted/xcrn0g04.png create mode 100644 image/test/reftest/pngsuite-corrupted/xlfn0g04.png create mode 100644 image/test/reftest/pngsuite-filtering/f00n0g08.html create mode 100644 image/test/reftest/pngsuite-filtering/f00n0g08.png create mode 100644 image/test/reftest/pngsuite-filtering/f00n2c08.html create mode 100644 image/test/reftest/pngsuite-filtering/f00n2c08.png create mode 100644 image/test/reftest/pngsuite-filtering/f01n0g08.html create mode 100644 image/test/reftest/pngsuite-filtering/f01n0g08.png create mode 100644 image/test/reftest/pngsuite-filtering/f01n2c08.html create mode 100644 image/test/reftest/pngsuite-filtering/f01n2c08.png create mode 100644 image/test/reftest/pngsuite-filtering/f02n0g08.html create mode 100644 image/test/reftest/pngsuite-filtering/f02n0g08.png create mode 100644 image/test/reftest/pngsuite-filtering/f02n2c08.html create mode 100644 image/test/reftest/pngsuite-filtering/f02n2c08.png create mode 100644 image/test/reftest/pngsuite-filtering/f03n0g08.html create mode 100644 image/test/reftest/pngsuite-filtering/f03n0g08.png create mode 100644 image/test/reftest/pngsuite-filtering/f03n2c08.html create mode 100644 image/test/reftest/pngsuite-filtering/f03n2c08.png create mode 100644 image/test/reftest/pngsuite-filtering/f04n0g08.html create mode 100644 image/test/reftest/pngsuite-filtering/f04n0g08.png create mode 100644 image/test/reftest/pngsuite-filtering/f04n2c08.html create mode 100644 image/test/reftest/pngsuite-filtering/f04n2c08.png create mode 100644 image/test/reftest/pngsuite-filtering/reftest-stylo.list create mode 100644 image/test/reftest/pngsuite-filtering/reftest.list create mode 100644 image/test/reftest/pngsuite-gamma/g03n0g16.html create mode 100644 image/test/reftest/pngsuite-gamma/g03n0g16.png create mode 100644 image/test/reftest/pngsuite-gamma/g03n2c08.html create mode 100644 image/test/reftest/pngsuite-gamma/g03n2c08.png create mode 100644 image/test/reftest/pngsuite-gamma/g03n3p04.html create mode 100644 image/test/reftest/pngsuite-gamma/g03n3p04.png create mode 100644 image/test/reftest/pngsuite-gamma/g04n0g16.html create mode 100644 image/test/reftest/pngsuite-gamma/g04n0g16.png create mode 100644 image/test/reftest/pngsuite-gamma/g04n2c08.html create mode 100644 image/test/reftest/pngsuite-gamma/g04n2c08.png create mode 100644 image/test/reftest/pngsuite-gamma/g04n3p04.html create mode 100644 image/test/reftest/pngsuite-gamma/g04n3p04.png create mode 100644 image/test/reftest/pngsuite-gamma/g05n0g16.html create mode 100644 image/test/reftest/pngsuite-gamma/g05n0g16.png create mode 100644 image/test/reftest/pngsuite-gamma/g05n2c08.html create mode 100644 image/test/reftest/pngsuite-gamma/g05n2c08.png create mode 100644 image/test/reftest/pngsuite-gamma/g05n3p04.html create mode 100644 image/test/reftest/pngsuite-gamma/g05n3p04.png create mode 100644 image/test/reftest/pngsuite-gamma/g07n0g16.html create mode 100644 image/test/reftest/pngsuite-gamma/g07n0g16.png create mode 100644 image/test/reftest/pngsuite-gamma/g07n2c08.html create mode 100644 image/test/reftest/pngsuite-gamma/g07n2c08.png create mode 100644 image/test/reftest/pngsuite-gamma/g07n3p04.html create mode 100644 image/test/reftest/pngsuite-gamma/g07n3p04.png create mode 100644 image/test/reftest/pngsuite-gamma/g10n0g16.html create mode 100644 image/test/reftest/pngsuite-gamma/g10n0g16.png create mode 100644 image/test/reftest/pngsuite-gamma/g10n2c08.html create mode 100644 image/test/reftest/pngsuite-gamma/g10n2c08.png create mode 100644 image/test/reftest/pngsuite-gamma/g10n3p04.html create mode 100644 image/test/reftest/pngsuite-gamma/g10n3p04.png create mode 100644 image/test/reftest/pngsuite-gamma/g25n0g16.html create mode 100644 image/test/reftest/pngsuite-gamma/g25n0g16.png create mode 100644 image/test/reftest/pngsuite-gamma/g25n2c08.html create mode 100644 image/test/reftest/pngsuite-gamma/g25n2c08.png create mode 100644 image/test/reftest/pngsuite-gamma/g25n3p04.html create mode 100644 image/test/reftest/pngsuite-gamma/g25n3p04.png create mode 100644 image/test/reftest/pngsuite-gamma/reftest-stylo.list create mode 100644 image/test/reftest/pngsuite-gamma/reftest.list create mode 100644 image/test/reftest/pngsuite-oddsizes/reftest-stylo.list create mode 100644 image/test/reftest/pngsuite-oddsizes/reftest.list create mode 100644 image/test/reftest/pngsuite-oddsizes/s01_3p01.html create mode 100644 image/test/reftest/pngsuite-oddsizes/s01i3p01.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s01n3p01.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s02_3p01.html create mode 100644 image/test/reftest/pngsuite-oddsizes/s02i3p01.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s02n3p01.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s03_3p01.html create mode 100644 image/test/reftest/pngsuite-oddsizes/s03i3p01.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s03n3p01.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s04_3p01.html create mode 100644 image/test/reftest/pngsuite-oddsizes/s04i3p01.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s04n3p01.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s05_3p02.html create mode 100644 image/test/reftest/pngsuite-oddsizes/s05i3p02.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s05n3p02.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s06_3p02.html create mode 100644 image/test/reftest/pngsuite-oddsizes/s06i3p02.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s06n3p02.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s07_3p02.html create mode 100644 image/test/reftest/pngsuite-oddsizes/s07i3p02.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s07n3p02.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s08_3p02.html create mode 100644 image/test/reftest/pngsuite-oddsizes/s08i3p02.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s08n3p02.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s09_3p02.html create mode 100644 image/test/reftest/pngsuite-oddsizes/s09i3p02.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s09n3p02.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s32_3p04.html create mode 100644 image/test/reftest/pngsuite-oddsizes/s32i3p04.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s32n3p04.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s33_3p04.html create mode 100644 image/test/reftest/pngsuite-oddsizes/s33i3p04.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s33n3p04.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s34_3p04.html create mode 100644 image/test/reftest/pngsuite-oddsizes/s34i3p04.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s34n3p04.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s35_3p04.html create mode 100644 image/test/reftest/pngsuite-oddsizes/s35i3p04.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s35n3p04.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s36_3p04.html create mode 100644 image/test/reftest/pngsuite-oddsizes/s36i3p04.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s36n3p04.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s37_3p04.html create mode 100644 image/test/reftest/pngsuite-oddsizes/s37i3p04.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s37n3p04.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s38_3p04.html create mode 100644 image/test/reftest/pngsuite-oddsizes/s38i3p04.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s38n3p04.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s39_3p04.html create mode 100644 image/test/reftest/pngsuite-oddsizes/s39i3p04.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s39n3p04.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s40_3p04.html create mode 100644 image/test/reftest/pngsuite-oddsizes/s40i3p04.png create mode 100644 image/test/reftest/pngsuite-oddsizes/s40n3p04.png create mode 100644 image/test/reftest/pngsuite-palettes/pp0n2c16.html create mode 100644 image/test/reftest/pngsuite-palettes/pp0n2c16.png create mode 100644 image/test/reftest/pngsuite-palettes/pp0n6a08.png create mode 100644 image/test/reftest/pngsuite-palettes/ps1n0g08.html create mode 100644 image/test/reftest/pngsuite-palettes/ps1n0g08.png create mode 100644 image/test/reftest/pngsuite-palettes/ps1n2c16.html create mode 100644 image/test/reftest/pngsuite-palettes/ps1n2c16.png create mode 100644 image/test/reftest/pngsuite-palettes/ps2n0g08.html create mode 100644 image/test/reftest/pngsuite-palettes/ps2n0g08.png create mode 100644 image/test/reftest/pngsuite-palettes/ps2n2c16.html create mode 100644 image/test/reftest/pngsuite-palettes/ps2n2c16.png create mode 100644 image/test/reftest/pngsuite-palettes/reftest-stylo.list create mode 100644 image/test/reftest/pngsuite-palettes/reftest.list create mode 100644 image/test/reftest/pngsuite-transparency/reftest-stylo.list create mode 100644 image/test/reftest/pngsuite-transparency/reftest.list create mode 100644 image/test/reftest/pngsuite-transparency/tbbn1g04.html create mode 100644 image/test/reftest/pngsuite-transparency/tbbn1g04.png create mode 100644 image/test/reftest/pngsuite-transparency/tbbn2c16.html create mode 100644 image/test/reftest/pngsuite-transparency/tbbn2c16.png create mode 100644 image/test/reftest/pngsuite-transparency/tbbn3p08.html create mode 100644 image/test/reftest/pngsuite-transparency/tbbn3p08.png create mode 100644 image/test/reftest/pngsuite-transparency/tbgn2c16.html create mode 100644 image/test/reftest/pngsuite-transparency/tbgn2c16.png create mode 100644 image/test/reftest/pngsuite-transparency/tbgn3p08.html create mode 100644 image/test/reftest/pngsuite-transparency/tbgn3p08.png create mode 100644 image/test/reftest/pngsuite-transparency/tbrn2c08.html create mode 100644 image/test/reftest/pngsuite-transparency/tbrn2c08.png create mode 100644 image/test/reftest/pngsuite-transparency/tbwn1g16.html create mode 100644 image/test/reftest/pngsuite-transparency/tbwn1g16.png create mode 100644 image/test/reftest/pngsuite-transparency/tbwn3p08.html create mode 100644 image/test/reftest/pngsuite-transparency/tbwn3p08.png create mode 100644 image/test/reftest/pngsuite-transparency/tbyn3p08.html create mode 100644 image/test/reftest/pngsuite-transparency/tbyn3p08.png create mode 100644 image/test/reftest/pngsuite-transparency/tp1n3p08.html create mode 100644 image/test/reftest/pngsuite-transparency/tp1n3p08.png create mode 100644 image/test/reftest/pngsuite-transparency/wrapper.html create mode 100644 image/test/reftest/pngsuite-zlib/reftest-stylo.list create mode 100644 image/test/reftest/pngsuite-zlib/reftest.list create mode 100644 image/test/reftest/pngsuite-zlib/z00n2c08.html create mode 100644 image/test/reftest/pngsuite-zlib/z00n2c08.png create mode 100644 image/test/reftest/pngsuite-zlib/z03n2c08.html create mode 100644 image/test/reftest/pngsuite-zlib/z03n2c08.png create mode 100644 image/test/reftest/pngsuite-zlib/z06n2c08.html create mode 100644 image/test/reftest/pngsuite-zlib/z06n2c08.png create mode 100644 image/test/reftest/pngsuite-zlib/z09n2c08.html create mode 100644 image/test/reftest/pngsuite-zlib/z09n2c08.png create mode 100644 image/test/reftest/reftest-stylo.list create mode 100644 image/test/reftest/reftest.list create mode 100644 image/test/unit/async_load_tests.js create mode 100644 image/test/unit/bug413512.ico create mode 100644 image/test/unit/bug815359.ico create mode 100644 image/test/unit/image1.png create mode 100644 image/test/unit/image1png16x16.jpg create mode 100644 image/test/unit/image1png64x64.jpg create mode 100644 image/test/unit/image2.jpg create mode 100644 image/test/unit/image2jpg16x16-win.png create mode 100644 image/test/unit/image2jpg16x16.png create mode 100644 image/test/unit/image2jpg16x16cropped.jpg create mode 100644 image/test/unit/image2jpg16x16cropped2.jpg create mode 100644 image/test/unit/image2jpg16x32cropped3.jpg create mode 100644 image/test/unit/image2jpg16x32scaled.jpg create mode 100644 image/test/unit/image2jpg32x16cropped4.jpg create mode 100644 image/test/unit/image2jpg32x16scaled.jpg create mode 100644 image/test/unit/image2jpg32x32-win.png create mode 100644 image/test/unit/image2jpg32x32.jpg create mode 100644 image/test/unit/image2jpg32x32.png create mode 100644 image/test/unit/image3.ico create mode 100644 image/test/unit/image3ico16x16.png create mode 100644 image/test/unit/image3ico32x32.png create mode 100644 image/test/unit/image4.gif create mode 100644 image/test/unit/image4gif16x16bmp24bpp.ico create mode 100644 image/test/unit/image4gif16x16bmp32bpp.ico create mode 100644 image/test/unit/image4gif32x32bmp24bpp.ico create mode 100644 image/test/unit/image4gif32x32bmp32bpp.ico create mode 100644 image/test/unit/image_load_helpers.js create mode 100644 image/test/unit/test_async_notification.js create mode 100644 image/test/unit/test_async_notification_404.js create mode 100644 image/test/unit/test_async_notification_animated.js create mode 100644 image/test/unit/test_encoder_apng.js create mode 100644 image/test/unit/test_encoder_png.js create mode 100644 image/test/unit/test_imgtools.js create mode 100644 image/test/unit/test_moz_icon_uri.js create mode 100644 image/test/unit/test_private_channel.js create mode 100644 image/test/unit/xpcshell.ini (limited to 'image') diff --git a/image/AnimationSurfaceProvider.cpp b/image/AnimationSurfaceProvider.cpp new file mode 100644 index 000000000..0dacf25c2 --- /dev/null +++ b/image/AnimationSurfaceProvider.cpp @@ -0,0 +1,291 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AnimationSurfaceProvider.h" + +#include "gfxPrefs.h" +#include "nsProxyRelease.h" + +#include "Decoder.h" + +using namespace mozilla::gfx; + +namespace mozilla { +namespace image { + +AnimationSurfaceProvider::AnimationSurfaceProvider(NotNull aImage, + const SurfaceKey& aSurfaceKey, + NotNull aDecoder) + : ISurfaceProvider(ImageKey(aImage.get()), aSurfaceKey, + AvailabilityState::StartAsPlaceholder()) + , mImage(aImage.get()) + , mDecodingMutex("AnimationSurfaceProvider::mDecoder") + , mDecoder(aDecoder.get()) + , mFramesMutex("AnimationSurfaceProvider::mFrames") +{ + MOZ_ASSERT(!mDecoder->IsMetadataDecode(), + "Use MetadataDecodingTask for metadata decodes"); + MOZ_ASSERT(!mDecoder->IsFirstFrameDecode(), + "Use DecodedSurfaceProvider for single-frame image decodes"); +} + +AnimationSurfaceProvider::~AnimationSurfaceProvider() +{ + DropImageReference(); +} + +void +AnimationSurfaceProvider::DropImageReference() +{ + if (!mImage) { + return; // Nothing to do. + } + + // RasterImage objects need to be destroyed on the main thread. We also need + // to destroy them asynchronously, because if our surface cache entry is + // destroyed and we were the only thing keeping |mImage| alive, RasterImage's + // destructor may call into the surface cache while whatever code caused us to + // get evicted is holding the surface cache lock, causing deadlock. + RefPtr image = mImage; + mImage = nullptr; + NS_ReleaseOnMainThread(image.forget(), /* aAlwaysProxy = */ true); +} + +DrawableFrameRef +AnimationSurfaceProvider::DrawableRef(size_t aFrame) +{ + MutexAutoLock lock(mFramesMutex); + + if (Availability().IsPlaceholder()) { + MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() on a placeholder"); + return DrawableFrameRef(); + } + + if (mFrames.IsEmpty()) { + MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() when we have no frames"); + return DrawableFrameRef(); + } + + // If we don't have that frame, return an empty frame ref. + if (aFrame >= mFrames.Length()) { + return DrawableFrameRef(); + } + + // We've got the requested frame. Return it. + MOZ_ASSERT(mFrames[aFrame]); + return mFrames[aFrame]->DrawableRef(); +} + +bool +AnimationSurfaceProvider::IsFinished() const +{ + MutexAutoLock lock(mFramesMutex); + + if (Availability().IsPlaceholder()) { + MOZ_ASSERT_UNREACHABLE("Calling IsFinished() on a placeholder"); + return false; + } + + if (mFrames.IsEmpty()) { + MOZ_ASSERT_UNREACHABLE("Calling IsFinished() when we have no frames"); + return false; + } + + // As long as we have at least one finished frame, we're finished. + return mFrames[0]->IsFinished(); +} + +size_t +AnimationSurfaceProvider::LogicalSizeInBytes() const +{ + // When decoding animated images, we need at most three live surfaces: the + // composited surface, the previous composited surface for + // DisposalMethod::RESTORE_PREVIOUS, and the surface we're currently decoding + // into. The composited surfaces are always BGRA. Although the surface we're + // decoding into may be paletted, and may be smaller than the real size of the + // image, we assume the worst case here. + // XXX(seth): Note that this is actually not accurate yet; we're storing the + // full sequence of frames, not just the three live surfaces mentioned above. + // Unfortunately there's no way to know in advance how many frames an + // animation has, so we really can't do better here. This will become correct + // once bug 1289954 is complete. + IntSize size = GetSurfaceKey().Size(); + return 3 * size.width * size.height * sizeof(uint32_t); +} + +void +AnimationSurfaceProvider::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + size_t& aHeapSizeOut, + size_t& aNonHeapSizeOut) +{ + // Note that the surface cache lock is already held here, and then we acquire + // mFramesMutex. For this method, this ordering is unavoidable, which means + // that we must be careful to always use the same ordering elsewhere. + MutexAutoLock lock(mFramesMutex); + + for (const RawAccessFrameRef& frame : mFrames) { + frame->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut, aNonHeapSizeOut); + } +} + +void +AnimationSurfaceProvider::Run() +{ + MutexAutoLock lock(mDecodingMutex); + + if (!mDecoder || !mImage) { + MOZ_ASSERT_UNREACHABLE("Running after decoding finished?"); + return; + } + + while (true) { + // Run the decoder. + LexerResult result = mDecoder->Decode(WrapNotNull(this)); + + if (result.is()) { + // We may have a new frame now, but it's not guaranteed - a decoding + // failure or truncated data may mean that no new frame got produced. + // Since we're not sure, rather than call CheckForNewFrameAtYield() here + // we call CheckForNewFrameAtTerminalState(), which handles both of these + // possibilities. + CheckForNewFrameAtTerminalState(); + + // We're done! + FinishDecoding(); + return; + } + + // Notify for the progress we've made so far. + if (mDecoder->HasProgress()) { + NotifyProgress(WrapNotNull(mImage), WrapNotNull(mDecoder)); + } + + if (result == LexerResult(Yield::NEED_MORE_DATA)) { + // We can't make any more progress right now. The decoder itself will ensure + // that we get reenqueued when more data is available; just return for now. + return; + } + + // There's new output available - a new frame! Grab it. + MOZ_ASSERT(result == LexerResult(Yield::OUTPUT_AVAILABLE)); + CheckForNewFrameAtYield(); + } +} + +void +AnimationSurfaceProvider::CheckForNewFrameAtYield() +{ + mDecodingMutex.AssertCurrentThreadOwns(); + MOZ_ASSERT(mDecoder); + + bool justGotFirstFrame = false; + + { + MutexAutoLock lock(mFramesMutex); + + // Try to get the new frame from the decoder. + RawAccessFrameRef frame = mDecoder->GetCurrentFrameRef(); + if (!frame) { + MOZ_ASSERT_UNREACHABLE("Decoder yielded but didn't produce a frame?"); + return; + } + + // We should've gotten a different frame than last time. + MOZ_ASSERT_IF(!mFrames.IsEmpty(), + mFrames.LastElement().get() != frame.get()); + + // Append the new frame to the list. + mFrames.AppendElement(Move(frame)); + + if (mFrames.Length() == 1) { + justGotFirstFrame = true; + } + } + + if (justGotFirstFrame) { + AnnounceSurfaceAvailable(); + } +} + +void +AnimationSurfaceProvider::CheckForNewFrameAtTerminalState() +{ + mDecodingMutex.AssertCurrentThreadOwns(); + MOZ_ASSERT(mDecoder); + + bool justGotFirstFrame = false; + + { + MutexAutoLock lock(mFramesMutex); + + RawAccessFrameRef frame = mDecoder->GetCurrentFrameRef(); + if (!frame) { + return; + } + + if (!mFrames.IsEmpty() && mFrames.LastElement().get() == frame.get()) { + return; // We already have this one. + } + + // Append the new frame to the list. + mFrames.AppendElement(Move(frame)); + + if (mFrames.Length() == 1) { + justGotFirstFrame = true; + } + } + + if (justGotFirstFrame) { + AnnounceSurfaceAvailable(); + } +} + +void +AnimationSurfaceProvider::AnnounceSurfaceAvailable() +{ + mFramesMutex.AssertNotCurrentThreadOwns(); + MOZ_ASSERT(mImage); + + // We just got the first frame; let the surface cache know. We deliberately do + // this outside of mFramesMutex to avoid a potential deadlock with + // AddSizeOfExcludingThis(), since otherwise we'd be acquiring mFramesMutex + // and then the surface cache lock, while the memory reporting code would + // acquire the surface cache lock and then mFramesMutex. + SurfaceCache::SurfaceAvailable(WrapNotNull(this)); +} + +void +AnimationSurfaceProvider::FinishDecoding() +{ + mDecodingMutex.AssertCurrentThreadOwns(); + MOZ_ASSERT(mImage); + MOZ_ASSERT(mDecoder); + + // Send notifications. + NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder)); + + // Destroy our decoder; we don't need it anymore. + mDecoder = nullptr; + + // We don't need a reference to our image anymore, either, and we don't want + // one. We may be stored in the surface cache for a long time after decoding + // finishes. If we don't drop our reference to the image, we'll end up + // keeping it alive as long as we remain in the surface cache, which could + // greatly extend the image's lifetime - in fact, if the image isn't + // discardable, it'd result in a leak! + DropImageReference(); +} + +bool +AnimationSurfaceProvider::ShouldPreferSyncRun() const +{ + MutexAutoLock lock(mDecodingMutex); + MOZ_ASSERT(mDecoder); + + return mDecoder->ShouldSyncDecode(gfxPrefs::ImageMemDecodeBytesAtATime()); +} + +} // namespace image +} // namespace mozilla diff --git a/image/AnimationSurfaceProvider.h b/image/AnimationSurfaceProvider.h new file mode 100644 index 000000000..bf87f37ac --- /dev/null +++ b/image/AnimationSurfaceProvider.h @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * An ISurfaceProvider for animated images. + */ + +#ifndef mozilla_image_AnimationSurfaceProvider_h +#define mozilla_image_AnimationSurfaceProvider_h + +#include "FrameAnimator.h" +#include "IDecodingTask.h" +#include "ISurfaceProvider.h" + +namespace mozilla { +namespace image { + +/** + * An ISurfaceProvider that manages the decoding of animated images and + * dynamically generates surfaces for the current playback state of the + * animation. + */ +class AnimationSurfaceProvider final + : public ISurfaceProvider + , public IDecodingTask +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AnimationSurfaceProvider, override) + + AnimationSurfaceProvider(NotNull aImage, + const SurfaceKey& aSurfaceKey, + NotNull aDecoder); + + + ////////////////////////////////////////////////////////////////////////////// + // ISurfaceProvider implementation. + ////////////////////////////////////////////////////////////////////////////// + +public: + // We use the ISurfaceProvider constructor of DrawableSurface to indicate that + // our surfaces are computed lazily. + DrawableSurface Surface() override { return DrawableSurface(WrapNotNull(this)); } + + bool IsFinished() const override; + size_t LogicalSizeInBytes() const override; + void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + size_t& aHeapSizeOut, + size_t& aNonHeapSizeOut) override; + +protected: + DrawableFrameRef DrawableRef(size_t aFrame) override; + + // Animation frames are always locked. This is because we only want to release + // their memory atomically (due to the surface cache discarding them). If they + // were unlocked, the OS could end up releasing the memory of random frames + // from the middle of the animation, which is not worth the complexity of + // dealing with. + bool IsLocked() const override { return true; } + void SetLocked(bool) override { } + + + ////////////////////////////////////////////////////////////////////////////// + // IDecodingTask implementation. + ////////////////////////////////////////////////////////////////////////////// + +public: + void Run() override; + bool ShouldPreferSyncRun() const override; + + // Full decodes are low priority compared to metadata decodes because they + // don't block layout or page load. + TaskPriority Priority() const override { return TaskPriority::eLow; } + +private: + virtual ~AnimationSurfaceProvider(); + + void DropImageReference(); + void CheckForNewFrameAtYield(); + void CheckForNewFrameAtTerminalState(); + void AnnounceSurfaceAvailable(); + void FinishDecoding(); + + /// The image associated with our decoder. + RefPtr mImage; + + /// A mutex to protect mDecoder. Always taken before mFramesMutex. + mutable Mutex mDecodingMutex; + + /// The decoder used to decode this animation. + RefPtr mDecoder; + + /// A mutex to protect mFrames. Always taken after mDecodingMutex. + mutable Mutex mFramesMutex; + + /// The frames of this animation, in order. + nsTArray mFrames; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_AnimationSurfaceProvider_h diff --git a/image/BMPHeaders.h b/image/BMPHeaders.h new file mode 100644 index 000000000..0fdc806bc --- /dev/null +++ b/image/BMPHeaders.h @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_BMPHeaders_h +#define mozilla_image_BMPHeaders_h + +#include +#include + +namespace mozilla { +namespace image { +namespace bmp { + +// The length of the file header as defined in the BMP spec. +static const size_t FILE_HEADER_LENGTH = 14; + +// This lengths of the info header for the different BMP versions. +struct InfoHeaderLength { + enum { + WIN_V2 = 12, + WIN_V3 = 40, + WIN_V4 = 108, + WIN_V5 = 124, + + // OS2_V1 is omitted; it's the same as WIN_V2. + OS2_V2_MIN = 16, // Minimum allowed value for OS2v2. + OS2_V2_MAX = 64, // Maximum allowed value for OS2v2. + + WIN_ICO = WIN_V3, + }; +}; + +} // namespace bmp +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_BMPHeaders_h diff --git a/image/ClippedImage.cpp b/image/ClippedImage.cpp new file mode 100644 index 000000000..539471851 --- /dev/null +++ b/image/ClippedImage.cpp @@ -0,0 +1,565 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ClippedImage.h" + +#include +#include // Workaround for bug in VS10; see bug 981264. +#include +#include + +#include "gfxDrawable.h" +#include "gfxPlatform.h" +#include "gfxUtils.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/Move.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Pair.h" +#include "mozilla/Tuple.h" + +#include "ImageRegion.h" +#include "Orientation.h" +#include "SVGImageContext.h" + +namespace mozilla { + +using namespace gfx; +using layers::LayerManager; +using layers::ImageContainer; +using std::make_pair; +using std::max; +using std::modf; +using std::pair; + +namespace image { + +class ClippedImageCachedSurface +{ +public: + ClippedImageCachedSurface(already_AddRefed aSurface, + const nsIntSize& aSize, + const Maybe& aSVGContext, + float aFrame, + uint32_t aFlags, + DrawResult aDrawResult) + : mSurface(aSurface) + , mSize(aSize) + , mSVGContext(aSVGContext) + , mFrame(aFrame) + , mFlags(aFlags) + , mDrawResult(aDrawResult) + { + MOZ_ASSERT(mSurface, "Must have a valid surface"); + } + + bool Matches(const nsIntSize& aSize, + const Maybe& aSVGContext, + float aFrame, + uint32_t aFlags) const + { + return mSize == aSize && + mSVGContext == aSVGContext && + mFrame == aFrame && + mFlags == aFlags; + } + + already_AddRefed Surface() const + { + RefPtr surf(mSurface); + return surf.forget(); + } + + DrawResult GetDrawResult() const + { + return mDrawResult; + } + + bool NeedsRedraw() const + { + return mDrawResult != DrawResult::SUCCESS && + mDrawResult != DrawResult::BAD_IMAGE; + } + +private: + RefPtr mSurface; + const nsIntSize mSize; + Maybe mSVGContext; + const float mFrame; + const uint32_t mFlags; + const DrawResult mDrawResult; +}; + +class DrawSingleTileCallback : public gfxDrawingCallback +{ +public: + DrawSingleTileCallback(ClippedImage* aImage, + const nsIntSize& aSize, + const Maybe& aSVGContext, + uint32_t aWhichFrame, + uint32_t aFlags) + : mImage(aImage) + , mSize(aSize) + , mSVGContext(aSVGContext) + , mWhichFrame(aWhichFrame) + , mFlags(aFlags) + , mDrawResult(DrawResult::NOT_READY) + { + MOZ_ASSERT(mImage, "Must have an image to clip"); + } + + virtual bool operator()(gfxContext* aContext, + const gfxRect& aFillRect, + const SamplingFilter aSamplingFilter, + const gfxMatrix& aTransform) + { + MOZ_ASSERT(aTransform.IsIdentity(), + "Caller is probably CreateSamplingRestrictedDrawable, " + "which should not happen"); + + // Draw the image. |gfxCallbackDrawable| always calls this function with + // arguments that guarantee we never tile. + mDrawResult = + mImage->DrawSingleTile(aContext, mSize, ImageRegion::Create(aFillRect), + mWhichFrame, aSamplingFilter, mSVGContext, mFlags); + + return true; + } + + DrawResult GetDrawResult() { return mDrawResult; } + +private: + RefPtr mImage; + const nsIntSize mSize; + const Maybe& mSVGContext; + const uint32_t mWhichFrame; + const uint32_t mFlags; + DrawResult mDrawResult; +}; + +ClippedImage::ClippedImage(Image* aImage, + nsIntRect aClip, + const Maybe& aSVGViewportSize) + : ImageWrapper(aImage) + , mClip(aClip) +{ + MOZ_ASSERT(aImage != nullptr, "ClippedImage requires an existing Image"); + MOZ_ASSERT_IF(aSVGViewportSize, + aImage->GetType() == imgIContainer::TYPE_VECTOR); + if (aSVGViewportSize) { + mSVGViewportSize = Some(aSVGViewportSize->ToNearestPixels( + nsPresContext::AppUnitsPerCSSPixel())); + } +} + +ClippedImage::~ClippedImage() +{ } + +bool +ClippedImage::ShouldClip() +{ + // We need to evaluate the clipping region against the image's width and + // height once they're available to determine if it's valid and whether we + // actually need to do any work. We may fail if the image's width and height + // aren't available yet, in which case we'll try again later. + if (mShouldClip.isNothing()) { + int32_t width, height; + RefPtr progressTracker = + InnerImage()->GetProgressTracker(); + if (InnerImage()->HasError()) { + // If there's a problem with the inner image we'll let it handle + // everything. + mShouldClip.emplace(false); + } else if (mSVGViewportSize && !mSVGViewportSize->IsEmpty()) { + // Clamp the clipping region to the size of the SVG viewport. + nsIntRect svgViewportRect(nsIntPoint(0,0), *mSVGViewportSize); + + mClip = mClip.Intersect(svgViewportRect); + + // If the clipping region is the same size as the SVG viewport size + // we don't have to do anything. + mShouldClip.emplace(!mClip.IsEqualInterior(svgViewportRect)); + } else if (NS_SUCCEEDED(InnerImage()->GetWidth(&width)) && width > 0 && + NS_SUCCEEDED(InnerImage()->GetHeight(&height)) && height > 0) { + // Clamp the clipping region to the size of the underlying image. + mClip = mClip.Intersect(nsIntRect(0, 0, width, height)); + + // If the clipping region is the same size as the underlying image we + // don't have to do anything. + mShouldClip.emplace(!mClip.IsEqualInterior(nsIntRect(0, 0, width, + height))); + } else if (progressTracker && + !(progressTracker->GetProgress() & FLAG_LOAD_COMPLETE)) { + // The image just hasn't finished loading yet. We don't yet know whether + // clipping with be needed or not for now. Just return without memorizing + // anything. + return false; + } else { + // We have a fully loaded image without a clearly defined width and + // height. This can happen with SVG images. + mShouldClip.emplace(false); + } + } + + MOZ_ASSERT(mShouldClip.isSome(), "Should have computed a result"); + return *mShouldClip; +} + +NS_IMPL_ISUPPORTS_INHERITED0(ClippedImage, ImageWrapper) + +NS_IMETHODIMP +ClippedImage::GetWidth(int32_t* aWidth) +{ + if (!ShouldClip()) { + return InnerImage()->GetWidth(aWidth); + } + + *aWidth = mClip.width; + return NS_OK; +} + +NS_IMETHODIMP +ClippedImage::GetHeight(int32_t* aHeight) +{ + if (!ShouldClip()) { + return InnerImage()->GetHeight(aHeight); + } + + *aHeight = mClip.height; + return NS_OK; +} + +NS_IMETHODIMP +ClippedImage::GetIntrinsicSize(nsSize* aSize) +{ + if (!ShouldClip()) { + return InnerImage()->GetIntrinsicSize(aSize); + } + + *aSize = nsSize(mClip.width, mClip.height); + return NS_OK; +} + +NS_IMETHODIMP +ClippedImage::GetIntrinsicRatio(nsSize* aRatio) +{ + if (!ShouldClip()) { + return InnerImage()->GetIntrinsicRatio(aRatio); + } + + *aRatio = nsSize(mClip.width, mClip.height); + return NS_OK; +} + +NS_IMETHODIMP_(already_AddRefed) +ClippedImage::GetFrame(uint32_t aWhichFrame, + uint32_t aFlags) +{ + DrawResult result; + RefPtr surface; + Tie(result, surface) = GetFrameInternal(mClip.Size(), Nothing(), aWhichFrame, aFlags); + return surface.forget(); +} + +NS_IMETHODIMP_(already_AddRefed) +ClippedImage::GetFrameAtSize(const IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags) +{ + // XXX(seth): It'd be nice to support downscale-during-decode for this case, + // but right now we just fall back to the intrinsic size. + return GetFrame(aWhichFrame, aFlags); +} + +Pair> +ClippedImage::GetFrameInternal(const nsIntSize& aSize, + const Maybe& aSVGContext, + uint32_t aWhichFrame, + uint32_t aFlags) +{ + if (!ShouldClip()) { + RefPtr surface = InnerImage()->GetFrame(aWhichFrame, aFlags); + return MakePair(surface ? DrawResult::SUCCESS : DrawResult::NOT_READY, + Move(surface)); + } + + float frameToDraw = InnerImage()->GetFrameIndex(aWhichFrame); + if (!mCachedSurface || + !mCachedSurface->Matches(aSize, aSVGContext, frameToDraw, aFlags) || + mCachedSurface->NeedsRedraw()) { + // Create a surface to draw into. + RefPtr target = gfxPlatform::GetPlatform()-> + CreateOffscreenContentDrawTarget(IntSize(aSize.width, aSize.height), + SurfaceFormat::B8G8R8A8); + if (!target || !target->IsValid()) { + NS_ERROR("Could not create a DrawTarget"); + return MakePair(DrawResult::TEMPORARY_ERROR, RefPtr()); + } + + RefPtr ctx = gfxContext::CreateOrNull(target); + MOZ_ASSERT(ctx); // already checked the draw target above + + // Create our callback. + RefPtr drawTileCallback = + new DrawSingleTileCallback(this, aSize, aSVGContext, aWhichFrame, aFlags); + RefPtr drawable = + new gfxCallbackDrawable(drawTileCallback, aSize); + + // Actually draw. The callback will end up invoking DrawSingleTile. + gfxUtils::DrawPixelSnapped(ctx, drawable, aSize, + ImageRegion::Create(aSize), + SurfaceFormat::B8G8R8A8, + SamplingFilter::LINEAR, + imgIContainer::FLAG_CLAMP); + + // Cache the resulting surface. + mCachedSurface = + MakeUnique(target->Snapshot(), aSize, aSVGContext, + frameToDraw, aFlags, + drawTileCallback->GetDrawResult()); + } + + MOZ_ASSERT(mCachedSurface, "Should have a cached surface now"); + RefPtr surface = mCachedSurface->Surface(); + return MakePair(mCachedSurface->GetDrawResult(), Move(surface)); +} + +NS_IMETHODIMP_(bool) +ClippedImage::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags) +{ + if (!ShouldClip()) { + return InnerImage()->IsImageContainerAvailable(aManager, aFlags); + } + return false; +} + +NS_IMETHODIMP_(already_AddRefed) +ClippedImage::GetImageContainer(LayerManager* aManager, uint32_t aFlags) +{ + // XXX(seth): We currently don't have a way of clipping the result of + // GetImageContainer. We work around this by always returning null, but if it + // ever turns out that ClippedImage is widely used on codepaths that can + // actually benefit from GetImageContainer, it would be a good idea to fix + // that method for performance reasons. + + if (!ShouldClip()) { + return InnerImage()->GetImageContainer(aManager, aFlags); + } + + return nullptr; +} + +static bool +MustCreateSurface(gfxContext* aContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, + const uint32_t aFlags) +{ + gfxRect imageRect(0, 0, aSize.width, aSize.height); + bool willTile = !imageRect.Contains(aRegion.Rect()) && + !(aFlags & imgIContainer::FLAG_CLAMP); + bool willResample = aContext->CurrentMatrix().HasNonIntegerTranslation() && + (willTile || !aRegion.RestrictionContains(imageRect)); + return willTile || willResample; +} + +NS_IMETHODIMP_(DrawResult) +ClippedImage::Draw(gfxContext* aContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, + uint32_t aWhichFrame, + SamplingFilter aSamplingFilter, + const Maybe& aSVGContext, + uint32_t aFlags) +{ + if (!ShouldClip()) { + return InnerImage()->Draw(aContext, aSize, aRegion, aWhichFrame, + aSamplingFilter, aSVGContext, aFlags); + } + + // Check for tiling. If we need to tile then we need to create a + // gfxCallbackDrawable to handle drawing for us. + if (MustCreateSurface(aContext, aSize, aRegion, aFlags)) { + // Create a temporary surface containing a single tile of this image. + // GetFrame will call DrawSingleTile internally. + DrawResult result; + RefPtr surface; + Tie(result, surface) = + GetFrameInternal(aSize, aSVGContext, aWhichFrame, aFlags); + if (!surface) { + MOZ_ASSERT(result != DrawResult::SUCCESS); + return result; + } + + // Create a drawable from that surface. + RefPtr drawable = + new gfxSurfaceDrawable(surface, aSize); + + // Draw. + gfxUtils::DrawPixelSnapped(aContext, drawable, aSize, aRegion, + SurfaceFormat::B8G8R8A8, aSamplingFilter); + + return result; + } + + return DrawSingleTile(aContext, aSize, aRegion, aWhichFrame, + aSamplingFilter, aSVGContext, aFlags); +} + +DrawResult +ClippedImage::DrawSingleTile(gfxContext* aContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, + uint32_t aWhichFrame, + SamplingFilter aSamplingFilter, + const Maybe& aSVGContext, + uint32_t aFlags) +{ + MOZ_ASSERT(!MustCreateSurface(aContext, aSize, aRegion, aFlags), + "Shouldn't need to create a surface"); + + gfxRect clip(mClip.x, mClip.y, mClip.width, mClip.height); + nsIntSize size(aSize), innerSize(aSize); + bool needScale = false; + if (mSVGViewportSize && !mSVGViewportSize->IsEmpty()) { + innerSize = *mSVGViewportSize; + needScale = true; + } else if (NS_SUCCEEDED(InnerImage()->GetWidth(&innerSize.width)) && + NS_SUCCEEDED(InnerImage()->GetHeight(&innerSize.height))) { + needScale = true; + } else { + MOZ_ASSERT_UNREACHABLE( + "If ShouldClip() led us to draw then we should never get here"); + } + + if (needScale) { + double scaleX = aSize.width / clip.width; + double scaleY = aSize.height / clip.height; + + // Map the clip and size to the scale requested by the caller. + clip.Scale(scaleX, scaleY); + size = innerSize; + size.Scale(scaleX, scaleY); + } + + // We restrict our drawing to only the clipping region, and translate so that + // the clipping region is placed at the position the caller expects. + ImageRegion region(aRegion); + region.MoveBy(clip.x, clip.y); + region = region.Intersect(clip); + + gfxContextMatrixAutoSaveRestore saveMatrix(aContext); + aContext->Multiply(gfxMatrix::Translation(-clip.x, -clip.y)); + + auto unclipViewport = [&](const SVGImageContext& aOldContext) { + // Map the viewport to the inner image. Note that we don't take the aSize + // parameter of imgIContainer::Draw into account, just the clipping region. + // The size in pixels at which the output will ultimately be drawn is + // irrelevant here since the purpose of the SVG viewport size is to + // determine what *region* of the SVG document will be drawn. + CSSIntSize vSize(aOldContext.GetViewportSize()); + vSize.width = ceil(vSize.width * double(innerSize.width) / mClip.width); + vSize.height = + ceil(vSize.height * double(innerSize.height) / mClip.height); + + return SVGImageContext(vSize, + aOldContext.GetPreserveAspectRatio()); + }; + + return InnerImage()->Draw(aContext, size, region, + aWhichFrame, aSamplingFilter, + aSVGContext.map(unclipViewport), + aFlags); +} + +NS_IMETHODIMP +ClippedImage::RequestDiscard() +{ + // We're very aggressive about discarding. + mCachedSurface = nullptr; + + return InnerImage()->RequestDiscard(); +} + +NS_IMETHODIMP_(Orientation) +ClippedImage::GetOrientation() +{ + // XXX(seth): This should not actually be here; this is just to work around a + // what appears to be a bug in MSVC's linker. + return InnerImage()->GetOrientation(); +} + +nsIntSize +ClippedImage::OptimalImageSizeForDest(const gfxSize& aDest, + uint32_t aWhichFrame, + SamplingFilter aSamplingFilter, + uint32_t aFlags) +{ + if (!ShouldClip()) { + return InnerImage()->OptimalImageSizeForDest(aDest, aWhichFrame, + aSamplingFilter, aFlags); + } + + int32_t imgWidth, imgHeight; + bool needScale = false; + bool forceUniformScaling = false; + if (mSVGViewportSize && !mSVGViewportSize->IsEmpty()) { + imgWidth = mSVGViewportSize->width; + imgHeight = mSVGViewportSize->height; + needScale = true; + forceUniformScaling = (aFlags & imgIContainer::FLAG_FORCE_UNIFORM_SCALING); + } else if (NS_SUCCEEDED(InnerImage()->GetWidth(&imgWidth)) && + NS_SUCCEEDED(InnerImage()->GetHeight(&imgHeight))) { + needScale = true; + } + + if (needScale) { + // To avoid ugly sampling artifacts, ClippedImage needs the image size to + // be chosen such that the clipping region lies on pixel boundaries. + + // First, we select a scale that's good for ClippedImage. An integer + // multiple of the size of the clipping region is always fine. + IntSize scale = IntSize::Ceil(aDest.width / mClip.width, + aDest.height / mClip.height); + + if (forceUniformScaling) { + scale.width = scale.height = max(scale.height, scale.width); + } + + // Determine the size we'd prefer to render the inner image at, and ask the + // inner image what size we should actually use. + gfxSize desiredSize(imgWidth * scale.width, imgHeight * scale.height); + nsIntSize innerDesiredSize = + InnerImage()->OptimalImageSizeForDest(desiredSize, aWhichFrame, + aSamplingFilter, aFlags); + + // To get our final result, we take the inner image's desired size and + // determine how large the clipped region would be at that scale. (Again, we + // ensure an integer multiple of the size of the clipping region.) + IntSize finalScale = IntSize::Ceil(double(innerDesiredSize.width) / imgWidth, + double(innerDesiredSize.height) / imgHeight); + return mClip.Size() * finalScale; + } + + MOZ_ASSERT(false, + "If ShouldClip() led us to draw then we should never get here"); + return InnerImage()->OptimalImageSizeForDest(aDest, aWhichFrame, + aSamplingFilter, aFlags); +} + +NS_IMETHODIMP_(nsIntRect) +ClippedImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect) +{ + if (!ShouldClip()) { + return InnerImage()->GetImageSpaceInvalidationRect(aRect); + } + + nsIntRect rect(InnerImage()->GetImageSpaceInvalidationRect(aRect)); + rect = rect.Intersect(mClip); + rect.MoveBy(-mClip.x, -mClip.y); + return rect; +} + +} // namespace image +} // namespace mozilla diff --git a/image/ClippedImage.h b/image/ClippedImage.h new file mode 100644 index 000000000..140cc1909 --- /dev/null +++ b/image/ClippedImage.h @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_ClippedImage_h +#define mozilla_image_ClippedImage_h + +#include "ImageWrapper.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/Maybe.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { +namespace image { + +class ClippedImageCachedSurface; +class DrawSingleTileCallback; + +/** + * An Image wrapper that clips an image against a rectangle. Right now only + * absolute coordinates in pixels are supported. + * + * XXX(seth): There a known (performance, not correctness) issue with + * GetImageContainer. See the comments for that method for more information. + */ +class ClippedImage : public ImageWrapper +{ + typedef gfx::SourceSurface SourceSurface; + +public: + NS_DECL_ISUPPORTS_INHERITED + + NS_IMETHOD GetWidth(int32_t* aWidth) override; + NS_IMETHOD GetHeight(int32_t* aHeight) override; + NS_IMETHOD GetIntrinsicSize(nsSize* aSize) override; + NS_IMETHOD GetIntrinsicRatio(nsSize* aRatio) override; + NS_IMETHOD_(already_AddRefed) + GetFrame(uint32_t aWhichFrame, uint32_t aFlags) override; + NS_IMETHOD_(already_AddRefed) + GetFrameAtSize(const gfx::IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags) override; + NS_IMETHOD_(bool) IsImageContainerAvailable(layers::LayerManager* aManager, + uint32_t aFlags) override; + NS_IMETHOD_(already_AddRefed) + GetImageContainer(layers::LayerManager* aManager, + uint32_t aFlags) override; + NS_IMETHOD_(DrawResult) Draw(gfxContext* aContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, + uint32_t aWhichFrame, + gfx::SamplingFilter aSamplingFilter, + const Maybe& aSVGContext, + uint32_t aFlags) override; + NS_IMETHOD RequestDiscard() override; + NS_IMETHOD_(Orientation) GetOrientation() override; + NS_IMETHOD_(nsIntRect) GetImageSpaceInvalidationRect(const nsIntRect& aRect) + override; + nsIntSize OptimalImageSizeForDest(const gfxSize& aDest, + uint32_t aWhichFrame, + gfx::SamplingFilter aSamplingFilter, + uint32_t aFlags) override; + +protected: + ClippedImage(Image* aImage, nsIntRect aClip, + const Maybe& aSVGViewportSize); + + virtual ~ClippedImage(); + +private: + Pair> + GetFrameInternal(const nsIntSize& aSize, + const Maybe& aSVGContext, + uint32_t aWhichFrame, + uint32_t aFlags); + bool ShouldClip(); + DrawResult DrawSingleTile(gfxContext* aContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, + uint32_t aWhichFrame, + gfx::SamplingFilter aSamplingFilter, + const Maybe& aSVGContext, + uint32_t aFlags); + + // If we are forced to draw a temporary surface, we cache it here. + UniquePtr mCachedSurface; + + nsIntRect mClip; // The region to clip to. + Maybe mShouldClip; // Memoized ShouldClip() if present. + Maybe mSVGViewportSize; // If we're clipping a VectorImage, this + // is the size of viewport of that image. + friend class DrawSingleTileCallback; + friend class ImageOps; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_ClippedImage_h diff --git a/image/CopyOnWrite.h b/image/CopyOnWrite.h new file mode 100644 index 000000000..21db5c3bc --- /dev/null +++ b/image/CopyOnWrite.h @@ -0,0 +1,250 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * CopyOnWrite allows code to safely read from a data structure without + * worrying that reentrant code will modify it. + */ + +#ifndef mozilla_image_CopyOnWrite_h +#define mozilla_image_CopyOnWrite_h + +#include "mozilla/RefPtr.h" +#include "MainThreadUtils.h" +#include "nsISupportsImpl.h" + +namespace mozilla { +namespace image { + +/////////////////////////////////////////////////////////////////////////////// +// Implementation Details +/////////////////////////////////////////////////////////////////////////////// + +namespace detail { + +template +class CopyOnWriteValue final +{ +public: + NS_INLINE_DECL_REFCOUNTING(CopyOnWriteValue) + + explicit CopyOnWriteValue(T* aValue) : mValue(aValue) { } + explicit CopyOnWriteValue(already_AddRefed& aValue) : mValue(aValue) { } + explicit CopyOnWriteValue(already_AddRefed&& aValue) : mValue(aValue) { } + explicit CopyOnWriteValue(const RefPtr& aValue) : mValue(aValue) { } + explicit CopyOnWriteValue(RefPtr&& aValue) : mValue(aValue) { } + + T* get() { return mValue.get(); } + const T* get() const { return mValue.get(); } + + bool HasReaders() const { return mReaders > 0; } + bool HasWriter() const { return mWriter; } + bool HasUsers() const { return HasReaders() || HasWriter(); } + + void LockForReading() { MOZ_ASSERT(!HasWriter()); mReaders++; } + void UnlockForReading() { MOZ_ASSERT(HasReaders()); mReaders--; } + + struct MOZ_STACK_CLASS AutoReadLock + { + explicit AutoReadLock(CopyOnWriteValue* aValue) + : mValue(aValue) + { + mValue->LockForReading(); + } + ~AutoReadLock() { mValue->UnlockForReading(); } + CopyOnWriteValue* mValue; + }; + + void LockForWriting() { MOZ_ASSERT(!HasUsers()); mWriter = true; } + void UnlockForWriting() { MOZ_ASSERT(HasWriter()); mWriter = false; } + + struct MOZ_STACK_CLASS AutoWriteLock + { + explicit AutoWriteLock(CopyOnWriteValue* aValue) + : mValue(aValue) + { + mValue->LockForWriting(); + } + ~AutoWriteLock() { mValue->UnlockForWriting(); } + CopyOnWriteValue* mValue; + }; + +private: + CopyOnWriteValue(const CopyOnWriteValue&) = delete; + CopyOnWriteValue(CopyOnWriteValue&&) = delete; + + ~CopyOnWriteValue() { } + + RefPtr mValue; + uint64_t mReaders = 0; + bool mWriter = false; +}; + +} // namespace detail + + +/////////////////////////////////////////////////////////////////////////////// +// Public API +/////////////////////////////////////////////////////////////////////////////// + +/** + * CopyOnWrite allows code to safely read from a data structure without + * worrying that reentrant code will modify it. If reentrant code would modify + * the data structure while other code is reading from it, a copy is made so + * that readers can continue to use the old version. + * + * Note that it's legal to nest a writer inside any number of readers, but + * nothing can be nested inside a writer. This is because it's assumed that the + * state of the contained data structure may not be consistent during the write. + * + * This is a main-thread-only data structure. + * + * To work with CopyOnWrite, a type T needs to be reference counted and to + * support copy construction. + */ +template +class CopyOnWrite final +{ + typedef detail::CopyOnWriteValue CopyOnWriteValue; + +public: + explicit CopyOnWrite(T* aValue) + : mValue(new CopyOnWriteValue(aValue)) + { } + + explicit CopyOnWrite(already_AddRefed& aValue) + : mValue(new CopyOnWriteValue(aValue)) + { } + + explicit CopyOnWrite(already_AddRefed&& aValue) + : mValue(new CopyOnWriteValue(aValue)) + { } + + explicit CopyOnWrite(const RefPtr& aValue) + : mValue(new CopyOnWriteValue(aValue)) + { } + + explicit CopyOnWrite(RefPtr&& aValue) + : mValue(new CopyOnWriteValue(aValue)) + { } + + /// @return true if it's safe to read at this time. + bool CanRead() const { return !mValue->HasWriter(); } + + /** + * Read from the contained data structure using the function @aReader. + * @aReader will be passed a pointer of type |const T*|. It's not legal to + * call this while a writer is active. + * + * @return whatever value @aReader returns, or nothing if @aReader is a void + * function. + */ + template + auto Read(ReadFunc aReader) const + -> decltype(aReader(static_cast(nullptr))) + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(CanRead()); + + // Run the provided function while holding a read lock. + RefPtr cowValue = mValue; + typename CopyOnWriteValue::AutoReadLock lock(cowValue); + return aReader(cowValue->get()); + } + + /** + * Read from the contained data structure using the function @aReader. + * @aReader will be passed a pointer of type |const T*|. If it's currently not + * possible to read because a writer is currently active, @aOnError will be + * called instead. + * + * @return whatever value @aReader or @aOnError returns (their return types + * must be consistent), or nothing if the provided functions are void. + */ + template + auto Read(ReadFunc aReader, ErrorFunc aOnError) const + -> decltype(aReader(static_cast(nullptr))) + { + MOZ_ASSERT(NS_IsMainThread()); + + if (!CanRead()) { + return aOnError(); + } + + return Read(aReader); + } + + /// @return true if it's safe to write at this time. + bool CanWrite() const { return !mValue->HasWriter(); } + + /** + * Write to the contained data structure using the function @aWriter. + * @aWriter will be passed a pointer of type |T*|. It's not legal to call this + * while another writer is active. + * + * If readers are currently active, they will be able to continue reading from + * a copy of the old version of the data structure. The copy will be destroyed + * when all its readers finish. Later readers and writers will see the + * version of the data structure produced by the most recent call to Write(). + * + * @return whatever value @aWriter returns, or nothing if @aWriter is a void + * function. + */ + template + auto Write(WriteFunc aWriter) + -> decltype(aWriter(static_cast(nullptr))) + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(CanWrite()); + + // If there are readers, we need to copy first. + if (mValue->HasReaders()) { + mValue = new CopyOnWriteValue(new T(*mValue->get())); + } + + // Run the provided function while holding a write lock. + RefPtr cowValue = mValue; + typename CopyOnWriteValue::AutoWriteLock lock(cowValue); + return aWriter(cowValue->get()); + } + + /** + * Write to the contained data structure using the function @aWriter. + * @aWriter will be passed a pointer of type |T*|. If it's currently not + * possible to write because a writer is currently active, @aOnError will be + * called instead. + * + * If readers are currently active, they will be able to continue reading from + * a copy of the old version of the data structure. The copy will be destroyed + * when all its readers finish. Later readers and writers will see the + * version of the data structure produced by the most recent call to Write(). + * + * @return whatever value @aWriter or @aOnError returns (their return types + * must be consistent), or nothing if the provided functions are void. + */ + template + auto Write(WriteFunc aWriter, ErrorFunc aOnError) + -> decltype(aWriter(static_cast(nullptr))) + { + MOZ_ASSERT(NS_IsMainThread()); + + if (!CanWrite()) { + return aOnError(); + } + + return Write(aWriter); + } + +private: + CopyOnWrite(const CopyOnWrite&) = delete; + CopyOnWrite(CopyOnWrite&&) = delete; + + RefPtr mValue; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_CopyOnWrite_h diff --git a/image/DecodePool.cpp b/image/DecodePool.cpp new file mode 100644 index 000000000..a8c4cbecc --- /dev/null +++ b/image/DecodePool.cpp @@ -0,0 +1,340 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DecodePool.h" + +#include + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Monitor.h" +#include "nsCOMPtr.h" +#include "nsIObserverService.h" +#include "nsIThreadPool.h" +#include "nsThreadManager.h" +#include "nsThreadUtils.h" +#include "nsXPCOMCIDInternal.h" +#include "prsystem.h" + +#include "gfxPrefs.h" + +#include "Decoder.h" +#include "IDecodingTask.h" +#include "RasterImage.h" + +using std::max; +using std::min; + +namespace mozilla { +namespace image { + +/////////////////////////////////////////////////////////////////////////////// +// DecodePool implementation. +/////////////////////////////////////////////////////////////////////////////// + +/* static */ StaticRefPtr DecodePool::sSingleton; +/* static */ uint32_t DecodePool::sNumCores = 0; + +NS_IMPL_ISUPPORTS(DecodePool, nsIObserver) + +struct Work +{ + enum class Type { + TASK, + SHUTDOWN + } mType; + + RefPtr mTask; +}; + +class DecodePoolImpl +{ +public: + MOZ_DECLARE_REFCOUNTED_TYPENAME(DecodePoolImpl) + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodePoolImpl) + + DecodePoolImpl() + : mMonitor("DecodePoolImpl") + , mShuttingDown(false) + { } + + /// Initialize the current thread for use by the decode pool. + void InitCurrentThread() + { + MOZ_ASSERT(!NS_IsMainThread()); + + mThreadNaming.SetThreadPoolName(NS_LITERAL_CSTRING("ImgDecoder")); + } + + /// Shut down the provided decode pool thread. + static void ShutdownThread(nsIThread* aThisThread) + { + // Threads have to be shut down from another thread, so we'll ask the + // main thread to do it for us. + NS_DispatchToMainThread(NewRunnableMethod(aThisThread, &nsIThread::Shutdown)); + } + + /** + * Requests shutdown. New work items will be dropped on the floor, and all + * decode pool threads will be shut down once existing work items have been + * processed. + */ + void RequestShutdown() + { + MonitorAutoLock lock(mMonitor); + mShuttingDown = true; + mMonitor.NotifyAll(); + } + + /// Pushes a new decode work item. + void PushWork(IDecodingTask* aTask) + { + MOZ_ASSERT(aTask); + RefPtr task(aTask); + + MonitorAutoLock lock(mMonitor); + + if (mShuttingDown) { + // Drop any new work on the floor if we're shutting down. + return; + } + + if (task->Priority() == TaskPriority::eHigh) { + mHighPriorityQueue.AppendElement(Move(task)); + } else { + mLowPriorityQueue.AppendElement(Move(task)); + } + + mMonitor.Notify(); + } + + /// Pops a new work item, blocking if necessary. + Work PopWork() + { + MonitorAutoLock lock(mMonitor); + + do { + if (!mHighPriorityQueue.IsEmpty()) { + return PopWorkFromQueue(mHighPriorityQueue); + } + + if (!mLowPriorityQueue.IsEmpty()) { + return PopWorkFromQueue(mLowPriorityQueue); + } + + if (mShuttingDown) { + Work work; + work.mType = Work::Type::SHUTDOWN; + return work; + } + + // Nothing to do; block until some work is available. + mMonitor.Wait(); + } while (true); + } + +private: + ~DecodePoolImpl() { } + + Work PopWorkFromQueue(nsTArray>& aQueue) + { + Work work; + work.mType = Work::Type::TASK; + work.mTask = aQueue.LastElement().forget(); + aQueue.RemoveElementAt(aQueue.Length() - 1); + + return work; + } + + nsThreadPoolNaming mThreadNaming; + + // mMonitor guards the queues and mShuttingDown. + Monitor mMonitor; + nsTArray> mHighPriorityQueue; + nsTArray> mLowPriorityQueue; + bool mShuttingDown; +}; + +class DecodePoolWorker : public Runnable +{ +public: + explicit DecodePoolWorker(DecodePoolImpl* aImpl) : mImpl(aImpl) { } + + NS_IMETHOD Run() override + { + MOZ_ASSERT(!NS_IsMainThread()); + + mImpl->InitCurrentThread(); + + nsCOMPtr thisThread; + nsThreadManager::get().GetCurrentThread(getter_AddRefs(thisThread)); + + do { + Work work = mImpl->PopWork(); + switch (work.mType) { + case Work::Type::TASK: + work.mTask->Run(); + break; + + case Work::Type::SHUTDOWN: + DecodePoolImpl::ShutdownThread(thisThread); + return NS_OK; + + default: + MOZ_ASSERT_UNREACHABLE("Unknown work type"); + } + } while (true); + + MOZ_ASSERT_UNREACHABLE("Exiting thread without Work::Type::SHUTDOWN"); + return NS_OK; + } + +private: + RefPtr mImpl; +}; + +/* static */ void +DecodePool::Initialize() +{ + MOZ_ASSERT(NS_IsMainThread()); + sNumCores = max(PR_GetNumberOfProcessors(), 1); + DecodePool::Singleton(); +} + +/* static */ DecodePool* +DecodePool::Singleton() +{ + if (!sSingleton) { + MOZ_ASSERT(NS_IsMainThread()); + sSingleton = new DecodePool(); + ClearOnShutdown(&sSingleton); + } + + return sSingleton; +} + +/* static */ uint32_t +DecodePool::NumberOfCores() +{ + return sNumCores; +} + +DecodePool::DecodePool() + : mImpl(new DecodePoolImpl) + , mMutex("image::DecodePool") +{ + // Determine the number of threads we want. + int32_t prefLimit = gfxPrefs::ImageMTDecodingLimit(); + uint32_t limit; + if (prefLimit <= 0) { + int32_t numCores = NumberOfCores(); + if (numCores <= 1) { + limit = 1; + } else if (numCores == 2) { + // On an otherwise mostly idle system, having two image decoding threads + // doubles decoding performance, so it's worth doing on dual-core devices, + // even if under load we can't actually get that level of parallelism. + limit = 2; + } else { + limit = numCores - 1; + } + } else { + limit = static_cast(prefLimit); + } + if (limit > 32) { + limit = 32; + } + + // Initialize the thread pool. + for (uint32_t i = 0 ; i < limit ; ++i) { + nsCOMPtr worker = new DecodePoolWorker(mImpl); + nsCOMPtr thread; + nsresult rv = NS_NewThread(getter_AddRefs(thread), worker); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && thread, + "Should successfully create image decoding threads"); + mThreads.AppendElement(Move(thread)); + } + + // Initialize the I/O thread. + nsresult rv = NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread)); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && mIOThread, + "Should successfully create image I/O thread"); + + nsCOMPtr obsSvc = services::GetObserverService(); + if (obsSvc) { + obsSvc->AddObserver(this, "xpcom-shutdown-threads", false); + } +} + +DecodePool::~DecodePool() +{ + MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodePool on main thread!"); +} + +NS_IMETHODIMP +DecodePool::Observe(nsISupports*, const char* aTopic, const char16_t*) +{ + MOZ_ASSERT(strcmp(aTopic, "xpcom-shutdown-threads") == 0, "Unexpected topic"); + + nsTArray> threads; + nsCOMPtr ioThread; + + { + MutexAutoLock lock(mMutex); + threads.SwapElements(mThreads); + ioThread.swap(mIOThread); + } + + mImpl->RequestShutdown(); + + for (uint32_t i = 0 ; i < threads.Length() ; ++i) { + threads[i]->Shutdown(); + } + + if (ioThread) { + ioThread->Shutdown(); + } + + return NS_OK; +} + +void +DecodePool::AsyncRun(IDecodingTask* aTask) +{ + MOZ_ASSERT(aTask); + mImpl->PushWork(aTask); +} + +void +DecodePool::SyncRunIfPreferred(IDecodingTask* aTask) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aTask); + + if (aTask->ShouldPreferSyncRun()) { + aTask->Run(); + return; + } + + AsyncRun(aTask); +} + +void +DecodePool::SyncRunIfPossible(IDecodingTask* aTask) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aTask); + aTask->Run(); +} + +already_AddRefed +DecodePool::GetIOEventTarget() +{ + MutexAutoLock threadPoolLock(mMutex); + nsCOMPtr target = do_QueryInterface(mIOThread); + return target.forget(); +} + +} // namespace image +} // namespace mozilla diff --git a/image/DecodePool.h b/image/DecodePool.h new file mode 100644 index 000000000..9d62731e5 --- /dev/null +++ b/image/DecodePool.h @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * DecodePool manages the threads used for decoding raster images. + */ + +#ifndef mozilla_image_DecodePool_h +#define mozilla_image_DecodePool_h + +#include "mozilla/Mutex.h" +#include "mozilla/StaticPtr.h" +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsIEventTarget.h" +#include "nsIObserver.h" +#include "mozilla/RefPtr.h" + +class nsIThread; +class nsIThreadPool; + +namespace mozilla { +namespace image { + +class Decoder; +class DecodePoolImpl; +class IDecodingTask; + +/** + * DecodePool is a singleton class that manages decoding of raster images. It + * owns a pool of image decoding threads that are used for asynchronous + * decoding. + * + * DecodePool allows callers to run a decoder, handling management of the + * decoder's lifecycle and whether it executes on the main thread, + * off-main-thread in the image decoding thread pool, or on some combination of + * the two. + */ +class DecodePool : public nsIObserver +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOBSERVER + + /// Initializes the singleton instance. Should be called from the main thread. + static void Initialize(); + + /// Returns the singleton instance. + static DecodePool* Singleton(); + + /// @return the number of processor cores we have available. This is not the + /// same as the number of decoding threads we're actually using. + static uint32_t NumberOfCores(); + + /// Ask the DecodePool to run @aTask asynchronously and return immediately. + void AsyncRun(IDecodingTask* aTask); + + /** + * Run @aTask synchronously if the task would prefer it. It's up to the task + * itself to make this decision; @see IDecodingTask::ShouldPreferSyncRun(). If + * @aTask doesn't prefer it, just run @aTask asynchronously and return + * immediately. + */ + void SyncRunIfPreferred(IDecodingTask* aTask); + + /** + * Run @aTask synchronously. This does not guarantee that @aTask will complete + * synchronously. If, for example, @aTask doesn't yet have the data it needs to + * run synchronously, it may recover by scheduling an async task to finish up + * the work when the remaining data is available. + */ + void SyncRunIfPossible(IDecodingTask* aTask); + + /** + * Returns an event target interface to the DecodePool's I/O thread. Callers + * who want to deliver data to workers on the DecodePool can use this event + * target. + * + * @return An nsIEventTarget interface to the thread pool's I/O thread. + */ + already_AddRefed GetIOEventTarget(); + +private: + friend class DecodePoolWorker; + + DecodePool(); + virtual ~DecodePool(); + + static StaticRefPtr sSingleton; + static uint32_t sNumCores; + + RefPtr mImpl; + + // mMutex protects mThreads and mIOThread. + Mutex mMutex; + nsTArray> mThreads; + nsCOMPtr mIOThread; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_DecodePool_h diff --git a/image/DecodedSurfaceProvider.cpp b/image/DecodedSurfaceProvider.cpp new file mode 100644 index 000000000..45b4849d2 --- /dev/null +++ b/image/DecodedSurfaceProvider.cpp @@ -0,0 +1,224 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DecodedSurfaceProvider.h" + +#include "gfxPrefs.h" +#include "nsProxyRelease.h" + +#include "Decoder.h" + +using namespace mozilla::gfx; + +namespace mozilla { +namespace image { + +DecodedSurfaceProvider::DecodedSurfaceProvider(NotNull aImage, + const SurfaceKey& aSurfaceKey, + NotNull aDecoder) + : ISurfaceProvider(ImageKey(aImage.get()), aSurfaceKey, + AvailabilityState::StartAsPlaceholder()) + , mImage(aImage.get()) + , mMutex("mozilla::image::DecodedSurfaceProvider") + , mDecoder(aDecoder.get()) +{ + MOZ_ASSERT(!mDecoder->IsMetadataDecode(), + "Use MetadataDecodingTask for metadata decodes"); + MOZ_ASSERT(mDecoder->IsFirstFrameDecode(), + "Use AnimationSurfaceProvider for animation decodes"); +} + +DecodedSurfaceProvider::~DecodedSurfaceProvider() +{ + DropImageReference(); +} + +void +DecodedSurfaceProvider::DropImageReference() +{ + if (!mImage) { + return; // Nothing to do. + } + + // RasterImage objects need to be destroyed on the main thread. We also need + // to destroy them asynchronously, because if our surface cache entry is + // destroyed and we were the only thing keeping |mImage| alive, RasterImage's + // destructor may call into the surface cache while whatever code caused us to + // get evicted is holding the surface cache lock, causing deadlock. + RefPtr image = mImage; + mImage = nullptr; + NS_ReleaseOnMainThread(image.forget(), /* aAlwaysProxy = */ true); +} + +DrawableFrameRef +DecodedSurfaceProvider::DrawableRef(size_t aFrame) +{ + MOZ_ASSERT(aFrame == 0, + "Requesting an animation frame from a DecodedSurfaceProvider?"); + + // We depend on SurfaceCache::SurfaceAvailable() to provide synchronization + // for methods that touch |mSurface|; after SurfaceAvailable() is called, + // |mSurface| should be non-null and shouldn't be mutated further until we get + // destroyed. That means that the assertions below are very important; we'll + // end up with data races if these assumptions are violated. + if (Availability().IsPlaceholder()) { + MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() on a placeholder"); + return DrawableFrameRef(); + } + + if (!mSurface) { + MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() when we have no surface"); + return DrawableFrameRef(); + } + + return mSurface->DrawableRef(); +} + +bool +DecodedSurfaceProvider::IsFinished() const +{ + // See DrawableRef() for commentary on these assertions. + if (Availability().IsPlaceholder()) { + MOZ_ASSERT_UNREACHABLE("Calling IsFinished() on a placeholder"); + return false; + } + + if (!mSurface) { + MOZ_ASSERT_UNREACHABLE("Calling IsFinished() when we have no surface"); + return false; + } + + return mSurface->IsFinished(); +} + +void +DecodedSurfaceProvider::SetLocked(bool aLocked) +{ + // See DrawableRef() for commentary on these assertions. + if (Availability().IsPlaceholder()) { + MOZ_ASSERT_UNREACHABLE("Calling SetLocked() on a placeholder"); + return; + } + + if (!mSurface) { + MOZ_ASSERT_UNREACHABLE("Calling SetLocked() when we have no surface"); + return; + } + + if (aLocked == IsLocked()) { + return; // Nothing to do. + } + + // If we're locked, hold a DrawableFrameRef to |mSurface|, which will keep any + // volatile buffer it owns in memory. + mLockRef = aLocked ? mSurface->DrawableRef() + : DrawableFrameRef(); +} + +size_t +DecodedSurfaceProvider::LogicalSizeInBytes() const +{ + // Single frame images are always 32bpp. + IntSize size = GetSurfaceKey().Size(); + return size.width * size.height * sizeof(uint32_t); +} + +void +DecodedSurfaceProvider::Run() +{ + MutexAutoLock lock(mMutex); + + if (!mDecoder || !mImage) { + MOZ_ASSERT_UNREACHABLE("Running after decoding finished?"); + return; + } + + // Run the decoder. + LexerResult result = mDecoder->Decode(WrapNotNull(this)); + + // If there's a new surface available, announce it to the surface cache. + CheckForNewSurface(); + + if (result.is()) { + FinishDecoding(); + return; // We're done. + } + + // Notify for the progress we've made so far. + if (mDecoder->HasProgress()) { + NotifyProgress(WrapNotNull(mImage), WrapNotNull(mDecoder)); + } + + MOZ_ASSERT(result.is()); + + if (result == LexerResult(Yield::NEED_MORE_DATA)) { + // We can't make any more progress right now. The decoder itself will ensure + // that we get reenqueued when more data is available; just return for now. + return; + } + + // Single-frame images shouldn't yield for any reason except NEED_MORE_DATA. + MOZ_ASSERT_UNREACHABLE("Unexpected yield for single-frame image"); + mDecoder->TerminateFailure(); + FinishDecoding(); +} + +void +DecodedSurfaceProvider::CheckForNewSurface() +{ + mMutex.AssertCurrentThreadOwns(); + MOZ_ASSERT(mDecoder); + + if (mSurface) { + // Single-frame images should produce no more than one surface, so if we + // have one, it should be the same one the decoder is working on. + MOZ_ASSERT(mSurface.get() == mDecoder->GetCurrentFrameRef().get(), + "DecodedSurfaceProvider and Decoder have different surfaces?"); + return; + } + + // We don't have a surface yet; try to get one from the decoder. + mSurface = mDecoder->GetCurrentFrameRef().get(); + if (!mSurface) { + return; // No surface yet. + } + + // We just got a surface for the first time; let the surface cache know. + MOZ_ASSERT(mImage); + SurfaceCache::SurfaceAvailable(WrapNotNull(this)); +} + +void +DecodedSurfaceProvider::FinishDecoding() +{ + mMutex.AssertCurrentThreadOwns(); + MOZ_ASSERT(mImage); + MOZ_ASSERT(mDecoder); + + // Send notifications. + NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder)); + + // Destroy our decoder; we don't need it anymore. (And if we don't destroy it, + // our surface can never be optimized, because the decoder has a + // RawAccessFrameRef to it.) + mDecoder = nullptr; + + // We don't need a reference to our image anymore, either, and we don't want + // one. We may be stored in the surface cache for a long time after decoding + // finishes. If we don't drop our reference to the image, we'll end up + // keeping it alive as long as we remain in the surface cache, which could + // greatly extend the image's lifetime - in fact, if the image isn't + // discardable, it'd result in a leak! + DropImageReference(); +} + +bool +DecodedSurfaceProvider::ShouldPreferSyncRun() const +{ + return mDecoder->ShouldSyncDecode(gfxPrefs::ImageMemDecodeBytesAtATime()); +} + +} // namespace image +} // namespace mozilla diff --git a/image/DecodedSurfaceProvider.h b/image/DecodedSurfaceProvider.h new file mode 100644 index 000000000..22fd33371 --- /dev/null +++ b/image/DecodedSurfaceProvider.h @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * An ISurfaceProvider implemented for single-frame decoded surfaces. + */ + +#ifndef mozilla_image_DecodedSurfaceProvider_h +#define mozilla_image_DecodedSurfaceProvider_h + +#include "IDecodingTask.h" +#include "ISurfaceProvider.h" +#include "SurfaceCache.h" + +namespace mozilla { +namespace image { + +/** + * An ISurfaceProvider that manages the decoding of a single-frame image and + * stores the resulting surface. + */ +class DecodedSurfaceProvider final + : public ISurfaceProvider + , public IDecodingTask +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodedSurfaceProvider, override) + + DecodedSurfaceProvider(NotNull aImage, + const SurfaceKey& aSurfaceKey, + NotNull aDecoder); + + + ////////////////////////////////////////////////////////////////////////////// + // ISurfaceProvider implementation. + ////////////////////////////////////////////////////////////////////////////// + +public: + bool IsFinished() const override; + size_t LogicalSizeInBytes() const override; + +protected: + DrawableFrameRef DrawableRef(size_t aFrame) override; + bool IsLocked() const override { return bool(mLockRef); } + void SetLocked(bool aLocked) override; + + + ////////////////////////////////////////////////////////////////////////////// + // IDecodingTask implementation. + ////////////////////////////////////////////////////////////////////////////// + +public: + void Run() override; + bool ShouldPreferSyncRun() const override; + + // Full decodes are low priority compared to metadata decodes because they + // don't block layout or page load. + TaskPriority Priority() const override { return TaskPriority::eLow; } + + +private: + virtual ~DecodedSurfaceProvider(); + + void DropImageReference(); + void CheckForNewSurface(); + void FinishDecoding(); + + /// The image associated with our decoder. Dropped after decoding. + RefPtr mImage; + + /// Mutex protecting access to mDecoder. + Mutex mMutex; + + /// The decoder that will generate our surface. Dropped after decoding. + RefPtr mDecoder; + + /// Our surface. Initially null until it's generated by the decoder. + RefPtr mSurface; + + /// A drawable reference to our service; used for locking. + DrawableFrameRef mLockRef; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_DecodedSurfaceProvider_h diff --git a/image/Decoder.cpp b/image/Decoder.cpp new file mode 100644 index 000000000..f9b1034cf --- /dev/null +++ b/image/Decoder.cpp @@ -0,0 +1,521 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Decoder.h" + +#include "mozilla/gfx/2D.h" +#include "DecodePool.h" +#include "GeckoProfiler.h" +#include "IDecodingTask.h" +#include "ISurfaceProvider.h" +#include "nsProxyRelease.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "mozilla/Telemetry.h" + +using mozilla::gfx::IntSize; +using mozilla::gfx::SurfaceFormat; + +namespace mozilla { +namespace image { + +class MOZ_STACK_CLASS AutoRecordDecoderTelemetry final +{ +public: + explicit AutoRecordDecoderTelemetry(Decoder* aDecoder) + : mDecoder(aDecoder) + { + MOZ_ASSERT(mDecoder); + + // Begin recording telemetry data. + mStartTime = TimeStamp::Now(); + } + + ~AutoRecordDecoderTelemetry() + { + // Finish telemetry. + mDecoder->mDecodeTime += (TimeStamp::Now() - mStartTime); + } + +private: + Decoder* mDecoder; + TimeStamp mStartTime; +}; + +Decoder::Decoder(RasterImage* aImage) + : mImageData(nullptr) + , mImageDataLength(0) + , mColormap(nullptr) + , mColormapSize(0) + , mImage(aImage) + , mProgress(NoProgress) + , mFrameCount(0) + , mLoopLength(FrameTimeout::Zero()) + , mDecoderFlags(DefaultDecoderFlags()) + , mSurfaceFlags(DefaultSurfaceFlags()) + , mInitialized(false) + , mMetadataDecode(false) + , mHaveExplicitOutputSize(false) + , mInFrame(false) + , mFinishedNewFrame(false) + , mReachedTerminalState(false) + , mDecodeDone(false) + , mError(false) + , mShouldReportError(false) +{ } + +Decoder::~Decoder() +{ + MOZ_ASSERT(mProgress == NoProgress || !mImage, + "Destroying Decoder without taking all its progress changes"); + MOZ_ASSERT(mInvalidRect.IsEmpty() || !mImage, + "Destroying Decoder without taking all its invalidations"); + mInitialized = false; + + if (mImage && !NS_IsMainThread()) { + // Dispatch mImage to main thread to prevent it from being destructed by the + // decode thread. + NS_ReleaseOnMainThread(mImage.forget()); + } +} + +/* + * Common implementation of the decoder interface. + */ + +nsresult +Decoder::Init() +{ + // No re-initializing + MOZ_ASSERT(!mInitialized, "Can't re-initialize a decoder!"); + + // All decoders must have a SourceBufferIterator. + MOZ_ASSERT(mIterator); + + // Metadata decoders must not set an output size. + MOZ_ASSERT_IF(mMetadataDecode, !mHaveExplicitOutputSize); + + // All decoders must be anonymous except for metadata decoders. + // XXX(seth): Soon that exception will be removed. + MOZ_ASSERT_IF(mImage, IsMetadataDecode()); + + // Implementation-specific initialization. + nsresult rv = InitInternal(); + + mInitialized = true; + + return rv; +} + +LexerResult +Decoder::Decode(IResumable* aOnResume /* = nullptr */) +{ + MOZ_ASSERT(mInitialized, "Should be initialized here"); + MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator"); + + // If we're already done, don't attempt to keep decoding. + if (GetDecodeDone()) { + return LexerResult(HasError() ? TerminalState::FAILURE + : TerminalState::SUCCESS); + } + + LexerResult lexerResult(TerminalState::FAILURE); + { + PROFILER_LABEL("ImageDecoder", "Decode", js::ProfileEntry::Category::GRAPHICS); + AutoRecordDecoderTelemetry telemetry(this); + + lexerResult = DoDecode(*mIterator, aOnResume); + }; + + if (lexerResult.is()) { + // We either need more data to continue (in which case either @aOnResume or + // the caller will reschedule us to run again later), or the decoder is + // yielding to allow the caller access to some intermediate output. + return lexerResult; + } + + // We reached a terminal state; we're now done decoding. + MOZ_ASSERT(lexerResult.is()); + mReachedTerminalState = true; + + // If decoding failed, record that fact. + if (lexerResult.as() == TerminalState::FAILURE) { + PostError(); + } + + // Perform final cleanup. + CompleteDecode(); + + return LexerResult(HasError() ? TerminalState::FAILURE + : TerminalState::SUCCESS); +} + +LexerResult +Decoder::TerminateFailure() +{ + PostError(); + + // Perform final cleanup if need be. + if (!mReachedTerminalState) { + mReachedTerminalState = true; + CompleteDecode(); + } + + return LexerResult(TerminalState::FAILURE); +} + +bool +Decoder::ShouldSyncDecode(size_t aByteLimit) +{ + MOZ_ASSERT(aByteLimit > 0); + MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator"); + + return mIterator->RemainingBytesIsNoMoreThan(aByteLimit); +} + +void +Decoder::CompleteDecode() +{ + // Implementation-specific finalization. + nsresult rv = BeforeFinishInternal(); + if (NS_FAILED(rv)) { + PostError(); + } + + rv = HasError() ? FinishWithErrorInternal() + : FinishInternal(); + if (NS_FAILED(rv)) { + PostError(); + } + + if (IsMetadataDecode()) { + // If this was a metadata decode and we never got a size, the decode failed. + if (!HasSize()) { + PostError(); + } + return; + } + + // If the implementation left us mid-frame, finish that up. Note that it may + // have left us transparent. + if (mInFrame) { + PostHasTransparency(); + PostFrameStop(); + } + + // If PostDecodeDone() has not been called, we may need to send teardown + // notifications if it is unrecoverable. + if (!mDecodeDone) { + // We should always report an error to the console in this case. + mShouldReportError = true; + + if (GetCompleteFrameCount() > 0) { + // We're usable if we have at least one complete frame, so do exactly + // what we should have when the decoder completed. + PostHasTransparency(); + PostDecodeDone(); + } else { + // We're not usable. Record some final progress indicating the error. + mProgress |= FLAG_DECODE_COMPLETE | FLAG_HAS_ERROR; + } + } + + if (mDecodeDone) { + MOZ_ASSERT(HasError() || mCurrentFrame, "Should have an error or a frame"); + + // If this image wasn't animated and isn't a transient image, mark its frame + // as optimizable. We don't support optimizing animated images and + // optimizing transient images isn't worth it. + if (!HasAnimation() && + !(mDecoderFlags & DecoderFlags::IMAGE_IS_TRANSIENT) && + mCurrentFrame) { + mCurrentFrame->SetOptimizable(); + } + } +} + +void +Decoder::SetOutputSize(const gfx::IntSize& aSize) +{ + mOutputSize = Some(aSize); + mHaveExplicitOutputSize = true; +} + +Maybe +Decoder::ExplicitOutputSize() const +{ + MOZ_ASSERT_IF(mHaveExplicitOutputSize, mOutputSize); + return mHaveExplicitOutputSize ? mOutputSize : Nothing(); +} + +Maybe +Decoder::TakeCompleteFrameCount() +{ + const bool finishedNewFrame = mFinishedNewFrame; + mFinishedNewFrame = false; + return finishedNewFrame ? Some(GetCompleteFrameCount()) : Nothing(); +} + +DecoderFinalStatus +Decoder::FinalStatus() const +{ + return DecoderFinalStatus(IsMetadataDecode(), + GetDecodeDone(), + HasError(), + ShouldReportError()); +} + +DecoderTelemetry +Decoder::Telemetry() const +{ + MOZ_ASSERT(mIterator); + return DecoderTelemetry(SpeedHistogram(), + mIterator->ByteCount(), + mIterator->ChunkCount(), + mDecodeTime); +} + +nsresult +Decoder::AllocateFrame(uint32_t aFrameNum, + const gfx::IntSize& aOutputSize, + const gfx::IntRect& aFrameRect, + gfx::SurfaceFormat aFormat, + uint8_t aPaletteDepth) +{ + mCurrentFrame = AllocateFrameInternal(aFrameNum, aOutputSize, aFrameRect, + aFormat, aPaletteDepth, + mCurrentFrame.get()); + + if (mCurrentFrame) { + // Gather the raw pointers the decoders will use. + mCurrentFrame->GetImageData(&mImageData, &mImageDataLength); + mCurrentFrame->GetPaletteData(&mColormap, &mColormapSize); + + // We should now be on |aFrameNum|. (Note that we're comparing the frame + // number, which is zero-based, with the frame count, which is one-based.) + MOZ_ASSERT(aFrameNum + 1 == mFrameCount); + + // If we're past the first frame, PostIsAnimated() should've been called. + MOZ_ASSERT_IF(mFrameCount > 1, HasAnimation()); + + // Update our state to reflect the new frame. + MOZ_ASSERT(!mInFrame, "Starting new frame but not done with old one!"); + mInFrame = true; + } + + return mCurrentFrame ? NS_OK : NS_ERROR_FAILURE; +} + +RawAccessFrameRef +Decoder::AllocateFrameInternal(uint32_t aFrameNum, + const gfx::IntSize& aOutputSize, + const gfx::IntRect& aFrameRect, + SurfaceFormat aFormat, + uint8_t aPaletteDepth, + imgFrame* aPreviousFrame) +{ + if (HasError()) { + return RawAccessFrameRef(); + } + + if (aFrameNum != mFrameCount) { + MOZ_ASSERT_UNREACHABLE("Allocating frames out of order"); + return RawAccessFrameRef(); + } + + if (aOutputSize.width <= 0 || aOutputSize.height <= 0 || + aFrameRect.width <= 0 || aFrameRect.height <= 0) { + NS_WARNING("Trying to add frame with zero or negative size"); + return RawAccessFrameRef(); + } + + NotNull> frame = WrapNotNull(new imgFrame()); + bool nonPremult = bool(mSurfaceFlags & SurfaceFlags::NO_PREMULTIPLY_ALPHA); + if (NS_FAILED(frame->InitForDecoder(aOutputSize, aFrameRect, aFormat, + aPaletteDepth, nonPremult))) { + NS_WARNING("imgFrame::Init should succeed"); + return RawAccessFrameRef(); + } + + RawAccessFrameRef ref = frame->RawAccessRef(); + if (!ref) { + frame->Abort(); + return RawAccessFrameRef(); + } + + if (aFrameNum == 1) { + MOZ_ASSERT(aPreviousFrame, "Must provide a previous frame when animated"); + aPreviousFrame->SetRawAccessOnly(); + + // If we dispose of the first frame by clearing it, then the first frame's + // refresh area is all of itself. + // RESTORE_PREVIOUS is invalid (assumed to be DISPOSE_CLEAR). + AnimationData previousFrameData = aPreviousFrame->GetAnimationData(); + if (previousFrameData.mDisposalMethod == DisposalMethod::CLEAR || + previousFrameData.mDisposalMethod == DisposalMethod::CLEAR_ALL || + previousFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) { + mFirstFrameRefreshArea = previousFrameData.mRect; + } + } + + if (aFrameNum > 0) { + ref->SetRawAccessOnly(); + + // Some GIFs are huge but only have a small area that they animate. We only + // need to refresh that small area when frame 0 comes around again. + mFirstFrameRefreshArea.UnionRect(mFirstFrameRefreshArea, frame->GetRect()); + } + + mFrameCount++; + + return ref; +} + +/* + * Hook stubs. Override these as necessary in decoder implementations. + */ + +nsresult Decoder::InitInternal() { return NS_OK; } +nsresult Decoder::BeforeFinishInternal() { return NS_OK; } +nsresult Decoder::FinishInternal() { return NS_OK; } +nsresult Decoder::FinishWithErrorInternal() { return NS_OK; } + +/* + * Progress Notifications + */ + +void +Decoder::PostSize(int32_t aWidth, + int32_t aHeight, + Orientation aOrientation /* = Orientation()*/) +{ + // Validate. + MOZ_ASSERT(aWidth >= 0, "Width can't be negative!"); + MOZ_ASSERT(aHeight >= 0, "Height can't be negative!"); + + // Set our intrinsic size. + mImageMetadata.SetSize(aWidth, aHeight, aOrientation); + + // Set our output size if it's not already set. + if (!mOutputSize) { + mOutputSize = Some(IntSize(aWidth, aHeight)); + } + + MOZ_ASSERT(mOutputSize->width <= aWidth && mOutputSize->height <= aHeight, + "Output size will result in upscaling"); + + // Create a downscaler if we need to downscale. This is used by legacy + // decoders that haven't been converted to use SurfacePipe yet. + // XXX(seth): Obviously, we'll remove this once all decoders use SurfacePipe. + if (mOutputSize->width < aWidth || mOutputSize->height < aHeight) { + mDownscaler.emplace(*mOutputSize); + } + + // Record this notification. + mProgress |= FLAG_SIZE_AVAILABLE; +} + +void +Decoder::PostHasTransparency() +{ + mProgress |= FLAG_HAS_TRANSPARENCY; +} + +void +Decoder::PostIsAnimated(FrameTimeout aFirstFrameTimeout) +{ + mProgress |= FLAG_IS_ANIMATED; + mImageMetadata.SetHasAnimation(); + mImageMetadata.SetFirstFrameTimeout(aFirstFrameTimeout); +} + +void +Decoder::PostFrameStop(Opacity aFrameOpacity + /* = Opacity::SOME_TRANSPARENCY */, + DisposalMethod aDisposalMethod + /* = DisposalMethod::KEEP */, + FrameTimeout aTimeout /* = FrameTimeout::Forever() */, + BlendMethod aBlendMethod /* = BlendMethod::OVER */, + const Maybe& aBlendRect /* = Nothing() */) +{ + // We should be mid-frame + MOZ_ASSERT(!IsMetadataDecode(), "Stopping frame during metadata decode"); + MOZ_ASSERT(mInFrame, "Stopping frame when we didn't start one"); + MOZ_ASSERT(mCurrentFrame, "Stopping frame when we don't have one"); + + // Update our state. + mInFrame = false; + mFinishedNewFrame = true; + + mCurrentFrame->Finish(aFrameOpacity, aDisposalMethod, aTimeout, + aBlendMethod, aBlendRect); + + mProgress |= FLAG_FRAME_COMPLETE; + + mLoopLength += aTimeout; + + // If we're not sending partial invalidations, then we send an invalidation + // here when the first frame is complete. + if (!ShouldSendPartialInvalidations() && mFrameCount == 1) { + mInvalidRect.UnionRect(mInvalidRect, + IntRect(IntPoint(), Size())); + } +} + +void +Decoder::PostInvalidation(const gfx::IntRect& aRect, + const Maybe& aRectAtOutputSize + /* = Nothing() */) +{ + // We should be mid-frame + MOZ_ASSERT(mInFrame, "Can't invalidate when not mid-frame!"); + MOZ_ASSERT(mCurrentFrame, "Can't invalidate when not mid-frame!"); + + // Record this invalidation, unless we're not sending partial invalidations + // or we're past the first frame. + if (ShouldSendPartialInvalidations() && mFrameCount == 1) { + mInvalidRect.UnionRect(mInvalidRect, aRect); + mCurrentFrame->ImageUpdated(aRectAtOutputSize.valueOr(aRect)); + } +} + +void +Decoder::PostDecodeDone(int32_t aLoopCount /* = 0 */) +{ + MOZ_ASSERT(!IsMetadataDecode(), "Done with decoding in metadata decode"); + MOZ_ASSERT(!mInFrame, "Can't be done decoding if we're mid-frame!"); + MOZ_ASSERT(!mDecodeDone, "Decode already done!"); + mDecodeDone = true; + + mImageMetadata.SetLoopCount(aLoopCount); + + // Some metadata that we track should take into account every frame in the + // image. If this is a first-frame-only decode, our accumulated loop length + // and first frame refresh area only includes the first frame, so it's not + // correct and we don't record it. + if (!IsFirstFrameDecode()) { + mImageMetadata.SetLoopLength(mLoopLength); + mImageMetadata.SetFirstFrameRefreshArea(mFirstFrameRefreshArea); + } + + mProgress |= FLAG_DECODE_COMPLETE; +} + +void +Decoder::PostError() +{ + mError = true; + + if (mInFrame) { + MOZ_ASSERT(mCurrentFrame); + MOZ_ASSERT(mFrameCount > 0); + mCurrentFrame->Abort(); + mInFrame = false; + --mFrameCount; + } +} + +} // namespace image +} // namespace mozilla diff --git a/image/Decoder.h b/image/Decoder.h new file mode 100644 index 000000000..065a3c213 --- /dev/null +++ b/image/Decoder.h @@ -0,0 +1,562 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_Decoder_h +#define mozilla_image_Decoder_h + +#include "FrameAnimator.h" +#include "RasterImage.h" +#include "mozilla/Maybe.h" +#include "mozilla/NotNull.h" +#include "mozilla/RefPtr.h" +#include "DecodePool.h" +#include "DecoderFlags.h" +#include "Downscaler.h" +#include "ImageMetadata.h" +#include "Orientation.h" +#include "SourceBuffer.h" +#include "StreamingLexer.h" +#include "SurfaceFlags.h" + +namespace mozilla { + +namespace Telemetry { + enum ID : uint32_t; +} // namespace Telemetry + +namespace image { + +struct DecoderFinalStatus final +{ + DecoderFinalStatus(bool aWasMetadataDecode, + bool aFinished, + bool aHadError, + bool aShouldReportError) + : mWasMetadataDecode(aWasMetadataDecode) + , mFinished(aFinished) + , mHadError(aHadError) + , mShouldReportError(aShouldReportError) + { } + + /// True if this was a metadata decode. + const bool mWasMetadataDecode : 1; + + /// True if this decoder finished, whether successfully or due to failure. + const bool mFinished : 1; + + /// True if this decoder encountered an error. + const bool mHadError : 1; + + /// True if this decoder encountered the kind of error that should be reported + /// to the console. + const bool mShouldReportError : 1; +}; + +struct DecoderTelemetry final +{ + DecoderTelemetry(Maybe aSpeedHistogram, + size_t aBytesDecoded, + uint32_t aChunkCount, + TimeDuration aDecodeTime) + : mSpeedHistogram(aSpeedHistogram) + , mBytesDecoded(aBytesDecoded) + , mChunkCount(aChunkCount) + , mDecodeTime(aDecodeTime) + { } + + /// @return our decoder's speed, in KBps. + int32_t Speed() const + { + return mBytesDecoded / (1024 * mDecodeTime.ToSeconds()); + } + + /// @return our decoder's decode time, in microseconds. + int32_t DecodeTimeMicros() { return mDecodeTime.ToMicroseconds(); } + + /// The per-image-format telemetry ID for recording our decoder's speed, or + /// Nothing() if we don't record speed telemetry for this kind of decoder. + const Maybe mSpeedHistogram; + + /// The number of bytes of input our decoder processed. + const size_t mBytesDecoded; + + /// The number of chunks our decoder's input was divided into. + const uint32_t mChunkCount; + + /// The amount of time our decoder spent inside DoDecode(). + const TimeDuration mDecodeTime; +}; + +class Decoder +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Decoder) + + explicit Decoder(RasterImage* aImage); + + /** + * Initialize an image decoder. Decoders may not be re-initialized. + * + * @return NS_OK if the decoder could be initialized successfully. + */ + nsresult Init(); + + /** + * Decodes, reading all data currently available in the SourceBuffer. + * + * If more data is needed and @aOnResume is non-null, Decode() will schedule + * @aOnResume to be called when more data is available. + * + * @return a LexerResult which may indicate: + * - the image has been successfully decoded (TerminalState::SUCCESS), or + * - the image has failed to decode (TerminalState::FAILURE), or + * - the decoder is yielding until it gets more data (Yield::NEED_MORE_DATA), or + * - the decoder is yielding to allow the caller to access intermediate + * output (Yield::OUTPUT_AVAILABLE). + */ + LexerResult Decode(IResumable* aOnResume = nullptr); + + /** + * Terminate this decoder in a failure state, just as if the decoder + * implementation had returned TerminalState::FAILURE from DoDecode(). + * + * XXX(seth): This method should be removed ASAP; it exists only because + * RasterImage::FinalizeDecoder() requires an actual Decoder object as an + * argument, so we can't simply tell RasterImage a decode failed except via an + * intervening decoder. We'll fix this in bug 1291071. + */ + LexerResult TerminateFailure(); + + /** + * Given a maximum number of bytes we're willing to decode, @aByteLimit, + * returns true if we should attempt to run this decoder synchronously. + */ + bool ShouldSyncDecode(size_t aByteLimit); + + /** + * Gets the invalidation region accumulated by the decoder so far, and clears + * the decoder's invalidation region. This means that each call to + * TakeInvalidRect() returns only the invalidation region accumulated since + * the last call to TakeInvalidRect(). + */ + nsIntRect TakeInvalidRect() + { + nsIntRect invalidRect = mInvalidRect; + mInvalidRect.SetEmpty(); + return invalidRect; + } + + /** + * Gets the progress changes accumulated by the decoder so far, and clears + * them. This means that each call to TakeProgress() returns only the changes + * accumulated since the last call to TakeProgress(). + */ + Progress TakeProgress() + { + Progress progress = mProgress; + mProgress = NoProgress; + return progress; + } + + /** + * Returns true if there's any progress to report. + */ + bool HasProgress() const + { + return mProgress != NoProgress || !mInvalidRect.IsEmpty() || mFinishedNewFrame; + } + + /* + * State. + */ + + /** + * If we're doing a metadata decode, we only decode the image's headers, which + * is enough to determine the image's intrinsic size. A metadata decode is + * enabled by calling SetMetadataDecode() *before* calling Init(). + */ + void SetMetadataDecode(bool aMetadataDecode) + { + MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet"); + mMetadataDecode = aMetadataDecode; + } + bool IsMetadataDecode() const { return mMetadataDecode; } + + /** + * Sets the output size of this decoder. If this is smaller than the intrinsic + * size of the image, we'll downscale it while decoding. For memory usage + * reasons, upscaling is forbidden and will trigger assertions in debug + * builds. + * + * Not calling SetOutputSize() means that we should just decode at the + * intrinsic size, whatever it is. + * + * If SetOutputSize() was called, ExplicitOutputSize() can be used to + * determine the value that was passed to it. + * + * This must be called before Init() is called. + */ + void SetOutputSize(const gfx::IntSize& aSize); + + /** + * @return the output size of this decoder. If this is smaller than the + * intrinsic size, then the image will be downscaled during the decoding + * process. + * + * Illegal to call if HasSize() returns false. + */ + gfx::IntSize OutputSize() const { MOZ_ASSERT(HasSize()); return *mOutputSize; } + + /** + * @return either the size passed to SetOutputSize() or Nothing(), indicating + * that SetOutputSize() was not explicitly called. + */ + Maybe ExplicitOutputSize() const; + + /** + * Set the requested sample size for this decoder. Used to implement the + * -moz-sample-size media fragment. + * + * XXX(seth): Support for -moz-sample-size will be removed in bug 1120056. + */ + virtual void SetSampleSize(int aSampleSize) { } + + /** + * Set an iterator to the SourceBuffer which will feed data to this decoder. + * This must always be called before calling Init(). (And only before Init().) + * + * XXX(seth): We should eliminate this method and pass a SourceBufferIterator + * to the various decoder constructors instead. + */ + void SetIterator(SourceBufferIterator&& aIterator) + { + MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet"); + mIterator.emplace(Move(aIterator)); + } + + /** + * Should this decoder send partial invalidations? + */ + bool ShouldSendPartialInvalidations() const + { + return !(mDecoderFlags & DecoderFlags::IS_REDECODE); + } + + /** + * Should we stop decoding after the first frame? + */ + bool IsFirstFrameDecode() const + { + return bool(mDecoderFlags & DecoderFlags::FIRST_FRAME_ONLY); + } + + /** + * @return the number of complete animation frames which have been decoded so + * far, if it has changed since the last call to TakeCompleteFrameCount(); + * otherwise, returns Nothing(). + */ + Maybe TakeCompleteFrameCount(); + + // The number of frames we have, including anything in-progress. Thus, this + // is only 0 if we haven't begun any frames. + uint32_t GetFrameCount() { return mFrameCount; } + + // Did we discover that the image we're decoding is animated? + bool HasAnimation() const { return mImageMetadata.HasAnimation(); } + + // Error tracking + bool HasError() const { return mError; } + bool ShouldReportError() const { return mShouldReportError; } + + /// Did we finish decoding enough that calling Decode() again would be useless? + bool GetDecodeDone() const + { + return mReachedTerminalState || mDecodeDone || + (mMetadataDecode && HasSize()) || HasError(); + } + + /// Are we in the middle of a frame right now? Used for assertions only. + bool InFrame() const { return mInFrame; } + + enum DecodeStyle { + PROGRESSIVE, // produce intermediate frames representing the partial + // state of the image + SEQUENTIAL // decode to final image immediately + }; + + /** + * Get or set the DecoderFlags that influence the behavior of this decoder. + */ + void SetDecoderFlags(DecoderFlags aDecoderFlags) + { + MOZ_ASSERT(!mInitialized); + mDecoderFlags = aDecoderFlags; + } + DecoderFlags GetDecoderFlags() const { return mDecoderFlags; } + + /** + * Get or set the SurfaceFlags that select the kind of output this decoder + * will produce. + */ + void SetSurfaceFlags(SurfaceFlags aSurfaceFlags) + { + MOZ_ASSERT(!mInitialized); + mSurfaceFlags = aSurfaceFlags; + } + SurfaceFlags GetSurfaceFlags() const { return mSurfaceFlags; } + + /// @return true if we know the intrinsic size of the image we're decoding. + bool HasSize() const { return mImageMetadata.HasSize(); } + + /** + * @return the intrinsic size of the image we're decoding. + * + * Illegal to call if HasSize() returns false. + */ + gfx::IntSize Size() const + { + MOZ_ASSERT(HasSize()); + return mImageMetadata.GetSize(); + } + + /** + * @return an IntRect which covers the entire area of this image at its + * intrinsic size, appropriate for use as a frame rect when the image itself + * does not specify one. + * + * Illegal to call if HasSize() returns false. + */ + gfx::IntRect FullFrame() const + { + return gfx::IntRect(gfx::IntPoint(), Size()); + } + + /** + * @return an IntRect which covers the entire area of this image at its size + * after scaling - that is, at its output size. + * + * XXX(seth): This is only used for decoders which are using the old + * Downscaler code instead of SurfacePipe, since the old AllocateFrame() and + * Downscaler APIs required that the frame rect be specified in output space. + * We should remove this once all decoders use SurfacePipe. + * + * Illegal to call if HasSize() returns false. + */ + gfx::IntRect FullOutputFrame() const + { + return gfx::IntRect(gfx::IntPoint(), OutputSize()); + } + + /// @return final status information about this decoder. Should be called + /// after we decide we're not going to run the decoder anymore. + DecoderFinalStatus FinalStatus() const; + + /// @return the metadata we collected about this image while decoding. + const ImageMetadata& GetImageMetadata() { return mImageMetadata; } + + /// @return performance telemetry we collected while decoding. + DecoderTelemetry Telemetry() const; + + /** + * @return a weak pointer to the image associated with this decoder. Illegal + * to call if this decoder is not associated with an image. + */ + NotNull GetImage() const { return WrapNotNull(mImage.get()); } + + /** + * @return a possibly-null weak pointer to the image associated with this + * decoder. May be called even if this decoder is not associated with an + * image. + */ + RasterImage* GetImageMaybeNull() const { return mImage.get(); } + + RawAccessFrameRef GetCurrentFrameRef() + { + return mCurrentFrame ? mCurrentFrame->RawAccessRef() + : RawAccessFrameRef(); + } + + +protected: + friend class AutoRecordDecoderTelemetry; + friend class nsICODecoder; + friend class PalettedSurfaceSink; + friend class SurfaceSink; + + virtual ~Decoder(); + + /* + * Internal hooks. Decoder implementations may override these and + * only these methods. + * + * BeforeFinishInternal() can be used to detect if decoding is in an + * incomplete state, e.g. due to file truncation, in which case it should + * return a failing nsresult. + */ + virtual nsresult InitInternal(); + virtual LexerResult DoDecode(SourceBufferIterator& aIterator, + IResumable* aOnResume) = 0; + virtual nsresult BeforeFinishInternal(); + virtual nsresult FinishInternal(); + virtual nsresult FinishWithErrorInternal(); + + /** + * @return the per-image-format telemetry ID for recording this decoder's + * speed, or Nothing() if we don't record speed telemetry for this kind of + * decoder. + */ + virtual Maybe SpeedHistogram() const { return Nothing(); } + + + /* + * Progress notifications. + */ + + // Called by decoders when they determine the size of the image. Informs + // the image of its size and sends notifications. + void PostSize(int32_t aWidth, + int32_t aHeight, + Orientation aOrientation = Orientation()); + + // Called by decoders if they determine that the image has transparency. + // + // This should be fired as early as possible to allow observers to do things + // that affect content, so it's necessarily pessimistic - if there's a + // possibility that the image has transparency, for example because its header + // specifies that it has an alpha channel, we fire PostHasTransparency + // immediately. PostFrameStop's aFrameOpacity argument, on the other hand, is + // only used internally to ImageLib. Because PostFrameStop isn't delivered + // until the entire frame has been decoded, decoders may take into account the + // actual contents of the frame and give a more accurate result. + void PostHasTransparency(); + + // Called by decoders if they determine that the image is animated. + // + // @param aTimeout The time for which the first frame should be shown before + // we advance to the next frame. + void PostIsAnimated(FrameTimeout aFirstFrameTimeout); + + // Called by decoders when they end a frame. Informs the image, sends + // notifications, and does internal book-keeping. + // Specify whether this frame is opaque as an optimization. + // For animated images, specify the disposal, blend method and timeout for + // this frame. + void PostFrameStop(Opacity aFrameOpacity = Opacity::SOME_TRANSPARENCY, + DisposalMethod aDisposalMethod = DisposalMethod::KEEP, + FrameTimeout aTimeout = FrameTimeout::Forever(), + BlendMethod aBlendMethod = BlendMethod::OVER, + const Maybe& aBlendRect = Nothing()); + + /** + * Called by the decoders when they have a region to invalidate. We may not + * actually pass these invalidations on right away. + * + * @param aRect The invalidation rect in the coordinate system of the unscaled + * image (that is, the image at its intrinsic size). + * @param aRectAtOutputSize If not Nothing(), the invalidation rect in the + * coordinate system of the scaled image (that is, + * the image at our output size). This must + * be supplied if we're downscaling during decode. + */ + void PostInvalidation(const gfx::IntRect& aRect, + const Maybe& aRectAtOutputSize = Nothing()); + + // Called by the decoders when they have successfully decoded the image. This + // may occur as the result of the decoder getting to the appropriate point in + // the stream, or by us calling FinishInternal(). + // + // May not be called mid-frame. + // + // For animated images, specify the loop count. -1 means loop forever, 0 + // means a single iteration, stopping on the last frame. + void PostDecodeDone(int32_t aLoopCount = 0); + + /** + * Allocates a new frame, making it our current frame if successful. + * + * The @aFrameNum parameter only exists as a sanity check; it's illegal to + * create a new frame anywhere but immediately after the existing frames. + * + * If a non-paletted frame is desired, pass 0 for aPaletteDepth. + */ + nsresult AllocateFrame(uint32_t aFrameNum, + const gfx::IntSize& aOutputSize, + const gfx::IntRect& aFrameRect, + gfx::SurfaceFormat aFormat, + uint8_t aPaletteDepth = 0); + +private: + /// Report that an error was encountered while decoding. + void PostError(); + + /** + * CompleteDecode() finishes up the decoding process after Decode() determines + * that we're finished. It records final progress and does all the cleanup + * that's possible off-main-thread. + */ + void CompleteDecode(); + + /// @return the number of complete frames we have. Does not include the + /// current frame if it's unfinished. + uint32_t GetCompleteFrameCount() + { + if (mFrameCount == 0) { + return 0; + } + + return mInFrame ? mFrameCount - 1 : mFrameCount; + } + + RawAccessFrameRef AllocateFrameInternal(uint32_t aFrameNum, + const gfx::IntSize& aOutputSize, + const gfx::IntRect& aFrameRect, + gfx::SurfaceFormat aFormat, + uint8_t aPaletteDepth, + imgFrame* aPreviousFrame); + +protected: + Maybe mDownscaler; + + uint8_t* mImageData; // Pointer to image data in either Cairo or 8bit format + uint32_t mImageDataLength; + uint32_t* mColormap; // Current colormap to be used in Cairo format + uint32_t mColormapSize; + +private: + RefPtr mImage; + Maybe mIterator; + RawAccessFrameRef mCurrentFrame; + ImageMetadata mImageMetadata; + gfx::IntRect mInvalidRect; // Tracks an invalidation region in the current frame. + Maybe mOutputSize; // The size of our output surface. + Progress mProgress; + + uint32_t mFrameCount; // Number of frames, including anything in-progress + FrameTimeout mLoopLength; // Length of a single loop of this image. + gfx::IntRect mFirstFrameRefreshArea; // The area of the image that needs to + // be invalidated when the animation loops. + + // Telemetry data for this decoder. + TimeDuration mDecodeTime; + + DecoderFlags mDecoderFlags; + SurfaceFlags mSurfaceFlags; + + bool mInitialized : 1; + bool mMetadataDecode : 1; + bool mHaveExplicitOutputSize : 1; + bool mInFrame : 1; + bool mFinishedNewFrame : 1; // True if PostFrameStop() has been called since + // the last call to TakeCompleteFrameCount(). + bool mReachedTerminalState : 1; + bool mDecodeDone : 1; + bool mError : 1; + bool mShouldReportError : 1; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_Decoder_h diff --git a/image/DecoderFactory.cpp b/image/DecoderFactory.cpp new file mode 100644 index 000000000..1c51d2fbc --- /dev/null +++ b/image/DecoderFactory.cpp @@ -0,0 +1,350 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DecoderFactory.h" + +#include "nsMimeTypes.h" +#include "mozilla/RefPtr.h" + +#include "AnimationSurfaceProvider.h" +#include "Decoder.h" +#include "IDecodingTask.h" +#include "nsPNGDecoder.h" +#include "nsGIFDecoder2.h" +#include "nsJPEGDecoder.h" +#include "nsBMPDecoder.h" +#include "nsICODecoder.h" +#include "nsIconDecoder.h" + +namespace mozilla { + +using namespace gfx; + +namespace image { + +/* static */ DecoderType +DecoderFactory::GetDecoderType(const char* aMimeType) +{ + // By default we don't know. + DecoderType type = DecoderType::UNKNOWN; + + // PNG + if (!strcmp(aMimeType, IMAGE_PNG)) { + type = DecoderType::PNG; + } else if (!strcmp(aMimeType, IMAGE_X_PNG)) { + type = DecoderType::PNG; + } else if (!strcmp(aMimeType, IMAGE_APNG)) { + type = DecoderType::PNG; + + // GIF + } else if (!strcmp(aMimeType, IMAGE_GIF)) { + type = DecoderType::GIF; + + // JPEG + } else if (!strcmp(aMimeType, IMAGE_JPEG)) { + type = DecoderType::JPEG; + } else if (!strcmp(aMimeType, IMAGE_PJPEG)) { + type = DecoderType::JPEG; + } else if (!strcmp(aMimeType, IMAGE_JPG)) { + type = DecoderType::JPEG; + + // BMP + } else if (!strcmp(aMimeType, IMAGE_BMP)) { + type = DecoderType::BMP; + } else if (!strcmp(aMimeType, IMAGE_BMP_MS)) { + type = DecoderType::BMP; + + // ICO + } else if (!strcmp(aMimeType, IMAGE_ICO)) { + type = DecoderType::ICO; + } else if (!strcmp(aMimeType, IMAGE_ICO_MS)) { + type = DecoderType::ICO; + + // Icon + } else if (!strcmp(aMimeType, IMAGE_ICON_MS)) { + type = DecoderType::ICON; + } + + return type; +} + +/* static */ already_AddRefed +DecoderFactory::GetDecoder(DecoderType aType, + RasterImage* aImage, + bool aIsRedecode) +{ + RefPtr decoder; + + switch (aType) { + case DecoderType::PNG: + decoder = new nsPNGDecoder(aImage); + break; + case DecoderType::GIF: + decoder = new nsGIFDecoder2(aImage); + break; + case DecoderType::JPEG: + // If we have all the data we don't want to waste cpu time doing + // a progressive decode. + decoder = new nsJPEGDecoder(aImage, + aIsRedecode ? Decoder::SEQUENTIAL + : Decoder::PROGRESSIVE); + break; + case DecoderType::BMP: + decoder = new nsBMPDecoder(aImage); + break; + case DecoderType::ICO: + decoder = new nsICODecoder(aImage); + break; + case DecoderType::ICON: + decoder = new nsIconDecoder(aImage); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unknown decoder type"); + } + + return decoder.forget(); +} + +/* static */ already_AddRefed +DecoderFactory::CreateDecoder(DecoderType aType, + NotNull aImage, + NotNull aSourceBuffer, + const IntSize& aIntrinsicSize, + const IntSize& aOutputSize, + DecoderFlags aDecoderFlags, + SurfaceFlags aSurfaceFlags, + int aSampleSize) +{ + if (aType == DecoderType::UNKNOWN) { + return nullptr; + } + + // Create an anonymous decoder. Interaction with the SurfaceCache and the + // owning RasterImage will be mediated by DecodedSurfaceProvider. + RefPtr decoder = + GetDecoder(aType, nullptr, bool(aDecoderFlags & DecoderFlags::IS_REDECODE)); + MOZ_ASSERT(decoder, "Should have a decoder now"); + + // Initialize the decoder. + decoder->SetMetadataDecode(false); + decoder->SetIterator(aSourceBuffer->Iterator()); + decoder->SetOutputSize(aOutputSize); + decoder->SetDecoderFlags(aDecoderFlags | DecoderFlags::FIRST_FRAME_ONLY); + decoder->SetSurfaceFlags(aSurfaceFlags); + decoder->SetSampleSize(aSampleSize); + + if (NS_FAILED(decoder->Init())) { + return nullptr; + } + + // Create a DecodedSurfaceProvider which will manage the decoding process and + // make this decoder's output available in the surface cache. + SurfaceKey surfaceKey = + RasterSurfaceKey(aOutputSize, aSurfaceFlags, PlaybackType::eStatic); + NotNull> provider = + WrapNotNull(new DecodedSurfaceProvider(aImage, + surfaceKey, + WrapNotNull(decoder))); + + // Attempt to insert the surface provider into the surface cache right away so + // we won't trigger any more decoders with the same parameters. + if (SurfaceCache::Insert(provider) != InsertOutcome::SUCCESS) { + return nullptr; + } + + // Return the surface provider in its IDecodingTask guise. + RefPtr task = provider.get(); + return task.forget(); +} + +/* static */ already_AddRefed +DecoderFactory::CreateAnimationDecoder(DecoderType aType, + NotNull aImage, + NotNull aSourceBuffer, + const IntSize& aIntrinsicSize, + DecoderFlags aDecoderFlags, + SurfaceFlags aSurfaceFlags) +{ + if (aType == DecoderType::UNKNOWN) { + return nullptr; + } + + MOZ_ASSERT(aType == DecoderType::GIF || aType == DecoderType::PNG, + "Calling CreateAnimationDecoder for non-animating DecoderType"); + + // Create an anonymous decoder. Interaction with the SurfaceCache and the + // owning RasterImage will be mediated by AnimationSurfaceProvider. + RefPtr decoder = GetDecoder(aType, nullptr, /* aIsRedecode = */ true); + MOZ_ASSERT(decoder, "Should have a decoder now"); + + // Initialize the decoder. + decoder->SetMetadataDecode(false); + decoder->SetIterator(aSourceBuffer->Iterator()); + decoder->SetDecoderFlags(aDecoderFlags | DecoderFlags::IS_REDECODE); + decoder->SetSurfaceFlags(aSurfaceFlags); + + if (NS_FAILED(decoder->Init())) { + return nullptr; + } + + // Create an AnimationSurfaceProvider which will manage the decoding process + // and make this decoder's output available in the surface cache. + SurfaceKey surfaceKey = + RasterSurfaceKey(aIntrinsicSize, aSurfaceFlags, PlaybackType::eAnimated); + NotNull> provider = + WrapNotNull(new AnimationSurfaceProvider(aImage, + surfaceKey, + WrapNotNull(decoder))); + + // Attempt to insert the surface provider into the surface cache right away so + // we won't trigger any more decoders with the same parameters. + if (SurfaceCache::Insert(provider) != InsertOutcome::SUCCESS) { + return nullptr; + } + + // Return the surface provider in its IDecodingTask guise. + RefPtr task = provider.get(); + return task.forget(); +} + +/* static */ already_AddRefed +DecoderFactory::CreateMetadataDecoder(DecoderType aType, + NotNull aImage, + NotNull aSourceBuffer, + int aSampleSize) +{ + if (aType == DecoderType::UNKNOWN) { + return nullptr; + } + + RefPtr decoder = + GetDecoder(aType, aImage, /* aIsRedecode = */ false); + MOZ_ASSERT(decoder, "Should have a decoder now"); + + // Initialize the decoder. + decoder->SetMetadataDecode(true); + decoder->SetIterator(aSourceBuffer->Iterator()); + decoder->SetSampleSize(aSampleSize); + + if (NS_FAILED(decoder->Init())) { + return nullptr; + } + + RefPtr task = new MetadataDecodingTask(WrapNotNull(decoder)); + return task.forget(); +} + +/* static */ already_AddRefed +DecoderFactory::CreateDecoderForICOResource(DecoderType aType, + NotNull aSourceBuffer, + NotNull aICODecoder, + const Maybe& aDataOffset + /* = Nothing() */) +{ + // Create the decoder. + RefPtr decoder; + switch (aType) { + case DecoderType::BMP: + MOZ_ASSERT(aDataOffset); + decoder = new nsBMPDecoder(aICODecoder->GetImageMaybeNull(), *aDataOffset); + break; + + case DecoderType::PNG: + MOZ_ASSERT(!aDataOffset); + decoder = new nsPNGDecoder(aICODecoder->GetImageMaybeNull()); + break; + + default: + MOZ_ASSERT_UNREACHABLE("Invalid ICO resource decoder type"); + return nullptr; + } + + MOZ_ASSERT(decoder); + + // Initialize the decoder, copying settings from @aICODecoder. + MOZ_ASSERT(!aICODecoder->IsMetadataDecode()); + decoder->SetMetadataDecode(aICODecoder->IsMetadataDecode()); + decoder->SetIterator(aSourceBuffer->Iterator()); + decoder->SetOutputSize(aICODecoder->OutputSize()); + decoder->SetDecoderFlags(aICODecoder->GetDecoderFlags()); + decoder->SetSurfaceFlags(aICODecoder->GetSurfaceFlags()); + + if (NS_FAILED(decoder->Init())) { + return nullptr; + } + + return decoder.forget(); +} + +/* static */ already_AddRefed +DecoderFactory::CreateAnonymousDecoder(DecoderType aType, + NotNull aSourceBuffer, + const Maybe& aOutputSize, + SurfaceFlags aSurfaceFlags) +{ + if (aType == DecoderType::UNKNOWN) { + return nullptr; + } + + RefPtr decoder = + GetDecoder(aType, /* aImage = */ nullptr, /* aIsRedecode = */ false); + MOZ_ASSERT(decoder, "Should have a decoder now"); + + // Initialize the decoder. + decoder->SetMetadataDecode(false); + decoder->SetIterator(aSourceBuffer->Iterator()); + + // Anonymous decoders are always transient; we don't want to optimize surfaces + // or do any other expensive work that might be wasted. + DecoderFlags decoderFlags = DecoderFlags::IMAGE_IS_TRANSIENT; + + // Without an image, the decoder can't store anything in the SurfaceCache, so + // callers will only be able to retrieve the most recent frame via + // Decoder::GetCurrentFrame(). That means that anonymous decoders should + // always be first-frame-only decoders, because nobody ever wants the *last* + // frame. + decoderFlags |= DecoderFlags::FIRST_FRAME_ONLY; + + decoder->SetDecoderFlags(decoderFlags); + decoder->SetSurfaceFlags(aSurfaceFlags); + + // Set an output size for downscale-during-decode if requested. + if (aOutputSize) { + decoder->SetOutputSize(*aOutputSize); + } + + if (NS_FAILED(decoder->Init())) { + return nullptr; + } + + return decoder.forget(); +} + +/* static */ already_AddRefed +DecoderFactory::CreateAnonymousMetadataDecoder(DecoderType aType, + NotNull aSourceBuffer) +{ + if (aType == DecoderType::UNKNOWN) { + return nullptr; + } + + RefPtr decoder = + GetDecoder(aType, /* aImage = */ nullptr, /* aIsRedecode = */ false); + MOZ_ASSERT(decoder, "Should have a decoder now"); + + // Initialize the decoder. + decoder->SetMetadataDecode(true); + decoder->SetIterator(aSourceBuffer->Iterator()); + decoder->SetDecoderFlags(DecoderFlags::FIRST_FRAME_ONLY); + + if (NS_FAILED(decoder->Init())) { + return nullptr; + } + + return decoder.forget(); +} + +} // namespace image +} // namespace mozilla diff --git a/image/DecoderFactory.h b/image/DecoderFactory.h new file mode 100644 index 000000000..107ebede6 --- /dev/null +++ b/image/DecoderFactory.h @@ -0,0 +1,193 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_DecoderFactory_h +#define mozilla_image_DecoderFactory_h + +#include "DecoderFlags.h" +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "mozilla/NotNull.h" +#include "mozilla/gfx/2D.h" +#include "nsCOMPtr.h" +#include "SurfaceFlags.h" + +namespace mozilla { +namespace image { + +class Decoder; +class IDecodingTask; +class nsICODecoder; +class RasterImage; +class SourceBuffer; + +/** + * The type of decoder; this is usually determined from a MIME type using + * DecoderFactory::GetDecoderType(). + */ +enum class DecoderType +{ + PNG, + GIF, + JPEG, + BMP, + ICO, + ICON, + UNKNOWN +}; + +class DecoderFactory +{ +public: + /// @return the type of decoder which is appropriate for @aMimeType. + static DecoderType GetDecoderType(const char* aMimeType); + + /** + * Creates and initializes a decoder for non-animated images of type @aType. + * (If the image *is* animated, only the first frame will be decoded.) The + * decoder will send notifications to @aImage. + * + * @param aType Which type of decoder to create - JPEG, PNG, etc. + * @param aImage The image will own the decoder and which should receive + * notifications as decoding progresses. + * @param aSourceBuffer The SourceBuffer which the decoder will read its data + * from. + * @param aIntrinsicSize The intrinsic size of the image, normally obtained + * during the metadata decode. + * @param aOutputSize The output size for the decoder. If this is smaller than + * the intrinsic size, the decoder will downscale the + * image. + * @param aDecoderFlags Flags specifying the behavior of this decoder. + * @param aSurfaceFlags Flags specifying the type of output this decoder + * should produce. + * @param aSampleSize The sample size requested using #-moz-samplesize (or 0 + * if none). + */ + static already_AddRefed + CreateDecoder(DecoderType aType, + NotNull aImage, + NotNull aSourceBuffer, + const gfx::IntSize& aIntrinsicSize, + const gfx::IntSize& aOutputSize, + DecoderFlags aDecoderFlags, + SurfaceFlags aSurfaceFlags, + int aSampleSize); + + /** + * Creates and initializes a decoder for animated images of type @aType. + * The decoder will send notifications to @aImage. + * + * @param aType Which type of decoder to create - JPEG, PNG, etc. + * @param aImage The image will own the decoder and which should receive + * notifications as decoding progresses. + * @param aSourceBuffer The SourceBuffer which the decoder will read its data + * from. + * @param aIntrinsicSize The intrinsic size of the image, normally obtained + * during the metadata decode. + * @param aDecoderFlags Flags specifying the behavior of this decoder. + * @param aSurfaceFlags Flags specifying the type of output this decoder + * should produce. + */ + static already_AddRefed + CreateAnimationDecoder(DecoderType aType, + NotNull aImage, + NotNull aSourceBuffer, + const gfx::IntSize& aIntrinsicSize, + DecoderFlags aDecoderFlags, + SurfaceFlags aSurfaceFlags); + + /** + * Creates and initializes a metadata decoder of type @aType. This decoder + * will only decode the image's header, extracting metadata like the size of + * the image. No actual image data will be decoded and no surfaces will be + * allocated. The decoder will send notifications to @aImage. + * + * @param aType Which type of decoder to create - JPEG, PNG, etc. + * @param aImage The image will own the decoder and which should receive + * notifications as decoding progresses. + * @param aSourceBuffer The SourceBuffer which the decoder will read its data + * from. + * @param aSampleSize The sample size requested using #-moz-samplesize (or 0 + * if none). + */ + static already_AddRefed + CreateMetadataDecoder(DecoderType aType, + NotNull aImage, + NotNull aSourceBuffer, + int aSampleSize); + + /** + * Creates and initializes a decoder for an ICO resource, which may be either + * a BMP or PNG image. + * + * @param aType Which type of decoder to create. This must be either BMP or + * PNG. + * @param aSourceBuffer The SourceBuffer which the decoder will read its data + * from. + * @param aICODecoder The ICO decoder which is controlling this resource + * decoder. @aICODecoder's settings will be copied to the + * resource decoder, so the two decoders will have the + * same decoder flags, surface flags, target size, and + * other parameters. + * @param aDataOffset If @aType is BMP, specifies the offset at which data + * begins in the BMP resource. Must be Some() if and only + * if @aType is BMP. + */ + static already_AddRefed + CreateDecoderForICOResource(DecoderType aType, + NotNull aSourceBuffer, + NotNull aICODecoder, + const Maybe& aDataOffset = Nothing()); + + /** + * Creates and initializes an anonymous decoder (one which isn't associated + * with an Image object). Only the first frame of the image will be decoded. + * + * @param aType Which type of decoder to create - JPEG, PNG, etc. + * @param aSourceBuffer The SourceBuffer which the decoder will read its data + * from. + * @param aOutputSize If Some(), the output size for the decoder. If this is + * smaller than the intrinsic size, the decoder will + * downscale the image. If Nothing(), the output size will + * be the intrinsic size. + * @param aSurfaceFlags Flags specifying the type of output this decoder + * should produce. + */ + static already_AddRefed + CreateAnonymousDecoder(DecoderType aType, + NotNull aSourceBuffer, + const Maybe& aOutputSize, + SurfaceFlags aSurfaceFlags); + + /** + * Creates and initializes an anonymous metadata decoder (one which isn't + * associated with an Image object). This decoder will only decode the image's + * header, extracting metadata like the size of the image. No actual image + * data will be decoded and no surfaces will be allocated. + * + * @param aType Which type of decoder to create - JPEG, PNG, etc. + * @param aSourceBuffer The SourceBuffer which the decoder will read its data + * from. + */ + static already_AddRefed + CreateAnonymousMetadataDecoder(DecoderType aType, + NotNull aSourceBuffer); + +private: + virtual ~DecoderFactory() = 0; + + /** + * An internal method which allocates a new decoder of the requested @aType. + */ + static already_AddRefed GetDecoder(DecoderType aType, + RasterImage* aImage, + bool aIsRedecode); +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_DecoderFactory_h diff --git a/image/DecoderFlags.h b/image/DecoderFlags.h new file mode 100644 index 000000000..c4a4df0e5 --- /dev/null +++ b/image/DecoderFlags.h @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_DecoderFlags_h +#define mozilla_image_DecoderFlags_h + +#include "mozilla/TypedEnumBits.h" + +namespace mozilla { +namespace image { + +/** + * Flags that influence decoder behavior. Note that these flags *don't* + * influence the logical content of the surfaces that the decoder generates, so + * they're not in a factor in SurfaceCache lookups and the like. These flags + * instead either influence which surfaces are generated at all or the tune the + * decoder's behavior for a particular scenario. + */ +enum class DecoderFlags : uint8_t +{ + FIRST_FRAME_ONLY = 1 << 0, + IS_REDECODE = 1 << 1, + IMAGE_IS_TRANSIENT = 1 << 2, + ASYNC_NOTIFY = 1 << 3 +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(DecoderFlags) + +/** + * @return the default set of decode flags. + */ +inline DecoderFlags +DefaultDecoderFlags() +{ + return DecoderFlags(); +} + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_DecoderFlags_h diff --git a/image/Downscaler.cpp b/image/Downscaler.cpp new file mode 100644 index 000000000..18f09c596 --- /dev/null +++ b/image/Downscaler.cpp @@ -0,0 +1,352 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Downscaler.h" + +#include +#include +#include "gfxPrefs.h" +#include "image_operations.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/SSE.h" +#include "mozilla/mips.h" +#include "convolver.h" +#include "skia/include/core/SkTypes.h" + +using std::max; +using std::swap; + +namespace mozilla { + +using gfx::IntRect; + +namespace image { + +Downscaler::Downscaler(const nsIntSize& aTargetSize) + : mTargetSize(aTargetSize) + , mOutputBuffer(nullptr) + , mXFilter(MakeUnique()) + , mYFilter(MakeUnique()) + , mWindowCapacity(0) + , mHasAlpha(true) + , mFlipVertically(false) +{ + MOZ_ASSERT(gfxPrefs::ImageDownscaleDuringDecodeEnabled(), + "Downscaling even though downscale-during-decode is disabled?"); + MOZ_ASSERT(mTargetSize.width > 0 && mTargetSize.height > 0, + "Invalid target size"); +} + +Downscaler::~Downscaler() +{ + ReleaseWindow(); +} + +void +Downscaler::ReleaseWindow() +{ + if (!mWindow) { + return; + } + + for (int32_t i = 0; i < mWindowCapacity; ++i) { + delete[] mWindow[i]; + } + + mWindow = nullptr; + mWindowCapacity = 0; +} + +nsresult +Downscaler::BeginFrame(const nsIntSize& aOriginalSize, + const Maybe& aFrameRect, + uint8_t* aOutputBuffer, + bool aHasAlpha, + bool aFlipVertically /* = false */) +{ + MOZ_ASSERT(aOutputBuffer); + MOZ_ASSERT(mTargetSize != aOriginalSize, + "Created a downscaler, but not downscaling?"); + MOZ_ASSERT(mTargetSize.width <= aOriginalSize.width, + "Created a downscaler, but width is larger"); + MOZ_ASSERT(mTargetSize.height <= aOriginalSize.height, + "Created a downscaler, but height is larger"); + MOZ_ASSERT(aOriginalSize.width > 0 && aOriginalSize.height > 0, + "Invalid original size"); + + // Only downscale from reasonable sizes to avoid using too much memory/cpu + // downscaling and decoding. 1 << 20 == 1,048,576 seems a reasonable limit. + if (aOriginalSize.width > (1 << 20) || aOriginalSize.height > (1 << 20)) { + NS_WARNING("Trying to downscale image frame that is too large"); + return NS_ERROR_INVALID_ARG; + } + + mFrameRect = aFrameRect.valueOr(nsIntRect(nsIntPoint(), aOriginalSize)); + MOZ_ASSERT(mFrameRect.x >= 0 && mFrameRect.y >= 0 && + mFrameRect.width >= 0 && mFrameRect.height >= 0, + "Frame rect must have non-negative components"); + MOZ_ASSERT(nsIntRect(0, 0, aOriginalSize.width, aOriginalSize.height) + .Contains(mFrameRect), + "Frame rect must fit inside image"); + MOZ_ASSERT_IF(!nsIntRect(0, 0, aOriginalSize.width, aOriginalSize.height) + .IsEqualEdges(mFrameRect), + aHasAlpha); + + mOriginalSize = aOriginalSize; + mScale = gfxSize(double(mOriginalSize.width) / mTargetSize.width, + double(mOriginalSize.height) / mTargetSize.height); + mOutputBuffer = aOutputBuffer; + mHasAlpha = aHasAlpha; + mFlipVertically = aFlipVertically; + + ReleaseWindow(); + + auto resizeMethod = skia::ImageOperations::RESIZE_LANCZOS3; + + skia::resize::ComputeFilters(resizeMethod, + mOriginalSize.width, mTargetSize.width, + 0, mTargetSize.width, + mXFilter.get()); + + if (mXFilter->max_filter() <= 0 || mXFilter->num_values() != mTargetSize.width) { + NS_WARNING("Failed to compute filters for image downscaling"); + return NS_ERROR_OUT_OF_MEMORY; + } + + skia::resize::ComputeFilters(resizeMethod, + mOriginalSize.height, mTargetSize.height, + 0, mTargetSize.height, + mYFilter.get()); + + if (mYFilter->max_filter() <= 0 || mYFilter->num_values() != mTargetSize.height) { + NS_WARNING("Failed to compute filters for image downscaling"); + return NS_ERROR_OUT_OF_MEMORY; + } + + // Allocate the buffer, which contains scanlines of the original image. + // pad by 15 to handle overreads by the simd code + size_t bufferLen = mOriginalSize.width * sizeof(uint32_t) + 15; + mRowBuffer.reset(new (fallible) uint8_t[bufferLen]); + if (MOZ_UNLIKELY(!mRowBuffer)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // Zero buffer to keep valgrind happy. + memset(mRowBuffer.get(), 0, bufferLen); + + // Allocate the window, which contains horizontally downscaled scanlines. (We + // can store scanlines which are already downscale because our downscaling + // filter is separable.) + mWindowCapacity = mYFilter->max_filter(); + mWindow.reset(new (fallible) uint8_t*[mWindowCapacity]); + if (MOZ_UNLIKELY(!mWindow)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + bool anyAllocationFailed = false; + // pad by 15 to handle overreads by the simd code + const int rowSize = mTargetSize.width * sizeof(uint32_t) + 15; + for (int32_t i = 0; i < mWindowCapacity; ++i) { + mWindow[i] = new (fallible) uint8_t[rowSize]; + anyAllocationFailed = anyAllocationFailed || mWindow[i] == nullptr; + } + + if (MOZ_UNLIKELY(anyAllocationFailed)) { + // We intentionally iterate through the entire array even if an allocation + // fails, to ensure that all the pointers in it are either valid or nullptr. + // That in turn ensures that ReleaseWindow() can clean up correctly. + return NS_ERROR_OUT_OF_MEMORY; + } + + ResetForNextProgressivePass(); + + return NS_OK; +} + +void +Downscaler::SkipToRow(int32_t aRow) +{ + if (mCurrentInLine < aRow) { + ClearRow(); + do { + CommitRow(); + } while (mCurrentInLine < aRow); + } +} + +void +Downscaler::ResetForNextProgressivePass() +{ + mPrevInvalidatedLine = 0; + mCurrentOutLine = 0; + mCurrentInLine = 0; + mLinesInBuffer = 0; + + if (mFrameRect.IsEmpty()) { + // Our frame rect is zero size; commit rows until the end of the image. + SkipToRow(mOriginalSize.height - 1); + } else { + // If we have a vertical offset, commit rows to shift us past it. + SkipToRow(mFrameRect.y); + } +} + +static void +GetFilterOffsetAndLength(UniquePtr& aFilter, + int32_t aOutputImagePosition, + int32_t* aFilterOffsetOut, + int32_t* aFilterLengthOut) +{ + MOZ_ASSERT(aOutputImagePosition < aFilter->num_values()); + aFilter->FilterForValue(aOutputImagePosition, + aFilterOffsetOut, + aFilterLengthOut); +} + +void +Downscaler::ClearRestOfRow(uint32_t aStartingAtCol) +{ + MOZ_ASSERT(int64_t(aStartingAtCol) <= int64_t(mOriginalSize.width)); + uint32_t bytesToClear = (mOriginalSize.width - aStartingAtCol) + * sizeof(uint32_t); + memset(mRowBuffer.get() + (aStartingAtCol * sizeof(uint32_t)), + 0, bytesToClear); +} + +void +Downscaler::CommitRow() +{ + MOZ_ASSERT(mOutputBuffer, "Should have a current frame"); + MOZ_ASSERT(mCurrentInLine < mOriginalSize.height, "Past end of input"); + + if (mCurrentOutLine < mTargetSize.height) { + int32_t filterOffset = 0; + int32_t filterLength = 0; + GetFilterOffsetAndLength(mYFilter, mCurrentOutLine, + &filterOffset, &filterLength); + + int32_t inLineToRead = filterOffset + mLinesInBuffer; + MOZ_ASSERT(mCurrentInLine <= inLineToRead, "Reading past end of input"); + if (mCurrentInLine == inLineToRead) { + skia::ConvolveHorizontally(mRowBuffer.get(), *mXFilter, + mWindow[mLinesInBuffer++], mHasAlpha, + supports_sse2() || supports_mmi()); + } + + MOZ_ASSERT(mCurrentOutLine < mTargetSize.height, + "Writing past end of output"); + + while (mLinesInBuffer == filterLength) { + DownscaleInputLine(); + + if (mCurrentOutLine == mTargetSize.height) { + break; // We're done. + } + + GetFilterOffsetAndLength(mYFilter, mCurrentOutLine, + &filterOffset, &filterLength); + } + } + + mCurrentInLine += 1; + + // If we're at the end of the part of the original image that has data, commit + // rows to shift us to the end. + if (mCurrentInLine == (mFrameRect.y + mFrameRect.height)) { + SkipToRow(mOriginalSize.height - 1); + } +} + +bool +Downscaler::HasInvalidation() const +{ + return mCurrentOutLine > mPrevInvalidatedLine; +} + +DownscalerInvalidRect +Downscaler::TakeInvalidRect() +{ + if (MOZ_UNLIKELY(!HasInvalidation())) { + return DownscalerInvalidRect(); + } + + DownscalerInvalidRect invalidRect; + + // Compute the target size invalid rect. + if (mFlipVertically) { + // We need to flip it. This will implicitly flip the original size invalid + // rect, since we compute it by scaling this rect. + invalidRect.mTargetSizeRect = + IntRect(0, mTargetSize.height - mCurrentOutLine, + mTargetSize.width, mCurrentOutLine - mPrevInvalidatedLine); + } else { + invalidRect.mTargetSizeRect = + IntRect(0, mPrevInvalidatedLine, + mTargetSize.width, mCurrentOutLine - mPrevInvalidatedLine); + } + + mPrevInvalidatedLine = mCurrentOutLine; + + // Compute the original size invalid rect. + invalidRect.mOriginalSizeRect = invalidRect.mTargetSizeRect; + invalidRect.mOriginalSizeRect.ScaleRoundOut(mScale.width, mScale.height); + + return invalidRect; +} + +void +Downscaler::DownscaleInputLine() +{ + typedef skia::ConvolutionFilter1D::Fixed FilterValue; + + MOZ_ASSERT(mOutputBuffer); + MOZ_ASSERT(mCurrentOutLine < mTargetSize.height, + "Writing past end of output"); + + int32_t filterOffset = 0; + int32_t filterLength = 0; + MOZ_ASSERT(mCurrentOutLine < mYFilter->num_values()); + auto filterValues = + mYFilter->FilterForValue(mCurrentOutLine, &filterOffset, &filterLength); + + int32_t currentOutLine = mFlipVertically + ? mTargetSize.height - (mCurrentOutLine + 1) + : mCurrentOutLine; + MOZ_ASSERT(currentOutLine >= 0); + + uint8_t* outputLine = + &mOutputBuffer[currentOutLine * mTargetSize.width * sizeof(uint32_t)]; + skia::ConvolveVertically(static_cast(filterValues), + filterLength, mWindow.get(), mXFilter->num_values(), + outputLine, mHasAlpha, supports_sse2() || supports_mmi()); + + mCurrentOutLine += 1; + + if (mCurrentOutLine == mTargetSize.height) { + // We're done. + return; + } + + int32_t newFilterOffset = 0; + int32_t newFilterLength = 0; + GetFilterOffsetAndLength(mYFilter, mCurrentOutLine, + &newFilterOffset, &newFilterLength); + + int diff = newFilterOffset - filterOffset; + MOZ_ASSERT(diff >= 0, "Moving backwards in the filter?"); + + // Shift the buffer. We're just moving pointers here, so this is cheap. + mLinesInBuffer -= diff; + mLinesInBuffer = max(mLinesInBuffer, 0); + for (int32_t i = 0; i < mLinesInBuffer; ++i) { + swap(mWindow[i], mWindow[filterLength - mLinesInBuffer + i]); + } +} + + + +} // namespace image +} // namespace mozilla diff --git a/image/Downscaler.h b/image/Downscaler.h new file mode 100644 index 000000000..21179a38f --- /dev/null +++ b/image/Downscaler.h @@ -0,0 +1,189 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Downscaler is a high-quality, streaming image downscaler based upon Skia's + * scaling implementation. + */ + +#ifndef mozilla_image_Downscaler_h +#define mozilla_image_Downscaler_h + +#include "mozilla/Maybe.h" +#include "mozilla/UniquePtr.h" +#include "gfxPoint.h" +#include "nsRect.h" + +namespace skia { + class ConvolutionFilter1D; +} // namespace skia + +namespace mozilla { +namespace image { + +/** + * DownscalerInvalidRect wraps two invalidation rects: one in terms of the + * original image size, and one in terms of the target size. + */ +struct DownscalerInvalidRect +{ + nsIntRect mOriginalSizeRect; + nsIntRect mTargetSizeRect; +}; + +#ifdef MOZ_ENABLE_SKIA + +/** + * Downscaler is a high-quality, streaming image downscaler based upon Skia's + * scaling implementation. + * + * Decoders can construct a Downscaler once they know their target size, then + * call BeginFrame() for each frame they decode. They should write a decoded row + * into the buffer returned by RowBuffer(), and then call CommitRow() to signal + * that they have finished. + * + + * Because invalidations need to be computed in terms of the scaled version of + * the image, Downscaler also tracks them. Decoders can call HasInvalidation() + * and TakeInvalidRect() instead of tracking invalidations themselves. + */ +class Downscaler +{ +public: + /// Constructs a new Downscaler which to scale to size @aTargetSize. + explicit Downscaler(const nsIntSize& aTargetSize); + ~Downscaler(); + + const nsIntSize& OriginalSize() const { return mOriginalSize; } + const nsIntSize& TargetSize() const { return mTargetSize; } + const nsIntSize FrameSize() const { return nsIntSize(mFrameRect.width, mFrameRect.height); } + const gfxSize& Scale() const { return mScale; } + + /** + * Begins a new frame and reinitializes the Downscaler. + * + * @param aOriginalSize The original size of this frame, before scaling. + * @param aFrameRect The region of the original image which has data. + * Every pixel outside @aFrameRect is considered blank and + * has zero alpha. + * @param aOutputBuffer The buffer to which the Downscaler should write its + * output; this is the same buffer where the Decoder + * would write its output when not downscaling during + * decode. + * @param aHasAlpha Whether or not this frame has an alpha channel. + * Performance is a little better if it doesn't have one. + * @param aFlipVertically If true, output rows will be written to the output + * buffer in reverse order vertically, which matches + * the way they are stored in some image formats. + */ + nsresult BeginFrame(const nsIntSize& aOriginalSize, + const Maybe& aFrameRect, + uint8_t* aOutputBuffer, + bool aHasAlpha, + bool aFlipVertically = false); + + bool IsFrameComplete() const { return mCurrentInLine >= mOriginalSize.height; } + + /// Retrieves the buffer into which the Decoder should write each row. + uint8_t* RowBuffer() + { + return mRowBuffer.get() + mFrameRect.x * sizeof(uint32_t); + } + + /// Clears the current row buffer. + void ClearRow() { ClearRestOfRow(0); } + + /// Clears the current row buffer starting at @aStartingAtCol. + void ClearRestOfRow(uint32_t aStartingAtCol); + + /// Signals that the decoder has finished writing a row into the row buffer. + void CommitRow(); + + /// Returns true if there is a non-empty invalid rect available. + bool HasInvalidation() const; + + /// Takes the Downscaler's current invalid rect and resets it. + DownscalerInvalidRect TakeInvalidRect(); + + /** + * Resets the Downscaler's position in the image, for a new progressive pass + * over the same frame. Because the same data structures can be reused, this + * is more efficient than calling BeginFrame. + */ + void ResetForNextProgressivePass(); + +private: + void DownscaleInputLine(); + void ReleaseWindow(); + void SkipToRow(int32_t aRow); + + nsIntSize mOriginalSize; + nsIntSize mTargetSize; + nsIntRect mFrameRect; + gfxSize mScale; + + uint8_t* mOutputBuffer; + + UniquePtr mRowBuffer; + UniquePtr mWindow; + + UniquePtr mXFilter; + UniquePtr mYFilter; + + int32_t mWindowCapacity; + + int32_t mLinesInBuffer; + int32_t mPrevInvalidatedLine; + int32_t mCurrentOutLine; + int32_t mCurrentInLine; + + bool mHasAlpha : 1; + bool mFlipVertically : 1; +}; + +#else + +/** + * Downscaler requires Skia to work, so we provide a dummy implementation if + * Skia is disabled that asserts if constructed. + */ + +class Downscaler +{ +public: + explicit Downscaler(const nsIntSize&) + { + MOZ_RELEASE_ASSERT(false, "Skia is not enabled"); + } + + const nsIntSize& OriginalSize() const { return nsIntSize(); } + const nsIntSize& TargetSize() const { return nsIntSize(); } + const gfxSize& Scale() const { return gfxSize(1.0, 1.0); } + + nsresult BeginFrame(const nsIntSize&, const Maybe&, uint8_t*, bool, bool = false) + { + return NS_ERROR_FAILURE; + } + + bool IsFrameComplete() const { return false; } + uint8_t* RowBuffer() { return nullptr; } + void ClearRow() { } + void ClearRestOfRow(uint32_t) { } + void CommitRow() { } + bool HasInvalidation() const { return false; } + DownscalerInvalidRect TakeInvalidRect() { return DownscalerInvalidRect(); } + void ResetForNextProgressivePass() { } + const nsIntSize FrameSize() const { return nsIntSize(0, 0); } +}; + +#endif // MOZ_ENABLE_SKIA + + + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_Downscaler_h diff --git a/image/DownscalingFilter.h b/image/DownscalingFilter.h new file mode 100644 index 000000000..6b0d3b26b --- /dev/null +++ b/image/DownscalingFilter.h @@ -0,0 +1,380 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * DownscalingSurfaceFilter is a SurfaceFilter implementation for use with + * SurfacePipe which performs Lanczos downscaling. + * + * It's in this header file, separated from the other SurfaceFilters, because + * some preprocessor magic is necessary to ensure that there aren't compilation + * issues on platforms where Skia is unavailable. + */ + +#ifndef mozilla_image_DownscalingFilter_h +#define mozilla_image_DownscalingFilter_h + +#include +#include +#include + +#include "mozilla/Maybe.h" +#include "mozilla/SSE.h" +#include "mozilla/mips.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/gfx/2D.h" +#include "gfxPrefs.h" + +#ifdef MOZ_ENABLE_SKIA +#include "convolver.h" +#include "image_operations.h" +#include "skia/include/core/SkTypes.h" +#endif + +#include "SurfacePipe.h" + +namespace mozilla { +namespace image { + +////////////////////////////////////////////////////////////////////////////// +// DownscalingFilter +////////////////////////////////////////////////////////////////////////////// + +template class DownscalingFilter; + +/** + * A configuration struct for DownscalingConfig. + */ +struct DownscalingConfig +{ + template using Filter = DownscalingFilter; + gfx::IntSize mInputSize; /// The size of the input image. We'll downscale + /// from this size to the input size of the next + /// SurfaceFilter in the chain. + gfx::SurfaceFormat mFormat; /// The pixel format - BGRA or BGRX. (BGRX has + /// slightly better performance.) +}; + +#ifndef MOZ_ENABLE_SKIA + +/** + * DownscalingFilter requires Skia. This is a fallback implementation for + * non-Skia builds that fails when Configure() is called (which will prevent + * SurfacePipeFactory from returning an instance of it) and crashes if a caller + * manually constructs an instance and attempts to actually use it. Callers + * should avoid this by ensuring that they do not request downscaling in + * non-Skia builds. + */ +template +class DownscalingFilter final : public SurfaceFilter +{ +public: + Maybe TakeInvalidRect() override { return Nothing(); } + + template + nsresult Configure(const DownscalingConfig& aConfig, Rest... aRest) + { + return NS_ERROR_FAILURE; + } + +protected: + uint8_t* DoResetToFirstRow() override { MOZ_CRASH(); return nullptr; } + uint8_t* DoAdvanceRow() override { MOZ_CRASH(); return nullptr; } +}; + +#else + +/** + * DownscalingFilter performs Lanczos downscaling, taking image input data at one size + * and outputting it rescaled to a different size. + * + * The 'Next' template parameter specifies the next filter in the chain. + */ +template +class DownscalingFilter final : public SurfaceFilter +{ +public: + DownscalingFilter() + : mXFilter(MakeUnique()) + , mYFilter(MakeUnique()) + , mWindowCapacity(0) + , mRowsInWindow(0) + , mInputRow(0) + , mOutputRow(0) + , mHasAlpha(true) + { + MOZ_ASSERT(gfxPrefs::ImageDownscaleDuringDecodeEnabled(), + "Downscaling even though downscale-during-decode is disabled?"); + } + + ~DownscalingFilter() + { + ReleaseWindow(); + } + + template + nsresult Configure(const DownscalingConfig& aConfig, Rest... aRest) + { + nsresult rv = mNext.Configure(aRest...); + if (NS_FAILED(rv)) { + return rv; + } + + if (mNext.IsValidPalettedPipe()) { + NS_WARNING("Created a downscaler for a paletted surface?"); + return NS_ERROR_INVALID_ARG; + } + if (mNext.InputSize() == aConfig.mInputSize) { + NS_WARNING("Created a downscaler, but not downscaling?"); + return NS_ERROR_INVALID_ARG; + } + if (mNext.InputSize().width > aConfig.mInputSize.width) { + NS_WARNING("Created a downscaler, but width is larger"); + return NS_ERROR_INVALID_ARG; + } + if (mNext.InputSize().height > aConfig.mInputSize.height) { + NS_WARNING("Created a downscaler, but height is larger"); + return NS_ERROR_INVALID_ARG; + } + if (aConfig.mInputSize.width <= 0 || aConfig.mInputSize.height <= 0) { + NS_WARNING("Invalid input size for DownscalingFilter"); + return NS_ERROR_INVALID_ARG; + } + + mInputSize = aConfig.mInputSize; + gfx::IntSize outputSize = mNext.InputSize(); + mScale = gfxSize(double(mInputSize.width) / outputSize.width, + double(mInputSize.height) / outputSize.height); + mHasAlpha = aConfig.mFormat == gfx::SurfaceFormat::B8G8R8A8; + + ReleaseWindow(); + + auto resizeMethod = skia::ImageOperations::RESIZE_LANCZOS3; + + skia::resize::ComputeFilters(resizeMethod, + mInputSize.width, outputSize.width, + 0, outputSize.width, + mXFilter.get()); + + skia::resize::ComputeFilters(resizeMethod, + mInputSize.height, outputSize.height, + 0, outputSize.height, + mYFilter.get()); + + // Allocate the buffer, which contains scanlines of the input image. + mRowBuffer.reset(new (fallible) + uint8_t[PaddedWidthInBytes(mInputSize.width)]); + if (MOZ_UNLIKELY(!mRowBuffer)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // Clear the buffer to avoid writing uninitialized memory to the output. + memset(mRowBuffer.get(), 0, PaddedWidthInBytes(mInputSize.width)); + + // Allocate the window, which contains horizontally downscaled scanlines. (We + // can store scanlines which are already downscaled because our downscaling + // filter is separable.) + mWindowCapacity = mYFilter->max_filter(); + mWindow.reset(new (fallible) uint8_t*[mWindowCapacity]); + if (MOZ_UNLIKELY(!mWindow)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // Allocate the "window" of recent rows that we keep in memory as input for + // the downscaling code. We intentionally iterate through the entire array + // even if an allocation fails, to ensure that all the pointers in it are + // either valid or nullptr. That in turn ensures that ReleaseWindow() can + // clean up correctly. + bool anyAllocationFailed = false; + const uint32_t windowRowSizeInBytes = PaddedWidthInBytes(outputSize.width); + for (int32_t i = 0; i < mWindowCapacity; ++i) { + mWindow[i] = new (fallible) uint8_t[windowRowSizeInBytes]; + anyAllocationFailed = anyAllocationFailed || mWindow[i] == nullptr; + } + + if (MOZ_UNLIKELY(anyAllocationFailed)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + ConfigureFilter(mInputSize, sizeof(uint32_t)); + return NS_OK; + } + + Maybe TakeInvalidRect() override + { + Maybe invalidRect = mNext.TakeInvalidRect(); + + if (invalidRect) { + // Compute the input space invalid rect by scaling. + invalidRect->mInputSpaceRect.ScaleRoundOut(mScale.width, mScale.height); + } + + return invalidRect; + } + +protected: + uint8_t* DoResetToFirstRow() override + { + mNext.ResetToFirstRow(); + + mInputRow = 0; + mOutputRow = 0; + mRowsInWindow = 0; + + return GetRowPointer(); + } + + uint8_t* DoAdvanceRow() override + { + if (mInputRow >= mInputSize.height) { + NS_WARNING("Advancing DownscalingFilter past the end of the input"); + return nullptr; + } + + if (mOutputRow >= mNext.InputSize().height) { + NS_WARNING("Advancing DownscalingFilter past the end of the output"); + return nullptr; + } + + int32_t filterOffset = 0; + int32_t filterLength = 0; + GetFilterOffsetAndLength(mYFilter, mOutputRow, + &filterOffset, &filterLength); + + int32_t inputRowToRead = filterOffset + mRowsInWindow; + MOZ_ASSERT(mInputRow <= inputRowToRead, "Reading past end of input"); + if (mInputRow == inputRowToRead) { + skia::ConvolveHorizontally(mRowBuffer.get(), *mXFilter, + mWindow[mRowsInWindow++], mHasAlpha, + supports_sse2() || supports_mmi()); + } + + MOZ_ASSERT(mOutputRow < mNext.InputSize().height, + "Writing past end of output"); + + while (mRowsInWindow == filterLength) { + DownscaleInputRow(); + + if (mOutputRow == mNext.InputSize().height) { + break; // We're done. + } + + GetFilterOffsetAndLength(mYFilter, mOutputRow, + &filterOffset, &filterLength); + } + + mInputRow++; + + return mInputRow < mInputSize.height ? GetRowPointer() + : nullptr; + } + +private: + uint8_t* GetRowPointer() const { return mRowBuffer.get(); } + + static uint32_t PaddedWidthInBytes(uint32_t aLogicalWidth) + { + // Convert from width in BGRA/BGRX pixels to width in bytes, padding by 15 + // to handle overreads by the SIMD code inside Skia. + return aLogicalWidth * sizeof(uint32_t) + 15; + } + + static void + GetFilterOffsetAndLength(UniquePtr& aFilter, + int32_t aOutputImagePosition, + int32_t* aFilterOffsetOut, + int32_t* aFilterLengthOut) + { + MOZ_ASSERT(aOutputImagePosition < aFilter->num_values()); + aFilter->FilterForValue(aOutputImagePosition, + aFilterOffsetOut, + aFilterLengthOut); + } + + void DownscaleInputRow() + { + typedef skia::ConvolutionFilter1D::Fixed FilterValue; + + MOZ_ASSERT(mOutputRow < mNext.InputSize().height, + "Writing past end of output"); + + int32_t filterOffset = 0; + int32_t filterLength = 0; + MOZ_ASSERT(mOutputRow < mYFilter->num_values()); + auto filterValues = + mYFilter->FilterForValue(mOutputRow, &filterOffset, &filterLength); + + mNext.template WriteUnsafeComputedRow([&](uint32_t* aRow, + uint32_t aLength) { + skia::ConvolveVertically(static_cast(filterValues), + filterLength, mWindow.get(), mXFilter->num_values(), + reinterpret_cast(aRow), mHasAlpha, + supports_sse2() || supports_mmi()); + }); + + mOutputRow++; + + if (mOutputRow == mNext.InputSize().height) { + return; // We're done. + } + + int32_t newFilterOffset = 0; + int32_t newFilterLength = 0; + GetFilterOffsetAndLength(mYFilter, mOutputRow, + &newFilterOffset, &newFilterLength); + + int diff = newFilterOffset - filterOffset; + MOZ_ASSERT(diff >= 0, "Moving backwards in the filter?"); + + // Shift the buffer. We're just moving pointers here, so this is cheap. + mRowsInWindow -= diff; + mRowsInWindow = std::max(mRowsInWindow, 0); + for (int32_t i = 0; i < mRowsInWindow; ++i) { + std::swap(mWindow[i], mWindow[filterLength - mRowsInWindow + i]); + } + } + + void ReleaseWindow() + { + if (!mWindow) { + return; + } + + for (int32_t i = 0; i < mWindowCapacity; ++i) { + delete[] mWindow[i]; + } + + mWindow = nullptr; + mWindowCapacity = 0; + } + + Next mNext; /// The next SurfaceFilter in the chain. + + gfx::IntSize mInputSize; /// The size of the input image. + gfxSize mScale; /// The scale factors in each dimension. + /// Computed from @mInputSize and + /// the next filter's input size. + + UniquePtr mRowBuffer; /// The buffer into which input is written. + UniquePtr mWindow; /// The last few rows which were written. + + UniquePtr mXFilter; /// The Lanczos filter in X. + UniquePtr mYFilter; /// The Lanczos filter in Y. + + int32_t mWindowCapacity; /// How many rows the window contains. + + int32_t mRowsInWindow; /// How many rows we've buffered in the window. + int32_t mInputRow; /// The current row we're reading. (0-indexed) + int32_t mOutputRow; /// The current row we're writing. (0-indexed) + + bool mHasAlpha; /// If true, the image has transparency. +}; + +#endif + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_DownscalingFilter_h diff --git a/image/DrawResult.h b/image/DrawResult.h new file mode 100644 index 000000000..8240ede26 --- /dev/null +++ b/image/DrawResult.h @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_DrawResult_h +#define mozilla_image_DrawResult_h + +#include "mozilla/Attributes.h" +#include "mozilla/Likely.h" + +namespace mozilla { +namespace image { + +/** + * An enumeration representing the result of a drawing operation. + * + * Most users of DrawResult will only be interested in whether the value is + * SUCCESS or not. The other values are primarily useful for debugging and error + * handling. + * + * SUCCESS: We successfully drew a completely decoded frame of the requested + * size. Drawing again with FLAG_SYNC_DECODE would not change the result. + * + * INCOMPLETE: We successfully drew a frame that was partially decoded. (Note + * that successfully drawing a partially decoded frame may not actually draw any + * pixels!) Drawing again with FLAG_SYNC_DECODE would improve the result. + * + * WRONG_SIZE: We successfully drew a wrongly-sized frame that had to be scaled. + * This is only returned if drawing again with FLAG_SYNC_DECODE would improve + * the result; if the size requested was larger than the intrinsic size of the + * image, for example, we would generally have to scale whether FLAG_SYNC_DECODE + * was specified or not, and therefore we would not return WRONG_SIZE. + * + * NOT_READY: We failed to draw because no decoded version of the image was + * available. Drawing again with FLAG_SYNC_DECODE would improve the result. + * (Though FLAG_SYNC_DECODE will not necessarily work until after the image's + * load event!) + * + * TEMPORARY_ERROR: We failed to draw due to a temporary error. Drawing may + * succeed at a later time. + * + * BAD_IMAGE: We failed to draw because the image has an error. This is a + * permanent condition. + * + * BAD_ARGS: We failed to draw because bad arguments were passed to draw(). + */ +enum class MOZ_MUST_USE_TYPE DrawResult : uint8_t +{ + SUCCESS, + INCOMPLETE, + WRONG_SIZE, + NOT_READY, + TEMPORARY_ERROR, + BAD_IMAGE, + BAD_ARGS +}; + +/** + * You can combine DrawResults with &. By analogy to bitwise-&, the result is + * DrawResult::SUCCESS only if both operands are DrawResult::SUCCESS. Otherwise, + * a failing DrawResult is returned; we favor the left operand's failure when + * deciding which failure to return, with the exception that we always prefer + * any other kind of failure over DrawResult::BAD_IMAGE, since other failures + * are recoverable and we want to know if any recoverable failures occurred. + */ +inline DrawResult +operator&(const DrawResult aLeft, const DrawResult aRight) +{ + if (MOZ_LIKELY(aLeft == DrawResult::SUCCESS)) { + return aRight; + } + if (aLeft == DrawResult::BAD_IMAGE && aRight != DrawResult::SUCCESS) { + return aRight; + } + return aLeft; +} + +inline DrawResult& +operator&=(DrawResult& aLeft, const DrawResult aRight) +{ + aLeft = aLeft & aRight; + return aLeft; +} + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_DrawResult_h diff --git a/image/DynamicImage.cpp b/image/DynamicImage.cpp new file mode 100644 index 000000000..670595aec --- /dev/null +++ b/image/DynamicImage.cpp @@ -0,0 +1,347 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DynamicImage.h" +#include "gfxPlatform.h" +#include "gfxUtils.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/RefPtr.h" +#include "ImageRegion.h" +#include "Orientation.h" +#include "SVGImageContext.h" + +#include "mozilla/MemoryReporting.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using mozilla::layers::LayerManager; +using mozilla::layers::ImageContainer; + +namespace mozilla { +namespace image { + +// Inherited methods from Image. + +already_AddRefed +DynamicImage::GetProgressTracker() +{ + return nullptr; +} + +size_t +DynamicImage::SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const +{ + return 0; +} + +void +DynamicImage::CollectSizeOfSurfaces(nsTArray& aCounters, + MallocSizeOf aMallocSizeOf) const +{ + // We can't report anything useful because gfxDrawable doesn't expose this + // information. +} + +void +DynamicImage::IncrementAnimationConsumers() +{ } + +void +DynamicImage::DecrementAnimationConsumers() +{ } + +#ifdef DEBUG +uint32_t +DynamicImage::GetAnimationConsumers() +{ + return 0; +} +#endif + +nsresult +DynamicImage::OnImageDataAvailable(nsIRequest* aRequest, + nsISupports* aContext, + nsIInputStream* aInStr, + uint64_t aSourceOffset, + uint32_t aCount) +{ + return NS_OK; +} + +nsresult +DynamicImage::OnImageDataComplete(nsIRequest* aRequest, + nsISupports* aContext, + nsresult aStatus, + bool aLastPart) +{ + return NS_OK; +} + +void +DynamicImage::OnSurfaceDiscarded() +{ } + +void +DynamicImage::SetInnerWindowID(uint64_t aInnerWindowId) +{ } + +uint64_t +DynamicImage::InnerWindowID() const +{ + return 0; +} + +bool +DynamicImage::HasError() +{ + return !mDrawable; +} + +void +DynamicImage::SetHasError() +{ } + +ImageURL* +DynamicImage::GetURI() +{ + return nullptr; +} + +// Methods inherited from XPCOM interfaces. + +NS_IMPL_ISUPPORTS(DynamicImage, imgIContainer) + +NS_IMETHODIMP +DynamicImage::GetWidth(int32_t* aWidth) +{ + *aWidth = mDrawable->Size().width; + return NS_OK; +} + +NS_IMETHODIMP +DynamicImage::GetHeight(int32_t* aHeight) +{ + *aHeight = mDrawable->Size().height; + return NS_OK; +} + +NS_IMETHODIMP +DynamicImage::GetIntrinsicSize(nsSize* aSize) +{ + IntSize intSize(mDrawable->Size()); + *aSize = nsSize(intSize.width, intSize.height); + return NS_OK; +} + +NS_IMETHODIMP +DynamicImage::GetIntrinsicRatio(nsSize* aSize) +{ + IntSize intSize(mDrawable->Size()); + *aSize = nsSize(intSize.width, intSize.height); + return NS_OK; +} + +NS_IMETHODIMP_(Orientation) +DynamicImage::GetOrientation() +{ + return Orientation(); +} + +NS_IMETHODIMP +DynamicImage::GetType(uint16_t* aType) +{ + *aType = imgIContainer::TYPE_RASTER; + return NS_OK; +} + +NS_IMETHODIMP +DynamicImage::GetAnimated(bool* aAnimated) +{ + *aAnimated = false; + return NS_OK; +} + +NS_IMETHODIMP_(already_AddRefed) +DynamicImage::GetFrame(uint32_t aWhichFrame, + uint32_t aFlags) +{ + IntSize size(mDrawable->Size()); + return GetFrameAtSize(IntSize(size.width, size.height), + aWhichFrame, + aFlags); +} + +NS_IMETHODIMP_(already_AddRefed) +DynamicImage::GetFrameAtSize(const IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags) +{ + RefPtr dt = gfxPlatform::GetPlatform()-> + CreateOffscreenContentDrawTarget(aSize, SurfaceFormat::B8G8R8A8); + if (!dt || !dt->IsValid()) { + gfxWarning() << + "DynamicImage::GetFrame failed in CreateOffscreenContentDrawTarget"; + return nullptr; + } + RefPtr context = gfxContext::CreateOrNull(dt); + MOZ_ASSERT(context); // already checked the draw target above + + auto result = Draw(context, aSize, ImageRegion::Create(aSize), + aWhichFrame, SamplingFilter::POINT, Nothing(), aFlags); + + return result == DrawResult::SUCCESS ? dt->Snapshot() : nullptr; +} + +NS_IMETHODIMP_(bool) +DynamicImage::WillDrawOpaqueNow() +{ + return false; +} + +NS_IMETHODIMP_(bool) +DynamicImage::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags) +{ + return false; +} + +NS_IMETHODIMP_(already_AddRefed) +DynamicImage::GetImageContainer(LayerManager* aManager, uint32_t aFlags) +{ + return nullptr; +} + +NS_IMETHODIMP_(DrawResult) +DynamicImage::Draw(gfxContext* aContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, + uint32_t aWhichFrame, + SamplingFilter aSamplingFilter, + const Maybe& aSVGContext, + uint32_t aFlags) +{ + MOZ_ASSERT(!aSize.IsEmpty(), "Unexpected empty size"); + + IntSize drawableSize(mDrawable->Size()); + + if (aSize == drawableSize) { + gfxUtils::DrawPixelSnapped(aContext, mDrawable, drawableSize, aRegion, + SurfaceFormat::B8G8R8A8, aSamplingFilter); + return DrawResult::SUCCESS; + } + + gfxSize scale(double(aSize.width) / drawableSize.width, + double(aSize.height) / drawableSize.height); + + ImageRegion region(aRegion); + region.Scale(1.0 / scale.width, 1.0 / scale.height); + + gfxContextMatrixAutoSaveRestore saveMatrix(aContext); + aContext->Multiply(gfxMatrix::Scaling(scale.width, scale.height)); + + gfxUtils::DrawPixelSnapped(aContext, mDrawable, drawableSize, region, + SurfaceFormat::B8G8R8A8, aSamplingFilter); + return DrawResult::SUCCESS; +} + +NS_IMETHODIMP +DynamicImage::StartDecoding() +{ + return NS_OK; +} + +NS_IMETHODIMP +DynamicImage::RequestDecodeForSize(const nsIntSize& aSize, uint32_t aFlags) +{ + return NS_OK; +} + +NS_IMETHODIMP +DynamicImage::LockImage() +{ + return NS_OK; +} + +NS_IMETHODIMP +DynamicImage::UnlockImage() +{ + return NS_OK; +} + +NS_IMETHODIMP +DynamicImage::RequestDiscard() +{ + return NS_OK; +} + +NS_IMETHODIMP_(void) +DynamicImage::RequestRefresh(const mozilla::TimeStamp& aTime) +{ } + +NS_IMETHODIMP +DynamicImage::GetAnimationMode(uint16_t* aAnimationMode) +{ + *aAnimationMode = kNormalAnimMode; + return NS_OK; +} + +NS_IMETHODIMP +DynamicImage::SetAnimationMode(uint16_t aAnimationMode) +{ + return NS_OK; +} + +NS_IMETHODIMP +DynamicImage::ResetAnimation() +{ + return NS_OK; +} + +NS_IMETHODIMP_(float) +DynamicImage::GetFrameIndex(uint32_t aWhichFrame) +{ + return 0; +} + +NS_IMETHODIMP_(int32_t) +DynamicImage::GetFirstFrameDelay() +{ + return 0; +} + +NS_IMETHODIMP_(void) +DynamicImage::SetAnimationStartTime(const mozilla::TimeStamp& aTime) +{ } + +nsIntSize +DynamicImage::OptimalImageSizeForDest(const gfxSize& aDest, + uint32_t aWhichFrame, + SamplingFilter aSamplingFilter, + uint32_t aFlags) +{ + IntSize size(mDrawable->Size()); + return nsIntSize(size.width, size.height); +} + +NS_IMETHODIMP_(nsIntRect) +DynamicImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect) +{ + return aRect; +} + +already_AddRefed +DynamicImage::Unwrap() +{ + nsCOMPtr self(this); + return self.forget(); +} + +void +DynamicImage::PropagateUseCounters(nsIDocument*) +{ + // No use counters. +} + +} // namespace image +} // namespace mozilla diff --git a/image/DynamicImage.h b/image/DynamicImage.h new file mode 100644 index 000000000..751bed82a --- /dev/null +++ b/image/DynamicImage.h @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_DynamicImage_h +#define mozilla_image_DynamicImage_h + +#include "mozilla/MemoryReporting.h" +#include "gfxDrawable.h" +#include "Image.h" + +namespace mozilla { +namespace image { + +/** + * An Image that is dynamically created. The content of the image is provided by + * a gfxDrawable. It's anticipated that most uses of DynamicImage will be + * ephemeral. + */ +class DynamicImage : public Image +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_IMGICONTAINER + + explicit DynamicImage(gfxDrawable* aDrawable) + : mDrawable(aDrawable) + { + MOZ_ASSERT(aDrawable, "Must have a gfxDrawable to wrap"); + } + + // Inherited methods from Image. + virtual already_AddRefed GetProgressTracker() override; + virtual size_t SizeOfSourceWithComputedFallback( + MallocSizeOf aMallocSizeOf) const override; + virtual void CollectSizeOfSurfaces(nsTArray& aCounters, + MallocSizeOf aMallocSizeOf) const override; + + virtual void IncrementAnimationConsumers() override; + virtual void DecrementAnimationConsumers() override; +#ifdef DEBUG + virtual uint32_t GetAnimationConsumers() override; +#endif + + virtual nsresult OnImageDataAvailable(nsIRequest* aRequest, + nsISupports* aContext, + nsIInputStream* aInStr, + uint64_t aSourceOffset, + uint32_t aCount) override; + virtual nsresult OnImageDataComplete(nsIRequest* aRequest, + nsISupports* aContext, + nsresult aStatus, + bool aLastPart) override; + + virtual void OnSurfaceDiscarded() override; + + virtual void SetInnerWindowID(uint64_t aInnerWindowId) override; + virtual uint64_t InnerWindowID() const override; + + virtual bool HasError() override; + virtual void SetHasError() override; + + virtual ImageURL* GetURI() override; + +private: + virtual ~DynamicImage() { } + + RefPtr mDrawable; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_DynamicImage_h diff --git a/image/FrameAnimator.cpp b/image/FrameAnimator.cpp new file mode 100644 index 000000000..8c29b5636 --- /dev/null +++ b/image/FrameAnimator.cpp @@ -0,0 +1,887 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FrameAnimator.h" + +#include "mozilla/MemoryReporting.h" +#include "mozilla/Move.h" +#include "imgIContainer.h" +#include "LookupResult.h" +#include "MainThreadUtils.h" +#include "RasterImage.h" + +#include "pixman.h" + +namespace mozilla { + +using namespace gfx; + +namespace image { + +/////////////////////////////////////////////////////////////////////////////// +// AnimationState implementation. +/////////////////////////////////////////////////////////////////////////////// + +void +AnimationState::SetDoneDecoding(bool aDone) +{ + mDoneDecoding = aDone; +} + +void +AnimationState::ResetAnimation() +{ + mCurrentAnimationFrameIndex = 0; +} + +void +AnimationState::SetAnimationMode(uint16_t aAnimationMode) +{ + mAnimationMode = aAnimationMode; +} + +void +AnimationState::UpdateKnownFrameCount(uint32_t aFrameCount) +{ + if (aFrameCount <= mFrameCount) { + // Nothing to do. Since we can redecode animated images, we may see the same + // sequence of updates replayed again, so seeing a smaller frame count than + // what we already know about doesn't indicate an error. + return; + } + + MOZ_ASSERT(!mDoneDecoding, "Adding new frames after decoding is finished?"); + MOZ_ASSERT(aFrameCount <= mFrameCount + 1, "Skipped a frame?"); + + mFrameCount = aFrameCount; +} + +Maybe +AnimationState::FrameCount() const +{ + return mDoneDecoding ? Some(mFrameCount) : Nothing(); +} + +void +AnimationState::SetFirstFrameRefreshArea(const IntRect& aRefreshArea) +{ + mFirstFrameRefreshArea = aRefreshArea; +} + +void +AnimationState::InitAnimationFrameTimeIfNecessary() +{ + if (mCurrentAnimationFrameTime.IsNull()) { + mCurrentAnimationFrameTime = TimeStamp::Now(); + } +} + +void +AnimationState::SetAnimationFrameTime(const TimeStamp& aTime) +{ + mCurrentAnimationFrameTime = aTime; +} + +uint32_t +AnimationState::GetCurrentAnimationFrameIndex() const +{ + return mCurrentAnimationFrameIndex; +} + +FrameTimeout +AnimationState::LoopLength() const +{ + // If we don't know the loop length yet, we have to treat it as infinite. + if (!mLoopLength) { + return FrameTimeout::Forever(); + } + + MOZ_ASSERT(mDoneDecoding, "We know the loop length but decoding isn't done?"); + + // If we're not looping, a single loop time has no meaning. + if (mAnimationMode != imgIContainer::kNormalAnimMode) { + return FrameTimeout::Forever(); + } + + return *mLoopLength; +} + + +/////////////////////////////////////////////////////////////////////////////// +// FrameAnimator implementation. +/////////////////////////////////////////////////////////////////////////////// + +TimeStamp +FrameAnimator::GetCurrentImgFrameEndTime(AnimationState& aState) const +{ + TimeStamp currentFrameTime = aState.mCurrentAnimationFrameTime; + FrameTimeout timeout = GetTimeoutForFrame(aState.mCurrentAnimationFrameIndex); + + if (timeout == FrameTimeout::Forever()) { + // We need to return a sentinel value in this case, because our logic + // doesn't work correctly if we have an infinitely long timeout. We use one + // year in the future as the sentinel because it works with the loop in + // RequestRefresh() below. + // XXX(seth): It'd be preferable to make our logic work correctly with + // infinitely long timeouts. + return TimeStamp::NowLoRes() + + TimeDuration::FromMilliseconds(31536000.0); + } + + TimeDuration durationOfTimeout = + TimeDuration::FromMilliseconds(double(timeout.AsMilliseconds())); + TimeStamp currentFrameEndTime = currentFrameTime + durationOfTimeout; + + return currentFrameEndTime; +} + +RefreshResult +FrameAnimator::AdvanceFrame(AnimationState& aState, TimeStamp aTime) +{ + NS_ASSERTION(aTime <= TimeStamp::Now(), + "Given time appears to be in the future"); + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::GRAPHICS); + + RefreshResult ret; + + // Determine what the next frame is, taking into account looping. + uint32_t currentFrameIndex = aState.mCurrentAnimationFrameIndex; + uint32_t nextFrameIndex = currentFrameIndex + 1; + + // Check if we're at the end of the loop. (FrameCount() returns Nothing() if + // we don't know the total count yet.) + if (aState.FrameCount() == Some(nextFrameIndex)) { + // If we are not looping forever, initialize the loop counter + if (aState.mLoopRemainingCount < 0 && aState.LoopCount() >= 0) { + aState.mLoopRemainingCount = aState.LoopCount(); + } + + // If animation mode is "loop once", or we're at end of loop counter, + // it's time to stop animating. + if (aState.mAnimationMode == imgIContainer::kLoopOnceAnimMode || + aState.mLoopRemainingCount == 0) { + ret.mAnimationFinished = true; + } + + nextFrameIndex = 0; + + if (aState.mLoopRemainingCount > 0) { + aState.mLoopRemainingCount--; + } + + // If we're done, exit early. + if (ret.mAnimationFinished) { + return ret; + } + } + + if (nextFrameIndex >= aState.KnownFrameCount()) { + // We've already advanced to the last decoded frame, nothing more we can do. + // We're blocked by network/decoding from displaying the animation at the + // rate specified, so that means the frame we are displaying (the latest + // available) is the frame we want to be displaying at this time. So we + // update the current animation time. If we didn't update the current + // animation time then it could lag behind, which would indicate that we are + // behind in the animation and should try to catch up. When we are done + // decoding (and thus can loop around back to the start of the animation) we + // would then jump to a random point in the animation to try to catch up. + // But we were never behind in the animation. + aState.mCurrentAnimationFrameTime = aTime; + return ret; + } + + // There can be frames in the surface cache with index >= KnownFrameCount() + // which GetRawFrame() can access because an async decoder has decoded them, + // but which AnimationState doesn't know about yet because we haven't received + // the appropriate notification on the main thread. Make sure we stay in sync + // with AnimationState. + MOZ_ASSERT(nextFrameIndex < aState.KnownFrameCount()); + RawAccessFrameRef nextFrame = GetRawFrame(nextFrameIndex); + + // We should always check to see if we have the next frame even if we have + // previously finished decoding. If we needed to redecode (e.g. due to a draw + // failure) we would have discarded all the old frames and may not yet have + // the new ones. + if (!nextFrame || !nextFrame->IsFinished()) { + // Uh oh, the frame we want to show is currently being decoded (partial) + // Wait until the next refresh driver tick and try again + return ret; + } + + if (GetTimeoutForFrame(nextFrameIndex) == FrameTimeout::Forever()) { + ret.mAnimationFinished = true; + } + + if (nextFrameIndex == 0) { + ret.mDirtyRect = aState.FirstFrameRefreshArea(); + } else { + MOZ_ASSERT(nextFrameIndex == currentFrameIndex + 1); + + // Change frame + if (!DoBlend(&ret.mDirtyRect, currentFrameIndex, nextFrameIndex)) { + // something went wrong, move on to next + NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed"); + nextFrame->SetCompositingFailed(true); + aState.mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime(aState); + aState.mCurrentAnimationFrameIndex = nextFrameIndex; + + return ret; + } + + nextFrame->SetCompositingFailed(false); + } + + aState.mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime(aState); + + // If we can get closer to the current time by a multiple of the image's loop + // time, we should. We can only do this if we're done decoding; otherwise, we + // don't know the full loop length, and LoopLength() will have to return + // FrameTimeout::Forever(). + FrameTimeout loopTime = aState.LoopLength(); + if (loopTime != FrameTimeout::Forever()) { + TimeDuration delay = aTime - aState.mCurrentAnimationFrameTime; + if (delay.ToMilliseconds() > loopTime.AsMilliseconds()) { + // Explicitly use integer division to get the floor of the number of + // loops. + uint64_t loops = static_cast(delay.ToMilliseconds()) + / loopTime.AsMilliseconds(); + aState.mCurrentAnimationFrameTime += + TimeDuration::FromMilliseconds(loops * loopTime.AsMilliseconds()); + } + } + + // Set currentAnimationFrameIndex at the last possible moment + aState.mCurrentAnimationFrameIndex = nextFrameIndex; + + // If we're here, we successfully advanced the frame. + ret.mFrameAdvanced = true; + + return ret; +} + +RefreshResult +FrameAnimator::RequestRefresh(AnimationState& aState, const TimeStamp& aTime) +{ + // only advance the frame if the current time is greater than or + // equal to the current frame's end time. + TimeStamp currentFrameEndTime = GetCurrentImgFrameEndTime(aState); + + // By default, an empty RefreshResult. + RefreshResult ret; + + while (currentFrameEndTime <= aTime) { + TimeStamp oldFrameEndTime = currentFrameEndTime; + + RefreshResult frameRes = AdvanceFrame(aState, aTime); + + // Accumulate our result for returning to callers. + ret.Accumulate(frameRes); + + currentFrameEndTime = GetCurrentImgFrameEndTime(aState); + + // If we didn't advance a frame, and our frame end time didn't change, + // then we need to break out of this loop & wait for the frame(s) + // to finish downloading. + if (!frameRes.mFrameAdvanced && (currentFrameEndTime == oldFrameEndTime)) { + break; + } + } + + return ret; +} + +LookupResult +FrameAnimator::GetCompositedFrame(uint32_t aFrameNum) +{ + // If we have a composited version of this frame, return that. + if (mLastCompositedFrameIndex == int32_t(aFrameNum)) { + return LookupResult(DrawableSurface(mCompositingFrame->DrawableRef()), + MatchType::EXACT); + } + + // Otherwise return the raw frame. DoBlend is required to ensure that we only + // hit this case if the frame is not paletted and doesn't require compositing. + LookupResult result = + SurfaceCache::Lookup(ImageKey(mImage), + RasterSurfaceKey(mSize, + DefaultSurfaceFlags(), + PlaybackType::eAnimated)); + if (!result) { + return result; + } + + // Seek to the appropriate frame. If seeking fails, it means that we couldn't + // get the frame we're looking for; treat this as if the lookup failed. + if (NS_FAILED(result.Surface().Seek(aFrameNum))) { + return LookupResult(MatchType::NOT_FOUND); + } + + MOZ_ASSERT(!result.Surface()->GetIsPaletted(), + "About to return a paletted frame"); + + return result; +} + +FrameTimeout +FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const +{ + RawAccessFrameRef frame = GetRawFrame(aFrameNum); + if (frame) { + AnimationData data = frame->GetAnimationData(); + return data.mTimeout; + } + + NS_WARNING("No frame; called GetTimeoutForFrame too early?"); + return FrameTimeout::FromRawMilliseconds(100); +} + +static void +DoCollectSizeOfCompositingSurfaces(const RawAccessFrameRef& aSurface, + SurfaceMemoryCounterType aType, + nsTArray& aCounters, + MallocSizeOf aMallocSizeOf) +{ + // Concoct a SurfaceKey for this surface. + SurfaceKey key = RasterSurfaceKey(aSurface->GetImageSize(), + DefaultSurfaceFlags(), + PlaybackType::eStatic); + + // Create a counter for this surface. + SurfaceMemoryCounter counter(key, /* aIsLocked = */ true, aType); + + // Extract the surface's memory usage information. + size_t heap = 0, nonHeap = 0; + aSurface->AddSizeOfExcludingThis(aMallocSizeOf, heap, nonHeap); + counter.Values().SetDecodedHeap(heap); + counter.Values().SetDecodedNonHeap(nonHeap); + + // Record it. + aCounters.AppendElement(counter); +} + +void +FrameAnimator::CollectSizeOfCompositingSurfaces( + nsTArray& aCounters, + MallocSizeOf aMallocSizeOf) const +{ + if (mCompositingFrame) { + DoCollectSizeOfCompositingSurfaces(mCompositingFrame, + SurfaceMemoryCounterType::COMPOSITING, + aCounters, + aMallocSizeOf); + } + + if (mCompositingPrevFrame) { + DoCollectSizeOfCompositingSurfaces(mCompositingPrevFrame, + SurfaceMemoryCounterType::COMPOSITING_PREV, + aCounters, + aMallocSizeOf); + } +} + +RawAccessFrameRef +FrameAnimator::GetRawFrame(uint32_t aFrameNum) const +{ + LookupResult result = + SurfaceCache::Lookup(ImageKey(mImage), + RasterSurfaceKey(mSize, + DefaultSurfaceFlags(), + PlaybackType::eAnimated)); + if (!result) { + return RawAccessFrameRef(); + } + + // Seek to the frame we want. If seeking fails, it means we couldn't get the + // frame we're looking for, so we bail here to avoid returning the wrong frame + // to the caller. + if (NS_FAILED(result.Surface().Seek(aFrameNum))) { + return RawAccessFrameRef(); // Not available yet. + } + + return result.Surface()->RawAccessRef(); +} + +//****************************************************************************** +// DoBlend gets called when the timer for animation get fired and we have to +// update the composited frame of the animation. +bool +FrameAnimator::DoBlend(IntRect* aDirtyRect, + uint32_t aPrevFrameIndex, + uint32_t aNextFrameIndex) +{ + RawAccessFrameRef prevFrame = GetRawFrame(aPrevFrameIndex); + RawAccessFrameRef nextFrame = GetRawFrame(aNextFrameIndex); + + MOZ_ASSERT(prevFrame && nextFrame, "Should have frames here"); + + AnimationData prevFrameData = prevFrame->GetAnimationData(); + if (prevFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS && + !mCompositingPrevFrame) { + prevFrameData.mDisposalMethod = DisposalMethod::CLEAR; + } + + IntRect prevRect = prevFrameData.mBlendRect + ? prevFrameData.mRect.Intersect(*prevFrameData.mBlendRect) + : prevFrameData.mRect; + + bool isFullPrevFrame = prevRect.x == 0 && prevRect.y == 0 && + prevRect.width == mSize.width && + prevRect.height == mSize.height; + + // Optimization: DisposeClearAll if the previous frame is the same size as + // container and it's clearing itself + if (isFullPrevFrame && + (prevFrameData.mDisposalMethod == DisposalMethod::CLEAR)) { + prevFrameData.mDisposalMethod = DisposalMethod::CLEAR_ALL; + } + + AnimationData nextFrameData = nextFrame->GetAnimationData(); + + IntRect nextRect = nextFrameData.mBlendRect + ? nextFrameData.mRect.Intersect(*nextFrameData.mBlendRect) + : nextFrameData.mRect; + + bool isFullNextFrame = nextRect.x == 0 && nextRect.y == 0 && + nextRect.width == mSize.width && + nextRect.height == mSize.height; + + if (!nextFrame->GetIsPaletted()) { + // Optimization: Skip compositing if the previous frame wants to clear the + // whole image + if (prevFrameData.mDisposalMethod == DisposalMethod::CLEAR_ALL) { + aDirtyRect->SetRect(0, 0, mSize.width, mSize.height); + return true; + } + + // Optimization: Skip compositing if this frame is the same size as the + // container and it's fully drawing over prev frame (no alpha) + if (isFullNextFrame && + (nextFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) && + !nextFrameData.mHasAlpha) { + aDirtyRect->SetRect(0, 0, mSize.width, mSize.height); + return true; + } + } + + // Calculate area that needs updating + switch (prevFrameData.mDisposalMethod) { + default: + MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod"); + case DisposalMethod::NOT_SPECIFIED: + case DisposalMethod::KEEP: + *aDirtyRect = nextRect; + break; + + case DisposalMethod::CLEAR_ALL: + // Whole image container is cleared + aDirtyRect->SetRect(0, 0, mSize.width, mSize.height); + break; + + case DisposalMethod::CLEAR: + // Calc area that needs to be redrawn (the combination of previous and + // this frame) + // XXX - This could be done with multiple framechanged calls + // Having prevFrame way at the top of the image, and nextFrame + // way at the bottom, and both frames being small, we'd be + // telling framechanged to refresh the whole image when only two + // small areas are needed. + aDirtyRect->UnionRect(nextRect, prevRect); + break; + + case DisposalMethod::RESTORE_PREVIOUS: + aDirtyRect->SetRect(0, 0, mSize.width, mSize.height); + break; + } + + // Optimization: + // Skip compositing if the last composited frame is this frame + // (Only one composited frame was made for this animation. Example: + // Only Frame 3 of a 10 frame image required us to build a composite frame + // On the second loop, we do not need to rebuild the frame + // since it's still sitting in compositingFrame) + if (mLastCompositedFrameIndex == int32_t(aNextFrameIndex)) { + return true; + } + + bool needToBlankComposite = false; + + // Create the Compositing Frame + if (!mCompositingFrame) { + RefPtr newFrame = new imgFrame; + nsresult rv = newFrame->InitForDecoder(mSize, + SurfaceFormat::B8G8R8A8); + if (NS_FAILED(rv)) { + mCompositingFrame.reset(); + return false; + } + mCompositingFrame = newFrame->RawAccessRef(); + needToBlankComposite = true; + } else if (int32_t(aNextFrameIndex) != mLastCompositedFrameIndex+1) { + + // If we are not drawing on top of last composited frame, + // then we are building a new composite frame, so let's clear it first. + needToBlankComposite = true; + } + + AnimationData compositingFrameData = mCompositingFrame->GetAnimationData(); + + // More optimizations possible when next frame is not transparent + // But if the next frame has DisposalMethod::RESTORE_PREVIOUS, + // this "no disposal" optimization is not possible, + // because the frame in "after disposal operation" state + // needs to be stored in compositingFrame, so it can be + // copied into compositingPrevFrame later. + bool doDisposal = true; + if (!nextFrameData.mHasAlpha && + nextFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) { + if (isFullNextFrame) { + // Optimization: No need to dispose prev.frame when + // next frame is full frame and not transparent. + doDisposal = false; + // No need to blank the composite frame + needToBlankComposite = false; + } else { + if ((prevRect.x >= nextRect.x) && (prevRect.y >= nextRect.y) && + (prevRect.x + prevRect.width <= nextRect.x + nextRect.width) && + (prevRect.y + prevRect.height <= nextRect.y + nextRect.height)) { + // Optimization: No need to dispose prev.frame when + // next frame fully overlaps previous frame. + doDisposal = false; + } + } + } + + if (doDisposal) { + // Dispose of previous: clear, restore, or keep (copy) + switch (prevFrameData.mDisposalMethod) { + case DisposalMethod::CLEAR: + if (needToBlankComposite) { + // If we just created the composite, it could have anything in its + // buffer. Clear whole frame + ClearFrame(compositingFrameData.mRawData, + compositingFrameData.mRect); + } else { + // Only blank out previous frame area (both color & Mask/Alpha) + ClearFrame(compositingFrameData.mRawData, + compositingFrameData.mRect, + prevRect); + } + break; + + case DisposalMethod::CLEAR_ALL: + ClearFrame(compositingFrameData.mRawData, + compositingFrameData.mRect); + break; + + case DisposalMethod::RESTORE_PREVIOUS: + // It would be better to copy only the area changed back to + // compositingFrame. + if (mCompositingPrevFrame) { + AnimationData compositingPrevFrameData = + mCompositingPrevFrame->GetAnimationData(); + + CopyFrameImage(compositingPrevFrameData.mRawData, + compositingPrevFrameData.mRect, + compositingFrameData.mRawData, + compositingFrameData.mRect); + + // destroy only if we don't need it for this frame's disposal + if (nextFrameData.mDisposalMethod != + DisposalMethod::RESTORE_PREVIOUS) { + mCompositingPrevFrame.reset(); + } + } else { + ClearFrame(compositingFrameData.mRawData, + compositingFrameData.mRect); + } + break; + + default: + MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod"); + case DisposalMethod::NOT_SPECIFIED: + case DisposalMethod::KEEP: + // Copy previous frame into compositingFrame before we put the new + // frame on top + // Assumes that the previous frame represents a full frame (it could be + // smaller in size than the container, as long as the frame before it + // erased itself) + // Note: Frame 1 never gets into DoBlend(), so (aNextFrameIndex - 1) + // will always be a valid frame number. + if (mLastCompositedFrameIndex != int32_t(aNextFrameIndex - 1)) { + if (isFullPrevFrame && !prevFrame->GetIsPaletted()) { + // Just copy the bits + CopyFrameImage(prevFrameData.mRawData, + prevRect, + compositingFrameData.mRawData, + compositingFrameData.mRect); + } else { + if (needToBlankComposite) { + // Only blank composite when prev is transparent or not full. + if (prevFrameData.mHasAlpha || !isFullPrevFrame) { + ClearFrame(compositingFrameData.mRawData, + compositingFrameData.mRect); + } + } + DrawFrameTo(prevFrameData.mRawData, prevFrameData.mRect, + prevFrameData.mPaletteDataLength, + prevFrameData.mHasAlpha, + compositingFrameData.mRawData, + compositingFrameData.mRect, + prevFrameData.mBlendMethod, + prevFrameData.mBlendRect); + } + } + } + } else if (needToBlankComposite) { + // If we just created the composite, it could have anything in its + // buffers. Clear them + ClearFrame(compositingFrameData.mRawData, + compositingFrameData.mRect); + } + + // Check if the frame we are composing wants the previous image restored after + // it is done. Don't store it (again) if last frame wanted its image restored + // too + if ((nextFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) && + (prevFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS)) { + // We are storing the whole image. + // It would be better if we just stored the area that nextFrame is going to + // overwrite. + if (!mCompositingPrevFrame) { + RefPtr newFrame = new imgFrame; + nsresult rv = newFrame->InitForDecoder(mSize, + SurfaceFormat::B8G8R8A8); + if (NS_FAILED(rv)) { + mCompositingPrevFrame.reset(); + return false; + } + + mCompositingPrevFrame = newFrame->RawAccessRef(); + } + + AnimationData compositingPrevFrameData = + mCompositingPrevFrame->GetAnimationData(); + + CopyFrameImage(compositingFrameData.mRawData, + compositingFrameData.mRect, + compositingPrevFrameData.mRawData, + compositingPrevFrameData.mRect); + + mCompositingPrevFrame->Finish(); + } + + // blit next frame into it's correct spot + DrawFrameTo(nextFrameData.mRawData, nextFrameData.mRect, + nextFrameData.mPaletteDataLength, + nextFrameData.mHasAlpha, + compositingFrameData.mRawData, + compositingFrameData.mRect, + nextFrameData.mBlendMethod, + nextFrameData.mBlendRect); + + // Tell the image that it is fully 'downloaded'. + mCompositingFrame->Finish(); + + mLastCompositedFrameIndex = int32_t(aNextFrameIndex); + + return true; +} + +//****************************************************************************** +// Fill aFrame with black. Does also clears the mask. +void +FrameAnimator::ClearFrame(uint8_t* aFrameData, const IntRect& aFrameRect) +{ + if (!aFrameData) { + return; + } + + memset(aFrameData, 0, aFrameRect.width * aFrameRect.height * 4); +} + +//****************************************************************************** +void +FrameAnimator::ClearFrame(uint8_t* aFrameData, const IntRect& aFrameRect, + const IntRect& aRectToClear) +{ + if (!aFrameData || aFrameRect.width <= 0 || aFrameRect.height <= 0 || + aRectToClear.width <= 0 || aRectToClear.height <= 0) { + return; + } + + IntRect toClear = aFrameRect.Intersect(aRectToClear); + if (toClear.IsEmpty()) { + return; + } + + uint32_t bytesPerRow = aFrameRect.width * 4; + for (int row = toClear.y; row < toClear.y + toClear.height; ++row) { + memset(aFrameData + toClear.x * 4 + row * bytesPerRow, 0, + toClear.width * 4); + } +} + +//****************************************************************************** +// Whether we succeed or fail will not cause a crash, and there's not much +// we can do about a failure, so there we don't return a nsresult +bool +FrameAnimator::CopyFrameImage(const uint8_t* aDataSrc, + const IntRect& aRectSrc, + uint8_t* aDataDest, + const IntRect& aRectDest) +{ + uint32_t dataLengthSrc = aRectSrc.width * aRectSrc.height * 4; + uint32_t dataLengthDest = aRectDest.width * aRectDest.height * 4; + + if (!aDataDest || !aDataSrc || dataLengthSrc != dataLengthDest) { + return false; + } + + memcpy(aDataDest, aDataSrc, dataLengthDest); + + return true; +} + +nsresult +FrameAnimator::DrawFrameTo(const uint8_t* aSrcData, const IntRect& aSrcRect, + uint32_t aSrcPaletteLength, bool aSrcHasAlpha, + uint8_t* aDstPixels, const IntRect& aDstRect, + BlendMethod aBlendMethod, const Maybe& aBlendRect) +{ + NS_ENSURE_ARG_POINTER(aSrcData); + NS_ENSURE_ARG_POINTER(aDstPixels); + + // According to both AGIF and APNG specs, offsets are unsigned + if (aSrcRect.x < 0 || aSrcRect.y < 0) { + NS_WARNING("FrameAnimator::DrawFrameTo: negative offsets not allowed"); + return NS_ERROR_FAILURE; + } + // Outside the destination frame, skip it + if ((aSrcRect.x > aDstRect.width) || (aSrcRect.y > aDstRect.height)) { + return NS_OK; + } + + if (aSrcPaletteLength) { + // Larger than the destination frame, clip it + int32_t width = std::min(aSrcRect.width, aDstRect.width - aSrcRect.x); + int32_t height = std::min(aSrcRect.height, aDstRect.height - aSrcRect.y); + + // The clipped image must now fully fit within destination image frame + NS_ASSERTION((aSrcRect.x >= 0) && (aSrcRect.y >= 0) && + (aSrcRect.x + width <= aDstRect.width) && + (aSrcRect.y + height <= aDstRect.height), + "FrameAnimator::DrawFrameTo: Invalid aSrcRect"); + + // clipped image size may be smaller than source, but not larger + NS_ASSERTION((width <= aSrcRect.width) && (height <= aSrcRect.height), + "FrameAnimator::DrawFrameTo: source must be smaller than dest"); + + // Get pointers to image data + const uint8_t* srcPixels = aSrcData + aSrcPaletteLength; + uint32_t* dstPixels = reinterpret_cast(aDstPixels); + const uint32_t* colormap = reinterpret_cast(aSrcData); + + // Skip to the right offset + dstPixels += aSrcRect.x + (aSrcRect.y * aDstRect.width); + if (!aSrcHasAlpha) { + for (int32_t r = height; r > 0; --r) { + for (int32_t c = 0; c < width; c++) { + dstPixels[c] = colormap[srcPixels[c]]; + } + // Go to the next row in the source resp. destination image + srcPixels += aSrcRect.width; + dstPixels += aDstRect.width; + } + } else { + for (int32_t r = height; r > 0; --r) { + for (int32_t c = 0; c < width; c++) { + const uint32_t color = colormap[srcPixels[c]]; + if (color) { + dstPixels[c] = color; + } + } + // Go to the next row in the source resp. destination image + srcPixels += aSrcRect.width; + dstPixels += aDstRect.width; + } + } + } else { + pixman_image_t* src = + pixman_image_create_bits( + aSrcHasAlpha ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8, + aSrcRect.width, aSrcRect.height, + reinterpret_cast(const_cast(aSrcData)), + aSrcRect.width * 4); + if (!src) { + return NS_ERROR_OUT_OF_MEMORY; + } + pixman_image_t* dst = + pixman_image_create_bits(PIXMAN_a8r8g8b8, + aDstRect.width, + aDstRect.height, + reinterpret_cast(aDstPixels), + aDstRect.width * 4); + if (!dst) { + pixman_image_unref(src); + return NS_ERROR_OUT_OF_MEMORY; + } + + // XXX(seth): This is inefficient but we'll remove it quite soon when we + // move frame compositing into SurfacePipe. For now we need this because + // RemoveFrameRectFilter has transformed PNG frames with frame rects into + // imgFrame's with no frame rects, but with a region of 0 alpha where the + // frame rect should be. This works really nicely if we're using + // BlendMethod::OVER, but BlendMethod::SOURCE will result in that frame rect + // area overwriting the previous frame, which makes the animation look + // wrong. This quick hack fixes that by first compositing the whle new frame + // with BlendMethod::OVER, and then recopying the area that uses + // BlendMethod::SOURCE if needed. To make this work, the decoder has to + // provide a "blend rect" that tells us where to do this. This is just the + // frame rect, but hidden in a way that makes it invisible to most of the + // system, so we can keep eliminating dependencies on it. + auto op = aBlendMethod == BlendMethod::SOURCE ? PIXMAN_OP_SRC + : PIXMAN_OP_OVER; + + if (aBlendMethod == BlendMethod::OVER || !aBlendRect || + (aBlendMethod == BlendMethod::SOURCE && aSrcRect.IsEqualEdges(*aBlendRect))) { + // We don't need to do anything clever. (Or, in the case where no blend + // rect was specified, we can't.) + pixman_image_composite32(op, + src, + nullptr, + dst, + 0, 0, + 0, 0, + aSrcRect.x, aSrcRect.y, + aSrcRect.width, aSrcRect.height); + } else { + // We need to do the OVER followed by SOURCE trick above. + pixman_image_composite32(PIXMAN_OP_OVER, + src, + nullptr, + dst, + 0, 0, + 0, 0, + aSrcRect.x, aSrcRect.y, + aSrcRect.width, aSrcRect.height); + pixman_image_composite32(PIXMAN_OP_SRC, + src, + nullptr, + dst, + aBlendRect->x, aBlendRect->y, + 0, 0, + aBlendRect->x, aBlendRect->y, + aBlendRect->width, aBlendRect->height); + } + + pixman_image_unref(src); + pixman_image_unref(dst); + } + + return NS_OK; +} + +} // namespace image +} // namespace mozilla diff --git a/image/FrameAnimator.h b/image/FrameAnimator.h new file mode 100644 index 000000000..da0cb4bf5 --- /dev/null +++ b/image/FrameAnimator.h @@ -0,0 +1,323 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_FrameAnimator_h +#define mozilla_image_FrameAnimator_h + +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/TimeStamp.h" +#include "gfxTypes.h" +#include "imgFrame.h" +#include "nsCOMPtr.h" +#include "nsRect.h" +#include "SurfaceCache.h" + +namespace mozilla { +namespace image { + +class RasterImage; + +class AnimationState +{ +public: + explicit AnimationState(uint16_t aAnimationMode) + : mFrameCount(0) + , mCurrentAnimationFrameIndex(0) + , mLoopRemainingCount(-1) + , mLoopCount(-1) + , mFirstFrameTimeout(FrameTimeout::FromRawMilliseconds(0)) + , mAnimationMode(aAnimationMode) + , mDoneDecoding(false) + { } + + /** + * Call when this image is finished decoding so we know that there aren't any + * more frames coming. + */ + void SetDoneDecoding(bool aDone); + + /** + * Call when you need to re-start animating. Ensures we start from the first + * frame. + */ + void ResetAnimation(); + + /** + * The animation mode of the image. + * + * Constants defined in imgIContainer.idl. + */ + void SetAnimationMode(uint16_t aAnimationMode); + + /// Update the number of frames of animation this image is known to have. + void UpdateKnownFrameCount(uint32_t aFrameCount); + + /// @return the number of frames of animation we know about so far. + uint32_t KnownFrameCount() const { return mFrameCount; } + + /// @return the number of frames this animation has, if we know for sure. + /// (In other words, if decoding is finished.) Otherwise, returns Nothing(). + Maybe FrameCount() const; + + /** + * Get or set the area of the image to invalidate when we loop around to the + * first frame. + */ + void SetFirstFrameRefreshArea(const gfx::IntRect& aRefreshArea); + gfx::IntRect FirstFrameRefreshArea() const { return mFirstFrameRefreshArea; } + + /** + * If the animation frame time has not yet been set, set it to + * TimeStamp::Now(). + */ + void InitAnimationFrameTimeIfNecessary(); + + /** + * Set the animation frame time to @aTime. + */ + void SetAnimationFrameTime(const TimeStamp& aTime); + + /** + * The current frame we're on, from 0 to (numFrames - 1). + */ + uint32_t GetCurrentAnimationFrameIndex() const; + + /* + * Set number of times to loop the image. + * @note -1 means loop forever. + */ + void SetLoopCount(int32_t aLoopCount) { mLoopCount = aLoopCount; } + int32_t LoopCount() const { return mLoopCount; } + + /// Set the @aLength of a single loop through this image. + void SetLoopLength(FrameTimeout aLength) { mLoopLength = Some(aLength); } + + /** + * @return the length of a single loop of this image. If this image is not + * finished decoding, is not animated, or it is animated but does not loop, + * returns FrameTimeout::Forever(). + */ + FrameTimeout LoopLength() const; + + /* + * Get or set the timeout for the first frame. This is used to allow animation + * scheduling even before a full decode runs for this image. + */ + void SetFirstFrameTimeout(FrameTimeout aTimeout) { mFirstFrameTimeout = aTimeout; } + FrameTimeout FirstFrameTimeout() const { return mFirstFrameTimeout; } + +private: + friend class FrameAnimator; + + //! Area of the first frame that needs to be redrawn on subsequent loops. + gfx::IntRect mFirstFrameRefreshArea; + + //! the time that the animation advanced to the current frame + TimeStamp mCurrentAnimationFrameTime; + + //! The number of frames of animation this image has. + uint32_t mFrameCount; + + //! The current frame index we're on, in the range [0, mFrameCount). + uint32_t mCurrentAnimationFrameIndex; + + //! number of loops remaining before animation stops (-1 no stop) + int32_t mLoopRemainingCount; + + //! The total number of loops for the image. + int32_t mLoopCount; + + //! The length of a single loop through this image. + Maybe mLoopLength; + + //! The timeout for the first frame of this image. + FrameTimeout mFirstFrameTimeout; + + //! The animation mode of this image. Constants defined in imgIContainer. + uint16_t mAnimationMode; + + //! Whether this image is done being decoded. + bool mDoneDecoding; +}; + +/** + * RefreshResult is used to let callers know how the state of the animation + * changed during a call to FrameAnimator::RequestRefresh(). + */ +struct RefreshResult +{ + RefreshResult() + : mFrameAdvanced(false) + , mAnimationFinished(false) + { } + + /// Merges another RefreshResult's changes into this RefreshResult. + void Accumulate(const RefreshResult& aOther) + { + mFrameAdvanced = mFrameAdvanced || aOther.mFrameAdvanced; + mAnimationFinished = mAnimationFinished || aOther.mAnimationFinished; + mDirtyRect = mDirtyRect.Union(aOther.mDirtyRect); + } + + // The region of the image that has changed. + gfx::IntRect mDirtyRect; + + // If true, we changed frames at least once. Note that, due to looping, we + // could still have ended up on the same frame! + bool mFrameAdvanced : 1; + + // Whether the animation has finished playing. + bool mAnimationFinished : 1; +}; + +class FrameAnimator +{ +public: + FrameAnimator(RasterImage* aImage, const gfx::IntSize& aSize) + : mImage(aImage) + , mSize(aSize) + , mLastCompositedFrameIndex(-1) + { + MOZ_COUNT_CTOR(FrameAnimator); + } + + ~FrameAnimator() + { + MOZ_COUNT_DTOR(FrameAnimator); + } + + /** + * Re-evaluate what frame we're supposed to be on, and do whatever blending + * is necessary to get us to that frame. + * + * Returns the result of that blending, including whether the current frame + * changed and what the resulting dirty rectangle is. + */ + RefreshResult RequestRefresh(AnimationState& aState, const TimeStamp& aTime); + + /** + * If we have a composited frame for @aFrameNum, returns it. Otherwise, + * returns an empty LookupResult. It is an error to call this method with + * aFrameNum == 0, because the first frame is never composited. + */ + LookupResult GetCompositedFrame(uint32_t aFrameNum); + + /** + * Collect an accounting of the memory occupied by the compositing surfaces we + * use during animation playback. All of the actual animation frames are + * stored in the SurfaceCache, so we don't need to report them here. + */ + void CollectSizeOfCompositingSurfaces(nsTArray& aCounters, + MallocSizeOf aMallocSizeOf) const; + +private: // methods + /** + * Advances the animation. Typically, this will advance a single frame, but it + * may advance multiple frames. This may happen if we have infrequently + * "ticking" refresh drivers (e.g. in background tabs), or extremely short- + * lived animation frames. + * + * @param aTime the time that the animation should advance to. This will + * typically be <= TimeStamp::Now(). + * + * @returns a RefreshResult that shows whether the frame was successfully + * advanced, and its resulting dirty rect. + */ + RefreshResult AdvanceFrame(AnimationState& aState, TimeStamp aTime); + + /** + * Get the @aIndex-th frame in the frame index, ignoring results of blending. + */ + RawAccessFrameRef GetRawFrame(uint32_t aFrameNum) const; + + /// @return the given frame's timeout. + FrameTimeout GetTimeoutForFrame(uint32_t aFrameNum) const; + + /** + * Get the time the frame we're currently displaying is supposed to end. + * + * In the error case, returns an "infinity" timestamp. + */ + TimeStamp GetCurrentImgFrameEndTime(AnimationState& aState) const; + + bool DoBlend(gfx::IntRect* aDirtyRect, + uint32_t aPrevFrameIndex, + uint32_t aNextFrameIndex); + + /** Clears an area of with transparent black. + * + * @param aFrameData Target Frame data + * @param aFrameRect The rectangle of the data pointed ot by aFrameData + * + * @note Does also clears the transparency mask + */ + static void ClearFrame(uint8_t* aFrameData, const gfx::IntRect& aFrameRect); + + //! @overload + static void ClearFrame(uint8_t* aFrameData, const gfx::IntRect& aFrameRect, + const gfx::IntRect& aRectToClear); + + //! Copy one frame's image and mask into another + static bool CopyFrameImage(const uint8_t* aDataSrc, const gfx::IntRect& aRectSrc, + uint8_t* aDataDest, const gfx::IntRect& aRectDest); + + /** + * Draws one frame's image to into another, at the position specified by + * aSrcRect. + * + * @aSrcData the raw data of the current frame being drawn + * @aSrcRect the size of the source frame, and the position of that frame in + * the composition frame + * @aSrcPaletteLength the length (in bytes) of the palette at the beginning + * of the source data (0 if image is not paletted) + * @aSrcHasAlpha whether the source data represents an image with alpha + * @aDstPixels the raw data of the composition frame where the current frame + * is drawn into (32-bit ARGB) + * @aDstRect the size of the composition frame + * @aBlendMethod the blend method for how to blend src on the composition + * frame. + */ + static nsresult DrawFrameTo(const uint8_t* aSrcData, + const gfx::IntRect& aSrcRect, + uint32_t aSrcPaletteLength, bool aSrcHasAlpha, + uint8_t* aDstPixels, const gfx::IntRect& aDstRect, + BlendMethod aBlendMethod, + const Maybe& aBlendRect); + +private: // data + //! A weak pointer to our owning image. + RasterImage* mImage; + + //! The intrinsic size of the image. + gfx::IntSize mSize; + + /** For managing blending of frames + * + * Some animations will use the compositingFrame to composite images + * and just hand this back to the caller when it is time to draw the frame. + * NOTE: When clearing compositingFrame, remember to set + * lastCompositedFrameIndex to -1. Code assume that if + * lastCompositedFrameIndex >= 0 then compositingFrame exists. + */ + RawAccessFrameRef mCompositingFrame; + + /** the previous composited frame, for DISPOSE_RESTORE_PREVIOUS + * + * The Previous Frame (all frames composited up to the current) needs to be + * stored in cases where the image specifies it wants the last frame back + * when it's done with the current frame. + */ + RawAccessFrameRef mCompositingPrevFrame; + + //! Track the last composited frame for Optimizations (See DoComposite code) + int32_t mLastCompositedFrameIndex; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_FrameAnimator_h diff --git a/image/FrozenImage.cpp b/image/FrozenImage.cpp new file mode 100644 index 000000000..46047e71f --- /dev/null +++ b/image/FrozenImage.cpp @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FrozenImage.h" + +namespace mozilla { + +using namespace gfx; +using layers::ImageContainer; +using layers::LayerManager; + +namespace image { + +NS_IMPL_ISUPPORTS_INHERITED0(FrozenImage, ImageWrapper) + +void +FrozenImage::IncrementAnimationConsumers() +{ + // Do nothing. This will prevent animation from starting if there are no other + // instances of this image. +} + +void +FrozenImage::DecrementAnimationConsumers() +{ + // Do nothing. +} + +NS_IMETHODIMP +FrozenImage::GetAnimated(bool* aAnimated) +{ + bool dummy; + nsresult rv = InnerImage()->GetAnimated(&dummy); + if (NS_SUCCEEDED(rv)) { + *aAnimated = false; + } + return rv; +} + +NS_IMETHODIMP_(already_AddRefed) +FrozenImage::GetFrame(uint32_t aWhichFrame, + uint32_t aFlags) +{ + return InnerImage()->GetFrame(FRAME_FIRST, aFlags); +} + +NS_IMETHODIMP_(already_AddRefed) +FrozenImage::GetFrameAtSize(const IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags) +{ + return InnerImage()->GetFrameAtSize(aSize, FRAME_FIRST, aFlags); +} + +NS_IMETHODIMP_(bool) +FrozenImage::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags) +{ + return false; +} + +NS_IMETHODIMP_(already_AddRefed) +FrozenImage::GetImageContainer(layers::LayerManager* aManager, uint32_t aFlags) +{ + // XXX(seth): GetImageContainer does not currently support anything but the + // current frame. We work around this by always returning null, but if it ever + // turns out that FrozenImage is widely used on codepaths that can actually + // benefit from GetImageContainer, it would be a good idea to fix that method + // for performance reasons. + return nullptr; +} + +NS_IMETHODIMP_(DrawResult) +FrozenImage::Draw(gfxContext* aContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, + uint32_t /* aWhichFrame - ignored */, + SamplingFilter aSamplingFilter, + const Maybe& aSVGContext, + uint32_t aFlags) +{ + return InnerImage()->Draw(aContext, aSize, aRegion, FRAME_FIRST, + aSamplingFilter, aSVGContext, aFlags); +} + +NS_IMETHODIMP_(void) +FrozenImage::RequestRefresh(const TimeStamp& aTime) +{ + // Do nothing. +} + +NS_IMETHODIMP +FrozenImage::GetAnimationMode(uint16_t* aAnimationMode) +{ + *aAnimationMode = kNormalAnimMode; + return NS_OK; +} + +NS_IMETHODIMP +FrozenImage::SetAnimationMode(uint16_t aAnimationMode) +{ + // Do nothing. + return NS_OK; +} + +NS_IMETHODIMP +FrozenImage::ResetAnimation() +{ + // Do nothing. + return NS_OK; +} + +NS_IMETHODIMP_(float) +FrozenImage::GetFrameIndex(uint32_t aWhichFrame) +{ + MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE, "Invalid argument"); + return 0; +} + +} // namespace image +} // namespace mozilla diff --git a/image/FrozenImage.h b/image/FrozenImage.h new file mode 100644 index 000000000..24b57304e --- /dev/null +++ b/image/FrozenImage.h @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_FrozenImage_h +#define mozilla_image_FrozenImage_h + +#include "ImageWrapper.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { +namespace image { + +/** + * An Image wrapper that disables animation, freezing the image at its first + * frame. It does this using two strategies. If this is the only instance of the + * image, animation will never start, because IncrementAnimationConsumers is + * ignored. If there is another instance that is animated, that's still OK, + * because any imgIContainer method that is affected by animation gets its + * aWhichFrame argument set to FRAME_FIRST when it passes through FrozenImage. + * + * XXX(seth): There a known (performance, not correctness) issue with + * GetImageContainer. See the comments for that method for more information. + */ +class FrozenImage : public ImageWrapper +{ + typedef gfx::SourceSurface SourceSurface; + +public: + NS_DECL_ISUPPORTS_INHERITED + + virtual void IncrementAnimationConsumers() override; + virtual void DecrementAnimationConsumers() override; + + NS_IMETHOD GetAnimated(bool* aAnimated) override; + NS_IMETHOD_(already_AddRefed) + GetFrame(uint32_t aWhichFrame, uint32_t aFlags) override; + NS_IMETHOD_(already_AddRefed) + GetFrameAtSize(const gfx::IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags) override; + NS_IMETHOD_(bool) IsImageContainerAvailable(layers::LayerManager* aManager, + uint32_t aFlags) override; + NS_IMETHOD_(already_AddRefed) + GetImageContainer(layers::LayerManager* aManager, + uint32_t aFlags) override; + NS_IMETHOD_(DrawResult) Draw(gfxContext* aContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, + uint32_t aWhichFrame, + gfx::SamplingFilter aSamplingFilter, + const Maybe& aSVGContext, + uint32_t aFlags) override; + NS_IMETHOD_(void) RequestRefresh(const TimeStamp& aTime) override; + NS_IMETHOD GetAnimationMode(uint16_t* aAnimationMode) override; + NS_IMETHOD SetAnimationMode(uint16_t aAnimationMode) override; + NS_IMETHOD ResetAnimation() override; + NS_IMETHOD_(float) GetFrameIndex(uint32_t aWhichFrame) override; + +protected: + explicit FrozenImage(Image* aImage) : ImageWrapper(aImage) { } + virtual ~FrozenImage() { } + +private: + friend class ImageOps; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_FrozenImage_h diff --git a/image/ICOFileHeaders.h b/image/ICOFileHeaders.h new file mode 100644 index 000000000..aae5c99e8 --- /dev/null +++ b/image/ICOFileHeaders.h @@ -0,0 +1,81 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#ifndef mozilla_image_ICOFileHeaders_h +#define mozilla_image_ICOFileHeaders_h + +namespace mozilla { +namespace image { + +#define ICONFILEHEADERSIZE 6 +#define ICODIRENTRYSIZE 16 +#define PNGSIGNATURESIZE 8 +#define BMPFILEHEADERSIZE 14 + +/** + * The header that comes right at the start of an icon file. (This + * corresponds to the Windows ICONDIR structure.) + */ +struct IconFileHeader +{ + /** + * Must be set to 0; + */ + uint16_t mReserved; + /** + * 1 for icon (.ICO) image (or 2 for cursor (.CUR) image (icon with the + * addition of a hotspot), but we don't support cursor). + */ + uint16_t mType; + /** + * The number of BMP/PNG images contained in the icon file. + */ + uint16_t mCount; +}; + +/** + * For each BMP/PNG image that the icon file contains there must be a + * corresponding icon dir entry. (This corresponds to the Windows + * ICONDIRENTRY structure.) These entries are encoded directly after the + * IconFileHeader. + */ +struct IconDirEntry +{ + uint8_t mWidth; + uint8_t mHeight; + /** + * The number of colors in the color palette of the BMP/PNG that this dir + * entry corresponds to, or 0 if the image does not use a color palette. + */ + uint8_t mColorCount; + /** + * Should be set to 0. + */ + uint8_t mReserved; + union { + uint16_t mPlanes; // ICO + uint16_t mXHotspot; // CUR + }; + union { + uint16_t mBitCount; // ICO (bits per pixel) + uint16_t mYHotspot; // CUR + }; + /** + * "bytes in resource" is the length of the encoded BMP/PNG that this dir + * entry corresponds to. + */ + uint32_t mBytesInRes; + /** + * The offset of the start of the encoded BMP/PNG that this dir entry + * corresponds to (from the start of the icon file). + */ + uint32_t mImageOffset; +}; + + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_ICOFileHeaders_h diff --git a/image/IDecodingTask.cpp b/image/IDecodingTask.cpp new file mode 100644 index 000000000..a067e7a7d --- /dev/null +++ b/image/IDecodingTask.cpp @@ -0,0 +1,172 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "IDecodingTask.h" + +#include "gfxPrefs.h" +#include "nsThreadUtils.h" + +#include "Decoder.h" +#include "DecodePool.h" +#include "RasterImage.h" +#include "SurfaceCache.h" + +namespace mozilla { + +using gfx::IntRect; + +namespace image { + +/////////////////////////////////////////////////////////////////////////////// +// Helpers for sending notifications to the image associated with a decoder. +/////////////////////////////////////////////////////////////////////////////// + +/* static */ void +IDecodingTask::NotifyProgress(NotNull aImage, + NotNull aDecoder) +{ + MOZ_ASSERT(aDecoder->HasProgress() && !aDecoder->IsMetadataDecode()); + + // Capture the decoder's state. If we need to notify asynchronously, it's + // important that we don't wait until the lambda actually runs to capture the + // state that we're going to notify. That would both introduce data races on + // the decoder's state and cause inconsistencies between the NotifyProgress() + // calls we make off-main-thread and the notifications that RasterImage + // actually receives, which would cause bugs. + Progress progress = aDecoder->TakeProgress(); + IntRect invalidRect = aDecoder->TakeInvalidRect(); + Maybe frameCount = aDecoder->TakeCompleteFrameCount(); + DecoderFlags decoderFlags = aDecoder->GetDecoderFlags(); + SurfaceFlags surfaceFlags = aDecoder->GetSurfaceFlags(); + + // Synchronously notify if we can. + if (NS_IsMainThread() && !(decoderFlags & DecoderFlags::ASYNC_NOTIFY)) { + aImage->NotifyProgress(progress, invalidRect, frameCount, + decoderFlags, surfaceFlags); + return; + } + + // We're forced to notify asynchronously. + NotNull> image = aImage; + NS_DispatchToMainThread(NS_NewRunnableFunction([=]() -> void { + image->NotifyProgress(progress, invalidRect, frameCount, + decoderFlags, surfaceFlags); + })); +} + +/* static */ void +IDecodingTask::NotifyDecodeComplete(NotNull aImage, + NotNull aDecoder) +{ + MOZ_ASSERT(aDecoder->HasError() || !aDecoder->InFrame(), + "Decode complete in the middle of a frame?"); + + // Capture the decoder's state. + DecoderFinalStatus finalStatus = aDecoder->FinalStatus(); + ImageMetadata metadata = aDecoder->GetImageMetadata(); + DecoderTelemetry telemetry = aDecoder->Telemetry(); + Progress progress = aDecoder->TakeProgress(); + IntRect invalidRect = aDecoder->TakeInvalidRect(); + Maybe frameCount = aDecoder->TakeCompleteFrameCount(); + DecoderFlags decoderFlags = aDecoder->GetDecoderFlags(); + SurfaceFlags surfaceFlags = aDecoder->GetSurfaceFlags(); + + // Synchronously notify if we can. + if (NS_IsMainThread() && !(decoderFlags & DecoderFlags::ASYNC_NOTIFY)) { + aImage->NotifyDecodeComplete(finalStatus, metadata, telemetry, progress, + invalidRect, frameCount, decoderFlags, + surfaceFlags); + return; + } + + // We're forced to notify asynchronously. + NotNull> image = aImage; + NS_DispatchToMainThread(NS_NewRunnableFunction([=]() -> void { + image->NotifyDecodeComplete(finalStatus, metadata, telemetry, progress, + invalidRect, frameCount, decoderFlags, + surfaceFlags); + })); +} + + +/////////////////////////////////////////////////////////////////////////////// +// IDecodingTask implementation. +/////////////////////////////////////////////////////////////////////////////// + +void +IDecodingTask::Resume() +{ + DecodePool::Singleton()->AsyncRun(this); +} + + +/////////////////////////////////////////////////////////////////////////////// +// MetadataDecodingTask implementation. +/////////////////////////////////////////////////////////////////////////////// + +MetadataDecodingTask::MetadataDecodingTask(NotNull aDecoder) + : mMutex("mozilla::image::MetadataDecodingTask") + , mDecoder(aDecoder) +{ + MOZ_ASSERT(mDecoder->IsMetadataDecode(), + "Use DecodingTask for non-metadata decodes"); +} + +void +MetadataDecodingTask::Run() +{ + MutexAutoLock lock(mMutex); + + LexerResult result = mDecoder->Decode(WrapNotNull(this)); + + if (result.is()) { + NotifyDecodeComplete(mDecoder->GetImage(), mDecoder); + return; // We're done. + } + + if (result == LexerResult(Yield::NEED_MORE_DATA)) { + // We can't make any more progress right now. We also don't want to report + // any progress, because it's important that metadata decode results are + // delivered atomically. The decoder itself will ensure that we get + // reenqueued when more data is available; just return for now. + return; + } + + MOZ_ASSERT_UNREACHABLE("Metadata decode yielded for an unexpected reason"); +} + + +/////////////////////////////////////////////////////////////////////////////// +// AnonymousDecodingTask implementation. +/////////////////////////////////////////////////////////////////////////////// + +AnonymousDecodingTask::AnonymousDecodingTask(NotNull aDecoder) + : mDecoder(aDecoder) +{ } + +void +AnonymousDecodingTask::Run() +{ + while (true) { + LexerResult result = mDecoder->Decode(WrapNotNull(this)); + + if (result.is()) { + return; // We're done. + } + + if (result == LexerResult(Yield::NEED_MORE_DATA)) { + // We can't make any more progress right now. Let the caller decide how to + // handle it. + return; + } + + // Right now we don't do anything special for other kinds of yields, so just + // keep working. + MOZ_ASSERT(result.is()); + } +} + +} // namespace image +} // namespace mozilla diff --git a/image/IDecodingTask.h b/image/IDecodingTask.h new file mode 100644 index 000000000..196ce5fdc --- /dev/null +++ b/image/IDecodingTask.h @@ -0,0 +1,126 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * An interface for tasks which can execute on the ImageLib DecodePool, and + * various implementations. + */ + +#ifndef mozilla_image_IDecodingTask_h +#define mozilla_image_IDecodingTask_h + +#include "mozilla/NotNull.h" +#include "mozilla/RefPtr.h" + +#include "imgFrame.h" +#include "SourceBuffer.h" + +namespace mozilla { +namespace image { + +class Decoder; +class RasterImage; + +/// A priority hint that DecodePool can use when scheduling an IDecodingTask. +enum class TaskPriority : uint8_t +{ + eLow, + eHigh +}; + +/** + * An interface for tasks which can execute on the ImageLib DecodePool. + */ +class IDecodingTask : public IResumable +{ +public: + /// Run the task. + virtual void Run() = 0; + + /// @return true if, given the option, this task prefers to run synchronously. + virtual bool ShouldPreferSyncRun() const = 0; + + /// @return a priority hint that DecodePool can use when scheduling this task. + virtual TaskPriority Priority() const = 0; + + /// A default implementation of IResumable which resubmits the task to the + /// DecodePool. Subclasses can override this if they need different behavior. + void Resume() override; + +protected: + /// Notify @aImage of @aDecoder's progress. + static void NotifyProgress(NotNull aImage, + NotNull aDecoder); + + /// Notify @aImage that @aDecoder has finished. + static void NotifyDecodeComplete(NotNull aImage, + NotNull aDecoder); + + virtual ~IDecodingTask() { } +}; + + +/** + * An IDecodingTask implementation for metadata decodes of images. + */ +class MetadataDecodingTask final : public IDecodingTask +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MetadataDecodingTask, override) + + explicit MetadataDecodingTask(NotNull aDecoder); + + void Run() override; + + // Metadata decodes are very fast (since they only need to examine an image's + // header) so there's no reason to refuse to run them synchronously if the + // caller will allow us to. + bool ShouldPreferSyncRun() const override { return true; } + + // Metadata decodes run at the highest priority because they block layout and + // page load. + TaskPriority Priority() const override { return TaskPriority::eHigh; } + +private: + virtual ~MetadataDecodingTask() { } + + /// Mutex protecting access to mDecoder. + Mutex mMutex; + + NotNull> mDecoder; +}; + + +/** + * An IDecodingTask implementation for anonymous decoders - that is, decoders + * with no associated Image object. + */ +class AnonymousDecodingTask final : public IDecodingTask +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AnonymousDecodingTask, override) + + explicit AnonymousDecodingTask(NotNull aDecoder); + + void Run() override; + + bool ShouldPreferSyncRun() const override { return true; } + TaskPriority Priority() const override { return TaskPriority::eLow; } + + // Anonymous decoders normally get all their data at once. We have tests where + // they don't; in these situations, the test re-runs them manually. So no + // matter what, we don't want to resume by posting a task to the DecodePool. + void Resume() override { } + +private: + virtual ~AnonymousDecodingTask() { } + + NotNull> mDecoder; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_IDecodingTask_h diff --git a/image/IProgressObserver.h b/image/IProgressObserver.h new file mode 100644 index 000000000..ba2f1e9ac --- /dev/null +++ b/image/IProgressObserver.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_IProgressObserver_h +#define mozilla_image_IProgressObserver_h + +#include "mozilla/WeakPtr.h" +#include "nsISupports.h" +#include "nsRect.h" + +namespace mozilla { +namespace image { + +/** + * An interface for observing changes to image state, as reported by + * ProgressTracker. + * + * This is the ImageLib-internal version of imgINotificationObserver, + * essentially, with implementation details that code outside of ImageLib + * shouldn't see. + * + * XXX(seth): It's preferable to avoid adding anything to this interface if + * possible. In the long term, it would be ideal to get to a place where we can + * just use the imgINotificationObserver interface internally as well. + */ +class IProgressObserver : public SupportsWeakPtr +{ +public: + MOZ_DECLARE_WEAKREFERENCE_TYPENAME(IProgressObserver) + + // Subclasses may or may not be XPCOM classes, so we just require that they + // implement AddRef and Release. + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0; + NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0; + + // imgINotificationObserver methods: + virtual void Notify(int32_t aType, const nsIntRect* aRect = nullptr) = 0; + virtual void OnLoadComplete(bool aLastPart) = 0; + + // imgIOnloadBlocker methods: + virtual void BlockOnload() = 0; + virtual void UnblockOnload() = 0; + + // Other, internal-only methods: + virtual void SetHasImage() = 0; + virtual bool NotificationsDeferred() const = 0; + virtual void SetNotificationsDeferred(bool aDeferNotifications) = 0; + +protected: + virtual ~IProgressObserver() { } +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_IProgressObserver_h diff --git a/image/ISurfaceProvider.h b/image/ISurfaceProvider.h new file mode 100644 index 000000000..80e1f8e9b --- /dev/null +++ b/image/ISurfaceProvider.h @@ -0,0 +1,290 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * An interface for objects which can either store a surface or dynamically + * generate one, and various implementations. + */ + +#ifndef mozilla_image_ISurfaceProvider_h +#define mozilla_image_ISurfaceProvider_h + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/NotNull.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Variant.h" +#include "mozilla/gfx/2D.h" + +#include "imgFrame.h" +#include "SurfaceCache.h" + +namespace mozilla { +namespace image { + +class CachedSurface; +class DrawableSurface; + +/** + * An interface for objects which can either store a surface or dynamically + * generate one. + */ +class ISurfaceProvider +{ +public: + // Subclasses may or may not be XPCOM classes, so we just require that they + // implement AddRef and Release. + NS_IMETHOD_(MozExternalRefCountType) AddRef() = 0; + NS_IMETHOD_(MozExternalRefCountType) Release() = 0; + + /// @return key data used for identifying which image this ISurfaceProvider is + /// associated with in the surface cache. + ImageKey GetImageKey() const { return mImageKey; } + + /// @return key data used to uniquely identify this ISurfaceProvider's cache + /// entry in the surface cache. + const SurfaceKey& GetSurfaceKey() const { return mSurfaceKey; } + + /// @return a (potentially lazily computed) drawable reference to a surface. + virtual DrawableSurface Surface(); + + /// @return true if DrawableRef() will return a completely decoded surface. + virtual bool IsFinished() const = 0; + + /// @return the number of bytes of memory this ISurfaceProvider is expected to + /// require. Optimizations may result in lower real memory usage. Trivial + /// overhead is ignored. Because this value is used in bookkeeping, it's + /// important that it be constant over the lifetime of this object. + virtual size_t LogicalSizeInBytes() const = 0; + + /// @return the actual number of bytes of memory this ISurfaceProvider is + /// using. May vary over the lifetime of the ISurfaceProvider. The default + /// implementation is appropriate for static ISurfaceProviders. + virtual void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + size_t& aHeapSizeOut, + size_t& aNonHeapSizeOut) + { + DrawableFrameRef ref = DrawableRef(/* aFrame = */ 0); + if (!ref) { + return; + } + + ref->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut, aNonHeapSizeOut); + } + + /// @return the availability state of this ISurfaceProvider, which indicates + /// whether DrawableRef() could successfully return a surface. Should only be + /// called from SurfaceCache code as it relies on SurfaceCache for + /// synchronization. + AvailabilityState& Availability() { return mAvailability; } + const AvailabilityState& Availability() const { return mAvailability; } + +protected: + ISurfaceProvider(const ImageKey aImageKey, + const SurfaceKey& aSurfaceKey, + AvailabilityState aAvailability) + : mImageKey(aImageKey) + , mSurfaceKey(aSurfaceKey) + , mAvailability(aAvailability) + { + MOZ_ASSERT(aImageKey, "Must have a valid image key"); + } + + virtual ~ISurfaceProvider() { } + + /// @return an eagerly computed drawable reference to a surface. For + /// dynamically generated animation surfaces, @aFrame specifies the 0-based + /// index of the desired frame. + virtual DrawableFrameRef DrawableRef(size_t aFrame) = 0; + + /// @return true if this ISurfaceProvider is locked. (@see SetLocked()) + /// Should only be called from SurfaceCache code as it relies on SurfaceCache + /// for synchronization. + virtual bool IsLocked() const = 0; + + /// If @aLocked is true, hint that this ISurfaceProvider is in use and it + /// should avoid releasing its resources. Should only be called from + /// SurfaceCache code as it relies on SurfaceCache for synchronization. + virtual void SetLocked(bool aLocked) = 0; + +private: + friend class CachedSurface; + friend class DrawableSurface; + + const ImageKey mImageKey; + const SurfaceKey mSurfaceKey; + AvailabilityState mAvailability; +}; + + +/** + * A reference to a surface (stored in an imgFrame) that holds the surface in + * memory, guaranteeing that it can be drawn. If you have a DrawableSurface + * |surf| and |if (surf)| returns true, then calls to |surf->Draw()| and + * |surf->GetSourceSurface()| are guaranteed to succeed. + * + * Note that the surface may be computed lazily, so a DrawableSurface should not + * be dereferenced (i.e., operator->() should not be called) until you're + * sure that you want to draw it. + */ +class MOZ_STACK_CLASS DrawableSurface final +{ +public: + DrawableSurface() : mHaveSurface(false) { } + + explicit DrawableSurface(DrawableFrameRef&& aDrawableRef) + : mDrawableRef(Move(aDrawableRef)) + , mHaveSurface(bool(mDrawableRef)) + { } + + explicit DrawableSurface(NotNull aProvider) + : mProvider(aProvider) + , mHaveSurface(true) + { } + + DrawableSurface(DrawableSurface&& aOther) + : mDrawableRef(Move(aOther.mDrawableRef)) + , mProvider(Move(aOther.mProvider)) + , mHaveSurface(aOther.mHaveSurface) + { + aOther.mHaveSurface = false; + } + + DrawableSurface& operator=(DrawableSurface&& aOther) + { + MOZ_ASSERT(this != &aOther, "Self-moves are prohibited"); + mDrawableRef = Move(aOther.mDrawableRef); + mProvider = Move(aOther.mProvider); + mHaveSurface = aOther.mHaveSurface; + aOther.mHaveSurface = false; + return *this; + } + + /** + * If this DrawableSurface is dynamically generated from an animation, attempt + * to seek to frame @aFrame, where @aFrame is a 0-based index into the frames + * of the animation. Otherwise, nothing will blow up at runtime, but we assert + * in debug builds, since calling this in an unexpected situation probably + * indicates a bug. + * + * @return a successful result if we could obtain frame @aFrame. Note that + * |mHaveSurface| being true means that we're guaranteed to have *some* frame, + * so the caller can dereference this DrawableSurface even if Seek() fails, + * but while nothing will blow up, the frame won't be the one they expect. + */ + nsresult Seek(size_t aFrame) + { + MOZ_ASSERT(mHaveSurface, "Trying to seek an empty DrawableSurface?"); + + if (!mProvider) { + MOZ_ASSERT_UNREACHABLE("Trying to seek a static DrawableSurface?"); + return NS_ERROR_FAILURE; + } + + mDrawableRef = mProvider->DrawableRef(aFrame); + + return mDrawableRef ? NS_OK : NS_ERROR_FAILURE; + } + + explicit operator bool() const { return mHaveSurface; } + imgFrame* operator->() { return DrawableRef().get(); } + +private: + DrawableSurface(const DrawableSurface& aOther) = delete; + DrawableSurface& operator=(const DrawableSurface& aOther) = delete; + + DrawableFrameRef& DrawableRef() + { + MOZ_ASSERT(mHaveSurface); + + // If we weren't created with a DrawableFrameRef directly, we should've been + // created with an ISurfaceProvider which can give us one. Note that if + // Seek() has been called, we'll already have a DrawableFrameRef, so we + // won't need to get one here. + if (!mDrawableRef) { + MOZ_ASSERT(mProvider); + mDrawableRef = mProvider->DrawableRef(/* aFrame = */ 0); + } + + MOZ_ASSERT(mDrawableRef); + return mDrawableRef; + } + + DrawableFrameRef mDrawableRef; + RefPtr mProvider; + bool mHaveSurface; +}; + + +// Surface() is implemented here so that DrawableSurface's definition is +// visible. This default implementation eagerly obtains a DrawableFrameRef for +// the first frame and is intended for static ISurfaceProviders. +inline DrawableSurface +ISurfaceProvider::Surface() +{ + return DrawableSurface(DrawableRef(/* aFrame = */ 0)); +} + + +/** + * An ISurfaceProvider that stores a single surface. + */ +class SimpleSurfaceProvider final : public ISurfaceProvider +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SimpleSurfaceProvider, override) + + SimpleSurfaceProvider(const ImageKey aImageKey, + const SurfaceKey& aSurfaceKey, + NotNull aSurface) + : ISurfaceProvider(aImageKey, aSurfaceKey, + AvailabilityState::StartAvailable()) + , mSurface(aSurface) + { + MOZ_ASSERT(aSurfaceKey.Size() == mSurface->GetSize()); + } + + bool IsFinished() const override { return mSurface->IsFinished(); } + + size_t LogicalSizeInBytes() const override + { + gfx::IntSize size = mSurface->GetSize(); + return size.width * size.height * mSurface->GetBytesPerPixel(); + } + +protected: + DrawableFrameRef DrawableRef(size_t aFrame) override + { + MOZ_ASSERT(aFrame == 0, + "Requesting an animation frame from a SimpleSurfaceProvider?"); + return mSurface->DrawableRef(); + } + + bool IsLocked() const override { return bool(mLockRef); } + + void SetLocked(bool aLocked) override + { + if (aLocked == IsLocked()) { + return; // Nothing changed. + } + + // If we're locked, hold a DrawableFrameRef to |mSurface|, which will keep + // any volatile buffer it owns in memory. + mLockRef = aLocked ? mSurface->DrawableRef() + : DrawableFrameRef(); + } + +private: + virtual ~SimpleSurfaceProvider() { } + + NotNull> mSurface; + DrawableFrameRef mLockRef; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_ISurfaceProvider_h diff --git a/image/Image.cpp b/image/Image.cpp new file mode 100644 index 000000000..b757a60f7 --- /dev/null +++ b/image/Image.cpp @@ -0,0 +1,150 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Image.h" +#include "nsRefreshDriver.h" +#include "mozilla/TimeStamp.h" + +namespace mozilla { +namespace image { + +/////////////////////////////////////////////////////////////////////////////// +// Memory Reporting +/////////////////////////////////////////////////////////////////////////////// + +ImageMemoryCounter::ImageMemoryCounter(Image* aImage, + MallocSizeOf aMallocSizeOf, + bool aIsUsed) + : mIsUsed(aIsUsed) +{ + MOZ_ASSERT(aImage); + + // Extract metadata about the image. + RefPtr imageURL(aImage->GetURI()); + if (imageURL) { + imageURL->GetSpec(mURI); + } + + int32_t width = 0; + int32_t height = 0; + aImage->GetWidth(&width); + aImage->GetHeight(&height); + mIntrinsicSize.SizeTo(width, height); + + mType = aImage->GetType(); + + // Populate memory counters for source and decoded data. + mValues.SetSource(aImage->SizeOfSourceWithComputedFallback(aMallocSizeOf)); + aImage->CollectSizeOfSurfaces(mSurfaces, aMallocSizeOf); + + // Compute totals. + for (const SurfaceMemoryCounter& surfaceCounter : mSurfaces) { + mValues += surfaceCounter.Values(); + } +} + + +/////////////////////////////////////////////////////////////////////////////// +// Image Base Types +/////////////////////////////////////////////////////////////////////////////// + +// Constructor +ImageResource::ImageResource(ImageURL* aURI) : + mURI(aURI), + mInnerWindowId(0), + mAnimationConsumers(0), + mAnimationMode(kNormalAnimMode), + mInitialized(false), + mAnimating(false), + mError(false) +{ } + +ImageResource::~ImageResource() +{ + // Ask our ProgressTracker to drop its weak reference to us. + mProgressTracker->ResetImage(); +} + +void +ImageResource::IncrementAnimationConsumers() +{ + MOZ_ASSERT(NS_IsMainThread(), "Main thread only to encourage serialization " + "with DecrementAnimationConsumers"); + mAnimationConsumers++; +} + +void +ImageResource::DecrementAnimationConsumers() +{ + MOZ_ASSERT(NS_IsMainThread(), "Main thread only to encourage serialization " + "with IncrementAnimationConsumers"); + MOZ_ASSERT(mAnimationConsumers >= 1, + "Invalid no. of animation consumers!"); + mAnimationConsumers--; +} + +nsresult +ImageResource::GetAnimationModeInternal(uint16_t* aAnimationMode) +{ + if (mError) { + return NS_ERROR_FAILURE; + } + + NS_ENSURE_ARG_POINTER(aAnimationMode); + + *aAnimationMode = mAnimationMode; + return NS_OK; +} + +nsresult +ImageResource::SetAnimationModeInternal(uint16_t aAnimationMode) +{ + if (mError) { + return NS_ERROR_FAILURE; + } + + NS_ASSERTION(aAnimationMode == kNormalAnimMode || + aAnimationMode == kDontAnimMode || + aAnimationMode == kLoopOnceAnimMode, + "Wrong Animation Mode is being set!"); + + mAnimationMode = aAnimationMode; + + return NS_OK; +} + +bool +ImageResource::HadRecentRefresh(const TimeStamp& aTime) +{ + // Our threshold for "recent" is 1/2 of the default refresh-driver interval. + // This ensures that we allow for frame rates at least as fast as the + // refresh driver's default rate. + static TimeDuration recentThreshold = + TimeDuration::FromMilliseconds(nsRefreshDriver::DefaultInterval() / 2.0); + + if (!mLastRefreshTime.IsNull() && + aTime - mLastRefreshTime < recentThreshold) { + return true; + } + + // else, we can proceed with a refresh. + // But first, update our last refresh time: + mLastRefreshTime = aTime; + return false; +} + +void +ImageResource::EvaluateAnimation() +{ + if (!mAnimating && ShouldAnimate()) { + nsresult rv = StartAnimation(); + mAnimating = NS_SUCCEEDED(rv); + } else if (mAnimating && !ShouldAnimate()) { + StopAnimation(); + } +} + +} // namespace image +} // namespace mozilla diff --git a/image/Image.h b/image/Image.h new file mode 100644 index 000000000..bcabd1cc7 --- /dev/null +++ b/image/Image.h @@ -0,0 +1,323 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_Image_h +#define mozilla_image_Image_h + +#include "mozilla/MemoryReporting.h" +#include "mozilla/TimeStamp.h" +#include "gfx2DGlue.h" +#include "imgIContainer.h" +#include "ImageURL.h" +#include "nsStringFwd.h" +#include "ProgressTracker.h" +#include "SurfaceCache.h" + +class nsIRequest; +class nsIInputStream; + +namespace mozilla { +namespace image { + +class Image; + +/////////////////////////////////////////////////////////////////////////////// +// Memory Reporting +/////////////////////////////////////////////////////////////////////////////// + +struct MemoryCounter +{ + MemoryCounter() + : mSource(0) + , mDecodedHeap(0) + , mDecodedNonHeap(0) + { } + + void SetSource(size_t aCount) { mSource = aCount; } + size_t Source() const { return mSource; } + void SetDecodedHeap(size_t aCount) { mDecodedHeap = aCount; } + size_t DecodedHeap() const { return mDecodedHeap; } + void SetDecodedNonHeap(size_t aCount) { mDecodedNonHeap = aCount; } + size_t DecodedNonHeap() const { return mDecodedNonHeap; } + + MemoryCounter& operator+=(const MemoryCounter& aOther) + { + mSource += aOther.mSource; + mDecodedHeap += aOther.mDecodedHeap; + mDecodedNonHeap += aOther.mDecodedNonHeap; + return *this; + } + +private: + size_t mSource; + size_t mDecodedHeap; + size_t mDecodedNonHeap; +}; + +enum class SurfaceMemoryCounterType +{ + NORMAL, + COMPOSITING, + COMPOSITING_PREV +}; + +struct SurfaceMemoryCounter +{ + SurfaceMemoryCounter(const SurfaceKey& aKey, + bool aIsLocked, + SurfaceMemoryCounterType aType = + SurfaceMemoryCounterType::NORMAL) + : mKey(aKey) + , mType(aType) + , mIsLocked(aIsLocked) + { } + + const SurfaceKey& Key() const { return mKey; } + MemoryCounter& Values() { return mValues; } + const MemoryCounter& Values() const { return mValues; } + SurfaceMemoryCounterType Type() const { return mType; } + bool IsLocked() const { return mIsLocked; } + +private: + const SurfaceKey mKey; + MemoryCounter mValues; + const SurfaceMemoryCounterType mType; + const bool mIsLocked; +}; + +struct ImageMemoryCounter +{ + ImageMemoryCounter(Image* aImage, + MallocSizeOf aMallocSizeOf, + bool aIsUsed); + + nsCString& URI() { return mURI; } + const nsCString& URI() const { return mURI; } + const nsTArray& Surfaces() const { return mSurfaces; } + const gfx::IntSize IntrinsicSize() const { return mIntrinsicSize; } + const MemoryCounter& Values() const { return mValues; } + uint16_t Type() const { return mType; } + bool IsUsed() const { return mIsUsed; } + + bool IsNotable() const + { + const size_t NotableThreshold = 16 * 1024; + size_t total = mValues.Source() + mValues.DecodedHeap() + + mValues.DecodedNonHeap(); + return total >= NotableThreshold; + } + +private: + nsCString mURI; + nsTArray mSurfaces; + gfx::IntSize mIntrinsicSize; + MemoryCounter mValues; + uint16_t mType; + const bool mIsUsed; +}; + + +/////////////////////////////////////////////////////////////////////////////// +// Image Base Types +/////////////////////////////////////////////////////////////////////////////// + +class Image : public imgIContainer +{ +public: + /** + * Flags for Image initialization. + * + * Meanings: + * + * INIT_FLAG_NONE: Lack of flags + * + * INIT_FLAG_DISCARDABLE: The container should be discardable + * + * INIT_FLAG_DECODE_IMMEDIATELY: The container should decode as soon as + * possible, regardless of what our heuristics say. + * + * INIT_FLAG_TRANSIENT: The container is likely to exist for only a short time + * before being destroyed. (For example, containers for + * multipart/x-mixed-replace image parts fall into this category.) If this + * flag is set, INIT_FLAG_DISCARDABLE and INIT_FLAG_DECODE_ONLY_ON_DRAW must + * not be set. + * + * INIT_FLAG_SYNC_LOAD: The container is being loaded synchronously, so + * it should avoid relying on async workers to get the container ready. + */ + static const uint32_t INIT_FLAG_NONE = 0x0; + static const uint32_t INIT_FLAG_DISCARDABLE = 0x1; + static const uint32_t INIT_FLAG_DECODE_IMMEDIATELY = 0x2; + static const uint32_t INIT_FLAG_TRANSIENT = 0x4; + static const uint32_t INIT_FLAG_SYNC_LOAD = 0x8; + + virtual already_AddRefed GetProgressTracker() = 0; + virtual void SetProgressTracker(ProgressTracker* aProgressTracker) {} + + /** + * The size, in bytes, occupied by the compressed source data of the image. + * If MallocSizeOf does not work on this platform, uses a fallback approach to + * ensure that something reasonable is always returned. + */ + virtual size_t + SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const = 0; + + /** + * Collect an accounting of the memory occupied by the image's surfaces (which + * together make up its decoded data). Each surface is recorded as a separate + * SurfaceMemoryCounter, stored in @aCounters. + */ + virtual void CollectSizeOfSurfaces(nsTArray& aCounters, + MallocSizeOf aMallocSizeOf) const = 0; + + virtual void IncrementAnimationConsumers() = 0; + virtual void DecrementAnimationConsumers() = 0; +#ifdef DEBUG + virtual uint32_t GetAnimationConsumers() = 0; +#endif + + /** + * Called from OnDataAvailable when the stream associated with the image has + * received new image data. The arguments are the same as OnDataAvailable's, + * but by separating this functionality into a different method we don't + * interfere with subclasses which wish to implement nsIStreamListener. + * + * Images should not do anything that could send out notifications until they + * have received their first OnImageDataAvailable notification; in + * particular, this means that instantiating decoders should be deferred + * until OnImageDataAvailable is called. + */ + virtual nsresult OnImageDataAvailable(nsIRequest* aRequest, + nsISupports* aContext, + nsIInputStream* aInStr, + uint64_t aSourceOffset, + uint32_t aCount) = 0; + + /** + * Called from OnStopRequest when the image's underlying request completes. + * + * @param aRequest The completed request. + * @param aContext Context from Necko's OnStopRequest. + * @param aStatus A success or failure code. + * @param aLastPart Whether this is the final part of the underlying request. + */ + virtual nsresult OnImageDataComplete(nsIRequest* aRequest, + nsISupports* aContext, + nsresult aStatus, + bool aLastPart) = 0; + + /** + * Called when the SurfaceCache discards a surface belonging to this image. + */ + virtual void OnSurfaceDiscarded() = 0; + + virtual void SetInnerWindowID(uint64_t aInnerWindowId) = 0; + virtual uint64_t InnerWindowID() const = 0; + + virtual bool HasError() = 0; + virtual void SetHasError() = 0; + + virtual ImageURL* GetURI() = 0; + + virtual void ReportUseCounters() { } +}; + +class ImageResource : public Image +{ +public: + already_AddRefed GetProgressTracker() override + { + RefPtr progressTracker = mProgressTracker; + MOZ_ASSERT(progressTracker); + return progressTracker.forget(); + } + + void SetProgressTracker( + ProgressTracker* aProgressTracker) override final + { + MOZ_ASSERT(aProgressTracker); + MOZ_ASSERT(!mProgressTracker); + mProgressTracker = aProgressTracker; + } + + virtual void IncrementAnimationConsumers() override; + virtual void DecrementAnimationConsumers() override; +#ifdef DEBUG + virtual uint32_t GetAnimationConsumers() override + { + return mAnimationConsumers; + } +#endif + + virtual void OnSurfaceDiscarded() override { } + + virtual void SetInnerWindowID(uint64_t aInnerWindowId) override + { + mInnerWindowId = aInnerWindowId; + } + virtual uint64_t InnerWindowID() const override { return mInnerWindowId; } + + virtual bool HasError() override { return mError; } + virtual void SetHasError() override { mError = true; } + + /* + * Returns a non-AddRefed pointer to the URI associated with this image. + * Illegal to use off-main-thread. + */ + virtual ImageURL* GetURI() override { return mURI.get(); } + +protected: + explicit ImageResource(ImageURL* aURI); + ~ImageResource(); + + // Shared functionality for implementors of imgIContainer. Every + // implementation of attribute animationMode should forward here. + nsresult GetAnimationModeInternal(uint16_t* aAnimationMode); + nsresult SetAnimationModeInternal(uint16_t aAnimationMode); + + /** + * Helper for RequestRefresh. + * + * If we've had a "recent" refresh (i.e. if this image is being used in + * multiple documents & some other document *just* called RequestRefresh() on + * this image with a timestamp close to aTime), this method returns true. + * + * Otherwise, this method updates mLastRefreshTime to aTime & returns false. + */ + bool HadRecentRefresh(const TimeStamp& aTime); + + /** + * Decides whether animation should or should not be happening, + * and makes sure the right thing is being done. + */ + virtual void EvaluateAnimation(); + + /** + * Extended by child classes, if they have additional + * conditions for being able to animate. + */ + virtual bool ShouldAnimate() { + return mAnimationConsumers > 0 && mAnimationMode != kDontAnimMode; + } + + virtual nsresult StartAnimation() = 0; + virtual nsresult StopAnimation() = 0; + + // Member data shared by all implementations of this abstract class + RefPtr mProgressTracker; + RefPtr mURI; + TimeStamp mLastRefreshTime; + uint64_t mInnerWindowId; + uint32_t mAnimationConsumers; + uint16_t mAnimationMode; // Enum values in imgIContainer + bool mInitialized:1; // Have we been initalized? + bool mAnimating:1; // Are we currently animating? + bool mError:1; // Error handling +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_Image_h diff --git a/image/ImageCacheKey.cpp b/image/ImageCacheKey.cpp new file mode 100644 index 000000000..da194fa55 --- /dev/null +++ b/image/ImageCacheKey.cpp @@ -0,0 +1,167 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageCacheKey.h" + +#include "mozilla/Move.h" +#include "File.h" +#include "ImageURL.h" +#include "nsHostObjectProtocolHandler.h" +#include "nsString.h" +#include "mozilla/dom/workers/ServiceWorkerManager.h" +#include "nsIDocument.h" +#include "nsPrintfCString.h" + +namespace mozilla { + +using namespace dom; + +namespace image { + +bool +URISchemeIs(ImageURL* aURI, const char* aScheme) +{ + bool schemeMatches = false; + if (NS_WARN_IF(NS_FAILED(aURI->SchemeIs(aScheme, &schemeMatches)))) { + return false; + } + return schemeMatches; +} + +static Maybe +BlobSerial(ImageURL* aURI) +{ + nsAutoCString spec; + aURI->GetSpec(spec); + + RefPtr blob; + if (NS_SUCCEEDED(NS_GetBlobForBlobURISpec(spec, getter_AddRefs(blob))) && + blob) { + return Some(blob->GetSerialNumber()); + } + + return Nothing(); +} + +ImageCacheKey::ImageCacheKey(nsIURI* aURI, + const PrincipalOriginAttributes& aAttrs, + nsIDocument* aDocument, + nsresult& aRv) + : mURI(new ImageURL(aURI, aRv)) + , mOriginAttributes(aAttrs) + , mControlledDocument(GetControlledDocumentToken(aDocument)) + , mIsChrome(URISchemeIs(mURI, "chrome")) +{ + NS_ENSURE_SUCCESS_VOID(aRv); + + MOZ_ASSERT(NS_IsMainThread()); + + if (URISchemeIs(mURI, "blob")) { + mBlobSerial = BlobSerial(mURI); + } + + mHash = ComputeHash(mURI, mBlobSerial, mOriginAttributes, mControlledDocument); +} + +ImageCacheKey::ImageCacheKey(ImageURL* aURI, + const PrincipalOriginAttributes& aAttrs, + nsIDocument* aDocument) + : mURI(aURI) + , mOriginAttributes(aAttrs) + , mControlledDocument(GetControlledDocumentToken(aDocument)) + , mIsChrome(URISchemeIs(mURI, "chrome")) +{ + MOZ_ASSERT(aURI); + + if (URISchemeIs(mURI, "blob")) { + mBlobSerial = BlobSerial(mURI); + } + + mHash = ComputeHash(mURI, mBlobSerial, mOriginAttributes, mControlledDocument); +} + +ImageCacheKey::ImageCacheKey(const ImageCacheKey& aOther) + : mURI(aOther.mURI) + , mBlobSerial(aOther.mBlobSerial) + , mOriginAttributes(aOther.mOriginAttributes) + , mControlledDocument(aOther.mControlledDocument) + , mHash(aOther.mHash) + , mIsChrome(aOther.mIsChrome) +{ } + +ImageCacheKey::ImageCacheKey(ImageCacheKey&& aOther) + : mURI(Move(aOther.mURI)) + , mBlobSerial(Move(aOther.mBlobSerial)) + , mOriginAttributes(aOther.mOriginAttributes) + , mControlledDocument(aOther.mControlledDocument) + , mHash(aOther.mHash) + , mIsChrome(aOther.mIsChrome) +{ } + +bool +ImageCacheKey::operator==(const ImageCacheKey& aOther) const +{ + // Don't share the image cache between a controlled document and anything else. + if (mControlledDocument != aOther.mControlledDocument) { + return false; + } + // The origin attributes always have to match. + if (mOriginAttributes != aOther.mOriginAttributes) { + return false; + } + if (mBlobSerial || aOther.mBlobSerial) { + // If at least one of us has a blob serial, just compare the blob serial and + // the ref portion of the URIs. + return mBlobSerial == aOther.mBlobSerial && + mURI->HasSameRef(*aOther.mURI); + } + + // For non-blob URIs, compare the URIs. + return *mURI == *aOther.mURI; +} + +const char* +ImageCacheKey::Spec() const +{ + return mURI->Spec(); +} + +/* static */ uint32_t +ImageCacheKey::ComputeHash(ImageURL* aURI, + const Maybe& aBlobSerial, + const PrincipalOriginAttributes& aAttrs, + void* aControlledDocument) +{ + // Since we frequently call Hash() several times in a row on the same + // ImageCacheKey, as an optimization we compute our hash once and store it. + + nsPrintfCString ptr("%p", aControlledDocument); + nsAutoCString suffix; + aAttrs.CreateSuffix(suffix); + + return AddToHash(0, aURI->ComputeHash(aBlobSerial), + HashString(suffix), HashString(ptr)); +} + +/* static */ void* +ImageCacheKey::GetControlledDocumentToken(nsIDocument* aDocument) +{ + // For non-controlled documents, we just return null. For controlled + // documents, we cast the pointer into a void* to avoid dereferencing + // it (since we only use it for comparisons), and return it. + void* pointer = nullptr; + using dom::workers::ServiceWorkerManager; + RefPtr swm = ServiceWorkerManager::GetInstance(); + if (aDocument && swm) { + ErrorResult rv; + if (swm->IsControlled(aDocument, rv)) { + pointer = aDocument; + } + } + return pointer; +} + +} // namespace image +} // namespace mozilla diff --git a/image/ImageCacheKey.h b/image/ImageCacheKey.h new file mode 100644 index 000000000..dd7340703 --- /dev/null +++ b/image/ImageCacheKey.h @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * ImageCacheKey is the key type for the image cache (see imgLoader.h). + */ + +#ifndef mozilla_image_src_ImageCacheKey_h +#define mozilla_image_src_ImageCacheKey_h + +#include "mozilla/BasePrincipal.h" +#include "mozilla/Maybe.h" +#include "mozilla/RefPtr.h" + +class nsIDocument; +class nsIURI; + +namespace mozilla { +namespace image { + +class ImageURL; + +/** + * An ImageLib cache entry key. + * + * We key the cache on the initial URI (before any redirects), with some + * canonicalization applied. See ComputeHash() for the details. + * Controlled documents do not share their cache entries with + * non-controlled documents, or other controlled documents. + */ +class ImageCacheKey final +{ +public: + ImageCacheKey(nsIURI* aURI, const PrincipalOriginAttributes& aAttrs, + nsIDocument* aDocument, nsresult& aRv); + ImageCacheKey(ImageURL* aURI, const PrincipalOriginAttributes& aAttrs, + nsIDocument* aDocument); + + ImageCacheKey(const ImageCacheKey& aOther); + ImageCacheKey(ImageCacheKey&& aOther); + + bool operator==(const ImageCacheKey& aOther) const; + uint32_t Hash() const { return mHash; } + + /// A weak pointer to the URI spec for this cache entry. For logging only. + const char* Spec() const; + + /// Is this cache entry for a chrome image? + bool IsChrome() const { return mIsChrome; } + + /// A token indicating which service worker controlled document this entry + /// belongs to, if any. + void* ControlledDocument() const { return mControlledDocument; } + +private: + static uint32_t ComputeHash(ImageURL* aURI, + const Maybe& aBlobSerial, + const PrincipalOriginAttributes& aAttrs, + void* aControlledDocument); + static void* GetControlledDocumentToken(nsIDocument* aDocument); + + RefPtr mURI; + Maybe mBlobSerial; + PrincipalOriginAttributes mOriginAttributes; + void* mControlledDocument; + uint32_t mHash; + bool mIsChrome; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_src_ImageCacheKey_h diff --git a/image/ImageFactory.cpp b/image/ImageFactory.cpp new file mode 100644 index 000000000..428be1424 --- /dev/null +++ b/image/ImageFactory.cpp @@ -0,0 +1,288 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageFactory.h" + +#include + +#include "mozilla/Likely.h" + +#include "nsIHttpChannel.h" +#include "nsIFileChannel.h" +#include "nsIFile.h" +#include "nsMimeTypes.h" +#include "nsIRequest.h" + +#include "MultipartImage.h" +#include "RasterImage.h" +#include "VectorImage.h" +#include "Image.h" +#include "nsMediaFragmentURIParser.h" +#include "nsContentUtils.h" +#include "nsIScriptSecurityManager.h" + +#include "gfxPrefs.h" + +namespace mozilla { +namespace image { + +/*static*/ void +ImageFactory::Initialize() +{ } + +static uint32_t +ComputeImageFlags(ImageURL* uri, const nsCString& aMimeType, bool isMultiPart) +{ + nsresult rv; + + // We default to the static globals. + bool isDiscardable = gfxPrefs::ImageMemDiscardable(); + bool doDecodeImmediately = gfxPrefs::ImageDecodeImmediatelyEnabled(); + + // We want UI to be as snappy as possible and not to flicker. Disable + // discarding for chrome URLS. + bool isChrome = false; + rv = uri->SchemeIs("chrome", &isChrome); + if (NS_SUCCEEDED(rv) && isChrome) { + isDiscardable = false; + } + + // We don't want resources like the "loading" icon to be discardable either. + bool isResource = false; + rv = uri->SchemeIs("resource", &isResource); + if (NS_SUCCEEDED(rv) && isResource) { + isDiscardable = false; + } + + // For multipart/x-mixed-replace, we basically want a direct channel to the + // decoder. Disable everything for this case. + if (isMultiPart) { + isDiscardable = false; + } + + // We have all the information we need. + uint32_t imageFlags = Image::INIT_FLAG_NONE; + if (isDiscardable) { + imageFlags |= Image::INIT_FLAG_DISCARDABLE; + } + if (doDecodeImmediately) { + imageFlags |= Image::INIT_FLAG_DECODE_IMMEDIATELY; + } + if (isMultiPart) { + imageFlags |= Image::INIT_FLAG_TRANSIENT; + } + + return imageFlags; +} + +/* static */ already_AddRefed +ImageFactory::CreateImage(nsIRequest* aRequest, + ProgressTracker* aProgressTracker, + const nsCString& aMimeType, + ImageURL* aURI, + bool aIsMultiPart, + uint32_t aInnerWindowId) +{ + MOZ_ASSERT(gfxPrefs::SingletonExists(), + "Pref observers should have been initialized already"); + + // Compute the image's initialization flags. + uint32_t imageFlags = ComputeImageFlags(aURI, aMimeType, aIsMultiPart); + + // Select the type of image to create based on MIME type. + if (aMimeType.EqualsLiteral(IMAGE_SVG_XML)) { + return CreateVectorImage(aRequest, aProgressTracker, aMimeType, + aURI, imageFlags, aInnerWindowId); + } else { + return CreateRasterImage(aRequest, aProgressTracker, aMimeType, + aURI, imageFlags, aInnerWindowId); + } +} + +// Marks an image as having an error before returning it. +template +static already_AddRefed +BadImage(const char* aMessage, RefPtr& aImage) +{ + aImage->SetHasError(); + return aImage.forget(); +} + +/* static */ already_AddRefed +ImageFactory::CreateAnonymousImage(const nsCString& aMimeType) +{ + nsresult rv; + + RefPtr newImage = new RasterImage(); + + RefPtr newTracker = new ProgressTracker(); + newTracker->SetImage(newImage); + newImage->SetProgressTracker(newTracker); + + rv = newImage->Init(aMimeType.get(), Image::INIT_FLAG_SYNC_LOAD); + if (NS_FAILED(rv)) { + return BadImage("RasterImage::Init failed", newImage); + } + + return newImage.forget(); +} + +/* static */ already_AddRefed +ImageFactory::CreateMultipartImage(Image* aFirstPart, + ProgressTracker* aProgressTracker) +{ + MOZ_ASSERT(aFirstPart); + MOZ_ASSERT(aProgressTracker); + + RefPtr newImage = new MultipartImage(aFirstPart); + aProgressTracker->SetImage(newImage); + newImage->SetProgressTracker(aProgressTracker); + + newImage->Init(); + + return newImage.forget(); +} + +int32_t +SaturateToInt32(int64_t val) +{ + if (val > INT_MAX) { + return INT_MAX; + } + if (val < INT_MIN) { + return INT_MIN; + } + + return static_cast(val); +} + +uint32_t +GetContentSize(nsIRequest* aRequest) +{ + nsCOMPtr channel(do_QueryInterface(aRequest)); + if (channel) { + int64_t size; + nsresult rv = channel->GetContentLength(&size); + if (NS_SUCCEEDED(rv)) { + return std::max(SaturateToInt32(size), 0); + } + } + + // Use the file size as a size hint for file channels. + nsCOMPtr fileChannel(do_QueryInterface(aRequest)); + if (fileChannel) { + nsCOMPtr file; + nsresult rv = fileChannel->GetFile(getter_AddRefs(file)); + if (NS_SUCCEEDED(rv)) { + int64_t filesize; + rv = file->GetFileSize(&filesize); + if (NS_SUCCEEDED(rv)) { + return std::max(SaturateToInt32(filesize), 0); + } + } + } + + // Fallback - neither http nor file. We'll use dynamic allocation. + return 0; +} + +/* static */ already_AddRefed +ImageFactory::CreateRasterImage(nsIRequest* aRequest, + ProgressTracker* aProgressTracker, + const nsCString& aMimeType, + ImageURL* aURI, + uint32_t aImageFlags, + uint32_t aInnerWindowId) +{ + MOZ_ASSERT(aProgressTracker); + + nsresult rv; + + RefPtr newImage = new RasterImage(aURI); + aProgressTracker->SetImage(newImage); + newImage->SetProgressTracker(aProgressTracker); + + nsAutoCString ref; + aURI->GetRef(ref); + net::nsMediaFragmentURIParser parser(ref); + if (parser.HasSampleSize()) { + /* Get our principal */ + nsCOMPtr chan(do_QueryInterface(aRequest)); + nsCOMPtr principal; + if (chan) { + nsContentUtils::GetSecurityManager() + ->GetChannelResultPrincipal(chan, getter_AddRefs(principal)); + } + + if ((principal && + principal->GetAppStatus() == nsIPrincipal::APP_STATUS_CERTIFIED) || + gfxPrefs::ImageMozSampleSizeEnabled()) { + newImage->SetRequestedSampleSize(parser.GetSampleSize()); + } + } + + rv = newImage->Init(aMimeType.get(), aImageFlags); + if (NS_FAILED(rv)) { + return BadImage("RasterImage::Init failed", newImage); + } + + newImage->SetInnerWindowID(aInnerWindowId); + + uint32_t len = GetContentSize(aRequest); + + // Pass anything usable on so that the RasterImage can preallocate + // its source buffer. + if (len > 0) { + // Bound by something reasonable + uint32_t sizeHint = std::min(len, 20000000); + rv = newImage->SetSourceSizeHint(sizeHint); + if (NS_FAILED(rv)) { + // Flush memory, try to get some back, and try again. + rv = nsMemory::HeapMinimize(true); + nsresult rv2 = newImage->SetSourceSizeHint(sizeHint); + // If we've still failed at this point, things are going downhill. + if (NS_FAILED(rv) || NS_FAILED(rv2)) { + NS_WARNING("About to hit OOM in imagelib!"); + } + } + } + + return newImage.forget(); +} + +/* static */ already_AddRefed +ImageFactory::CreateVectorImage(nsIRequest* aRequest, + ProgressTracker* aProgressTracker, + const nsCString& aMimeType, + ImageURL* aURI, + uint32_t aImageFlags, + uint32_t aInnerWindowId) +{ + MOZ_ASSERT(aProgressTracker); + + nsresult rv; + + RefPtr newImage = new VectorImage(aURI); + aProgressTracker->SetImage(newImage); + newImage->SetProgressTracker(aProgressTracker); + + rv = newImage->Init(aMimeType.get(), aImageFlags); + if (NS_FAILED(rv)) { + return BadImage("VectorImage::Init failed", newImage); + } + + newImage->SetInnerWindowID(aInnerWindowId); + + rv = newImage->OnStartRequest(aRequest, nullptr); + if (NS_FAILED(rv)) { + return BadImage("VectorImage::OnStartRequest failed", newImage); + } + + return newImage.forget(); +} + +} // namespace image +} // namespace mozilla diff --git a/image/ImageFactory.h b/image/ImageFactory.h new file mode 100644 index 000000000..6c2e0f504 --- /dev/null +++ b/image/ImageFactory.h @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_ImageFactory_h +#define mozilla_image_ImageFactory_h + +#include "nsCOMPtr.h" +#include "nsProxyRelease.h" + +class nsCString; +class nsIRequest; + +namespace mozilla { +namespace image { + +class Image; +class ImageURL; +class MultipartImage; +class ProgressTracker; + +class ImageFactory +{ +public: + /** + * Registers vars with Preferences. Should only be called on the main thread. + */ + static void Initialize(); + + /** + * Creates a new image with the given properties. + * Can be called on or off the main thread. + * + * @param aRequest The associated request. + * @param aProgressTracker A status tracker for the image to use. + * @param aMimeType The mimetype of the image. + * @param aURI The URI of the image. + * @param aIsMultiPart Whether the image is part of a multipart request. + * @param aInnerWindowId The window this image belongs to. + */ + static already_AddRefed CreateImage(nsIRequest* aRequest, + ProgressTracker* aProgressTracker, + const nsCString& aMimeType, + ImageURL* aURI, + bool aIsMultiPart, + uint32_t aInnerWindowId); + /** + * Creates a new image which isn't associated with a URI or loaded through + * the usual image loading mechanism. + * + * @param aMimeType The mimetype of the image. + */ + static already_AddRefed + CreateAnonymousImage(const nsCString& aMimeType); + + /** + * Creates a new multipart/x-mixed-replace image wrapper, and initializes it + * with the first part. Subsequent parts should be passed to the existing + * MultipartImage via MultipartImage::BeginTransitionToPart(). + * + * @param aFirstPart An image containing the first part of the multipart + * stream. + * @param aProgressTracker A progress tracker for the multipart image. + */ + static already_AddRefed + CreateMultipartImage(Image* aFirstPart, ProgressTracker* aProgressTracker); + +private: + // Factory functions that create specific types of image containers. + static already_AddRefed + CreateRasterImage(nsIRequest* aRequest, + ProgressTracker* aProgressTracker, + const nsCString& aMimeType, + ImageURL* aURI, + uint32_t aImageFlags, + uint32_t aInnerWindowId); + + static already_AddRefed + CreateVectorImage(nsIRequest* aRequest, + ProgressTracker* aProgressTracker, + const nsCString& aMimeType, + ImageURL* aURI, + uint32_t aImageFlags, + uint32_t aInnerWindowId); + + // This is a static factory class, so disallow instantiation. + virtual ~ImageFactory() = 0; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_ImageFactory_h diff --git a/image/ImageLogging.h b/image/ImageLogging.h new file mode 100644 index 000000000..4e4a4b9c5 --- /dev/null +++ b/image/ImageLogging.h @@ -0,0 +1,161 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_ImageLogging_h +#define mozilla_image_ImageLogging_h + +#include "mozilla/Logging.h" +#include "prinrval.h" + +static mozilla::LazyLogModule gImgLog("imgRequest"); + +#define GIVE_ME_MS_NOW() PR_IntervalToMilliseconds(PR_IntervalNow()) + +using mozilla::LogLevel; + +class LogScope { +public: + + LogScope(mozilla::LogModule* aLog, void* aFrom, const char* aFunc) + : mLog(aLog) + , mFrom(aFrom) + , mFunc(aFunc) + { + MOZ_LOG(mLog, LogLevel::Debug, ("%d [this=%p] %s {ENTER}\n", + GIVE_ME_MS_NOW(), mFrom, mFunc)); + } + + /* const char * constructor */ + LogScope(mozilla::LogModule* aLog, void* from, const char* fn, + const char* paramName, const char* paramValue) + : mLog(aLog) + , mFrom(from) + , mFunc(fn) + { + MOZ_LOG(mLog, LogLevel::Debug, ("%d [this=%p] %s (%s=\"%s\") {ENTER}\n", + GIVE_ME_MS_NOW(), mFrom, mFunc, + paramName, paramValue)); + } + + /* void ptr constructor */ + LogScope(mozilla::LogModule* aLog, void* from, const char* fn, + const char* paramName, const void* paramValue) + : mLog(aLog) + , mFrom(from) + , mFunc(fn) + { + MOZ_LOG(mLog, LogLevel::Debug, ("%d [this=%p] %s (%s=%p) {ENTER}\n", + GIVE_ME_MS_NOW(), mFrom, mFunc, + paramName, paramValue)); + } + + /* int32_t constructor */ + LogScope(mozilla::LogModule* aLog, void* from, const char* fn, + const char* paramName, int32_t paramValue) + : mLog(aLog) + , mFrom(from) + , mFunc(fn) + { + MOZ_LOG(mLog, LogLevel::Debug, ("%d [this=%p] %s (%s=\"%d\") {ENTER}\n", + GIVE_ME_MS_NOW(), mFrom, mFunc, + paramName, paramValue)); + } + + /* uint32_t constructor */ + LogScope(mozilla::LogModule* aLog, void* from, const char* fn, + const char* paramName, uint32_t paramValue) + : mLog(aLog) + , mFrom(from) + , mFunc(fn) + { + MOZ_LOG(mLog, LogLevel::Debug, ("%d [this=%p] %s (%s=\"%d\") {ENTER}\n", + GIVE_ME_MS_NOW(), mFrom, mFunc, + paramName, paramValue)); + } + + ~LogScope() + { + MOZ_LOG(mLog, LogLevel::Debug, ("%d [this=%p] %s {EXIT}\n", + GIVE_ME_MS_NOW(), mFrom, mFunc)); + } + +private: + mozilla::LogModule* mLog; + void* mFrom; + const char* mFunc; +}; + +class LogFunc { +public: + LogFunc(mozilla::LogModule* aLog, void* from, const char* fn) + { + MOZ_LOG(aLog, LogLevel::Debug, ("%d [this=%p] %s\n", + GIVE_ME_MS_NOW(), from, fn)); + } + + LogFunc(mozilla::LogModule* aLog, void* from, const char* fn, + const char* paramName, const char* paramValue) + { + MOZ_LOG(aLog, LogLevel::Debug, ("%d [this=%p] %s (%s=\"%s\")\n", + GIVE_ME_MS_NOW(), from, fn, + paramName, paramValue)); + } + + LogFunc(mozilla::LogModule* aLog, void* from, const char* fn, + const char* paramName, const void* paramValue) + { + MOZ_LOG(aLog, LogLevel::Debug, ("%d [this=%p] %s (%s=\"%p\")\n", + GIVE_ME_MS_NOW(), from, fn, + paramName, paramValue)); + } + + + LogFunc(mozilla::LogModule* aLog, void* from, const char* fn, + const char* paramName, uint32_t paramValue) + { + MOZ_LOG(aLog, LogLevel::Debug, ("%d [this=%p] %s (%s=\"%d\")\n", + GIVE_ME_MS_NOW(), from, fn, + paramName, paramValue)); + } + +}; + + +class LogMessage { +public: + LogMessage(mozilla::LogModule* aLog, void* from, const char* fn, + const char* msg) + { + MOZ_LOG(aLog, LogLevel::Debug, ("%d [this=%p] %s -- %s\n", + GIVE_ME_MS_NOW(), from, fn, msg)); + } +}; + +#define LOG_SCOPE_APPEND_LINE_NUMBER_PASTE(id, line) id ## line +#define LOG_SCOPE_APPEND_LINE_NUMBER_EXPAND(id, line) \ + LOG_SCOPE_APPEND_LINE_NUMBER_PASTE(id, line) +#define LOG_SCOPE_APPEND_LINE_NUMBER(id) \ + LOG_SCOPE_APPEND_LINE_NUMBER_EXPAND(id, __LINE__) + +#define LOG_SCOPE(l, s) \ + LogScope LOG_SCOPE_APPEND_LINE_NUMBER(LOG_SCOPE_TMP_VAR) (l, this, s) + +#define LOG_SCOPE_WITH_PARAM(l, s, pn, pv) \ + LogScope LOG_SCOPE_APPEND_LINE_NUMBER(LOG_SCOPE_TMP_VAR) (l, this, s, pn, pv) + +#define LOG_FUNC(l, s) LogFunc(l, this, s) + +#define LOG_FUNC_WITH_PARAM(l, s, pn, pv) LogFunc(l, this, s, pn, pv) + +#define LOG_STATIC_FUNC(l, s) LogFunc(l, nullptr, s) + +#define LOG_STATIC_FUNC_WITH_PARAM(l, s, pn, pv) LogFunc(l, nullptr, s, pn, pv) + +#define LOG_MSG(l, s, m) LogMessage(l, this, s, m) + +#define LOG_MSG_WITH_PARAM LOG_FUNC_WITH_PARAM + +#endif // mozilla_image_ImageLogging_h diff --git a/image/ImageMetadata.h b/image/ImageMetadata.h new file mode 100644 index 000000000..05f572980 --- /dev/null +++ b/image/ImageMetadata.h @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_ImageMetadata_h +#define mozilla_image_ImageMetadata_h + +#include +#include "mozilla/Maybe.h" +#include "nsSize.h" +#include "Orientation.h" + +namespace mozilla { +namespace image { + +class RasterImage; + +// The metadata about an image that decoders accumulate as they decode. +class ImageMetadata +{ +public: + ImageMetadata() + : mLoopCount(-1) + , mFirstFrameTimeout(FrameTimeout::Forever()) + , mHasAnimation(false) + { } + + void SetHotspot(uint16_t aHotspotX, uint16_t aHotspotY) + { + mHotspot = Some(gfx::IntPoint(aHotspotX, aHotspotY)); + } + gfx::IntPoint GetHotspot() const { return *mHotspot; } + bool HasHotspot() const { return mHotspot.isSome(); } + + void SetLoopCount(int32_t loopcount) + { + mLoopCount = loopcount; + } + int32_t GetLoopCount() const { return mLoopCount; } + + void SetLoopLength(FrameTimeout aLength) { mLoopLength = Some(aLength); } + FrameTimeout GetLoopLength() const { return *mLoopLength; } + bool HasLoopLength() const { return mLoopLength.isSome(); } + + void SetFirstFrameTimeout(FrameTimeout aTimeout) { mFirstFrameTimeout = aTimeout; } + FrameTimeout GetFirstFrameTimeout() const { return mFirstFrameTimeout; } + + void SetFirstFrameRefreshArea(const gfx::IntRect& aRefreshArea) + { + mFirstFrameRefreshArea = Some(aRefreshArea); + } + gfx::IntRect GetFirstFrameRefreshArea() const { return *mFirstFrameRefreshArea; } + bool HasFirstFrameRefreshArea() const { return mFirstFrameRefreshArea.isSome(); } + + void SetSize(int32_t width, int32_t height, Orientation orientation) + { + if (!HasSize()) { + mSize.emplace(nsIntSize(width, height)); + mOrientation.emplace(orientation); + } + } + nsIntSize GetSize() const { return *mSize; } + bool HasSize() const { return mSize.isSome(); } + + Orientation GetOrientation() const { return *mOrientation; } + bool HasOrientation() const { return mOrientation.isSome(); } + + void SetHasAnimation() { mHasAnimation = true; } + bool HasAnimation() const { return mHasAnimation; } + +private: + /// The hotspot found on cursors, if present. + Maybe mHotspot; + + /// The loop count for animated images, or -1 for infinite loop. + int32_t mLoopCount; + + // The total length of a single loop through an animated image. + Maybe mLoopLength; + + /// The timeout of an animated image's first frame. + FrameTimeout mFirstFrameTimeout; + + // The area of the image that needs to be invalidated when the animation + // loops. + Maybe mFirstFrameRefreshArea; + + Maybe mSize; + Maybe mOrientation; + + bool mHasAnimation : 1; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_ImageMetadata_h diff --git a/image/ImageOps.cpp b/image/ImageOps.cpp new file mode 100644 index 000000000..addee7f15 --- /dev/null +++ b/image/ImageOps.cpp @@ -0,0 +1,150 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageOps.h" + +#include "ClippedImage.h" +#include "DecodePool.h" +#include "Decoder.h" +#include "DecoderFactory.h" +#include "DynamicImage.h" +#include "FrozenImage.h" +#include "IDecodingTask.h" +#include "Image.h" +#include "imgIContainer.h" +#include "mozilla/gfx/2D.h" +#include "nsStreamUtils.h" +#include "OrientedImage.h" +#include "SourceBuffer.h" + +using namespace mozilla::gfx; + +namespace mozilla { +namespace image { + +/* static */ already_AddRefed +ImageOps::Freeze(Image* aImage) +{ + RefPtr frozenImage = new FrozenImage(aImage); + return frozenImage.forget(); +} + +/* static */ already_AddRefed +ImageOps::Freeze(imgIContainer* aImage) +{ + nsCOMPtr frozenImage = + new FrozenImage(static_cast(aImage)); + return frozenImage.forget(); +} + +/* static */ already_AddRefed +ImageOps::Clip(Image* aImage, nsIntRect aClip, + const Maybe& aSVGViewportSize) +{ + RefPtr clippedImage = new ClippedImage(aImage, aClip, aSVGViewportSize); + return clippedImage.forget(); +} + +/* static */ already_AddRefed +ImageOps::Clip(imgIContainer* aImage, nsIntRect aClip, + const Maybe& aSVGViewportSize) +{ + nsCOMPtr clippedImage = + new ClippedImage(static_cast(aImage), aClip, aSVGViewportSize); + return clippedImage.forget(); +} + +/* static */ already_AddRefed +ImageOps::Orient(Image* aImage, Orientation aOrientation) +{ + RefPtr orientedImage = new OrientedImage(aImage, aOrientation); + return orientedImage.forget(); +} + +/* static */ already_AddRefed +ImageOps::Orient(imgIContainer* aImage, Orientation aOrientation) +{ + nsCOMPtr orientedImage = + new OrientedImage(static_cast(aImage), aOrientation); + return orientedImage.forget(); +} + +/* static */ already_AddRefed +ImageOps::CreateFromDrawable(gfxDrawable* aDrawable) +{ + nsCOMPtr drawableImage = new DynamicImage(aDrawable); + return drawableImage.forget(); +} + +/* static */ already_AddRefed +ImageOps::DecodeToSurface(nsIInputStream* aInputStream, + const nsACString& aMimeType, + uint32_t aFlags) +{ + MOZ_ASSERT(aInputStream); + + nsresult rv; + + // Prepare the input stream. + nsCOMPtr inputStream = aInputStream; + if (!NS_InputStreamIsBuffered(aInputStream)) { + nsCOMPtr bufStream; + rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream), + aInputStream, 1024); + if (NS_SUCCEEDED(rv)) { + inputStream = bufStream; + } + } + + // Figure out how much data we've been passed. + uint64_t length; + rv = inputStream->Available(&length); + if (NS_FAILED(rv) || length > UINT32_MAX) { + return nullptr; + } + + // Write the data into a SourceBuffer. + NotNull> sourceBuffer = WrapNotNull(new SourceBuffer()); + sourceBuffer->ExpectLength(length); + rv = sourceBuffer->AppendFromInputStream(inputStream, length); + if (NS_FAILED(rv)) { + return nullptr; + } + sourceBuffer->Complete(NS_OK); + + // Create a decoder. + DecoderType decoderType = + DecoderFactory::GetDecoderType(PromiseFlatCString(aMimeType).get()); + RefPtr decoder = + DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, + Nothing(), ToSurfaceFlags(aFlags)); + if (!decoder) { + return nullptr; + } + + // Run the decoder synchronously. + RefPtr task = new AnonymousDecodingTask(WrapNotNull(decoder)); + task->Run(); + if (!decoder->GetDecodeDone() || decoder->HasError()) { + return nullptr; + } + + // Pull out the surface. + RawAccessFrameRef frame = decoder->GetCurrentFrameRef(); + if (!frame) { + return nullptr; + } + + RefPtr surface = frame->GetSourceSurface(); + if (!surface) { + return nullptr; + } + + return surface.forget(); +} + +} // namespace image +} // namespace mozilla diff --git a/image/ImageOps.h b/image/ImageOps.h new file mode 100644 index 000000000..7a8e19be3 --- /dev/null +++ b/image/ImageOps.h @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_ImageOps_h +#define mozilla_image_ImageOps_h + +#include "nsCOMPtr.h" +#include "nsRect.h" + +class gfxDrawable; +class imgIContainer; +class nsIInputStream; + +namespace mozilla { + +namespace gfx { +class SourceSurface; +} + +namespace image { + +class Image; +struct Orientation; + +class ImageOps +{ +public: + /** + * Creates a version of an existing image which does not animate and is frozen + * at the first frame. + * + * @param aImage The existing image. + */ + static already_AddRefed Freeze(Image* aImage); + static already_AddRefed Freeze(imgIContainer* aImage); + + /** + * Creates a clipped version of an existing image. Animation is unaffected. + * + * @param aImage The existing image. + * @param aClip The rectangle to clip the image against. + * @param aSVGViewportSize The specific viewort size of aImage. Unless aImage + * is a vector image without intrinsic size, this + * argument should be pass as Nothing(). + */ + static already_AddRefed Clip(Image* aImage, nsIntRect aClip, + const Maybe& aSVGViewportSize = + Nothing()); + static already_AddRefed Clip(imgIContainer* aImage, + nsIntRect aClip, + const Maybe& aSVGViewportSize = + Nothing()); + + /** + * Creates a version of an existing image which is rotated and/or flipped to + * the specified orientation. + * + * @param aImage The existing image. + * @param aOrientation The desired orientation. + */ + static already_AddRefed Orient(Image* aImage, + Orientation aOrientation); + static already_AddRefed Orient(imgIContainer* aImage, + Orientation aOrientation); + + /** + * Creates an image from a gfxDrawable. + * + * @param aDrawable The gfxDrawable. + */ + static already_AddRefed + CreateFromDrawable(gfxDrawable* aDrawable); + + /** + * Decodes an image from an nsIInputStream directly into a SourceSurface, + * without ever creating an Image or imgIContainer (which are mostly + * main-thread-only). That means that this function may be called + * off-main-thread. + * + * @param aInputStream An input stream containing an encoded image. + * @param aMimeType The MIME type of the image. + * @param aFlags Flags of the imgIContainer::FLAG_DECODE_* variety. + * @return A SourceSurface containing the first frame of the image at its + * intrinsic size, or nullptr if the image cannot be decoded. + */ + static already_AddRefed + DecodeToSurface(nsIInputStream* aInputStream, + const nsACString& aMimeType, + uint32_t aFlags); + +private: + // This is a static utility class, so disallow instantiation. + virtual ~ImageOps() = 0; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_ImageOps_h diff --git a/image/ImageRegion.h b/image/ImageRegion.h new file mode 100644 index 000000000..3b7ff7482 --- /dev/null +++ b/image/ImageRegion.h @@ -0,0 +1,173 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_ImageRegion_h +#define mozilla_image_ImageRegion_h + +#include "gfxRect.h" +#include "mozilla/gfx/Types.h" + +namespace mozilla { +namespace image { + +/** + * An axis-aligned rectangle in tiled image space, with an optional sampling + * restriction rect. The drawing code ensures that if a sampling restriction + * rect is present, any pixels sampled during the drawing process are found + * within that rect. + * + * The sampling restriction rect exists primarily for callers which perform + * pixel snapping. Other callers should generally use one of the Create() + * overloads. + */ +class ImageRegion +{ + typedef mozilla::gfx::ExtendMode ExtendMode; + +public: + static ImageRegion Empty() + { + return ImageRegion(gfxRect(), ExtendMode::CLAMP); + } + + static ImageRegion Create(const gfxRect& aRect, + ExtendMode aExtendMode = ExtendMode::CLAMP) + { + return ImageRegion(aRect, aExtendMode); + } + + static ImageRegion Create(const gfxSize& aSize, + ExtendMode aExtendMode = ExtendMode::CLAMP) + { + return ImageRegion(gfxRect(0, 0, aSize.width, aSize.height), aExtendMode); + } + + static ImageRegion Create(const nsIntSize& aSize, + ExtendMode aExtendMode = ExtendMode::CLAMP) + { + return ImageRegion(gfxRect(0, 0, aSize.width, aSize.height), aExtendMode); + } + + static ImageRegion CreateWithSamplingRestriction(const gfxRect& aRect, + const gfxRect& aRestriction, + ExtendMode aExtendMode = ExtendMode::CLAMP) + { + return ImageRegion(aRect, aRestriction, aExtendMode); + } + + bool IsRestricted() const { return mIsRestricted; } + const gfxRect& Rect() const { return mRect; } + + const gfxRect& Restriction() const + { + MOZ_ASSERT(mIsRestricted); + return mRestriction; + } + + bool RestrictionContains(const gfxRect& aRect) const + { + if (!mIsRestricted) { + return true; + } + return mRestriction.Contains(aRect); + } + + ImageRegion Intersect(const gfxRect& aRect) const + { + if (mIsRestricted) { + return CreateWithSamplingRestriction(aRect.Intersect(mRect), + aRect.Intersect(mRestriction)); + } + return Create(aRect.Intersect(mRect)); + } + + gfxRect IntersectAndRestrict(const gfxRect& aRect) const + { + gfxRect intersection = mRect.Intersect(aRect); + if (mIsRestricted) { + intersection = mRestriction.Intersect(intersection); + } + return intersection; + } + + void MoveBy(gfxFloat dx, gfxFloat dy) + { + mRect.MoveBy(dx, dy); + if (mIsRestricted) { + mRestriction.MoveBy(dx, dy); + } + } + + void Scale(gfxFloat sx, gfxFloat sy) + { + mRect.Scale(sx, sy); + if (mIsRestricted) { + mRestriction.Scale(sx, sy); + } + } + + void TransformBy(const gfxMatrix& aMatrix) + { + mRect = aMatrix.Transform(mRect); + if (mIsRestricted) { + mRestriction = aMatrix.Transform(mRestriction); + } + } + + void TransformBoundsBy(const gfxMatrix& aMatrix) + { + mRect = aMatrix.TransformBounds(mRect); + if (mIsRestricted) { + mRestriction = aMatrix.TransformBounds(mRestriction); + } + } + + ImageRegion operator-(const gfxPoint& aPt) const + { + if (mIsRestricted) { + return CreateWithSamplingRestriction(mRect - aPt, mRestriction - aPt); + } + return Create(mRect - aPt); + } + + ImageRegion operator+(const gfxPoint& aPt) const + { + if (mIsRestricted) { + return CreateWithSamplingRestriction(mRect + aPt, mRestriction + aPt); + } + return Create(mRect + aPt); + } + + gfx::ExtendMode GetExtendMode() const + { + return mExtendMode; + } + + /* ImageRegion() : mIsRestricted(false) { } */ + +private: + explicit ImageRegion(const gfxRect& aRect, ExtendMode aExtendMode) + : mRect(aRect) + , mExtendMode(aExtendMode) + , mIsRestricted(false) + { } + + ImageRegion(const gfxRect& aRect, const gfxRect& aRestriction, ExtendMode aExtendMode) + : mRect(aRect) + , mRestriction(aRestriction) + , mExtendMode(aExtendMode) + , mIsRestricted(true) + { } + + gfxRect mRect; + gfxRect mRestriction; + ExtendMode mExtendMode; + bool mIsRestricted; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_ImageRegion_h diff --git a/image/ImageURL.h b/image/ImageURL.h new file mode 100644 index 000000000..fa60ff79e --- /dev/null +++ b/image/ImageURL.h @@ -0,0 +1,151 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_ImageURL_h +#define mozilla_image_ImageURL_h + +#include "nsIURI.h" +#include "MainThreadUtils.h" +#include "nsNetUtil.h" +#include "mozilla/HashFunctions.h" +#include "nsHashKeys.h" + +namespace mozilla { +namespace image { + +class ImageCacheKey; + +/** ImageURL + * + * nsStandardURL is not threadsafe, so this class is created to hold only the + * necessary URL data required for image loading and decoding. + * + * Note: Although several APIs have the same or similar prototypes as those + * found in nsIURI/nsStandardURL, the class does not implement nsIURI. This is + * intentional; functionality is limited, and is only useful for imagelib code. + * By not implementing nsIURI, external code cannot unintentionally be given an + * nsIURI pointer with this limited class behind it; instead, conversion to a + * fully implemented nsIURI is required (e.g. through NS_NewURI). + */ +class ImageURL +{ +public: + explicit ImageURL(nsIURI* aURI, nsresult& aRv) + { + MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!"); + + aRv = aURI->GetSpec(mSpec); + NS_ENSURE_SUCCESS_VOID(aRv); + + aRv = aURI->GetScheme(mScheme); + NS_ENSURE_SUCCESS_VOID(aRv); + + aRv = aURI->GetRef(mRef); + NS_ENSURE_SUCCESS_VOID(aRv); + } + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ImageURL) + + nsresult GetSpec(nsACString& result) + { + result = mSpec; + return NS_OK; + } + + /// A weak pointer to the URI spec for this ImageURL. For logging only. + const char* Spec() const { return mSpec.get(); } + + enum TruncatedSpecStatus { + FitsInto1k, + TruncatedTo1k + }; + TruncatedSpecStatus GetSpecTruncatedTo1k(nsACString& result) + { + static const size_t sMaxTruncatedLength = 1024; + + if (sMaxTruncatedLength >= mSpec.Length()) { + result = mSpec; + return FitsInto1k; + } + + result = Substring(mSpec, 0, sMaxTruncatedLength); + return TruncatedTo1k; + } + + nsresult GetScheme(nsACString& result) + { + result = mScheme; + return NS_OK; + } + + nsresult SchemeIs(const char* scheme, bool* result) + { + NS_PRECONDITION(scheme, "scheme is null"); + NS_PRECONDITION(result, "result is null"); + + *result = mScheme.Equals(scheme); + return NS_OK; + } + + nsresult GetRef(nsACString& result) + { + result = mRef; + return NS_OK; + } + + already_AddRefed ToIURI() + { + MOZ_ASSERT(NS_IsMainThread(), + "Convert to nsIURI on main thread only; it is not threadsafe."); + nsCOMPtr newURI; + NS_NewURI(getter_AddRefs(newURI), mSpec); + return newURI.forget(); + } + + bool operator==(const ImageURL& aOther) const + { + // Note that we don't need to consider mScheme and mRef, because they're + // already represented in mSpec. + return mSpec == aOther.mSpec; + } + + bool HasSameRef(const ImageURL& aOther) const + { + return mRef == aOther.mRef; + } + +private: + friend class ImageCacheKey; + + uint32_t ComputeHash(const Maybe& aBlobSerial) const + { + if (aBlobSerial) { + // For blob URIs, we hash the serial number of the underlying blob, so that + // different blob URIs which point to the same blob share a cache entry. We + // also include the ref portion of the URI to support media fragments which + // requires us to create different Image objects even if the source data is + // the same. + return HashGeneric(*aBlobSerial, HashString(mRef)); + } + // For non-blob URIs, we hash the URI spec. + return HashString(mSpec); + } + + // Since this is a basic storage class, no duplication of spec parsing is + // included in the functionality. Instead, the class depends upon the + // parsing implementation in the nsIURI class used in object construction. + // This means each field is stored separately, but since only a few are + // required, this small memory tradeoff for threadsafe usage should be ok. + nsAutoCString mSpec; + nsAutoCString mScheme; + nsAutoCString mRef; + + ~ImageURL() { } +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_ImageURL_h diff --git a/image/ImageWrapper.cpp b/image/ImageWrapper.cpp new file mode 100644 index 000000000..f7068dfc5 --- /dev/null +++ b/image/ImageWrapper.cpp @@ -0,0 +1,323 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageWrapper.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/RefPtr.h" +#include "Orientation.h" + +#include "mozilla/MemoryReporting.h" + +namespace mozilla { + +using gfx::DataSourceSurface; +using gfx::IntSize; +using gfx::SamplingFilter; +using gfx::SourceSurface; +using layers::LayerManager; +using layers::ImageContainer; + +namespace image { + +// Inherited methods from Image. + +already_AddRefed +ImageWrapper::GetProgressTracker() +{ + return mInnerImage->GetProgressTracker(); +} + +size_t +ImageWrapper::SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const +{ + return mInnerImage->SizeOfSourceWithComputedFallback(aMallocSizeOf); +} + +void +ImageWrapper::CollectSizeOfSurfaces(nsTArray& aCounters, + MallocSizeOf aMallocSizeOf) const +{ + mInnerImage->CollectSizeOfSurfaces(aCounters, aMallocSizeOf); +} + +void +ImageWrapper::IncrementAnimationConsumers() +{ + MOZ_ASSERT(NS_IsMainThread(), "Main thread only to encourage serialization " + "with DecrementAnimationConsumers"); + mInnerImage->IncrementAnimationConsumers(); +} + +void +ImageWrapper::DecrementAnimationConsumers() +{ + MOZ_ASSERT(NS_IsMainThread(), "Main thread only to encourage serialization " + "with IncrementAnimationConsumers"); + mInnerImage->DecrementAnimationConsumers(); +} + +#ifdef DEBUG +uint32_t +ImageWrapper::GetAnimationConsumers() +{ + return mInnerImage->GetAnimationConsumers(); +} +#endif + +nsresult +ImageWrapper::OnImageDataAvailable(nsIRequest* aRequest, + nsISupports* aContext, + nsIInputStream* aInStr, + uint64_t aSourceOffset, + uint32_t aCount) +{ + return mInnerImage->OnImageDataAvailable(aRequest, aContext, aInStr, + aSourceOffset, aCount); +} + +nsresult +ImageWrapper::OnImageDataComplete(nsIRequest* aRequest, + nsISupports* aContext, + nsresult aStatus, + bool aLastPart) +{ + return mInnerImage->OnImageDataComplete(aRequest, aContext, aStatus, + aLastPart); +} + +void +ImageWrapper::OnSurfaceDiscarded() +{ + return mInnerImage->OnSurfaceDiscarded(); +} + +void +ImageWrapper::SetInnerWindowID(uint64_t aInnerWindowId) +{ + mInnerImage->SetInnerWindowID(aInnerWindowId); +} + +uint64_t +ImageWrapper::InnerWindowID() const +{ + return mInnerImage->InnerWindowID(); +} + +bool +ImageWrapper::HasError() +{ + return mInnerImage->HasError(); +} + +void +ImageWrapper::SetHasError() +{ + mInnerImage->SetHasError(); +} + +ImageURL* +ImageWrapper::GetURI() +{ + return mInnerImage->GetURI(); +} + +// Methods inherited from XPCOM interfaces. + +NS_IMPL_ISUPPORTS(ImageWrapper, imgIContainer) + +NS_IMETHODIMP +ImageWrapper::GetWidth(int32_t* aWidth) +{ + return mInnerImage->GetWidth(aWidth); +} + +NS_IMETHODIMP +ImageWrapper::GetHeight(int32_t* aHeight) +{ + return mInnerImage->GetHeight(aHeight); +} + +NS_IMETHODIMP +ImageWrapper::GetIntrinsicSize(nsSize* aSize) +{ + return mInnerImage->GetIntrinsicSize(aSize); +} + +NS_IMETHODIMP +ImageWrapper::GetIntrinsicRatio(nsSize* aSize) +{ + return mInnerImage->GetIntrinsicRatio(aSize); +} + +NS_IMETHODIMP_(Orientation) +ImageWrapper::GetOrientation() +{ + return mInnerImage->GetOrientation(); +} + +NS_IMETHODIMP +ImageWrapper::GetType(uint16_t* aType) +{ + return mInnerImage->GetType(aType); +} + +NS_IMETHODIMP +ImageWrapper::GetAnimated(bool* aAnimated) +{ + return mInnerImage->GetAnimated(aAnimated); +} + +NS_IMETHODIMP_(already_AddRefed) +ImageWrapper::GetFrame(uint32_t aWhichFrame, + uint32_t aFlags) +{ + return mInnerImage->GetFrame(aWhichFrame, aFlags); +} + +NS_IMETHODIMP_(already_AddRefed) +ImageWrapper::GetFrameAtSize(const IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags) +{ + return mInnerImage->GetFrameAtSize(aSize, aWhichFrame, aFlags); +} + +NS_IMETHODIMP_(bool) +ImageWrapper::WillDrawOpaqueNow() +{ + return mInnerImage->WillDrawOpaqueNow(); +} + +NS_IMETHODIMP_(bool) +ImageWrapper::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags) +{ + return mInnerImage->IsImageContainerAvailable(aManager, aFlags); +} + +NS_IMETHODIMP_(already_AddRefed) +ImageWrapper::GetImageContainer(LayerManager* aManager, uint32_t aFlags) +{ + return mInnerImage->GetImageContainer(aManager, aFlags); +} + +NS_IMETHODIMP_(DrawResult) +ImageWrapper::Draw(gfxContext* aContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, + uint32_t aWhichFrame, + SamplingFilter aSamplingFilter, + const Maybe& aSVGContext, + uint32_t aFlags) +{ + return mInnerImage->Draw(aContext, aSize, aRegion, aWhichFrame, + aSamplingFilter, aSVGContext, aFlags); +} + +NS_IMETHODIMP +ImageWrapper::StartDecoding() +{ + return mInnerImage->StartDecoding(); +} + +NS_IMETHODIMP +ImageWrapper::RequestDecodeForSize(const nsIntSize& aSize, uint32_t aFlags) +{ + return mInnerImage->RequestDecodeForSize(aSize, aFlags); +} + +NS_IMETHODIMP +ImageWrapper::LockImage() +{ + MOZ_ASSERT(NS_IsMainThread(), + "Main thread to encourage serialization with UnlockImage"); + return mInnerImage->LockImage(); +} + +NS_IMETHODIMP +ImageWrapper::UnlockImage() +{ + MOZ_ASSERT(NS_IsMainThread(), + "Main thread to encourage serialization with LockImage"); + return mInnerImage->UnlockImage(); +} + +NS_IMETHODIMP +ImageWrapper::RequestDiscard() +{ + return mInnerImage->RequestDiscard(); +} + +NS_IMETHODIMP_(void) +ImageWrapper::RequestRefresh(const TimeStamp& aTime) +{ + return mInnerImage->RequestRefresh(aTime); +} + +NS_IMETHODIMP +ImageWrapper::GetAnimationMode(uint16_t* aAnimationMode) +{ + return mInnerImage->GetAnimationMode(aAnimationMode); +} + +NS_IMETHODIMP +ImageWrapper::SetAnimationMode(uint16_t aAnimationMode) +{ + return mInnerImage->SetAnimationMode(aAnimationMode); +} + +NS_IMETHODIMP +ImageWrapper::ResetAnimation() +{ + return mInnerImage->ResetAnimation(); +} + +NS_IMETHODIMP_(float) +ImageWrapper::GetFrameIndex(uint32_t aWhichFrame) +{ + return mInnerImage->GetFrameIndex(aWhichFrame); +} + +NS_IMETHODIMP_(int32_t) +ImageWrapper::GetFirstFrameDelay() +{ + return mInnerImage->GetFirstFrameDelay(); +} + +NS_IMETHODIMP_(void) +ImageWrapper::SetAnimationStartTime(const TimeStamp& aTime) +{ + mInnerImage->SetAnimationStartTime(aTime); +} + +void +ImageWrapper::PropagateUseCounters(nsIDocument* aParentDocument) +{ + mInnerImage->PropagateUseCounters(aParentDocument); +} + +nsIntSize +ImageWrapper::OptimalImageSizeForDest(const gfxSize& aDest, + uint32_t aWhichFrame, + SamplingFilter aSamplingFilter, + uint32_t aFlags) +{ + return mInnerImage->OptimalImageSizeForDest(aDest, aWhichFrame, + aSamplingFilter, aFlags); +} + +NS_IMETHODIMP_(nsIntRect) +ImageWrapper::GetImageSpaceInvalidationRect(const nsIntRect& aRect) +{ + return mInnerImage->GetImageSpaceInvalidationRect(aRect); +} + +already_AddRefed +ImageWrapper::Unwrap() +{ + return mInnerImage->Unwrap(); +} + +} // namespace image +} // namespace mozilla diff --git a/image/ImageWrapper.h b/image/ImageWrapper.h new file mode 100644 index 000000000..f60a1c09c --- /dev/null +++ b/image/ImageWrapper.h @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_ImageWrapper_h +#define mozilla_image_ImageWrapper_h + +#include "mozilla/MemoryReporting.h" +#include "Image.h" + +namespace mozilla { +namespace image { + +/** + * Abstract superclass for Images that wrap other Images. + */ +class ImageWrapper : public Image +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_IMGICONTAINER + + // Inherited methods from Image. + virtual already_AddRefed GetProgressTracker() override; + + virtual size_t + SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const override; + virtual void CollectSizeOfSurfaces(nsTArray& aCounters, + MallocSizeOf aMallocSizeOf) const override; + + virtual void IncrementAnimationConsumers() override; + virtual void DecrementAnimationConsumers() override; +#ifdef DEBUG + virtual uint32_t GetAnimationConsumers() override; +#endif + + virtual nsresult OnImageDataAvailable(nsIRequest* aRequest, + nsISupports* aContext, + nsIInputStream* aInStr, + uint64_t aSourceOffset, + uint32_t aCount) override; + virtual nsresult OnImageDataComplete(nsIRequest* aRequest, + nsISupports* aContext, + nsresult aStatus, + bool aLastPart) override; + + virtual void OnSurfaceDiscarded() override; + + virtual void SetInnerWindowID(uint64_t aInnerWindowId) override; + virtual uint64_t InnerWindowID() const override; + + virtual bool HasError() override; + virtual void SetHasError() override; + + virtual ImageURL* GetURI() override; + +protected: + explicit ImageWrapper(Image* aInnerImage) + : mInnerImage(aInnerImage) + { + MOZ_ASSERT(aInnerImage, "Need an image to wrap"); + } + + virtual ~ImageWrapper() { } + + /** + * Returns a weak reference to the inner image wrapped by this ImageWrapper. + */ + Image* InnerImage() const { return mInnerImage.get(); } + + void SetInnerImage(Image* aInnerImage) + { + MOZ_ASSERT(aInnerImage, "Need an image to wrap"); + mInnerImage = aInnerImage; + } + +private: + RefPtr mInnerImage; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_ImageWrapper_h diff --git a/image/LookupResult.h b/image/LookupResult.h new file mode 100644 index 000000000..b6fc09111 --- /dev/null +++ b/image/LookupResult.h @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * LookupResult is the return type of SurfaceCache's Lookup*() functions. It + * combines a surface with relevant metadata tracked by SurfaceCache. + */ + +#ifndef mozilla_image_LookupResult_h +#define mozilla_image_LookupResult_h + +#include "mozilla/Attributes.h" +#include "mozilla/Move.h" +#include "ISurfaceProvider.h" + +namespace mozilla { +namespace image { + +enum class MatchType : uint8_t +{ + NOT_FOUND, // No matching surface and no placeholder. + PENDING, // Found a matching placeholder, but no surface. + EXACT, // Found a surface that matches exactly. + SUBSTITUTE_BECAUSE_NOT_FOUND, // No exact match, but found a similar one. + SUBSTITUTE_BECAUSE_PENDING // Found a similar surface and a placeholder + // for an exact match. +}; + +/** + * LookupResult is the return type of SurfaceCache's Lookup*() functions. It + * combines a surface with relevant metadata tracked by SurfaceCache. + */ +class MOZ_STACK_CLASS LookupResult +{ +public: + explicit LookupResult(MatchType aMatchType) + : mMatchType(aMatchType) + { + MOZ_ASSERT(mMatchType == MatchType::NOT_FOUND || + mMatchType == MatchType::PENDING, + "Only NOT_FOUND or PENDING make sense with no surface"); + } + + LookupResult(LookupResult&& aOther) + : mSurface(Move(aOther.mSurface)) + , mMatchType(aOther.mMatchType) + { } + + LookupResult(DrawableSurface&& aSurface, MatchType aMatchType) + : mSurface(Move(aSurface)) + , mMatchType(aMatchType) + { + MOZ_ASSERT(!mSurface || !(mMatchType == MatchType::NOT_FOUND || + mMatchType == MatchType::PENDING), + "Only NOT_FOUND or PENDING make sense with no surface"); + MOZ_ASSERT(mSurface || mMatchType == MatchType::NOT_FOUND || + mMatchType == MatchType::PENDING, + "NOT_FOUND or PENDING do not make sense with a surface"); + } + + LookupResult& operator=(LookupResult&& aOther) + { + MOZ_ASSERT(&aOther != this, "Self-move-assignment is not supported"); + mSurface = Move(aOther.mSurface); + mMatchType = aOther.mMatchType; + return *this; + } + + DrawableSurface& Surface() { return mSurface; } + const DrawableSurface& Surface() const { return mSurface; } + + /// @return true if this LookupResult contains a surface. + explicit operator bool() const { return bool(mSurface); } + + /// @return what kind of match this is (exact, substitute, etc.) + MatchType Type() const { return mMatchType; } + +private: + LookupResult(const LookupResult&) = delete; + + DrawableSurface mSurface; + MatchType mMatchType; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_LookupResult_h diff --git a/image/MultipartImage.cpp b/image/MultipartImage.cpp new file mode 100644 index 000000000..59aa24aaf --- /dev/null +++ b/image/MultipartImage.cpp @@ -0,0 +1,346 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MultipartImage.h" + +#include "imgINotificationObserver.h" + +namespace mozilla { + +using gfx::IntSize; +using gfx::SourceSurface; + +namespace image { + +/////////////////////////////////////////////////////////////////////////////// +// Helpers +/////////////////////////////////////////////////////////////////////////////// + +class NextPartObserver : public IProgressObserver +{ +public: + MOZ_DECLARE_REFCOUNTED_TYPENAME(NextPartObserver) + NS_INLINE_DECL_REFCOUNTING(NextPartObserver, override) + + explicit NextPartObserver(MultipartImage* aOwner) + : mOwner(aOwner) + { + MOZ_ASSERT(mOwner); + } + + void BeginObserving(Image* aImage) + { + MOZ_ASSERT(aImage); + mImage = aImage; + + RefPtr tracker = mImage->GetProgressTracker(); + tracker->AddObserver(this); + } + + void BlockUntilDecodedAndFinishObserving() + { + // Use GetFrame() to block until our image finishes decoding. + RefPtr surface = + mImage->GetFrame(imgIContainer::FRAME_CURRENT, + imgIContainer::FLAG_SYNC_DECODE); + + // GetFrame() should've sent synchronous notifications that would have + // caused us to call FinishObserving() (and null out mImage) already. If for + // some reason it didn't, we should do so here. + if (mImage) { + FinishObserving(); + } + } + + virtual void Notify(int32_t aType, + const nsIntRect* aRect = nullptr) override + { + if (!mImage) { + // We've already finished observing the last image we were given. + return; + } + + if (aType == imgINotificationObserver::FRAME_COMPLETE) { + FinishObserving(); + } + } + + virtual void OnLoadComplete(bool aLastPart) override + { + if (!mImage) { + // We've already finished observing the last image we were given. + return; + } + + // Retrieve the image's intrinsic size. + int32_t width = 0; + int32_t height = 0; + mImage->GetWidth(&width); + mImage->GetHeight(&height); + + // Request decoding at the intrinsic size. + mImage->RequestDecodeForSize(IntSize(width, height), + imgIContainer::DECODE_FLAGS_DEFAULT); + + // If there's already an error, we may never get a FRAME_COMPLETE + // notification, so go ahead and notify our owner right away. + RefPtr tracker = mImage->GetProgressTracker(); + if (tracker->GetProgress() & FLAG_HAS_ERROR) { + FinishObserving(); + } + } + + // Other notifications are ignored. + virtual void BlockOnload() override { } + virtual void UnblockOnload() override { } + virtual void SetHasImage() override { } + virtual bool NotificationsDeferred() const override { return false; } + virtual void SetNotificationsDeferred(bool) override { } + +private: + virtual ~NextPartObserver() { } + + void FinishObserving() + { + MOZ_ASSERT(mImage); + + RefPtr tracker = mImage->GetProgressTracker(); + tracker->RemoveObserver(this); + mImage = nullptr; + + mOwner->FinishTransition(); + } + + MultipartImage* mOwner; + RefPtr mImage; +}; + + +/////////////////////////////////////////////////////////////////////////////// +// Implementation +/////////////////////////////////////////////////////////////////////////////// + +MultipartImage::MultipartImage(Image* aFirstPart) + : ImageWrapper(aFirstPart) + , mDeferNotifications(false) +{ + mNextPartObserver = new NextPartObserver(this); +} + +void +MultipartImage::Init() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mTracker, "Should've called SetProgressTracker() by now"); + + // Start observing the first part. + RefPtr firstPartTracker = + InnerImage()->GetProgressTracker(); + firstPartTracker->AddObserver(this); + InnerImage()->IncrementAnimationConsumers(); +} + +MultipartImage::~MultipartImage() +{ + // Ask our ProgressTracker to drop its weak reference to us. + mTracker->ResetImage(); +} + +NS_IMPL_ISUPPORTS_INHERITED0(MultipartImage, ImageWrapper) + +void +MultipartImage::BeginTransitionToPart(Image* aNextPart) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aNextPart); + + if (mNextPart) { + // Let the decoder catch up so we don't drop frames. + mNextPartObserver->BlockUntilDecodedAndFinishObserving(); + MOZ_ASSERT(!mNextPart); + } + + mNextPart = aNextPart; + + // Start observing the next part; we'll complete the transition when + // NextPartObserver calls FinishTransition. + mNextPartObserver->BeginObserving(mNextPart); + mNextPart->IncrementAnimationConsumers(); +} + +static Progress +FilterProgress(Progress aProgress) +{ + // Filter out onload blocking notifications, since we don't want to block + // onload for multipart images. + return aProgress & ~(FLAG_ONLOAD_BLOCKED | FLAG_ONLOAD_UNBLOCKED); +} + +void +MultipartImage::FinishTransition() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mNextPart, "Should have a next part here"); + + RefPtr newCurrentPartTracker = + mNextPart->GetProgressTracker(); + if (newCurrentPartTracker->GetProgress() & FLAG_HAS_ERROR) { + // This frame has an error; drop it. + mNextPart = nullptr; + + // We still need to notify, though. + mTracker->ResetForNewRequest(); + RefPtr currentPartTracker = + InnerImage()->GetProgressTracker(); + mTracker + ->SyncNotifyProgress(FilterProgress(currentPartTracker->GetProgress())); + + return; + } + + // Stop observing the current part. + { + RefPtr currentPartTracker = + InnerImage()->GetProgressTracker(); + currentPartTracker->RemoveObserver(this); + } + + // Make the next part become the current part. + mTracker->ResetForNewRequest(); + SetInnerImage(mNextPart); + mNextPart = nullptr; + newCurrentPartTracker->AddObserver(this); + + // Finally, send all the notifications for the new current part and send a + // FRAME_UPDATE notification so that observers know to redraw. + mTracker + ->SyncNotifyProgress(FilterProgress(newCurrentPartTracker->GetProgress()), + GetMaxSizedIntRect()); +} + +already_AddRefed +MultipartImage::Unwrap() +{ + // Although we wrap another image, we don't allow callers to unwrap as. As far + // as external code is concerned, MultipartImage is atomic. + nsCOMPtr image = this; + return image.forget(); +} + +already_AddRefed +MultipartImage::GetProgressTracker() +{ + MOZ_ASSERT(mTracker); + RefPtr tracker = mTracker; + return tracker.forget(); +} + +void +MultipartImage::SetProgressTracker(ProgressTracker* aTracker) +{ + MOZ_ASSERT(aTracker); + MOZ_ASSERT(!mTracker); + mTracker = aTracker; +} + +nsresult +MultipartImage::OnImageDataAvailable(nsIRequest* aRequest, + nsISupports* aContext, + nsIInputStream* aInStr, + uint64_t aSourceOffset, + uint32_t aCount) +{ + // Note that this method is special in that we forward it to the next part if + // one exists, and *not* the current part. + + // We may trigger notifications that will free mNextPart, so keep it alive. + RefPtr nextPart = mNextPart; + if (nextPart) { + nextPart->OnImageDataAvailable(aRequest, aContext, aInStr, + aSourceOffset, aCount); + } else { + InnerImage()->OnImageDataAvailable(aRequest, aContext, aInStr, + aSourceOffset, aCount); + } + + return NS_OK; +} + +nsresult +MultipartImage::OnImageDataComplete(nsIRequest* aRequest, + nsISupports* aContext, + nsresult aStatus, + bool aLastPart) +{ + // Note that this method is special in that we forward it to the next part if + // one exists, and *not* the current part. + + // We may trigger notifications that will free mNextPart, so keep it alive. + RefPtr nextPart = mNextPart; + if (nextPart) { + nextPart->OnImageDataComplete(aRequest, aContext, aStatus, aLastPart); + } else { + InnerImage()->OnImageDataComplete(aRequest, aContext, aStatus, aLastPart); + } + + return NS_OK; +} + +void +MultipartImage::Notify(int32_t aType, const nsIntRect* aRect /* = nullptr*/) +{ + if (aType == imgINotificationObserver::SIZE_AVAILABLE) { + mTracker->SyncNotifyProgress(FLAG_SIZE_AVAILABLE); + } else if (aType == imgINotificationObserver::FRAME_UPDATE) { + mTracker->SyncNotifyProgress(NoProgress, *aRect); + } else if (aType == imgINotificationObserver::FRAME_COMPLETE) { + mTracker->SyncNotifyProgress(FLAG_FRAME_COMPLETE); + } else if (aType == imgINotificationObserver::LOAD_COMPLETE) { + mTracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE); + } else if (aType == imgINotificationObserver::DECODE_COMPLETE) { + mTracker->SyncNotifyProgress(FLAG_DECODE_COMPLETE); + } else if (aType == imgINotificationObserver::DISCARD) { + mTracker->OnDiscard(); + } else if (aType == imgINotificationObserver::UNLOCKED_DRAW) { + mTracker->OnUnlockedDraw(); + } else if (aType == imgINotificationObserver::IS_ANIMATED) { + mTracker->SyncNotifyProgress(FLAG_IS_ANIMATED); + } else if (aType == imgINotificationObserver::HAS_TRANSPARENCY) { + mTracker->SyncNotifyProgress(FLAG_HAS_TRANSPARENCY); + } else { + NS_NOTREACHED("Notification list should be exhaustive"); + } +} + +void +MultipartImage::OnLoadComplete(bool aLastPart) +{ + Progress progress = FLAG_LOAD_COMPLETE; + if (aLastPart) { + progress |= FLAG_LAST_PART_COMPLETE; + } + mTracker->SyncNotifyProgress(progress); +} + +void +MultipartImage::SetHasImage() +{ + mTracker->OnImageAvailable(); +} + +bool +MultipartImage::NotificationsDeferred() const +{ + return mDeferNotifications; +} + +void +MultipartImage::SetNotificationsDeferred(bool aDeferNotifications) +{ + mDeferNotifications = aDeferNotifications; +} + +} // namespace image +} // namespace mozilla diff --git a/image/MultipartImage.h b/image/MultipartImage.h new file mode 100644 index 000000000..089ec993b --- /dev/null +++ b/image/MultipartImage.h @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_MultipartImage_h +#define mozilla_image_MultipartImage_h + +#include "ImageWrapper.h" +#include "IProgressObserver.h" +#include "ProgressTracker.h" + +namespace mozilla { +namespace image { + +class NextPartObserver; + +/** + * An Image wrapper that implements support for multipart/x-mixed-replace + * images. + */ +class MultipartImage + : public ImageWrapper + , public IProgressObserver +{ +public: + MOZ_DECLARE_REFCOUNTED_TYPENAME(MultipartImage) + NS_DECL_ISUPPORTS_INHERITED + + void BeginTransitionToPart(Image* aNextPart); + + // Overridden ImageWrapper methods: + virtual already_AddRefed Unwrap() override; + virtual already_AddRefed GetProgressTracker() override; + virtual void SetProgressTracker(ProgressTracker* aTracker) override; + virtual nsresult OnImageDataAvailable(nsIRequest* aRequest, + nsISupports* aContext, + nsIInputStream* aInStr, + uint64_t aSourceOffset, + uint32_t aCount) override; + virtual nsresult OnImageDataComplete(nsIRequest* aRequest, + nsISupports* aContext, + nsresult aStatus, + bool aLastPart) override; + + // We don't support locking or track animation consumers for individual parts, + // so we override these methods to do nothing. + NS_IMETHOD LockImage() override { return NS_OK; } + NS_IMETHOD UnlockImage() override { return NS_OK; } + virtual void IncrementAnimationConsumers() override { } + virtual void DecrementAnimationConsumers() override { } +#ifdef DEBUG + virtual uint32_t GetAnimationConsumers() override { return 1; } +#endif + + // Overridden IProgressObserver methods: + virtual void Notify(int32_t aType, + const nsIntRect* aRect = nullptr) override; + virtual void OnLoadComplete(bool aLastPart) override; + virtual void SetHasImage() override; + virtual bool NotificationsDeferred() const override; + virtual void SetNotificationsDeferred(bool aDeferNotifications) override; + + // We don't allow multipart images to block onload, so we override these + // methods to do nothing. + virtual void BlockOnload() override { } + virtual void UnblockOnload() override { } + +protected: + virtual ~MultipartImage(); + +private: + friend class ImageFactory; + friend class NextPartObserver; + + explicit MultipartImage(Image* aFirstPart); + void Init(); + + void FinishTransition(); + + RefPtr mTracker; + RefPtr mNextPartObserver; + RefPtr mNextPart; + bool mDeferNotifications : 1; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_MultipartImage_h diff --git a/image/Orientation.h b/image/Orientation.h new file mode 100644 index 000000000..a0f2e1b75 --- /dev/null +++ b/image/Orientation.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_Orientation_h +#define mozilla_image_Orientation_h + +#include + +namespace mozilla { +namespace image { + +enum class Angle : uint8_t { + D0, + D90, + D180, + D270 +}; + +enum class Flip : uint8_t { + Unflipped, + Horizontal +}; + +/** + * A struct that describes an image's orientation as a rotation optionally + * followed by a reflection. This may be used to be indicate an image's inherent + * orientation or a desired orientation for the image. + */ +struct Orientation +{ + explicit Orientation(Angle aRotation = Angle::D0, + Flip mFlip = Flip::Unflipped) + : rotation(aRotation) + , flip(mFlip) + { } + + bool IsIdentity() const { + return (rotation == Angle::D0) && (flip == Flip::Unflipped); + } + + bool SwapsWidthAndHeight() const { + return (rotation == Angle::D90) || (rotation == Angle::D270); + } + + bool operator==(const Orientation& aOther) const { + return (rotation == aOther.rotation) && (flip == aOther.flip); + } + + bool operator!=(const Orientation& aOther) const { + return !(*this == aOther); + } + + Angle rotation; + Flip flip; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_Orientation_h diff --git a/image/OrientedImage.cpp b/image/OrientedImage.cpp new file mode 100644 index 000000000..9489ceafd --- /dev/null +++ b/image/OrientedImage.cpp @@ -0,0 +1,354 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "OrientedImage.h" + +#include + +#include "gfx2DGlue.h" +#include "gfxDrawable.h" +#include "gfxPlatform.h" +#include "gfxUtils.h" +#include "ImageRegion.h" +#include "SVGImageContext.h" + +using std::swap; + +namespace mozilla { + +using namespace gfx; +using layers::LayerManager; +using layers::ImageContainer; + +namespace image { + +NS_IMPL_ISUPPORTS_INHERITED0(OrientedImage, ImageWrapper) + +NS_IMETHODIMP +OrientedImage::GetWidth(int32_t* aWidth) +{ + if (mOrientation.SwapsWidthAndHeight()) { + return InnerImage()->GetHeight(aWidth); + } else { + return InnerImage()->GetWidth(aWidth); + } +} + +NS_IMETHODIMP +OrientedImage::GetHeight(int32_t* aHeight) +{ + if (mOrientation.SwapsWidthAndHeight()) { + return InnerImage()->GetWidth(aHeight); + } else { + return InnerImage()->GetHeight(aHeight); + } +} + +NS_IMETHODIMP +OrientedImage::GetIntrinsicSize(nsSize* aSize) +{ + nsresult rv = InnerImage()->GetIntrinsicSize(aSize); + + if (mOrientation.SwapsWidthAndHeight()) { + swap(aSize->width, aSize->height); + } + + return rv; +} + +NS_IMETHODIMP +OrientedImage::GetIntrinsicRatio(nsSize* aRatio) +{ + nsresult rv = InnerImage()->GetIntrinsicRatio(aRatio); + + if (mOrientation.SwapsWidthAndHeight()) { + swap(aRatio->width, aRatio->height); + } + + return rv; +} + +NS_IMETHODIMP_(already_AddRefed) +OrientedImage::GetFrame(uint32_t aWhichFrame, + uint32_t aFlags) +{ + nsresult rv; + + if (mOrientation.IsIdentity()) { + return InnerImage()->GetFrame(aWhichFrame, aFlags); + } + + // Get the underlying dimensions. + IntSize size; + rv = InnerImage()->GetWidth(&size.width); + NS_ENSURE_SUCCESS(rv, nullptr); + rv = InnerImage()->GetHeight(&size.height); + NS_ENSURE_SUCCESS(rv, nullptr); + + // Determine an appropriate format for the surface. + gfx::SurfaceFormat surfaceFormat; + if (InnerImage()->WillDrawOpaqueNow()) { + surfaceFormat = gfx::SurfaceFormat::B8G8R8X8; + } else { + surfaceFormat = gfx::SurfaceFormat::B8G8R8A8; + } + + // Create a surface to draw into. + RefPtr target = + gfxPlatform::GetPlatform()-> + CreateOffscreenContentDrawTarget(size, surfaceFormat); + if (!target || !target->IsValid()) { + NS_ERROR("Could not create a DrawTarget"); + return nullptr; + } + + + // Create our drawable. + RefPtr innerSurface = + InnerImage()->GetFrame(aWhichFrame, aFlags); + NS_ENSURE_TRUE(innerSurface, nullptr); + RefPtr drawable = + new gfxSurfaceDrawable(innerSurface, size); + + // Draw. + RefPtr ctx = gfxContext::CreateOrNull(target); + MOZ_ASSERT(ctx); // already checked the draw target above + ctx->Multiply(OrientationMatrix(size)); + gfxUtils::DrawPixelSnapped(ctx, drawable, size, ImageRegion::Create(size), + surfaceFormat, SamplingFilter::LINEAR); + + return target->Snapshot(); +} + +NS_IMETHODIMP_(already_AddRefed) +OrientedImage::GetFrameAtSize(const IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags) +{ + // XXX(seth): It'd be nice to support downscale-during-decode for this case, + // but right now we just fall back to the intrinsic size. + return GetFrame(aWhichFrame, aFlags); +} + +NS_IMETHODIMP_(bool) +OrientedImage::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags) +{ + if (mOrientation.IsIdentity()) { + return InnerImage()->IsImageContainerAvailable(aManager, aFlags); + } + return false; +} + +NS_IMETHODIMP_(already_AddRefed) +OrientedImage::GetImageContainer(LayerManager* aManager, uint32_t aFlags) +{ + // XXX(seth): We currently don't have a way of orienting the result of + // GetImageContainer. We work around this by always returning null, but if it + // ever turns out that OrientedImage is widely used on codepaths that can + // actually benefit from GetImageContainer, it would be a good idea to fix + // that method for performance reasons. + + if (mOrientation.IsIdentity()) { + return InnerImage()->GetImageContainer(aManager, aFlags); + } + + return nullptr; +} + +struct MatrixBuilder +{ + explicit MatrixBuilder(bool aInvert) : mInvert(aInvert) { } + + gfxMatrix Build() { return mMatrix; } + + void Scale(gfxFloat aX, gfxFloat aY) + { + if (mInvert) { + mMatrix *= gfxMatrix::Scaling(1.0 / aX, 1.0 / aY); + } else { + mMatrix.Scale(aX, aY); + } + } + + void Rotate(gfxFloat aPhi) + { + if (mInvert) { + mMatrix *= gfxMatrix::Rotation(-aPhi); + } else { + mMatrix.Rotate(aPhi); + } + } + + void Translate(gfxPoint aDelta) + { + if (mInvert) { + mMatrix *= gfxMatrix::Translation(-aDelta); + } else { + mMatrix.Translate(aDelta); + } + } + +private: + gfxMatrix mMatrix; + bool mInvert; +}; + +/* + * OrientationMatrix() computes a matrix that applies the rotation and + * reflection specified by mOrientation, or that matrix's inverse if aInvert is + * true. + * + * @param aSize The scaled size of the inner image. (When outside code specifies + * the scaled size, as with imgIContainer::Draw and its aSize + * parameter, it's necessary to swap the width and height if + * mOrientation.SwapsWidthAndHeight() is true.) + * @param aInvert If true, compute the inverse of the orientation matrix. Prefer + * this approach to OrientationMatrix(..).Invert(), because it's + * more numerically accurate. + */ +gfxMatrix +OrientedImage::OrientationMatrix(const nsIntSize& aSize, + bool aInvert /* = false */) +{ + MatrixBuilder builder(aInvert); + + // Apply reflection, if present. (This logically happens second, but we + // apply it first because these transformations are all premultiplied.) A + // translation is necessary to place the image back in the first quadrant. + switch (mOrientation.flip) { + case Flip::Unflipped: + break; + case Flip::Horizontal: + if (mOrientation.SwapsWidthAndHeight()) { + builder.Translate(gfxPoint(aSize.height, 0)); + } else { + builder.Translate(gfxPoint(aSize.width, 0)); + } + builder.Scale(-1.0, 1.0); + break; + default: + MOZ_ASSERT(false, "Invalid flip value"); + } + + // Apply rotation, if present. Again, a translation is used to place the + // image back in the first quadrant. + switch (mOrientation.rotation) { + case Angle::D0: + break; + case Angle::D90: + builder.Translate(gfxPoint(aSize.height, 0)); + builder.Rotate(-1.5 * M_PI); + break; + case Angle::D180: + builder.Translate(gfxPoint(aSize.width, aSize.height)); + builder.Rotate(-1.0 * M_PI); + break; + case Angle::D270: + builder.Translate(gfxPoint(0, aSize.width)); + builder.Rotate(-0.5 * M_PI); + break; + default: + MOZ_ASSERT(false, "Invalid rotation value"); + } + + return builder.Build(); +} + +NS_IMETHODIMP_(DrawResult) +OrientedImage::Draw(gfxContext* aContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, + uint32_t aWhichFrame, + SamplingFilter aSamplingFilter, + const Maybe& aSVGContext, + uint32_t aFlags) +{ + if (mOrientation.IsIdentity()) { + return InnerImage()->Draw(aContext, aSize, aRegion, + aWhichFrame, aSamplingFilter, + aSVGContext, aFlags); + } + + // Update the image size to match the image's coordinate system. (This could + // be done using TransformBounds but since it's only a size a swap is enough.) + nsIntSize size(aSize); + if (mOrientation.SwapsWidthAndHeight()) { + swap(size.width, size.height); + } + + // Update the matrix so that we transform the image into the orientation + // expected by the caller before drawing. + gfxMatrix matrix(OrientationMatrix(size)); + gfxContextMatrixAutoSaveRestore saveMatrix(aContext); + aContext->Multiply(matrix); + + // The region is already in the orientation expected by the caller, but we + // need it to be in the image's coordinate system, so we transform it using + // the inverse of the orientation matrix. + gfxMatrix inverseMatrix(OrientationMatrix(size, /* aInvert = */ true)); + ImageRegion region(aRegion); + region.TransformBoundsBy(inverseMatrix); + + auto orientViewport = [&](const SVGImageContext& aOldContext) { + CSSIntSize viewportSize(aOldContext.GetViewportSize()); + if (mOrientation.SwapsWidthAndHeight()) { + swap(viewportSize.width, viewportSize.height); + } + return SVGImageContext(viewportSize, + aOldContext.GetPreserveAspectRatio()); + }; + + return InnerImage()->Draw(aContext, size, region, aWhichFrame, aSamplingFilter, + aSVGContext.map(orientViewport), aFlags); +} + +nsIntSize +OrientedImage::OptimalImageSizeForDest(const gfxSize& aDest, + uint32_t aWhichFrame, + SamplingFilter aSamplingFilter, + uint32_t aFlags) +{ + if (!mOrientation.SwapsWidthAndHeight()) { + return InnerImage()->OptimalImageSizeForDest(aDest, aWhichFrame, + aSamplingFilter, aFlags); + } + + // Swap the size for the calculation, then swap it back for the caller. + gfxSize destSize(aDest.height, aDest.width); + nsIntSize innerImageSize(InnerImage()->OptimalImageSizeForDest(destSize, + aWhichFrame, + aSamplingFilter, + aFlags)); + return nsIntSize(innerImageSize.height, innerImageSize.width); +} + +NS_IMETHODIMP_(nsIntRect) +OrientedImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect) +{ + nsIntRect rect(InnerImage()->GetImageSpaceInvalidationRect(aRect)); + + if (mOrientation.IsIdentity()) { + return rect; + } + + nsIntSize innerSize; + nsresult rv = InnerImage()->GetWidth(&innerSize.width); + rv = NS_FAILED(rv) ? rv : InnerImage()->GetHeight(&innerSize.height); + if (NS_FAILED(rv)) { + // Fall back to identity if the width and height aren't available. + return rect; + } + + // Transform the invalidation rect into the correct orientation. + gfxMatrix matrix(OrientationMatrix(innerSize)); + gfxRect invalidRect(matrix.TransformBounds(gfxRect(rect.x, rect.y, + rect.width, rect.height))); + + return IntRect::RoundOut(invalidRect.x, invalidRect.y, + invalidRect.width, invalidRect.height); +} + +} // namespace image +} // namespace mozilla diff --git a/image/OrientedImage.h b/image/OrientedImage.h new file mode 100644 index 000000000..604ab3d4f --- /dev/null +++ b/image/OrientedImage.h @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_OrientedImage_h +#define mozilla_image_OrientedImage_h + +#include "ImageWrapper.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/RefPtr.h" +#include "Orientation.h" + +namespace mozilla { +namespace image { + +/** + * An Image wrapper that rotates and/or flips an image according to a specified + * Orientation. + * + * XXX(seth): There a known (performance, not correctness) issue with + * GetImageContainer. See the comments for that method for more information. + */ +class OrientedImage : public ImageWrapper +{ + typedef gfx::SourceSurface SourceSurface; + +public: + NS_DECL_ISUPPORTS_INHERITED + + NS_IMETHOD GetWidth(int32_t* aWidth) override; + NS_IMETHOD GetHeight(int32_t* aHeight) override; + NS_IMETHOD GetIntrinsicSize(nsSize* aSize) override; + NS_IMETHOD GetIntrinsicRatio(nsSize* aRatio) override; + NS_IMETHOD_(already_AddRefed) + GetFrame(uint32_t aWhichFrame, uint32_t aFlags) override; + NS_IMETHOD_(already_AddRefed) + GetFrameAtSize(const gfx::IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags) override; + NS_IMETHOD_(bool) IsImageContainerAvailable(layers::LayerManager* aManager, + uint32_t aFlags) override; + NS_IMETHOD_(already_AddRefed) + GetImageContainer(layers::LayerManager* aManager, + uint32_t aFlags) override; + NS_IMETHOD_(DrawResult) Draw(gfxContext* aContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, + uint32_t aWhichFrame, + gfx::SamplingFilter aSamplingFilter, + const Maybe& aSVGContext, + uint32_t aFlags) override; + NS_IMETHOD_(nsIntRect) GetImageSpaceInvalidationRect( + const nsIntRect& aRect) override; + nsIntSize OptimalImageSizeForDest(const gfxSize& aDest, + uint32_t aWhichFrame, + gfx::SamplingFilter aSamplingFilter, + uint32_t aFlags) override; + +protected: + OrientedImage(Image* aImage, Orientation aOrientation) + : ImageWrapper(aImage) + , mOrientation(aOrientation) + { } + + virtual ~OrientedImage() { } + + gfxMatrix OrientationMatrix(const nsIntSize& aSize, bool aInvert = false); + +private: + Orientation mOrientation; + + friend class ImageOps; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_OrientedImage_h diff --git a/image/PlaybackType.h b/image/PlaybackType.h new file mode 100644 index 000000000..0fbc6b024 --- /dev/null +++ b/image/PlaybackType.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_PlaybackType_h +#define mozilla_image_PlaybackType_h + +#include "imgIContainer.h" + +namespace mozilla { +namespace image { + +/** + * PlaybackType identifies a surface cache entry as either a static surface or + * an animation. Note that a specific cache entry is one or the other, but + * images may be associated with both types of cache entries, since in some + * circumstances we may want to treat an animated image as if it were static. + */ +enum class PlaybackType : uint8_t +{ + eStatic, // Calls to DrawableRef() will always return the same surface. + eAnimated // An animation; calls to DrawableRef() may return different + // surfaces at different times. +}; + +/** + * Given an imgIContainer FRAME_* value, returns the corresponding PlaybackType + * for use in surface cache lookups. + */ +inline PlaybackType +ToPlaybackType(uint32_t aWhichFrame) +{ + MOZ_ASSERT(aWhichFrame == imgIContainer::FRAME_FIRST || + aWhichFrame == imgIContainer::FRAME_CURRENT); + return aWhichFrame == imgIContainer::FRAME_CURRENT + ? PlaybackType::eAnimated + : PlaybackType::eStatic; +} + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_PlaybackType_h diff --git a/image/ProgressTracker.cpp b/image/ProgressTracker.cpp new file mode 100644 index 000000000..912b1d17c --- /dev/null +++ b/image/ProgressTracker.cpp @@ -0,0 +1,570 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageLogging.h" +#include "ProgressTracker.h" + +#include "imgIContainer.h" +#include "imgINotificationObserver.h" +#include "imgIRequest.h" +#include "Image.h" +#include "nsNetUtil.h" +#include "nsIObserverService.h" + +#include "mozilla/Assertions.h" +#include "mozilla/Services.h" + +using mozilla::WeakPtr; + +namespace mozilla { +namespace image { + +static void +CheckProgressConsistency(Progress aOldProgress, Progress aNewProgress) +{ + // Check preconditions for every progress bit. + + if (aNewProgress & FLAG_SIZE_AVAILABLE) { + // No preconditions. + } + if (aNewProgress & FLAG_DECODE_COMPLETE) { + MOZ_ASSERT(aNewProgress & FLAG_SIZE_AVAILABLE); + MOZ_ASSERT(aNewProgress & (FLAG_FRAME_COMPLETE | FLAG_HAS_ERROR)); + } + if (aNewProgress & FLAG_FRAME_COMPLETE) { + MOZ_ASSERT(aNewProgress & FLAG_SIZE_AVAILABLE); + } + if (aNewProgress & FLAG_LOAD_COMPLETE) { + MOZ_ASSERT(aNewProgress & (FLAG_SIZE_AVAILABLE | FLAG_HAS_ERROR)); + } + if (aNewProgress & FLAG_ONLOAD_BLOCKED) { + // No preconditions. + } + if (aNewProgress & FLAG_ONLOAD_UNBLOCKED) { + MOZ_ASSERT(aNewProgress & FLAG_ONLOAD_BLOCKED); + MOZ_ASSERT(aNewProgress & (FLAG_SIZE_AVAILABLE | FLAG_HAS_ERROR)); + } + if (aNewProgress & FLAG_IS_ANIMATED) { + // No preconditions; like FLAG_HAS_TRANSPARENCY, we should normally never + // discover this *after* FLAG_SIZE_AVAILABLE, but unfortunately some corrupt + // GIFs may fool us. + } + if (aNewProgress & FLAG_HAS_TRANSPARENCY) { + // XXX We'd like to assert that transparency is only set during metadata + // decode but we don't have any way to assert that until bug 1254892 is fixed. + } + if (aNewProgress & FLAG_LAST_PART_COMPLETE) { + MOZ_ASSERT(aNewProgress & FLAG_LOAD_COMPLETE); + } + if (aNewProgress & FLAG_HAS_ERROR) { + // No preconditions. + } +} + +void +ProgressTracker::SetImage(Image* aImage) +{ + MutexAutoLock lock(mImageMutex); + MOZ_ASSERT(aImage, "Setting null image"); + MOZ_ASSERT(!mImage, "Setting image when we already have one"); + mImage = aImage; +} + +void +ProgressTracker::ResetImage() +{ + MutexAutoLock lock(mImageMutex); + MOZ_ASSERT(mImage, "Resetting image when it's already null!"); + mImage = nullptr; +} + +uint32_t +ProgressTracker::GetImageStatus() const +{ + uint32_t status = imgIRequest::STATUS_NONE; + + // Translate our current state to a set of imgIRequest::STATE_* flags. + if (mProgress & FLAG_SIZE_AVAILABLE) { + status |= imgIRequest::STATUS_SIZE_AVAILABLE; + } + if (mProgress & FLAG_DECODE_COMPLETE) { + status |= imgIRequest::STATUS_DECODE_COMPLETE; + } + if (mProgress & FLAG_FRAME_COMPLETE) { + status |= imgIRequest::STATUS_FRAME_COMPLETE; + } + if (mProgress & FLAG_LOAD_COMPLETE) { + status |= imgIRequest::STATUS_LOAD_COMPLETE; + } + if (mProgress & FLAG_IS_ANIMATED) { + status |= imgIRequest::STATUS_IS_ANIMATED; + } + if (mProgress & FLAG_HAS_TRANSPARENCY) { + status |= imgIRequest::STATUS_HAS_TRANSPARENCY; + } + if (mProgress & FLAG_HAS_ERROR) { + status |= imgIRequest::STATUS_ERROR; + } + + return status; +} + +// A helper class to allow us to call SyncNotify asynchronously. +class AsyncNotifyRunnable : public Runnable +{ + public: + AsyncNotifyRunnable(ProgressTracker* aTracker, + IProgressObserver* aObserver) + : mTracker(aTracker) + { + MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread"); + MOZ_ASSERT(aTracker, "aTracker should not be null"); + MOZ_ASSERT(aObserver, "aObserver should not be null"); + mObservers.AppendElement(aObserver); + } + + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread"); + MOZ_ASSERT(mTracker, "mTracker should not be null"); + for (uint32_t i = 0; i < mObservers.Length(); ++i) { + mObservers[i]->SetNotificationsDeferred(false); + mTracker->SyncNotify(mObservers[i]); + } + + mTracker->mRunnable = nullptr; + return NS_OK; + } + + void AddObserver(IProgressObserver* aObserver) + { + mObservers.AppendElement(aObserver); + } + + void RemoveObserver(IProgressObserver* aObserver) + { + mObservers.RemoveElement(aObserver); + } + + private: + friend class ProgressTracker; + + RefPtr mTracker; + nsTArray> mObservers; +}; + +void +ProgressTracker::Notify(IProgressObserver* aObserver) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) { + RefPtr image = GetImage(); + if (image && image->GetURI()) { + RefPtr uri(image->GetURI()); + nsAutoCString spec; + uri->GetSpec(spec); + LOG_FUNC_WITH_PARAM(gImgLog, + "ProgressTracker::Notify async", "uri", spec.get()); + } else { + LOG_FUNC_WITH_PARAM(gImgLog, + "ProgressTracker::Notify async", "uri", ""); + } + } + + aObserver->SetNotificationsDeferred(true); + + // If we have an existing runnable that we can use, we just append this + // observer to its list of observers to be notified. This ensures we don't + // unnecessarily delay onload. + AsyncNotifyRunnable* runnable = + static_cast(mRunnable.get()); + + if (runnable) { + runnable->AddObserver(aObserver); + } else { + mRunnable = new AsyncNotifyRunnable(this, aObserver); + NS_DispatchToCurrentThread(mRunnable); + } +} + +// A helper class to allow us to call SyncNotify asynchronously for a given, +// fixed, state. +class AsyncNotifyCurrentStateRunnable : public Runnable +{ + public: + AsyncNotifyCurrentStateRunnable(ProgressTracker* aProgressTracker, + IProgressObserver* aObserver) + : mProgressTracker(aProgressTracker) + , mObserver(aObserver) + { + MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread"); + MOZ_ASSERT(mProgressTracker, "mProgressTracker should not be null"); + MOZ_ASSERT(mObserver, "mObserver should not be null"); + mImage = mProgressTracker->GetImage(); + } + + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread"); + mObserver->SetNotificationsDeferred(false); + + mProgressTracker->SyncNotify(mObserver); + return NS_OK; + } + + private: + RefPtr mProgressTracker; + RefPtr mObserver; + + // We have to hold on to a reference to the tracker's image, just in case + // it goes away while we're in the event queue. + RefPtr mImage; +}; + +void +ProgressTracker::NotifyCurrentState(IProgressObserver* aObserver) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) { + RefPtr image = GetImage(); + nsAutoCString spec; + if (image && image->GetURI()) { + image->GetURI()->GetSpec(spec); + } + LOG_FUNC_WITH_PARAM(gImgLog, + "ProgressTracker::NotifyCurrentState", "uri", spec.get()); + } + + aObserver->SetNotificationsDeferred(true); + + nsCOMPtr ev = new AsyncNotifyCurrentStateRunnable(this, + aObserver); + NS_DispatchToCurrentThread(ev); +} + +/** + * ImageObserverNotifier is a helper type that abstracts over the difference + * between sending notifications to all of the observers in an ObserverTable, + * and sending them to a single observer. This allows the same notification code + * to be used for both cases. + */ +template struct ImageObserverNotifier; + +template <> +struct MOZ_STACK_CLASS ImageObserverNotifier +{ + explicit ImageObserverNotifier(const ObserverTable* aObservers, + bool aIgnoreDeferral = false) + : mObservers(aObservers) + , mIgnoreDeferral(aIgnoreDeferral) + { } + + template + void operator()(Lambda aFunc) + { + for (auto iter = mObservers->ConstIter(); !iter.Done(); iter.Next()) { + RefPtr observer = iter.Data().get(); + if (observer && + (mIgnoreDeferral || !observer->NotificationsDeferred())) { + aFunc(observer); + } + } + } + +private: + const ObserverTable* mObservers; + const bool mIgnoreDeferral; +}; + +template <> +struct MOZ_STACK_CLASS ImageObserverNotifier +{ + explicit ImageObserverNotifier(IProgressObserver* aObserver) + : mObserver(aObserver) + { } + + template + void operator()(Lambda aFunc) + { + if (mObserver && !mObserver->NotificationsDeferred()) { + aFunc(mObserver); + } + } + +private: + IProgressObserver* mObserver; +}; + +template void +SyncNotifyInternal(const T& aObservers, + bool aHasImage, + Progress aProgress, + const nsIntRect& aDirtyRect) +{ + MOZ_ASSERT(NS_IsMainThread()); + + typedef imgINotificationObserver I; + ImageObserverNotifier notify(aObservers); + + if (aProgress & FLAG_SIZE_AVAILABLE) { + notify([](IProgressObserver* aObs) { aObs->Notify(I::SIZE_AVAILABLE); }); + } + + if (aProgress & FLAG_ONLOAD_BLOCKED) { + notify([](IProgressObserver* aObs) { aObs->BlockOnload(); }); + } + + if (aHasImage) { + // OnFrameUpdate + // If there's any content in this frame at all (always true for + // vector images, true for raster images that have decoded at + // least one frame) then send OnFrameUpdate. + if (!aDirtyRect.IsEmpty()) { + notify([&](IProgressObserver* aObs) { + aObs->Notify(I::FRAME_UPDATE, &aDirtyRect); + }); + } + + if (aProgress & FLAG_FRAME_COMPLETE) { + notify([](IProgressObserver* aObs) { aObs->Notify(I::FRAME_COMPLETE); }); + } + + if (aProgress & FLAG_HAS_TRANSPARENCY) { + notify([](IProgressObserver* aObs) { aObs->Notify(I::HAS_TRANSPARENCY); }); + } + + if (aProgress & FLAG_IS_ANIMATED) { + notify([](IProgressObserver* aObs) { aObs->Notify(I::IS_ANIMATED); }); + } + } + + // Send UnblockOnload before OnStopDecode and OnStopRequest. This allows + // observers that can fire events when they receive those notifications to do + // so then, instead of being forced to wait for UnblockOnload. + if (aProgress & FLAG_ONLOAD_UNBLOCKED) { + notify([](IProgressObserver* aObs) { aObs->UnblockOnload(); }); + } + + if (aProgress & FLAG_DECODE_COMPLETE) { + MOZ_ASSERT(aHasImage, "Stopped decoding without ever having an image?"); + notify([](IProgressObserver* aObs) { aObs->Notify(I::DECODE_COMPLETE); }); + } + + if (aProgress & FLAG_LOAD_COMPLETE) { + notify([=](IProgressObserver* aObs) { + aObs->OnLoadComplete(aProgress & FLAG_LAST_PART_COMPLETE); + }); + } +} + +void +ProgressTracker::SyncNotifyProgress(Progress aProgress, + const nsIntRect& aInvalidRect + /* = nsIntRect() */) +{ + MOZ_ASSERT(NS_IsMainThread(), "Use mObservers on main thread only"); + + // Don't unblock onload if we're not blocked. + Progress progress = Difference(aProgress); + if (!((mProgress | progress) & FLAG_ONLOAD_BLOCKED)) { + progress &= ~FLAG_ONLOAD_UNBLOCKED; + } + + CheckProgressConsistency(mProgress, mProgress | progress); + + // XXX(seth): Hack to work around the fact that some observers have bugs and + // need to get onload blocking notifications multiple times. We should fix + // those observers and remove this. + if ((aProgress & FLAG_DECODE_COMPLETE) && + (mProgress & FLAG_ONLOAD_BLOCKED) && + (mProgress & FLAG_ONLOAD_UNBLOCKED)) { + progress |= FLAG_ONLOAD_BLOCKED | FLAG_ONLOAD_UNBLOCKED; + } + + // Apply the changes. + mProgress |= progress; + + // Send notifications. + mObservers.Read([&](const ObserverTable* aTable) { + SyncNotifyInternal(aTable, HasImage(), progress, aInvalidRect); + }); + + if (progress & FLAG_HAS_ERROR) { + FireFailureNotification(); + } +} + +void +ProgressTracker::SyncNotify(IProgressObserver* aObserver) +{ + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr image = GetImage(); + + nsAutoCString spec; + if (image && image->GetURI()) { + image->GetURI()->GetSpec(spec); + } + LOG_SCOPE_WITH_PARAM(gImgLog, + "ProgressTracker::SyncNotify", "uri", spec.get()); + + nsIntRect rect; + if (image) { + if (NS_FAILED(image->GetWidth(&rect.width)) || + NS_FAILED(image->GetHeight(&rect.height))) { + // Either the image has no intrinsic size, or it has an error. + rect = GetMaxSizedIntRect(); + } + } + + SyncNotifyInternal(aObserver, !!image, mProgress, rect); +} + +void +ProgressTracker::EmulateRequestFinished(IProgressObserver* aObserver) +{ + MOZ_ASSERT(NS_IsMainThread(), + "SyncNotifyState and mObservers are not threadsafe"); + RefPtr kungFuDeathGrip(aObserver); + + if (mProgress & FLAG_ONLOAD_BLOCKED && !(mProgress & FLAG_ONLOAD_UNBLOCKED)) { + aObserver->UnblockOnload(); + } + + if (!(mProgress & FLAG_LOAD_COMPLETE)) { + aObserver->OnLoadComplete(true); + } +} + +void +ProgressTracker::AddObserver(IProgressObserver* aObserver) +{ + MOZ_ASSERT(NS_IsMainThread()); + RefPtr observer = aObserver; + + mObservers.Write([=](ObserverTable* aTable) { + MOZ_ASSERT(!aTable->Get(observer, nullptr), + "Adding duplicate entry for image observer"); + + WeakPtr weakPtr = observer.get(); + aTable->Put(observer, weakPtr); + }); +} + +bool +ProgressTracker::RemoveObserver(IProgressObserver* aObserver) +{ + MOZ_ASSERT(NS_IsMainThread()); + RefPtr observer = aObserver; + + // Remove the observer from the list. + bool removed = mObservers.Write([=](ObserverTable* aTable) { + bool removed = aTable->Get(observer, nullptr); + aTable->Remove(observer); + return removed; + }); + + // Observers can get confused if they don't get all the proper teardown + // notifications. Part ways on good terms. + if (removed && !aObserver->NotificationsDeferred()) { + EmulateRequestFinished(aObserver); + } + + // Make sure we don't give callbacks to an observer that isn't interested in + // them any more. + AsyncNotifyRunnable* runnable = + static_cast(mRunnable.get()); + + if (aObserver->NotificationsDeferred() && runnable) { + runnable->RemoveObserver(aObserver); + aObserver->SetNotificationsDeferred(false); + } + + return removed; +} + +uint32_t +ProgressTracker::ObserverCount() const +{ + MOZ_ASSERT(NS_IsMainThread()); + return mObservers.Read([](const ObserverTable* aTable) { + return aTable->Count(); + }); +} + +void +ProgressTracker::OnUnlockedDraw() +{ + MOZ_ASSERT(NS_IsMainThread()); + mObservers.Read([](const ObserverTable* aTable) { + ImageObserverNotifier notify(aTable); + notify([](IProgressObserver* aObs) { + aObs->Notify(imgINotificationObserver::UNLOCKED_DRAW); + }); + }); +} + +void +ProgressTracker::ResetForNewRequest() +{ + MOZ_ASSERT(NS_IsMainThread()); + mProgress = NoProgress; +} + +void +ProgressTracker::OnDiscard() +{ + MOZ_ASSERT(NS_IsMainThread()); + mObservers.Read([](const ObserverTable* aTable) { + ImageObserverNotifier notify(aTable); + notify([](IProgressObserver* aObs) { + aObs->Notify(imgINotificationObserver::DISCARD); + }); + }); +} + +void +ProgressTracker::OnImageAvailable() +{ + MOZ_ASSERT(NS_IsMainThread()); + // Notify any imgRequestProxys that are observing us that we have an Image. + mObservers.Read([](const ObserverTable* aTable) { + ImageObserverNotifier + notify(aTable, /* aIgnoreDeferral = */ true); + notify([](IProgressObserver* aObs) { + aObs->SetHasImage(); + }); + }); +} + +void +ProgressTracker::FireFailureNotification() +{ + MOZ_ASSERT(NS_IsMainThread()); + + // Some kind of problem has happened with image decoding. + // Report the URI to net:failed-to-process-uri-conent observers. + RefPtr image = GetImage(); + if (image) { + // Should be on main thread, so ok to create a new nsIURI. + nsCOMPtr uri; + { + RefPtr threadsafeUriData = image->GetURI(); + uri = threadsafeUriData ? threadsafeUriData->ToIURI() : nullptr; + } + if (uri) { + nsCOMPtr os = mozilla::services::GetObserverService(); + if (os) { + os->NotifyObservers(uri, "net:failed-to-process-uri-content", nullptr); + } + } + } +} + +} // namespace image +} // namespace mozilla diff --git a/image/ProgressTracker.h b/image/ProgressTracker.h new file mode 100644 index 000000000..e15e85323 --- /dev/null +++ b/image/ProgressTracker.h @@ -0,0 +1,234 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_ProgressTracker_h +#define mozilla_image_ProgressTracker_h + +#include "CopyOnWrite.h" +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" +#include "mozilla/WeakPtr.h" +#include "nsDataHashtable.h" +#include "nsCOMPtr.h" +#include "nsTObserverArray.h" +#include "nsThreadUtils.h" +#include "nsRect.h" +#include "IProgressObserver.h" + +class nsIRunnable; + +namespace mozilla { +namespace image { + +class AsyncNotifyRunnable; +class AsyncNotifyCurrentStateRunnable; +class Image; + +/** + * Image progress bitflags. + * + * See CheckProgressConsistency() for the invariants we enforce about the + * ordering dependencies betweeen these flags. + */ +enum { + FLAG_SIZE_AVAILABLE = 1u << 0, // STATUS_SIZE_AVAILABLE + FLAG_DECODE_COMPLETE = 1u << 1, // STATUS_DECODE_COMPLETE + FLAG_FRAME_COMPLETE = 1u << 2, // STATUS_FRAME_COMPLETE + FLAG_LOAD_COMPLETE = 1u << 3, // STATUS_LOAD_COMPLETE + FLAG_ONLOAD_BLOCKED = 1u << 4, + FLAG_ONLOAD_UNBLOCKED = 1u << 5, + FLAG_IS_ANIMATED = 1u << 6, // STATUS_IS_ANIMATED + FLAG_HAS_TRANSPARENCY = 1u << 7, // STATUS_HAS_TRANSPARENCY + FLAG_LAST_PART_COMPLETE = 1u << 8, + FLAG_HAS_ERROR = 1u << 9 // STATUS_ERROR +}; + +typedef uint32_t Progress; + +const uint32_t NoProgress = 0; + +inline Progress LoadCompleteProgress(bool aLastPart, + bool aError, + nsresult aStatus) +{ + Progress progress = FLAG_LOAD_COMPLETE; + if (aLastPart) { + progress |= FLAG_LAST_PART_COMPLETE; + } + if (NS_FAILED(aStatus) || aError) { + progress |= FLAG_HAS_ERROR; + } + return progress; +} + +/** + * ProgressTracker stores its observers in an ObserverTable, which is a hash + * table mapping raw pointers to WeakPtr's to the same objects. This sounds like + * unnecessary duplication of information, but it's necessary for stable hash + * values since WeakPtr's lose the knowledge of which object they used to point + * to when that object is destroyed. + * + * ObserverTable subclasses nsDataHashtable to add reference counting support + * and a copy constructor, both of which are needed for use with CopyOnWrite. + */ +class ObserverTable + : public nsDataHashtable, + WeakPtr> +{ +public: + NS_INLINE_DECL_REFCOUNTING(ObserverTable); + + ObserverTable() = default; + + ObserverTable(const ObserverTable& aOther) + { + NS_WARNING("Forced to copy ObserverTable due to nested notifications"); + for (auto iter = aOther.ConstIter(); !iter.Done(); iter.Next()) { + this->Put(iter.Key(), iter.Data()); + } + } + +private: + ~ObserverTable() { } +}; + +/** + * ProgressTracker is a class that records an Image's progress through the + * loading and decoding process, and makes it possible to send notifications to + * IProgressObservers, both synchronously and asynchronously. + * + * When a new observer needs to be notified of the current progress of an image, + * call the Notify() method on this class with the relevant observer as its + * argument, and the notifications will be replayed to the observer + * asynchronously. + */ +class ProgressTracker : public mozilla::SupportsWeakPtr +{ + virtual ~ProgressTracker() { } + +public: + MOZ_DECLARE_WEAKREFERENCE_TYPENAME(ProgressTracker) + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ProgressTracker) + + ProgressTracker() + : mImageMutex("ProgressTracker::mImage") + , mImage(nullptr) + , mObservers(new ObserverTable) + , mProgress(NoProgress) + { } + + bool HasImage() const { MutexAutoLock lock(mImageMutex); return mImage; } + already_AddRefed GetImage() const + { + MutexAutoLock lock(mImageMutex); + RefPtr image = mImage; + return image.forget(); + } + + // Get the current image status (as in imgIRequest). + uint32_t GetImageStatus() const; + + // Get the current Progress. + Progress GetProgress() const { return mProgress; } + + // Schedule an asynchronous "replaying" of all the notifications that would + // have to happen to put us in the current state. + // We will also take note of any notifications that happen between the time + // Notify() is called and when we call SyncNotify on |aObserver|, and replay + // them as well. + // Should be called on the main thread only, since observers and GetURI are + // not threadsafe. + void Notify(IProgressObserver* aObserver); + + // Schedule an asynchronous "replaying" of all the notifications that would + // have to happen to put us in the state we are in right now. + // Unlike Notify(), does *not* take into account future notifications. + // This is only useful if you do not have an imgRequest, e.g., if you are a + // static request returned from imgIRequest::GetStaticRequest(). + // Should be called on the main thread only, since observers and GetURI are + // not threadsafe. + void NotifyCurrentState(IProgressObserver* aObserver); + + // "Replay" all of the notifications that would have to happen to put us in + // the state we're currently in. + // Only use this if you're already servicing an asynchronous call (e.g. + // OnStartRequest). + // Should be called on the main thread only, since observers and GetURI are + // not threadsafe. + void SyncNotify(IProgressObserver* aObserver); + + // Get this ProgressTracker ready for a new request. This resets all the + // state that doesn't persist between requests. + void ResetForNewRequest(); + + // Stateless notifications. These are dispatched and immediately forgotten + // about. All of these notifications are main thread only. + void OnDiscard(); + void OnUnlockedDraw(); + void OnImageAvailable(); + + // Compute the difference between this our progress and aProgress. This allows + // callers to predict whether SyncNotifyProgress will send any notifications. + Progress Difference(Progress aProgress) const + { + return ~mProgress & aProgress; + } + + // Update our state to incorporate the changes in aProgress and synchronously + // notify our observers. + // + // Because this may result in recursive notifications, no decoding locks may + // be held. Called on the main thread only. + void SyncNotifyProgress(Progress aProgress, + const nsIntRect& aInvalidRect = nsIntRect()); + + // We manage a set of observers that are using an image and thus concerned + // with its loading progress. Weak pointers. + void AddObserver(IProgressObserver* aObserver); + bool RemoveObserver(IProgressObserver* aObserver); + uint32_t ObserverCount() const; + + // Resets our weak reference to our image. Image subclasses should call this + // in their destructor. + void ResetImage(); + +private: + friend class AsyncNotifyRunnable; + friend class AsyncNotifyCurrentStateRunnable; + friend class ImageFactory; + + ProgressTracker(const ProgressTracker& aOther) = delete; + + // Sets our weak reference to our image. Only ImageFactory should call this. + void SetImage(Image* aImage); + + // Send some notifications that would be necessary to make |aObserver| believe + // the request is finished downloading and decoding. We only send + // FLAG_LOAD_COMPLETE and FLAG_ONLOAD_UNBLOCKED, and only if necessary. + void EmulateRequestFinished(IProgressObserver* aObserver); + + // Main thread only because it deals with the observer service. + void FireFailureNotification(); + + // The runnable, if any, that we've scheduled to deliver async notifications. + nsCOMPtr mRunnable; + + // mImage is a weak ref; it should be set to null when the image goes out of + // scope. mImageMutex protects mImage. + mutable Mutex mImageMutex; + Image* mImage; + + // Hashtable of observers attached to the image. Each observer represents a + // consumer using the image. Main thread only. + CopyOnWrite mObservers; + + Progress mProgress; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_ProgressTracker_h diff --git a/image/RasterImage.cpp b/image/RasterImage.cpp new file mode 100644 index 000000000..aad705473 --- /dev/null +++ b/image/RasterImage.cpp @@ -0,0 +1,1724 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Must #include ImageLogging.h before any IPDL-generated files or other files +// that #include prlog.h +#include "ImageLogging.h" + +#include "RasterImage.h" + +#include "gfxPlatform.h" +#include "nsComponentManagerUtils.h" +#include "nsError.h" +#include "Decoder.h" +#include "prenv.h" +#include "prsystem.h" +#include "IDecodingTask.h" +#include "ImageContainer.h" +#include "ImageRegion.h" +#include "Layers.h" +#include "LookupResult.h" +#include "nsIConsoleService.h" +#include "nsIInputStream.h" +#include "nsIScriptError.h" +#include "nsISupportsPrimitives.h" +#include "nsPresContext.h" +#include "SourceBuffer.h" +#include "SurfaceCache.h" +#include "FrameAnimator.h" + +#include "gfxContext.h" + +#include "mozilla/gfx/2D.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Likely.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Move.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Services.h" +#include +#include "mozilla/Telemetry.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Tuple.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/gfx/Scale.h" + +#include "GeckoProfiler.h" +#include "gfx2DGlue.h" +#include "gfxPrefs.h" +#include + +namespace mozilla { + +using namespace gfx; +using namespace layers; + +namespace image { + +using std::ceil; +using std::min; + +#ifndef DEBUG +NS_IMPL_ISUPPORTS(RasterImage, imgIContainer, nsIProperties) +#else +NS_IMPL_ISUPPORTS(RasterImage, imgIContainer, nsIProperties, + imgIContainerDebug) +#endif + +//****************************************************************************** +RasterImage::RasterImage(ImageURL* aURI /* = nullptr */) : + ImageResource(aURI), // invoke superclass's constructor + mSize(0,0), + mLockCount(0), + mDecodeCount(0), + mRequestedSampleSize(0), + mImageProducerID(ImageContainer::AllocateProducerID()), + mLastFrameID(0), + mLastImageContainerDrawResult(DrawResult::NOT_READY), +#ifdef DEBUG + mFramesNotified(0), +#endif + mSourceBuffer(WrapNotNull(new SourceBuffer())), + mHasSize(false), + mTransient(false), + mSyncLoad(false), + mDiscardable(false), + mHasSourceData(false), + mHasBeenDecoded(false), + mPendingAnimation(false), + mAnimationFinished(false), + mWantFullDecode(false) +{ +} + +//****************************************************************************** +RasterImage::~RasterImage() +{ + // Make sure our SourceBuffer is marked as complete. This will ensure that any + // outstanding decoders terminate. + if (!mSourceBuffer->IsComplete()) { + mSourceBuffer->Complete(NS_ERROR_ABORT); + } + + // Release all frames from the surface cache. + SurfaceCache::RemoveImage(ImageKey(this)); + + // Record Telemetry. + Telemetry::Accumulate(Telemetry::IMAGE_DECODE_COUNT, mDecodeCount); +} + +nsresult +RasterImage::Init(const char* aMimeType, + uint32_t aFlags) +{ + // We don't support re-initialization + if (mInitialized) { + return NS_ERROR_ILLEGAL_VALUE; + } + + // Not sure an error can happen before init, but be safe + if (mError) { + return NS_ERROR_FAILURE; + } + + // We want to avoid redecodes for transient images. + MOZ_ASSERT_IF(aFlags & INIT_FLAG_TRANSIENT, + !(aFlags & INIT_FLAG_DISCARDABLE)); + + // Store initialization data + mDiscardable = !!(aFlags & INIT_FLAG_DISCARDABLE); + mWantFullDecode = !!(aFlags & INIT_FLAG_DECODE_IMMEDIATELY); + mTransient = !!(aFlags & INIT_FLAG_TRANSIENT); + mSyncLoad = !!(aFlags & INIT_FLAG_SYNC_LOAD); + + // Use the MIME type to select a decoder type, and make sure there *is* a + // decoder for this MIME type. + NS_ENSURE_ARG_POINTER(aMimeType); + mDecoderType = DecoderFactory::GetDecoderType(aMimeType); + if (mDecoderType == DecoderType::UNKNOWN) { + return NS_ERROR_FAILURE; + } + + // Lock this image's surfaces in the SurfaceCache if we're not discardable. + if (!mDiscardable) { + mLockCount++; + SurfaceCache::LockImage(ImageKey(this)); + } + + if (!mSyncLoad) { + // Create an async metadata decoder and verify we succeed in doing so. + nsresult rv = DecodeMetadata(DECODE_FLAGS_DEFAULT); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + } + + // Mark us as initialized + mInitialized = true; + + return NS_OK; +} + +//****************************************************************************** +NS_IMETHODIMP_(void) +RasterImage::RequestRefresh(const TimeStamp& aTime) +{ + if (HadRecentRefresh(aTime)) { + return; + } + + EvaluateAnimation(); + + if (!mAnimating) { + return; + } + + RefreshResult res; + if (mAnimationState) { + MOZ_ASSERT(mFrameAnimator); + res = mFrameAnimator->RequestRefresh(*mAnimationState, aTime); + } + + if (res.mFrameAdvanced) { + // Notify listeners that our frame has actually changed, but do this only + // once for all frames that we've now passed (if AdvanceFrame() was called + // more than once). + #ifdef DEBUG + mFramesNotified++; + #endif + + NotifyProgress(NoProgress, res.mDirtyRect); + } + + if (res.mAnimationFinished) { + mAnimationFinished = true; + EvaluateAnimation(); + } +} + +//****************************************************************************** +NS_IMETHODIMP +RasterImage::GetWidth(int32_t* aWidth) +{ + NS_ENSURE_ARG_POINTER(aWidth); + + if (mError) { + *aWidth = 0; + return NS_ERROR_FAILURE; + } + + *aWidth = mSize.width; + return NS_OK; +} + +//****************************************************************************** +NS_IMETHODIMP +RasterImage::GetHeight(int32_t* aHeight) +{ + NS_ENSURE_ARG_POINTER(aHeight); + + if (mError) { + *aHeight = 0; + return NS_ERROR_FAILURE; + } + + *aHeight = mSize.height; + return NS_OK; +} + +//****************************************************************************** +NS_IMETHODIMP +RasterImage::GetIntrinsicSize(nsSize* aSize) +{ + if (mError) { + return NS_ERROR_FAILURE; + } + + *aSize = nsSize(nsPresContext::CSSPixelsToAppUnits(mSize.width), + nsPresContext::CSSPixelsToAppUnits(mSize.height)); + return NS_OK; +} + +//****************************************************************************** +NS_IMETHODIMP +RasterImage::GetIntrinsicRatio(nsSize* aRatio) +{ + if (mError) { + return NS_ERROR_FAILURE; + } + + *aRatio = nsSize(mSize.width, mSize.height); + return NS_OK; +} + +NS_IMETHODIMP_(Orientation) +RasterImage::GetOrientation() +{ + return mOrientation; +} + +//****************************************************************************** +NS_IMETHODIMP +RasterImage::GetType(uint16_t* aType) +{ + NS_ENSURE_ARG_POINTER(aType); + + *aType = imgIContainer::TYPE_RASTER; + return NS_OK; +} + +LookupResult +RasterImage::LookupFrameInternal(const IntSize& aSize, + uint32_t aFlags, + PlaybackType aPlaybackType) +{ + if (mAnimationState && aPlaybackType == PlaybackType::eAnimated) { + MOZ_ASSERT(mFrameAnimator); + MOZ_ASSERT(ToSurfaceFlags(aFlags) == DefaultSurfaceFlags(), + "Can't composite frames with non-default surface flags"); + const size_t index = mAnimationState->GetCurrentAnimationFrameIndex(); + return mFrameAnimator->GetCompositedFrame(index); + } + + SurfaceFlags surfaceFlags = ToSurfaceFlags(aFlags); + + // We don't want any substitution for sync decodes, and substitution would be + // illegal when high quality downscaling is disabled, so we use + // SurfaceCache::Lookup in this case. + if ((aFlags & FLAG_SYNC_DECODE) || !(aFlags & FLAG_HIGH_QUALITY_SCALING)) { + return SurfaceCache::Lookup(ImageKey(this), + RasterSurfaceKey(aSize, + surfaceFlags, + PlaybackType::eStatic)); + } + + // We'll return the best match we can find to the requested frame. + return SurfaceCache::LookupBestMatch(ImageKey(this), + RasterSurfaceKey(aSize, + surfaceFlags, + PlaybackType::eStatic)); +} + +DrawableSurface +RasterImage::LookupFrame(const IntSize& aSize, + uint32_t aFlags, + PlaybackType aPlaybackType) +{ + MOZ_ASSERT(NS_IsMainThread()); + + // If we're opaque, we don't need to care about premultiplied alpha, because + // that can only matter for frames with transparency. + if (IsOpaque()) { + aFlags &= ~FLAG_DECODE_NO_PREMULTIPLY_ALPHA; + } + + IntSize requestedSize = CanDownscaleDuringDecode(aSize, aFlags) + ? aSize : mSize; + if (requestedSize.IsEmpty()) { + return DrawableSurface(); // Can't decode to a surface of zero size. + } + + LookupResult result = + LookupFrameInternal(requestedSize, aFlags, aPlaybackType); + + if (!result && !mHasSize) { + // We can't request a decode without knowing our intrinsic size. Give up. + return DrawableSurface(); + } + + if (result.Type() == MatchType::NOT_FOUND || + result.Type() == MatchType::SUBSTITUTE_BECAUSE_NOT_FOUND || + ((aFlags & FLAG_SYNC_DECODE) && !result)) { + // We don't have a copy of this frame, and there's no decoder working on + // one. (Or we're sync decoding and the existing decoder hasn't even started + // yet.) Trigger decoding so it'll be available next time. + MOZ_ASSERT(aPlaybackType != PlaybackType::eAnimated || + !mAnimationState || mAnimationState->KnownFrameCount() < 1, + "Animated frames should be locked"); + + Decode(requestedSize, aFlags, aPlaybackType); + + // If we can sync decode, we should already have the frame. + if (aFlags & FLAG_SYNC_DECODE) { + result = LookupFrameInternal(requestedSize, aFlags, aPlaybackType); + } + } + + if (!result) { + // We still weren't able to get a frame. Give up. + return DrawableSurface(); + } + + if (result.Surface()->GetCompositingFailed()) { + return DrawableSurface(); + } + + MOZ_ASSERT(!result.Surface()->GetIsPaletted(), + "Should not have a paletted frame"); + + // Sync decoding guarantees that we got the frame, but if it's owned by an + // async decoder that's currently running, the contents of the frame may not + // be available yet. Make sure we get everything. + if (mHasSourceData && (aFlags & FLAG_SYNC_DECODE)) { + result.Surface()->WaitUntilFinished(); + } + + // If we could have done some decoding in this function we need to check if + // that decoding encountered an error and hence aborted the surface. We want + // to avoid calling IsAborted if we weren't passed any sync decode flag because + // IsAborted acquires the monitor for the imgFrame. + if (aFlags & (FLAG_SYNC_DECODE | FLAG_SYNC_DECODE_IF_FAST) && + result.Surface()->IsAborted()) { + return DrawableSurface(); + } + + return Move(result.Surface()); +} + +bool +RasterImage::IsOpaque() +{ + if (mError) { + return false; + } + + Progress progress = mProgressTracker->GetProgress(); + + // If we haven't yet finished decoding, the safe answer is "not opaque". + if (!(progress & FLAG_DECODE_COMPLETE)) { + return false; + } + + // Other, we're opaque if FLAG_HAS_TRANSPARENCY is not set. + return !(progress & FLAG_HAS_TRANSPARENCY); +} + +NS_IMETHODIMP_(bool) +RasterImage::WillDrawOpaqueNow() +{ + if (!IsOpaque()) { + return false; + } + + if (mAnimationState) { + // We never discard frames of animated images. + return true; + } + + // If we are not locked our decoded data could get discard at any time (ie + // between the call to this function and when we are asked to draw), so we + // have to return false if we are unlocked. + if (IsUnlocked()) { + return false; + } + + LookupResult result = + SurfaceCache::LookupBestMatch(ImageKey(this), + RasterSurfaceKey(mSize, + DefaultSurfaceFlags(), + PlaybackType::eStatic)); + MatchType matchType = result.Type(); + if (matchType == MatchType::NOT_FOUND || matchType == MatchType::PENDING || + !result.Surface()->IsFinished()) { + return false; + } + + return true; +} + +void +RasterImage::OnSurfaceDiscarded() +{ + MOZ_ASSERT(mProgressTracker); + + NS_DispatchToMainThread(NewRunnableMethod(mProgressTracker, &ProgressTracker::OnDiscard)); +} + +//****************************************************************************** +NS_IMETHODIMP +RasterImage::GetAnimated(bool* aAnimated) +{ + if (mError) { + return NS_ERROR_FAILURE; + } + + NS_ENSURE_ARG_POINTER(aAnimated); + + // If we have an AnimationState, we can know for sure. + if (mAnimationState) { + *aAnimated = true; + return NS_OK; + } + + // Otherwise, we need to have been decoded to know for sure, since if we were + // decoded at least once mAnimationState would have been created for animated + // images. This is true even though we check for animation during the + // metadata decode, because we may still discover animation only during the + // full decode for corrupt images. + if (!mHasBeenDecoded) { + return NS_ERROR_NOT_AVAILABLE; + } + + // We know for sure + *aAnimated = false; + + return NS_OK; +} + +//****************************************************************************** +NS_IMETHODIMP_(int32_t) +RasterImage::GetFirstFrameDelay() +{ + if (mError) { + return -1; + } + + bool animated = false; + if (NS_FAILED(GetAnimated(&animated)) || !animated) { + return -1; + } + + MOZ_ASSERT(mAnimationState, "Animated images should have an AnimationState"); + return mAnimationState->FirstFrameTimeout().AsEncodedValueDeprecated(); +} + +NS_IMETHODIMP_(already_AddRefed) +RasterImage::GetFrame(uint32_t aWhichFrame, + uint32_t aFlags) +{ + return GetFrameInternal(mSize, aWhichFrame, aFlags).second().forget(); +} + +NS_IMETHODIMP_(already_AddRefed) +RasterImage::GetFrameAtSize(const IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags) +{ + return GetFrameInternal(aSize, aWhichFrame, aFlags).second().forget(); +} + +Pair> +RasterImage::GetFrameInternal(const IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags) +{ + MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE); + + if (aSize.IsEmpty()) { + return MakePair(DrawResult::BAD_ARGS, RefPtr()); + } + + if (aWhichFrame > FRAME_MAX_VALUE) { + return MakePair(DrawResult::BAD_ARGS, RefPtr()); + } + + if (mError) { + return MakePair(DrawResult::BAD_IMAGE, RefPtr()); + } + + // Get the frame. If it's not there, it's probably the caller's fault for + // not waiting for the data to be loaded from the network or not passing + // FLAG_SYNC_DECODE. + DrawableSurface surface = + LookupFrame(aSize, aFlags, ToPlaybackType(aWhichFrame)); + if (!surface) { + // The OS threw this frame away and we couldn't redecode it. + return MakePair(DrawResult::TEMPORARY_ERROR, RefPtr()); + } + + RefPtr sourceSurface = surface->GetSourceSurface(); + + if (!surface->IsFinished()) { + return MakePair(DrawResult::INCOMPLETE, Move(sourceSurface)); + } + + return MakePair(DrawResult::SUCCESS, Move(sourceSurface)); +} + +Pair> +RasterImage::GetCurrentImage(ImageContainer* aContainer, uint32_t aFlags) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aContainer); + + DrawResult drawResult; + RefPtr surface; + Tie(drawResult, surface) = + GetFrameInternal(mSize, FRAME_CURRENT, aFlags | FLAG_ASYNC_NOTIFY); + if (!surface) { + // The OS threw out some or all of our buffer. We'll need to wait for the + // redecode (which was automatically triggered by GetFrame) to complete. + return MakePair(drawResult, RefPtr()); + } + + RefPtr image = new layers::SourceSurfaceImage(surface); + return MakePair(drawResult, Move(image)); +} + +NS_IMETHODIMP_(bool) +RasterImage::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags) +{ + int32_t maxTextureSize = aManager->GetMaxTextureSize(); + if (!mHasSize || + mSize.width > maxTextureSize || + mSize.height > maxTextureSize) { + return false; + } + + return true; +} + +NS_IMETHODIMP_(already_AddRefed) +RasterImage::GetImageContainer(LayerManager* aManager, uint32_t aFlags) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aManager); + MOZ_ASSERT((aFlags & ~(FLAG_SYNC_DECODE | + FLAG_SYNC_DECODE_IF_FAST | + FLAG_ASYNC_NOTIFY)) + == FLAG_NONE, + "Unsupported flag passed to GetImageContainer"); + + int32_t maxTextureSize = aManager->GetMaxTextureSize(); + if (!mHasSize || + mSize.width > maxTextureSize || + mSize.height > maxTextureSize) { + return nullptr; + } + + if (IsUnlocked() && mProgressTracker) { + mProgressTracker->OnUnlockedDraw(); + } + + RefPtr container = mImageContainer.get(); + + bool mustRedecode = + (aFlags & (FLAG_SYNC_DECODE | FLAG_SYNC_DECODE_IF_FAST)) && + mLastImageContainerDrawResult != DrawResult::SUCCESS && + mLastImageContainerDrawResult != DrawResult::BAD_IMAGE; + + if (container && !mustRedecode) { + return container.forget(); + } + + // We need a new ImageContainer, so create one. + container = LayerManager::CreateImageContainer(); + + DrawResult drawResult; + RefPtr image; + Tie(drawResult, image) = GetCurrentImage(container, aFlags); + if (!image) { + return nullptr; + } + + // |image| holds a reference to a SourceSurface which in turn holds a lock on + // the current frame's VolatileBuffer, ensuring that it doesn't get freed as + // long as the layer system keeps this ImageContainer alive. + AutoTArray imageList; + imageList.AppendElement(ImageContainer::NonOwningImage(image, TimeStamp(), + mLastFrameID++, + mImageProducerID)); + container->SetCurrentImagesInTransaction(imageList); + + mLastImageContainerDrawResult = drawResult; + mImageContainer = container; + + return container.forget(); +} + +void +RasterImage::UpdateImageContainer() +{ + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr container = mImageContainer.get(); + if (!container) { + return; + } + + DrawResult drawResult; + RefPtr image; + Tie(drawResult, image) = GetCurrentImage(container, FLAG_NONE); + if (!image) { + return; + } + + mLastImageContainerDrawResult = drawResult; + AutoTArray imageList; + imageList.AppendElement(ImageContainer::NonOwningImage(image, TimeStamp(), + mLastFrameID++, + mImageProducerID)); + container->SetCurrentImages(imageList); +} + +size_t +RasterImage::SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const +{ + return mSourceBuffer->SizeOfIncludingThisWithComputedFallback(aMallocSizeOf); +} + +void +RasterImage::CollectSizeOfSurfaces(nsTArray& aCounters, + MallocSizeOf aMallocSizeOf) const +{ + SurfaceCache::CollectSizeOfSurfaces(ImageKey(this), aCounters, aMallocSizeOf); + if (mFrameAnimator) { + mFrameAnimator->CollectSizeOfCompositingSurfaces(aCounters, aMallocSizeOf); + } +} + +bool +RasterImage::SetMetadata(const ImageMetadata& aMetadata, + bool aFromMetadataDecode) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (mError) { + return true; + } + + if (aMetadata.HasSize()) { + IntSize size = aMetadata.GetSize(); + if (size.width < 0 || size.height < 0) { + NS_WARNING("Image has negative intrinsic size"); + DoError(); + return true; + } + + MOZ_ASSERT(aMetadata.HasOrientation()); + Orientation orientation = aMetadata.GetOrientation(); + + // If we already have a size, check the new size against the old one. + if (mHasSize && (size != mSize || orientation != mOrientation)) { + NS_WARNING("Image changed size or orientation on redecode! " + "This should not happen!"); + DoError(); + return true; + } + + // Set the size and flag that we have it. + mSize = size; + mOrientation = orientation; + mHasSize = true; + } + + if (mHasSize && aMetadata.HasAnimation() && !mAnimationState) { + // We're becoming animated, so initialize animation stuff. + mAnimationState.emplace(mAnimationMode); + mFrameAnimator = MakeUnique(this, mSize); + + // We don't support discarding animated images (See bug 414259). + // Lock the image and throw away the key. + LockImage(); + + if (!aFromMetadataDecode) { + // The metadata decode reported that this image isn't animated, but we + // discovered that it actually was during the full decode. This is a + // rare failure that only occurs for corrupt images. To recover, we need + // to discard all existing surfaces and redecode. + return false; + } + } + + if (mAnimationState) { + mAnimationState->SetLoopCount(aMetadata.GetLoopCount()); + mAnimationState->SetFirstFrameTimeout(aMetadata.GetFirstFrameTimeout()); + + if (aMetadata.HasLoopLength()) { + mAnimationState->SetLoopLength(aMetadata.GetLoopLength()); + } + if (aMetadata.HasFirstFrameRefreshArea()) { + mAnimationState + ->SetFirstFrameRefreshArea(aMetadata.GetFirstFrameRefreshArea()); + } + } + + if (aMetadata.HasHotspot()) { + IntPoint hotspot = aMetadata.GetHotspot(); + + nsCOMPtr intwrapx = + do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID); + nsCOMPtr intwrapy = + do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID); + intwrapx->SetData(hotspot.x); + intwrapy->SetData(hotspot.y); + + Set("hotspotX", intwrapx); + Set("hotspotY", intwrapy); + } + + return true; +} + +NS_IMETHODIMP +RasterImage::SetAnimationMode(uint16_t aAnimationMode) +{ + if (mAnimationState) { + mAnimationState->SetAnimationMode(aAnimationMode); + } + return SetAnimationModeInternal(aAnimationMode); +} + +//****************************************************************************** + +nsresult +RasterImage::StartAnimation() +{ + if (mError) { + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(ShouldAnimate(), "Should not animate!"); + + // If we're not ready to animate, then set mPendingAnimation, which will cause + // us to start animating if and when we do become ready. + mPendingAnimation = !mAnimationState || mAnimationState->KnownFrameCount() < 1; + if (mPendingAnimation) { + return NS_OK; + } + + // Don't bother to animate if we're displaying the first frame forever. + if (mAnimationState->GetCurrentAnimationFrameIndex() == 0 && + mAnimationState->FirstFrameTimeout() == FrameTimeout::Forever()) { + mAnimationFinished = true; + return NS_ERROR_ABORT; + } + + // We need to set the time that this initial frame was first displayed, as + // this is used in AdvanceFrame(). + mAnimationState->InitAnimationFrameTimeIfNecessary(); + + return NS_OK; +} + +//****************************************************************************** +nsresult +RasterImage::StopAnimation() +{ + MOZ_ASSERT(mAnimating, "Should be animating!"); + + nsresult rv = NS_OK; + if (mError) { + rv = NS_ERROR_FAILURE; + } else { + mAnimationState->SetAnimationFrameTime(TimeStamp()); + } + + mAnimating = false; + return rv; +} + +//****************************************************************************** +NS_IMETHODIMP +RasterImage::ResetAnimation() +{ + if (mError) { + return NS_ERROR_FAILURE; + } + + mPendingAnimation = false; + + if (mAnimationMode == kDontAnimMode || !mAnimationState || + mAnimationState->GetCurrentAnimationFrameIndex() == 0) { + return NS_OK; + } + + mAnimationFinished = false; + + if (mAnimating) { + StopAnimation(); + } + + MOZ_ASSERT(mAnimationState, "Should have AnimationState"); + mAnimationState->ResetAnimation(); + + NotifyProgress(NoProgress, mAnimationState->FirstFrameRefreshArea()); + + // Start the animation again. It may not have been running before, if + // mAnimationFinished was true before entering this function. + EvaluateAnimation(); + + return NS_OK; +} + +//****************************************************************************** +NS_IMETHODIMP_(void) +RasterImage::SetAnimationStartTime(const TimeStamp& aTime) +{ + if (mError || mAnimationMode == kDontAnimMode || mAnimating || !mAnimationState) { + return; + } + + mAnimationState->SetAnimationFrameTime(aTime); +} + +NS_IMETHODIMP_(float) +RasterImage::GetFrameIndex(uint32_t aWhichFrame) +{ + MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE, "Invalid argument"); + return (aWhichFrame == FRAME_FIRST || !mAnimationState) + ? 0.0f + : mAnimationState->GetCurrentAnimationFrameIndex(); +} + +NS_IMETHODIMP_(IntRect) +RasterImage::GetImageSpaceInvalidationRect(const IntRect& aRect) +{ + return aRect; +} + +nsresult +RasterImage::OnImageDataComplete(nsIRequest*, nsISupports*, nsresult aStatus, + bool aLastPart) +{ + MOZ_ASSERT(NS_IsMainThread()); + + // Record that we have all the data we're going to get now. + mHasSourceData = true; + + // Let decoders know that there won't be any more data coming. + mSourceBuffer->Complete(aStatus); + + // Allow a synchronous metadata decode if mSyncLoad was set, or if we're + // running on a single thread (in which case waiting for the async metadata + // decoder could delay this image's load event quite a bit), or if this image + // is transient. + bool canSyncDecodeMetadata = mSyncLoad || mTransient || + DecodePool::NumberOfCores() < 2; + + if (canSyncDecodeMetadata && !mHasSize) { + // We're loading this image synchronously, so it needs to be usable after + // this call returns. Since we haven't gotten our size yet, we need to do a + // synchronous metadata decode here. + DecodeMetadata(FLAG_SYNC_DECODE); + } + + // Determine our final status, giving precedence to Necko failure codes. We + // check after running the metadata decode in case it triggered an error. + nsresult finalStatus = mError ? NS_ERROR_FAILURE : NS_OK; + if (NS_FAILED(aStatus)) { + finalStatus = aStatus; + } + + // If loading failed, report an error. + if (NS_FAILED(finalStatus)) { + DoError(); + } + + Progress loadProgress = LoadCompleteProgress(aLastPart, mError, finalStatus); + + if (!mHasSize && !mError) { + // We don't have our size yet, so we'll fire the load event in SetSize(). + MOZ_ASSERT(!canSyncDecodeMetadata, + "Firing load async after metadata sync decode?"); + NotifyProgress(FLAG_ONLOAD_BLOCKED); + mLoadProgress = Some(loadProgress); + return finalStatus; + } + + NotifyForLoadEvent(loadProgress); + + return finalStatus; +} + +void +RasterImage::NotifyForLoadEvent(Progress aProgress) +{ + MOZ_ASSERT(mHasSize || mError, "Need to know size before firing load event"); + MOZ_ASSERT(!mHasSize || + (mProgressTracker->GetProgress() & FLAG_SIZE_AVAILABLE), + "Should have notified that the size is available if we have it"); + + // If we encountered an error, make sure we notify for that as well. + if (mError) { + aProgress |= FLAG_HAS_ERROR; + } + + // Notify our listeners, which will fire this image's load event. + NotifyProgress(aProgress); +} + +nsresult +RasterImage::OnImageDataAvailable(nsIRequest*, + nsISupports*, + nsIInputStream* aInputStream, + uint64_t, + uint32_t aCount) +{ + nsresult rv = mSourceBuffer->AppendFromInputStream(aInputStream, aCount); + if (NS_FAILED(rv)) { + DoError(); + } + return rv; +} + +nsresult +RasterImage::SetSourceSizeHint(uint32_t aSizeHint) +{ + return mSourceBuffer->ExpectLength(aSizeHint); +} + +/********* Methods to implement lazy allocation of nsIProperties object *******/ +NS_IMETHODIMP +RasterImage::Get(const char* prop, const nsIID& iid, void** result) +{ + if (!mProperties) { + return NS_ERROR_FAILURE; + } + return mProperties->Get(prop, iid, result); +} + +NS_IMETHODIMP +RasterImage::Set(const char* prop, nsISupports* value) +{ + if (!mProperties) { + mProperties = do_CreateInstance("@mozilla.org/properties;1"); + } + if (!mProperties) { + return NS_ERROR_OUT_OF_MEMORY; + } + return mProperties->Set(prop, value); +} + +NS_IMETHODIMP +RasterImage::Has(const char* prop, bool* _retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + if (!mProperties) { + *_retval = false; + return NS_OK; + } + return mProperties->Has(prop, _retval); +} + +NS_IMETHODIMP +RasterImage::Undefine(const char* prop) +{ + if (!mProperties) { + return NS_ERROR_FAILURE; + } + return mProperties->Undefine(prop); +} + +NS_IMETHODIMP +RasterImage::GetKeys(uint32_t* count, char*** keys) +{ + if (!mProperties) { + *count = 0; + *keys = nullptr; + return NS_OK; + } + return mProperties->GetKeys(count, keys); +} + +void +RasterImage::Discard() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(CanDiscard(), "Asked to discard but can't"); + MOZ_ASSERT(!mAnimationState, "Asked to discard for animated image"); + + // Delete all the decoded frames. + SurfaceCache::RemoveImage(ImageKey(this)); + + // Notify that we discarded. + if (mProgressTracker) { + mProgressTracker->OnDiscard(); + } +} + +bool +RasterImage::CanDiscard() { + return mHasSourceData && // ...have the source data... + !mAnimationState; // Can never discard animated images +} + +NS_IMETHODIMP +RasterImage::StartDecoding() +{ + if (mError) { + return NS_ERROR_FAILURE; + } + + if (!mHasSize) { + mWantFullDecode = true; + return NS_OK; + } + + return RequestDecodeForSize(mSize, FLAG_SYNC_DECODE_IF_FAST); +} + +NS_IMETHODIMP +RasterImage::RequestDecodeForSize(const IntSize& aSize, uint32_t aFlags) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (mError) { + return NS_ERROR_FAILURE; + } + + if (!mHasSize) { + return NS_OK; + } + + // Decide whether to sync decode images we can decode quickly. Here we are + // explicitly trading off flashing for responsiveness in the case that we're + // redecoding an image (see bug 845147). + bool shouldSyncDecodeIfFast = + !mHasBeenDecoded && (aFlags & FLAG_SYNC_DECODE_IF_FAST); + + uint32_t flags = shouldSyncDecodeIfFast + ? aFlags + : aFlags & ~FLAG_SYNC_DECODE_IF_FAST; + + // Perform a frame lookup, which will implicitly start decoding if needed. + LookupFrame(aSize, flags, mAnimationState ? PlaybackType::eAnimated + : PlaybackType::eStatic); + + return NS_OK; +} + +static void +LaunchDecodingTask(IDecodingTask* aTask, + RasterImage* aImage, + uint32_t aFlags, + bool aHaveSourceData) +{ + if (aHaveSourceData) { + // If we have all the data, we can sync decode if requested. + if (aFlags & imgIContainer::FLAG_SYNC_DECODE) { + PROFILER_LABEL_PRINTF("DecodePool", "SyncRunIfPossible", + js::ProfileEntry::Category::GRAPHICS, + "%s", aImage->GetURIString().get()); + DecodePool::Singleton()->SyncRunIfPossible(aTask); + return; + } + + if (aFlags & imgIContainer::FLAG_SYNC_DECODE_IF_FAST) { + PROFILER_LABEL_PRINTF("DecodePool", "SyncRunIfPreferred", + js::ProfileEntry::Category::GRAPHICS, + "%s", aImage->GetURIString().get()); + DecodePool::Singleton()->SyncRunIfPreferred(aTask); + return; + } + } + + // Perform an async decode. We also take this path if we don't have all the + // source data yet, since sync decoding is impossible in that situation. + DecodePool::Singleton()->AsyncRun(aTask); +} + +NS_IMETHODIMP +RasterImage::Decode(const IntSize& aSize, + uint32_t aFlags, + PlaybackType aPlaybackType) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (mError) { + return NS_ERROR_FAILURE; + } + + // If we don't have a size yet, we can't do any other decoding. + if (!mHasSize) { + mWantFullDecode = true; + return NS_OK; + } + + // We're about to decode again, which may mean that some of the previous sizes + // we've decoded at aren't useful anymore. We can allow them to expire from + // the cache by unlocking them here. When the decode finishes, it will send an + // invalidation that will cause all instances of this image to redraw. If this + // image is locked, any surfaces that are still useful will become locked + // again when LookupFrame touches them, and the remainder will eventually + // expire. + SurfaceCache::UnlockEntries(ImageKey(this)); + + // Determine which flags we need to decode this image with. + DecoderFlags decoderFlags = DefaultDecoderFlags(); + if (aFlags & FLAG_ASYNC_NOTIFY) { + decoderFlags |= DecoderFlags::ASYNC_NOTIFY; + } + if (mTransient) { + decoderFlags |= DecoderFlags::IMAGE_IS_TRANSIENT; + } + if (mHasBeenDecoded) { + decoderFlags |= DecoderFlags::IS_REDECODE; + } + + SurfaceFlags surfaceFlags = ToSurfaceFlags(aFlags); + if (IsOpaque()) { + // If there's no transparency, it doesn't matter whether we premultiply + // alpha or not. + surfaceFlags &= ~SurfaceFlags::NO_PREMULTIPLY_ALPHA; + } + + // Create a decoder. + RefPtr task; + if (mAnimationState && aPlaybackType == PlaybackType::eAnimated) { + task = DecoderFactory::CreateAnimationDecoder(mDecoderType, WrapNotNull(this), + mSourceBuffer, mSize, + decoderFlags, surfaceFlags); + } else { + task = DecoderFactory::CreateDecoder(mDecoderType, WrapNotNull(this), + mSourceBuffer, mSize, aSize, + decoderFlags, surfaceFlags, + mRequestedSampleSize); + } + + // Make sure DecoderFactory was able to create a decoder successfully. + if (!task) { + return NS_ERROR_FAILURE; + } + + mDecodeCount++; + + // We're ready to decode; start the decoder. + LaunchDecodingTask(task, this, aFlags, mHasSourceData); + return NS_OK; +} + +NS_IMETHODIMP +RasterImage::DecodeMetadata(uint32_t aFlags) +{ + if (mError) { + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(!mHasSize, "Should not do unnecessary metadata decodes"); + + // Create a decoder. + RefPtr task = + DecoderFactory::CreateMetadataDecoder(mDecoderType, WrapNotNull(this), + mSourceBuffer, mRequestedSampleSize); + + // Make sure DecoderFactory was able to create a decoder successfully. + if (!task) { + return NS_ERROR_FAILURE; + } + + // We're ready to decode; start the decoder. + LaunchDecodingTask(task, this, aFlags, mHasSourceData); + return NS_OK; +} + +void +RasterImage::RecoverFromInvalidFrames(const IntSize& aSize, uint32_t aFlags) +{ + if (!mHasSize) { + return; + } + + NS_WARNING("A RasterImage's frames became invalid. Attempting to recover..."); + + // Discard all existing frames, since they're probably all now invalid. + SurfaceCache::RemoveImage(ImageKey(this)); + + // Relock the image if it's supposed to be locked. + if (mLockCount > 0) { + SurfaceCache::LockImage(ImageKey(this)); + } + + // Animated images require some special handling, because we normally require + // that they never be discarded. + if (mAnimationState) { + Decode(mSize, aFlags | FLAG_SYNC_DECODE, PlaybackType::eAnimated); + ResetAnimation(); + return; + } + + // For non-animated images, it's fine to recover using an async decode. + Decode(aSize, aFlags, PlaybackType::eStatic); +} + +static bool +HaveSkia() +{ +#ifdef MOZ_ENABLE_SKIA + return true; +#else + return false; +#endif +} + +bool +RasterImage::CanDownscaleDuringDecode(const IntSize& aSize, uint32_t aFlags) +{ + // Check basic requirements: downscale-during-decode is enabled, Skia is + // available, this image isn't transient, we have all the source data and know + // our size, and the flags allow us to do it. + if (!mHasSize || mTransient || !HaveSkia() || + !gfxPrefs::ImageDownscaleDuringDecodeEnabled() || + !(aFlags & imgIContainer::FLAG_HIGH_QUALITY_SCALING)) { + return false; + } + + // We don't downscale animated images during decode. + if (mAnimationState) { + return false; + } + + // Never upscale. + if (aSize.width >= mSize.width || aSize.height >= mSize.height) { + return false; + } + + // Zero or negative width or height is unacceptable. + if (aSize.width < 1 || aSize.height < 1) { + return false; + } + + // There's no point in scaling if we can't store the result. + if (!SurfaceCache::CanHold(aSize)) { + return false; + } + + return true; +} + +DrawResult +RasterImage::DrawInternal(DrawableSurface&& aSurface, + gfxContext* aContext, + const IntSize& aSize, + const ImageRegion& aRegion, + SamplingFilter aSamplingFilter, + uint32_t aFlags) +{ + gfxContextMatrixAutoSaveRestore saveMatrix(aContext); + ImageRegion region(aRegion); + bool frameIsFinished = aSurface->IsFinished(); + + // By now we may have a frame with the requested size. If not, we need to + // adjust the drawing parameters accordingly. + IntSize finalSize = aSurface->GetImageSize(); + bool couldRedecodeForBetterFrame = false; + if (finalSize != aSize) { + gfx::Size scale(double(aSize.width) / finalSize.width, + double(aSize.height) / finalSize.height); + aContext->Multiply(gfxMatrix::Scaling(scale.width, scale.height)); + region.Scale(1.0 / scale.width, 1.0 / scale.height); + + couldRedecodeForBetterFrame = CanDownscaleDuringDecode(aSize, aFlags); + } + + if (!aSurface->Draw(aContext, region, aSamplingFilter, aFlags)) { + RecoverFromInvalidFrames(aSize, aFlags); + return DrawResult::TEMPORARY_ERROR; + } + if (!frameIsFinished) { + return DrawResult::INCOMPLETE; + } + if (couldRedecodeForBetterFrame) { + return DrawResult::WRONG_SIZE; + } + return DrawResult::SUCCESS; +} + +//****************************************************************************** +NS_IMETHODIMP_(DrawResult) +RasterImage::Draw(gfxContext* aContext, + const IntSize& aSize, + const ImageRegion& aRegion, + uint32_t aWhichFrame, + SamplingFilter aSamplingFilter, + const Maybe& /*aSVGContext - ignored*/, + uint32_t aFlags) +{ + if (aWhichFrame > FRAME_MAX_VALUE) { + return DrawResult::BAD_ARGS; + } + + if (mError) { + return DrawResult::BAD_IMAGE; + } + + // Illegal -- you can't draw with non-default decode flags. + // (Disabling colorspace conversion might make sense to allow, but + // we don't currently.) + if (ToSurfaceFlags(aFlags) != DefaultSurfaceFlags()) { + return DrawResult::BAD_ARGS; + } + + if (!aContext) { + return DrawResult::BAD_ARGS; + } + + if (IsUnlocked() && mProgressTracker) { + mProgressTracker->OnUnlockedDraw(); + } + + // If we're not using SamplingFilter::GOOD, we shouldn't high-quality scale or + // downscale during decode. + uint32_t flags = aSamplingFilter == SamplingFilter::GOOD + ? aFlags + : aFlags & ~FLAG_HIGH_QUALITY_SCALING; + + DrawableSurface surface = + LookupFrame(aSize, flags, ToPlaybackType(aWhichFrame)); + if (!surface) { + // Getting the frame (above) touches the image and kicks off decoding. + if (mDrawStartTime.IsNull()) { + mDrawStartTime = TimeStamp::Now(); + } + return DrawResult::NOT_READY; + } + + bool shouldRecordTelemetry = !mDrawStartTime.IsNull() && + surface->IsFinished(); + + auto result = DrawInternal(Move(surface), aContext, aSize, + aRegion, aSamplingFilter, flags); + + if (shouldRecordTelemetry) { + TimeDuration drawLatency = TimeStamp::Now() - mDrawStartTime; + Telemetry::Accumulate(Telemetry::IMAGE_DECODE_ON_DRAW_LATENCY, + int32_t(drawLatency.ToMicroseconds())); + mDrawStartTime = TimeStamp(); + } + + return result; +} + +//****************************************************************************** + +NS_IMETHODIMP +RasterImage::LockImage() +{ + MOZ_ASSERT(NS_IsMainThread(), + "Main thread to encourage serialization with UnlockImage"); + if (mError) { + return NS_ERROR_FAILURE; + } + + // Increment the lock count + mLockCount++; + + // Lock this image's surfaces in the SurfaceCache. + if (mLockCount == 1) { + SurfaceCache::LockImage(ImageKey(this)); + } + + return NS_OK; +} + +//****************************************************************************** + +NS_IMETHODIMP +RasterImage::UnlockImage() +{ + MOZ_ASSERT(NS_IsMainThread(), + "Main thread to encourage serialization with LockImage"); + if (mError) { + return NS_ERROR_FAILURE; + } + + // It's an error to call this function if the lock count is 0 + MOZ_ASSERT(mLockCount > 0, + "Calling UnlockImage with mLockCount == 0!"); + if (mLockCount == 0) { + return NS_ERROR_ABORT; + } + + // Decrement our lock count + mLockCount--; + + // Unlock this image's surfaces in the SurfaceCache. + if (mLockCount == 0 ) { + SurfaceCache::UnlockImage(ImageKey(this)); + } + + return NS_OK; +} + +//****************************************************************************** + +NS_IMETHODIMP +RasterImage::RequestDiscard() +{ + if (mDiscardable && // Enabled at creation time... + mLockCount == 0 && // ...not temporarily disabled... + CanDiscard()) { + Discard(); + } + + return NS_OK; +} + +// Indempotent error flagging routine. If a decoder is open, shuts it down. +void +RasterImage::DoError() +{ + // If we've flagged an error before, we have nothing to do + if (mError) { + return; + } + + // We can't safely handle errors off-main-thread, so dispatch a worker to + // do it. + if (!NS_IsMainThread()) { + HandleErrorWorker::DispatchIfNeeded(this); + return; + } + + // Put the container in an error state. + mError = true; + + // Stop animation and release our FrameAnimator. + if (mAnimating) { + StopAnimation(); + } + mAnimationState = Nothing(); + mFrameAnimator = nullptr; + + // Release all locks. + mLockCount = 0; + SurfaceCache::UnlockImage(ImageKey(this)); + + // Release all frames from the surface cache. + SurfaceCache::RemoveImage(ImageKey(this)); + + // Invalidate to get rid of any partially-drawn image content. + NotifyProgress(NoProgress, IntRect(0, 0, mSize.width, mSize.height)); + + MOZ_LOG(gImgLog, LogLevel::Error, + ("RasterImage: [this=%p] Error detected for image\n", this)); +} + +/* static */ void +RasterImage::HandleErrorWorker::DispatchIfNeeded(RasterImage* aImage) +{ + RefPtr worker = new HandleErrorWorker(aImage); + NS_DispatchToMainThread(worker); +} + +RasterImage::HandleErrorWorker::HandleErrorWorker(RasterImage* aImage) + : mImage(aImage) +{ + MOZ_ASSERT(mImage, "Should have image"); +} + +NS_IMETHODIMP +RasterImage::HandleErrorWorker::Run() +{ + mImage->DoError(); + + return NS_OK; +} + +bool +RasterImage::ShouldAnimate() +{ + return ImageResource::ShouldAnimate() && + mAnimationState && + mAnimationState->KnownFrameCount() >= 1 && + !mAnimationFinished; +} + +#ifdef DEBUG +NS_IMETHODIMP +RasterImage::GetFramesNotified(uint32_t* aFramesNotified) +{ + NS_ENSURE_ARG_POINTER(aFramesNotified); + + *aFramesNotified = mFramesNotified; + + return NS_OK; +} +#endif + +void +RasterImage::NotifyProgress(Progress aProgress, + const IntRect& aInvalidRect /* = IntRect() */, + const Maybe& aFrameCount /* = Nothing() */, + DecoderFlags aDecoderFlags + /* = DefaultDecoderFlags() */, + SurfaceFlags aSurfaceFlags + /* = DefaultSurfaceFlags() */) +{ + MOZ_ASSERT(NS_IsMainThread()); + + // Ensure that we stay alive long enough to finish notifying. + RefPtr image = this; + + const bool wasDefaultFlags = aSurfaceFlags == DefaultSurfaceFlags(); + + if (!aInvalidRect.IsEmpty() && wasDefaultFlags) { + // Update our image container since we're invalidating. + UpdateImageContainer(); + } + + if (!(aDecoderFlags & DecoderFlags::FIRST_FRAME_ONLY)) { + // We may have decoded new animation frames; update our animation state. + MOZ_ASSERT_IF(aFrameCount && *aFrameCount > 1, mAnimationState || mError); + if (mAnimationState && aFrameCount) { + mAnimationState->UpdateKnownFrameCount(*aFrameCount); + } + + // If we should start animating right now, do so. + if (mAnimationState && aFrameCount == Some(1u) && + mPendingAnimation && ShouldAnimate()) { + StartAnimation(); + } + } + + // Tell the observers what happened. + image->mProgressTracker->SyncNotifyProgress(aProgress, aInvalidRect); +} + +void +RasterImage::NotifyDecodeComplete(const DecoderFinalStatus& aStatus, + const ImageMetadata& aMetadata, + const DecoderTelemetry& aTelemetry, + Progress aProgress, + const IntRect& aInvalidRect, + const Maybe& aFrameCount, + DecoderFlags aDecoderFlags, + SurfaceFlags aSurfaceFlags) +{ + MOZ_ASSERT(NS_IsMainThread()); + + // If the decoder detected an error, log it to the error console. + if (aStatus.mShouldReportError) { + ReportDecoderError(); + } + + // Record all the metadata the decoder gathered about this image. + bool metadataOK = SetMetadata(aMetadata, aStatus.mWasMetadataDecode); + if (!metadataOK) { + // This indicates a serious error that requires us to discard all existing + // surfaces and redecode to recover. We'll drop the results from this + // decoder on the floor, since they aren't valid. + RecoverFromInvalidFrames(mSize, + FromSurfaceFlags(aSurfaceFlags)); + return; + } + + MOZ_ASSERT(mError || mHasSize || !aMetadata.HasSize(), + "SetMetadata should've gotten a size"); + + if (!aStatus.mWasMetadataDecode && aStatus.mFinished) { + // Flag that we've been decoded before. + mHasBeenDecoded = true; + } + + // Send out any final notifications. + NotifyProgress(aProgress, aInvalidRect, aFrameCount, + aDecoderFlags, aSurfaceFlags); + + if (!(aDecoderFlags & DecoderFlags::FIRST_FRAME_ONLY) && + mHasBeenDecoded && mAnimationState) { + // We've finished a full decode of all animation frames and our AnimationState + // has been notified about them all, so let it know not to expect anymore. + mAnimationState->SetDoneDecoding(true); + } + + // Do some telemetry if this isn't a metadata decode. + if (!aStatus.mWasMetadataDecode) { + if (aTelemetry.mChunkCount) { + Telemetry::Accumulate(Telemetry::IMAGE_DECODE_CHUNKS, aTelemetry.mChunkCount); + } + + if (aStatus.mFinished) { + Telemetry::Accumulate(Telemetry::IMAGE_DECODE_TIME, + int32_t(aTelemetry.mDecodeTime.ToMicroseconds())); + + if (aTelemetry.mSpeedHistogram) { + Telemetry::Accumulate(*aTelemetry.mSpeedHistogram, aTelemetry.Speed()); + } + } + } + + // Only act on errors if we have no usable frames from the decoder. + if (aStatus.mHadError && + (!mAnimationState || mAnimationState->KnownFrameCount() == 0)) { + DoError(); + } else if (aStatus.mWasMetadataDecode && !mHasSize) { + DoError(); + } + + // XXX(aosmond): Can we get this far without mFinished == true? + if (aStatus.mFinished && aStatus.mWasMetadataDecode) { + // If we were waiting to fire the load event, go ahead and fire it now. + if (mLoadProgress) { + NotifyForLoadEvent(*mLoadProgress); + mLoadProgress = Nothing(); + NotifyProgress(FLAG_ONLOAD_UNBLOCKED); + } + + // If we were a metadata decode and a full decode was requested, do it. + if (mWantFullDecode) { + mWantFullDecode = false; + RequestDecodeForSize(mSize, DECODE_FLAGS_DEFAULT); + } + } +} + +void +RasterImage::ReportDecoderError() +{ + nsCOMPtr consoleService = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + nsCOMPtr errorObject = + do_CreateInstance(NS_SCRIPTERROR_CONTRACTID); + + if (consoleService && errorObject) { + nsAutoString msg(NS_LITERAL_STRING("Image corrupt or truncated.")); + nsAutoString src; + if (GetURI()) { + nsCString uri; + if (GetURI()->GetSpecTruncatedTo1k(uri) == ImageURL::TruncatedTo1k) { + msg += NS_LITERAL_STRING(" URI in this note truncated due to length."); + } + src = NS_ConvertUTF8toUTF16(uri); + } + if (NS_SUCCEEDED(errorObject->InitWithWindowID( + msg, + src, + EmptyString(), 0, 0, nsIScriptError::errorFlag, + "Image", InnerWindowID() + ))) { + consoleService->LogMessage(errorObject); + } + } +} + +already_AddRefed +RasterImage::Unwrap() +{ + nsCOMPtr self(this); + return self.forget(); +} + +void +RasterImage::PropagateUseCounters(nsIDocument*) +{ + // No use counters. +} + +IntSize +RasterImage::OptimalImageSizeForDest(const gfxSize& aDest, uint32_t aWhichFrame, + SamplingFilter aSamplingFilter, uint32_t aFlags) +{ + MOZ_ASSERT(aDest.width >= 0 || ceil(aDest.width) <= INT32_MAX || + aDest.height >= 0 || ceil(aDest.height) <= INT32_MAX, + "Unexpected destination size"); + + if (mSize.IsEmpty() || aDest.IsEmpty()) { + return IntSize(0, 0); + } + + IntSize destSize = IntSize::Ceil(aDest.width, aDest.height); + + if (aSamplingFilter == SamplingFilter::GOOD && + CanDownscaleDuringDecode(destSize, aFlags)) { + return destSize; + } + + // We can't scale to this size. Use our intrinsic size for now. + return mSize; +} + +} // namespace image +} // namespace mozilla diff --git a/image/RasterImage.h b/image/RasterImage.h new file mode 100644 index 000000000..d664471dd --- /dev/null +++ b/image/RasterImage.h @@ -0,0 +1,514 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** @file + * This file declares the RasterImage class, which + * handles static and animated rasterized images. + * + * @author Stuart Parmenter + * @author Chris Saari + * @author Arron Mogge + * @author Andrew Smith + */ + +#ifndef mozilla_image_RasterImage_h +#define mozilla_image_RasterImage_h + +#include "Image.h" +#include "nsCOMPtr.h" +#include "imgIContainer.h" +#include "nsIProperties.h" +#include "nsTArray.h" +#include "LookupResult.h" +#include "nsThreadUtils.h" +#include "DecodePool.h" +#include "DecoderFactory.h" +#include "FrameAnimator.h" +#include "ImageMetadata.h" +#include "ISurfaceProvider.h" +#include "Orientation.h" +#include "nsIObserver.h" +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/NotNull.h" +#include "mozilla/Pair.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/WeakPtr.h" +#include "mozilla/UniquePtr.h" +#include "ImageContainer.h" +#include "PlaybackType.h" +#ifdef DEBUG + #include "imgIContainerDebug.h" +#endif + +class nsIInputStream; +class nsIRequest; + +#define NS_RASTERIMAGE_CID \ +{ /* 376ff2c1-9bf6-418a-b143-3340c00112f7 */ \ + 0x376ff2c1, \ + 0x9bf6, \ + 0x418a, \ + {0xb1, 0x43, 0x33, 0x40, 0xc0, 0x01, 0x12, 0xf7} \ +} + +/** + * Handles static and animated image containers. + * + * + * @par A Quick Walk Through + * The decoder initializes this class and calls AppendFrame() to add a frame. + * Once RasterImage detects more than one frame, it starts the animation + * with StartAnimation(). Note that the invalidation events for RasterImage are + * generated automatically using nsRefreshDriver. + * + * @par + * StartAnimation() initializes the animation helper object and sets the time + * the first frame was displayed to the current clock time. + * + * @par + * When the refresh driver corresponding to the imgIContainer that this image is + * a part of notifies the RasterImage that it's time to invalidate, + * RequestRefresh() is called with a given TimeStamp to advance to. As long as + * the timeout of the given frame (the frame's "delay") plus the time that frame + * was first displayed is less than or equal to the TimeStamp given, + * RequestRefresh() calls AdvanceFrame(). + * + * @par + * AdvanceFrame() is responsible for advancing a single frame of the animation. + * It can return true, meaning that the frame advanced, or false, meaning that + * the frame failed to advance (usually because the next frame hasn't been + * decoded yet). It is also responsible for performing the final animation stop + * procedure if the final frame of a non-looping animation is reached. + * + * @par + * Each frame can have a different method of removing itself. These are + * listed as imgIContainer::cDispose... constants. Notify() calls + * DoComposite() to handle any special frame destruction. + * + * @par + * The basic path through DoComposite() is: + * 1) Calculate Area that needs updating, which is at least the area of + * aNextFrame. + * 2) Dispose of previous frame. + * 3) Draw new image onto compositingFrame. + * See comments in DoComposite() for more information and optimizations. + * + * @par + * The rest of the RasterImage specific functions are used by DoComposite to + * destroy the old frame and build the new one. + * + * @note + *
  • "Mask", "Alpha", and "Alpha Level" are interchangeable phrases in + * respects to RasterImage. + * + * @par + *
  • GIFs never have more than a 1 bit alpha. + *
  • APNGs may have a full alpha channel. + * + * @par + *
  • Background color specified in GIF is ignored by web browsers. + * + * @par + *
  • If Frame 3 wants to dispose by restoring previous, what it wants is to + * restore the composition up to and including Frame 2, as well as Frame 2s + * disposal. So, in the middle of DoComposite when composing Frame 3, right + * after destroying Frame 2's area, we copy compositingFrame to + * prevCompositingFrame. When DoComposite gets called to do Frame 4, we + * copy prevCompositingFrame back, and then draw Frame 4 on top. + * + * @par + * The mAnim structure has members only needed for animated images, so + * it's not allocated until the second frame is added. + */ + +namespace mozilla { + +namespace layers { +class ImageContainer; +class Image; +} // namespace layers + +namespace image { + +class Decoder; +struct DecoderFinalStatus; +struct DecoderTelemetry; +class ImageMetadata; +class SourceBuffer; + +class RasterImage final : public ImageResource + , public nsIProperties + , public SupportsWeakPtr +#ifdef DEBUG + , public imgIContainerDebug +#endif +{ + // (no public constructor - use ImageFactory) + virtual ~RasterImage(); + +public: + MOZ_DECLARE_WEAKREFERENCE_TYPENAME(RasterImage) + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPROPERTIES + NS_DECL_IMGICONTAINER +#ifdef DEBUG + NS_DECL_IMGICONTAINERDEBUG +#endif + + virtual nsresult StartAnimation() override; + virtual nsresult StopAnimation() override; + + // Methods inherited from Image + virtual void OnSurfaceDiscarded() override; + + virtual size_t SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) + const override; + virtual void CollectSizeOfSurfaces(nsTArray& aCounters, + MallocSizeOf aMallocSizeOf) const override; + + /* Triggers discarding. */ + void Discard(); + + + ////////////////////////////////////////////////////////////////////////////// + // Decoder callbacks. + ////////////////////////////////////////////////////////////////////////////// + + /** + * Sends the provided progress notifications to ProgressTracker. + * + * Main-thread only. + * + * @param aProgress The progress notifications to send. + * @param aInvalidRect An invalidation rect to send. + * @param aFrameCount If Some(), an updated count of the number of frames of + * animation the decoder has finished decoding so far. This + * is a lower bound for the total number of animation + * frames this image has. + * @param aDecoderFlags The decoder flags used by the decoder that generated + * these notifications, or DefaultDecoderFlags() if the + * notifications don't come from a decoder. + * @param aSurfaceFlags The surface flags used by the decoder that generated + * these notifications, or DefaultSurfaceFlags() if the + * notifications don't come from a decoder. + */ + void NotifyProgress(Progress aProgress, + const gfx::IntRect& aInvalidRect = nsIntRect(), + const Maybe& aFrameCount = Nothing(), + DecoderFlags aDecoderFlags = DefaultDecoderFlags(), + SurfaceFlags aSurfaceFlags = DefaultSurfaceFlags()); + + /** + * Records decoding results, sends out any final notifications, updates the + * state of this image, and records telemetry. + * + * Main-thread only. + * + * @param aStatus Final status information about the decoder. (Whether it + * encountered an error, etc.) + * @param aMetadata Metadata about this image that the decoder gathered. + * @param aTelemetry Telemetry data about the decoder. + * @param aProgress Any final progress notifications to send. + * @param aInvalidRect Any final invalidation rect to send. + * @param aFrameCount If Some(), a final updated count of the number of frames + * of animation the decoder has finished decoding so far. + * This is a lower bound for the total number of animation + * frames this image has. + * @param aDecoderFlags The decoder flags used by the decoder. + * @param aSurfaceFlags The surface flags used by the decoder. + */ + void NotifyDecodeComplete(const DecoderFinalStatus& aStatus, + const ImageMetadata& aMetadata, + const DecoderTelemetry& aTelemetry, + Progress aProgress, + const gfx::IntRect& aInvalidRect, + const Maybe& aFrameCount, + DecoderFlags aDecoderFlags, + SurfaceFlags aSurfaceFlags); + + // Helper method for NotifyDecodeComplete. + void ReportDecoderError(); + + + ////////////////////////////////////////////////////////////////////////////// + // Network callbacks. + ////////////////////////////////////////////////////////////////////////////// + + virtual nsresult OnImageDataAvailable(nsIRequest* aRequest, + nsISupports* aContext, + nsIInputStream* aInStr, + uint64_t aSourceOffset, + uint32_t aCount) override; + virtual nsresult OnImageDataComplete(nsIRequest* aRequest, + nsISupports* aContext, + nsresult aStatus, + bool aLastPart) override; + + void NotifyForLoadEvent(Progress aProgress); + + /** + * A hint of the number of bytes of source data that the image contains. If + * called early on, this can help reduce copying and reallocations by + * appropriately preallocating the source data buffer. + * + * We take this approach rather than having the source data management code do + * something more complicated (like chunklisting) because HTTP is by far the + * dominant source of images, and the Content-Length header is quite reliable. + * Thus, pre-allocation simplifies code and reduces the total number of + * allocations. + */ + nsresult SetSourceSizeHint(uint32_t aSizeHint); + + /* Provide a hint for the requested dimension of the resulting image. */ + void SetRequestedSampleSize(int requestedSampleSize) { + mRequestedSampleSize = requestedSampleSize; + } + + nsCString GetURIString() { + nsCString spec; + if (GetURI()) { + GetURI()->GetSpec(spec); + } + return spec; + } + +private: + nsresult Init(const char* aMimeType, uint32_t aFlags); + + /** + * Tries to retrieve a surface for this image with size @aSize, surface flags + * matching @aFlags, and a playback type of @aPlaybackType. + * + * If @aFlags specifies FLAG_SYNC_DECODE and we already have all the image + * data, we'll attempt a sync decode if no matching surface is found. If + * FLAG_SYNC_DECODE was not specified and no matching surface was found, we'll + * kick off an async decode so that the surface is (hopefully) available next + * time it's requested. + * + * @return a drawable surface, which may be empty if the requested surface + * could not be found. + */ + DrawableSurface LookupFrame(const gfx::IntSize& aSize, + uint32_t aFlags, + PlaybackType aPlaybackType); + + /// Helper method for LookupFrame(). + LookupResult LookupFrameInternal(const gfx::IntSize& aSize, + uint32_t aFlags, + PlaybackType aPlaybackType); + + DrawResult DrawInternal(DrawableSurface&& aFrameRef, + gfxContext* aContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, + gfx::SamplingFilter aSamplingFilter, + uint32_t aFlags); + + Pair> + GetFrameInternal(const gfx::IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags); + + Pair> + GetCurrentImage(layers::ImageContainer* aContainer, uint32_t aFlags); + + void UpdateImageContainer(); + + // We would like to just check if we have a zero lock count, but we can't do + // that for animated images because in EnsureAnimExists we lock the image and + // never unlock so that animated images always have their lock count >= 1. In + // that case we use our animation consumers count as a proxy for lock count. + bool IsUnlocked() { + return (mLockCount == 0 || (mAnimationState && mAnimationConsumers == 0)); + } + + + ////////////////////////////////////////////////////////////////////////////// + // Decoding. + ////////////////////////////////////////////////////////////////////////////// + + /** + * Creates and runs a decoder, either synchronously or asynchronously + * according to @aFlags. Decodes at the provided target size @aSize, using + * decode flags @aFlags. Performs a single-frame decode of this image unless + * we know the image is animated *and* @aPlaybackType is + * PlaybackType::eAnimated. + * + * It's an error to call Decode() before this image's intrinsic size is + * available. A metadata decode must successfully complete first. + */ + NS_IMETHOD Decode(const gfx::IntSize& aSize, + uint32_t aFlags, + PlaybackType aPlaybackType); + + /** + * Creates and runs a metadata decoder, either synchronously or + * asynchronously according to @aFlags. + */ + NS_IMETHOD DecodeMetadata(uint32_t aFlags); + + /** + * Sets the size, inherent orientation, animation metadata, and other + * information about the image gathered during decoding. + * + * This function may be called multiple times, but will throw an error if + * subsequent calls do not match the first. + * + * @param aMetadata The metadata to set on this image. + * @param aFromMetadataDecode True if this metadata came from a metadata + * decode; false if it came from a full decode. + * @return |true| unless a catastrophic failure was discovered. If |false| is + * returned, it indicates that the image is corrupt in a way that requires all + * surfaces to be discarded to recover. + */ + bool SetMetadata(const ImageMetadata& aMetadata, bool aFromMetadataDecode); + + /** + * In catastrophic circumstances like a GPU driver crash, the contents of our + * frames may become invalid. If the information we gathered during the + * metadata decode proves to be wrong due to image corruption, the frames we + * have may violate this class's invariants. Either way, we need to + * immediately discard the invalid frames and redecode so that callers don't + * perceive that we've entered an invalid state. + * + * RecoverFromInvalidFrames discards all existing frames and redecodes using + * the provided @aSize and @aFlags. + */ + void RecoverFromInvalidFrames(const nsIntSize& aSize, uint32_t aFlags); + +private: // data + nsIntSize mSize; + Orientation mOrientation; + + /// If this has a value, we're waiting for SetSize() to send the load event. + Maybe mLoadProgress; + + nsCOMPtr mProperties; + + /// If this image is animated, a FrameAnimator which manages its animation. + UniquePtr mFrameAnimator; + + /// Animation timeline and other state for animation images. + Maybe mAnimationState; + + // Image locking. + uint32_t mLockCount; + + // The type of decoder this image needs. Computed from the MIME type in Init(). + DecoderType mDecoderType; + + // How many times we've decoded this image. + // This is currently only used for statistics + int32_t mDecodeCount; + + // A hint for image decoder that directly scale the image to smaller buffer + int mRequestedSampleSize; + + // A weak pointer to our ImageContainer, which stays alive only as long as + // the layer system needs it. + WeakPtr mImageContainer; + + layers::ImageContainer::ProducerID mImageProducerID; + layers::ImageContainer::FrameID mLastFrameID; + + // If mImageContainer is non-null, this contains the DrawResult we obtained + // the last time we updated it. + DrawResult mLastImageContainerDrawResult; + +#ifdef DEBUG + uint32_t mFramesNotified; +#endif + + // The source data for this image. + NotNull> mSourceBuffer; + + // Boolean flags (clustered together to conserve space): + bool mHasSize:1; // Has SetSize() been called? + bool mTransient:1; // Is the image short-lived? + bool mSyncLoad:1; // Are we loading synchronously? + bool mDiscardable:1; // Is container discardable? + bool mHasSourceData:1; // Do we have source data? + bool mHasBeenDecoded:1; // Decoded at least once? + + // Whether we're waiting to start animation. If we get a StartAnimation() call + // but we don't yet have more than one frame, mPendingAnimation is set so that + // we know to start animation later if/when we have more frames. + bool mPendingAnimation:1; + + // Whether the animation can stop, due to running out + // of frames, or no more owning request + bool mAnimationFinished:1; + + // Whether, once we are done doing a metadata decode, we should immediately + // kick off a full decode. + bool mWantFullDecode:1; + + TimeStamp mDrawStartTime; + + + ////////////////////////////////////////////////////////////////////////////// + // Scaling. + ////////////////////////////////////////////////////////////////////////////// + + // Determines whether we can downscale during decode with the given + // parameters. + bool CanDownscaleDuringDecode(const nsIntSize& aSize, uint32_t aFlags); + + + // Error handling. + void DoError(); + + class HandleErrorWorker : public Runnable + { + public: + /** + * Called from decoder threads when DoError() is called, since errors can't + * be handled safely off-main-thread. Dispatches an event which reinvokes + * DoError on the main thread if there isn't one already pending. + */ + static void DispatchIfNeeded(RasterImage* aImage); + + NS_IMETHOD Run(); + + private: + explicit HandleErrorWorker(RasterImage* aImage); + + RefPtr mImage; + }; + + // Helpers + bool CanDiscard(); + + bool IsOpaque(); + +protected: + explicit RasterImage(ImageURL* aURI = nullptr); + + bool ShouldAnimate() override; + + friend class ImageFactory; +}; + +inline NS_IMETHODIMP +RasterImage::GetAnimationMode(uint16_t* aAnimationMode) { + return GetAnimationModeInternal(aAnimationMode); +} + +} // namespace image +} // namespace mozilla + +/** + * Casting RasterImage to nsISupports is ambiguous. This method handles that. + */ +inline nsISupports* +ToSupports(mozilla::image::RasterImage* p) +{ + return NS_ISUPPORTS_CAST(mozilla::image::ImageResource*, p); +} + +#endif /* mozilla_image_RasterImage_h */ diff --git a/image/SVGDocumentWrapper.cpp b/image/SVGDocumentWrapper.cpp new file mode 100644 index 000000000..d4b71b907 --- /dev/null +++ b/image/SVGDocumentWrapper.cpp @@ -0,0 +1,460 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGDocumentWrapper.h" + +#include "mozilla/dom/DocumentTimeline.h" +#include "mozilla/dom/Element.h" +#include "nsICategoryManager.h" +#include "nsIChannel.h" +#include "nsIContentViewer.h" +#include "nsIDocument.h" +#include "nsIDocumentLoaderFactory.h" +#include "nsIDOMSVGLength.h" +#include "nsIHttpChannel.h" +#include "nsIObserverService.h" +#include "nsIParser.h" +#include "nsIPresShell.h" +#include "nsIRequest.h" +#include "nsIStreamListener.h" +#include "nsIXMLContentSink.h" +#include "nsNetCID.h" +#include "nsComponentManagerUtils.h" +#include "nsSMILAnimationController.h" +#include "nsServiceManagerUtils.h" +#include "mozilla/dom/SVGSVGElement.h" +#include "nsSVGEffects.h" +#include "mozilla/dom/SVGAnimatedLength.h" +#include "nsMimeTypes.h" +#include "DOMSVGLength.h" +#include "nsDocument.h" +#include "mozilla/dom/ImageTracker.h" + +// undef the GetCurrentTime macro defined in WinBase.h from the MS Platform SDK +#undef GetCurrentTime + +namespace mozilla { + +using namespace dom; +using namespace gfx; + +namespace image { + +NS_IMPL_ISUPPORTS(SVGDocumentWrapper, + nsIStreamListener, + nsIRequestObserver, + nsIObserver, + nsISupportsWeakReference) + +SVGDocumentWrapper::SVGDocumentWrapper() + : mIgnoreInvalidation(false), + mRegisteredForXPCOMShutdown(false) +{ } + +SVGDocumentWrapper::~SVGDocumentWrapper() +{ + DestroyViewer(); + if (mRegisteredForXPCOMShutdown) { + UnregisterForXPCOMShutdown(); + } +} + +void +SVGDocumentWrapper::DestroyViewer() +{ + if (mViewer) { + mViewer->GetDocument()->OnPageHide(false, nullptr); + mViewer->Close(nullptr); + mViewer->Destroy(); + mViewer = nullptr; + } +} + +nsIFrame* +SVGDocumentWrapper::GetRootLayoutFrame() +{ + Element* rootElem = GetRootSVGElem(); + return rootElem ? rootElem->GetPrimaryFrame() : nullptr; +} + +void +SVGDocumentWrapper::UpdateViewportBounds(const nsIntSize& aViewportSize) +{ + MOZ_ASSERT(!mIgnoreInvalidation, "shouldn't be reentrant"); + mIgnoreInvalidation = true; + + nsIntRect currentBounds; + mViewer->GetBounds(currentBounds); + + // If the bounds have changed, we need to do a layout flush. + if (currentBounds.Size() != aViewportSize) { + mViewer->SetBounds(IntRect(IntPoint(0, 0), aViewportSize)); + FlushLayout(); + } + + mIgnoreInvalidation = false; +} + +void +SVGDocumentWrapper::FlushImageTransformInvalidation() +{ + MOZ_ASSERT(!mIgnoreInvalidation, "shouldn't be reentrant"); + + SVGSVGElement* svgElem = GetRootSVGElem(); + if (!svgElem) { + return; + } + + mIgnoreInvalidation = true; + svgElem->FlushImageTransformInvalidation(); + FlushLayout(); + mIgnoreInvalidation = false; +} + +bool +SVGDocumentWrapper::IsAnimated() +{ + // Can be called for animated images during shutdown, after we've + // already Observe()'d XPCOM shutdown and cleared out our mViewer pointer. + if (!mViewer) { + return false; + } + + nsIDocument* doc = mViewer->GetDocument(); + if (!doc) { + return false; + } + if (doc->Timeline()->HasAnimations()) { + // CSS animations (technically HasAnimations() also checks for CSS + // transitions and Web animations but since SVG-as-an-image doesn't run + // script they will never run in the document that we wrap). + return true; + } + if (doc->HasAnimationController() && + doc->GetAnimationController()->HasRegisteredAnimations()) { + // SMIL animations + return true; + } + return false; +} + +void +SVGDocumentWrapper::StartAnimation() +{ + // Can be called for animated images during shutdown, after we've + // already Observe()'d XPCOM shutdown and cleared out our mViewer pointer. + if (!mViewer) { + return; + } + + nsIDocument* doc = mViewer->GetDocument(); + if (doc) { + nsSMILAnimationController* controller = doc->GetAnimationController(); + if (controller) { + controller->Resume(nsSMILTimeContainer::PAUSE_IMAGE); + } + doc->ImageTracker()->SetAnimatingState(true); + } +} + +void +SVGDocumentWrapper::StopAnimation() +{ + // Can be called for animated images during shutdown, after we've + // already Observe()'d XPCOM shutdown and cleared out our mViewer pointer. + if (!mViewer) { + return; + } + + nsIDocument* doc = mViewer->GetDocument(); + if (doc) { + nsSMILAnimationController* controller = doc->GetAnimationController(); + if (controller) { + controller->Pause(nsSMILTimeContainer::PAUSE_IMAGE); + } + doc->ImageTracker()->SetAnimatingState(false); + } +} + +void +SVGDocumentWrapper::ResetAnimation() +{ + SVGSVGElement* svgElem = GetRootSVGElem(); + if (!svgElem) { + return; + } + + svgElem->SetCurrentTime(0.0f); +} + +float +SVGDocumentWrapper::GetCurrentTime() +{ + SVGSVGElement* svgElem = GetRootSVGElem(); + return svgElem ? svgElem->GetCurrentTime() + : 0.0f; +} + +void +SVGDocumentWrapper::SetCurrentTime(float aTime) +{ + SVGSVGElement* svgElem = GetRootSVGElem(); + if (svgElem && svgElem->GetCurrentTime() != aTime) { + svgElem->SetCurrentTime(aTime); + } +} + +void +SVGDocumentWrapper::TickRefreshDriver() +{ + nsCOMPtr presShell; + mViewer->GetPresShell(getter_AddRefs(presShell)); + if (presShell) { + nsPresContext* presContext = presShell->GetPresContext(); + if (presContext) { + presContext->RefreshDriver()->DoTick(); + } + } +} + +/** nsIStreamListener methods **/ + +NS_IMETHODIMP +SVGDocumentWrapper::OnDataAvailable(nsIRequest* aRequest, nsISupports* ctxt, + nsIInputStream* inStr, + uint64_t sourceOffset, + uint32_t count) +{ + return mListener->OnDataAvailable(aRequest, ctxt, inStr, + sourceOffset, count); +} + +/** nsIRequestObserver methods **/ + +NS_IMETHODIMP +SVGDocumentWrapper::OnStartRequest(nsIRequest* aRequest, nsISupports* ctxt) +{ + nsresult rv = SetupViewer(aRequest, + getter_AddRefs(mViewer), + getter_AddRefs(mLoadGroup)); + + if (NS_SUCCEEDED(rv) && + NS_SUCCEEDED(mListener->OnStartRequest(aRequest, nullptr))) { + mViewer->GetDocument()->SetIsBeingUsedAsImage(); + StopAnimation(); // otherwise animations start automatically in helper doc + + rv = mViewer->Init(nullptr, nsIntRect(0, 0, 0, 0)); + if (NS_SUCCEEDED(rv)) { + rv = mViewer->Open(nullptr, nullptr); + } + } + return rv; +} + + +NS_IMETHODIMP +SVGDocumentWrapper::OnStopRequest(nsIRequest* aRequest, nsISupports* ctxt, + nsresult status) +{ + if (mListener) { + mListener->OnStopRequest(aRequest, ctxt, status); + mListener = nullptr; + } + + return NS_OK; +} + +/** nsIObserver Methods **/ +NS_IMETHODIMP +SVGDocumentWrapper::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + // Sever ties from rendering observers to helper-doc's root SVG node + SVGSVGElement* svgElem = GetRootSVGElem(); + if (svgElem) { + nsSVGEffects::RemoveAllRenderingObservers(svgElem); + } + + // Clean up at XPCOM shutdown time. + DestroyViewer(); + if (mListener) { + mListener = nullptr; + } + if (mLoadGroup) { + mLoadGroup = nullptr; + } + + // Turn off "registered" flag, or else we'll try to unregister when we die. + // (No need for that now, and the try would fail anyway -- it's too late.) + mRegisteredForXPCOMShutdown = false; + } else { + NS_ERROR("Unexpected observer topic."); + } + return NS_OK; +} + +/** Private helper methods **/ + +// This method is largely cribbed from +// nsExternalResourceMap::PendingLoad::SetupViewer. +nsresult +SVGDocumentWrapper::SetupViewer(nsIRequest* aRequest, + nsIContentViewer** aViewer, + nsILoadGroup** aLoadGroup) +{ + nsCOMPtr chan(do_QueryInterface(aRequest)); + NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED); + + // Check for HTTP error page + nsCOMPtr httpChannel(do_QueryInterface(aRequest)); + if (httpChannel) { + bool requestSucceeded; + if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) || + !requestSucceeded) { + return NS_ERROR_FAILURE; + } + } + + // Give this document its own loadgroup + nsCOMPtr loadGroup; + chan->GetLoadGroup(getter_AddRefs(loadGroup)); + + nsCOMPtr newLoadGroup = + do_CreateInstance(NS_LOADGROUP_CONTRACTID); + NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY); + newLoadGroup->SetLoadGroup(loadGroup); + + nsCOMPtr catMan = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID); + NS_ENSURE_TRUE(catMan, NS_ERROR_NOT_AVAILABLE); + nsXPIDLCString contractId; + nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", IMAGE_SVG_XML, + getter_Copies(contractId)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr docLoaderFactory = + do_GetService(contractId); + NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE); + + nsCOMPtr viewer; + nsCOMPtr listener; + rv = docLoaderFactory->CreateInstance("external-resource", chan, + newLoadGroup, + NS_LITERAL_CSTRING(IMAGE_SVG_XML), + nullptr, nullptr, + getter_AddRefs(listener), + getter_AddRefs(viewer)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE(viewer, NS_ERROR_UNEXPECTED); + + // Create a navigation time object and pass it to the SVG document through + // the viewer. + // The timeline(DocumentTimeline, used in CSS animation) of this SVG + // document needs this navigation timing object for time computation, such + // as to calculate current time stamp based on the start time of navigation + // time object. + // + // For a root document, DocShell would do these sort of things + // automatically. Since there is no DocShell for this wrapped SVG document, + // we must set it up manually. + RefPtr timing = new nsDOMNavigationTiming(); + timing->NotifyNavigationStart(nsDOMNavigationTiming::DocShellState::eInactive); + viewer->SetNavigationTiming(timing); + + nsCOMPtr parser = do_QueryInterface(listener); + NS_ENSURE_TRUE(parser, NS_ERROR_UNEXPECTED); + + // XML-only, because this is for SVG content + nsCOMPtr sink = parser->GetContentSink(); + NS_ENSURE_TRUE(sink, NS_ERROR_UNEXPECTED); + + listener.swap(mListener); + viewer.forget(aViewer); + newLoadGroup.forget(aLoadGroup); + + RegisterForXPCOMShutdown(); + return NS_OK; +} + +void +SVGDocumentWrapper::RegisterForXPCOMShutdown() +{ + MOZ_ASSERT(!mRegisteredForXPCOMShutdown, + "re-registering for XPCOM shutdown"); + // Listen for xpcom-shutdown so that we can drop references to our + // helper-document at that point. (Otherwise, we won't get cleaned up + // until imgLoader::Shutdown, which can happen after the JAR service + // and RDF service have been unregistered.) + nsresult rv; + nsCOMPtr obsSvc = do_GetService(OBSERVER_SVC_CID, &rv); + if (NS_FAILED(rv) || + NS_FAILED(obsSvc->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, + true))) { + NS_WARNING("Failed to register as observer of XPCOM shutdown"); + } else { + mRegisteredForXPCOMShutdown = true; + } +} + +void +SVGDocumentWrapper::UnregisterForXPCOMShutdown() +{ + MOZ_ASSERT(mRegisteredForXPCOMShutdown, + "unregistering for XPCOM shutdown w/out being registered"); + + nsresult rv; + nsCOMPtr obsSvc = do_GetService(OBSERVER_SVC_CID, &rv); + if (NS_FAILED(rv) || + NS_FAILED(obsSvc->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) { + NS_WARNING("Failed to unregister as observer of XPCOM shutdown"); + } else { + mRegisteredForXPCOMShutdown = false; + } +} + +void +SVGDocumentWrapper::FlushLayout() +{ + nsCOMPtr presShell; + mViewer->GetPresShell(getter_AddRefs(presShell)); + if (presShell) { + presShell->FlushPendingNotifications(Flush_Layout); + } +} + +nsIDocument* +SVGDocumentWrapper::GetDocument() +{ + if (!mViewer) { + return nullptr; + } + + return mViewer->GetDocument(); // May be nullptr. +} + +SVGSVGElement* +SVGDocumentWrapper::GetRootSVGElem() +{ + if (!mViewer) { + return nullptr; // Can happen during destruction + } + + nsIDocument* doc = mViewer->GetDocument(); + if (!doc) { + return nullptr; // Can happen during destruction + } + + Element* rootElem = mViewer->GetDocument()->GetRootElement(); + if (!rootElem || !rootElem->IsSVGElement(nsGkAtoms::svg)) { + return nullptr; + } + + return static_cast(rootElem); +} + +} // namespace image +} // namespace mozilla diff --git a/image/SVGDocumentWrapper.h b/image/SVGDocumentWrapper.h new file mode 100644 index 000000000..7d7586ae3 --- /dev/null +++ b/image/SVGDocumentWrapper.h @@ -0,0 +1,151 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* This class wraps an SVG document, for use by VectorImage objects. */ + +#ifndef mozilla_image_SVGDocumentWrapper_h +#define mozilla_image_SVGDocumentWrapper_h + +#include "mozilla/Attributes.h" + +#include "nsCOMPtr.h" +#include "nsIStreamListener.h" +#include "nsIObserver.h" +#include "nsIContentViewer.h" +#include "nsWeakReference.h" +#include "nsSize.h" + +class nsIPresShell; +class nsIRequest; +class nsILoadGroup; +class nsIFrame; + +#define OBSERVER_SVC_CID "@mozilla.org/observer-service;1" + +// undef the GetCurrentTime macro defined in WinBase.h from the MS Platform SDK +#undef GetCurrentTime + +namespace mozilla { +namespace dom { +class SVGSVGElement; +} // namespace dom + +namespace image { + +class SVGDocumentWrapper final : public nsIStreamListener, + public nsIObserver, + nsSupportsWeakReference +{ +public: + SVGDocumentWrapper(); + + NS_DECL_ISUPPORTS + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSIOBSERVER + + enum Dimension { + eWidth, + eHeight + }; + + /** + * Returns the wrapped document, or nullptr on failure. (No AddRef.) + */ + nsIDocument* GetDocument(); + + /** + * Returns the root element for the wrapped document, or nullptr on + * failure. + */ + mozilla::dom::SVGSVGElement* GetRootSVGElem(); + + /** + * Returns the root nsIFrame* for the wrapped document, or nullptr on failure. + * + * @return the root nsIFrame* for the wrapped document, or nullptr on failure. + */ + nsIFrame* GetRootLayoutFrame(); + + /** + * Returns (by reference) the nsIPresShell for the wrapped document. + * + * @param[out] aPresShell On success, this will be populated with a pointer + * to the wrapped document's nsIPresShell. + * + * @return NS_OK on success, or an error code on failure. + */ + inline nsresult GetPresShell(nsIPresShell** aPresShell) + { return mViewer->GetPresShell(aPresShell); } + + /** + * Modifier to update the viewport dimensions of the wrapped document. This + * method performs a synchronous "Flush_Layout" on the wrapped document, + * since a viewport-change affects layout. + * + * @param aViewportSize The new viewport dimensions. + */ + void UpdateViewportBounds(const nsIntSize& aViewportSize); + + /** + * If an SVG image's helper document has a pending notification for an + * override on the root node's "preserveAspectRatio" attribute, then this + * method will flush that notification so that the image can paint correctly. + * (First, though, it sets the mIgnoreInvalidation flag so that we won't + * notify the image's observers and trigger unwanted repaint-requests.) + */ + void FlushImageTransformInvalidation(); + + /** + * Returns a bool indicating whether the document has any SMIL animations. + * + * @return true if the document has any SMIL animations. Else, false. + */ + bool IsAnimated(); + + /** + * Indicates whether we should currently ignore rendering invalidations sent + * from the wrapped SVG doc. + * + * @return true if we should ignore invalidations sent from this SVG doc. + */ + bool ShouldIgnoreInvalidation() { return mIgnoreInvalidation; } + + /** + * Methods to control animation. + */ + void StartAnimation(); + void StopAnimation(); + void ResetAnimation(); + float GetCurrentTime(); + void SetCurrentTime(float aTime); + void TickRefreshDriver(); + + /** + * Force a layout flush of the underlying SVG document. + */ + void FlushLayout(); + +private: + ~SVGDocumentWrapper(); + + nsresult SetupViewer(nsIRequest* aRequest, + nsIContentViewer** aViewer, + nsILoadGroup** aLoadGroup); + void DestroyViewer(); + void RegisterForXPCOMShutdown(); + void UnregisterForXPCOMShutdown(); + + nsCOMPtr mViewer; + nsCOMPtr mLoadGroup; + nsCOMPtr mListener; + bool mIgnoreInvalidation; + bool mRegisteredForXPCOMShutdown; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_SVGDocumentWrapper_h diff --git a/image/ScriptedNotificationObserver.cpp b/image/ScriptedNotificationObserver.cpp new file mode 100644 index 000000000..3c7f05296 --- /dev/null +++ b/image/ScriptedNotificationObserver.cpp @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ScriptedNotificationObserver.h" +#include "imgIScriptedNotificationObserver.h" +#include "nsCycleCollectionParticipant.h" + +namespace mozilla { +namespace image { + +NS_IMPL_CYCLE_COLLECTION(ScriptedNotificationObserver, mInner) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScriptedNotificationObserver) + NS_INTERFACE_MAP_ENTRY(imgINotificationObserver) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptedNotificationObserver) +NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptedNotificationObserver) + +ScriptedNotificationObserver::ScriptedNotificationObserver( + imgIScriptedNotificationObserver* aInner) +: mInner(aInner) +{ } + +NS_IMETHODIMP +ScriptedNotificationObserver::Notify(imgIRequest* aRequest, + int32_t aType, + const nsIntRect* /*aUnused*/) +{ + if (aType == imgINotificationObserver::SIZE_AVAILABLE) { + return mInner->SizeAvailable(aRequest); + } + if (aType == imgINotificationObserver::FRAME_UPDATE) { + return mInner->FrameUpdate(aRequest); + } + if (aType == imgINotificationObserver::FRAME_COMPLETE) { + return mInner->FrameComplete(aRequest); + } + if (aType == imgINotificationObserver::DECODE_COMPLETE) { + return mInner->DecodeComplete(aRequest); + } + if (aType == imgINotificationObserver::LOAD_COMPLETE) { + return mInner->LoadComplete(aRequest); + } + if (aType == imgINotificationObserver::DISCARD) { + return mInner->Discard(aRequest); + } + if (aType == imgINotificationObserver::IS_ANIMATED) { + return mInner->IsAnimated(aRequest); + } + if (aType == imgINotificationObserver::HAS_TRANSPARENCY) { + return mInner->HasTransparency(aRequest); + } + return NS_OK; +} + +} // namespace image +} // namespace mozilla diff --git a/image/ScriptedNotificationObserver.h b/image/ScriptedNotificationObserver.h new file mode 100644 index 000000000..1a1e35d70 --- /dev/null +++ b/image/ScriptedNotificationObserver.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_ScriptedNotificationObserver_h +#define mozilla_image_ScriptedNotificationObserver_h + +#include "imgINotificationObserver.h" +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" + +class imgIScriptedNotificationObserver; + +namespace mozilla { +namespace image { + +class ScriptedNotificationObserver : public imgINotificationObserver +{ +public: + explicit + ScriptedNotificationObserver(imgIScriptedNotificationObserver* aInner); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_IMGINOTIFICATIONOBSERVER + NS_DECL_CYCLE_COLLECTION_CLASS(ScriptedNotificationObserver) + +private: + virtual ~ScriptedNotificationObserver() { } + nsCOMPtr mInner; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_ScriptedNotificationObserver_h diff --git a/image/ShutdownTracker.cpp b/image/ShutdownTracker.cpp new file mode 100644 index 000000000..c52c0b79e --- /dev/null +++ b/image/ShutdownTracker.cpp @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ShutdownTracker.h" + +#include "mozilla/Services.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" + +namespace mozilla { +namespace image { + +class ShutdownTrackerImpl; + +/////////////////////////////////////////////////////////////////////////////// +// Static Data +/////////////////////////////////////////////////////////////////////////////// + +// Whether we've observed shutdown starting yet. +static bool sShutdownHasStarted = false; + + +/////////////////////////////////////////////////////////////////////////////// +// Implementation +/////////////////////////////////////////////////////////////////////////////// + +struct ShutdownObserver : public nsIObserver +{ + NS_DECL_ISUPPORTS + + NS_IMETHOD Observe(nsISupports*, const char* aTopic, const char16_t*) override + { + if (strcmp(aTopic, "xpcom-will-shutdown") != 0) { + return NS_OK; + } + + nsCOMPtr os = services::GetObserverService(); + if (os) { + os->RemoveObserver(this, "xpcom-will-shutdown"); + } + + sShutdownHasStarted = true; + return NS_OK; + } + +private: + virtual ~ShutdownObserver() { } +}; + +NS_IMPL_ISUPPORTS(ShutdownObserver, nsIObserver) + + +/////////////////////////////////////////////////////////////////////////////// +// Public API +/////////////////////////////////////////////////////////////////////////////// + +/* static */ void +ShutdownTracker::Initialize() +{ + nsCOMPtr os = services::GetObserverService(); + if (os) { + os->AddObserver(new ShutdownObserver, "xpcom-will-shutdown", false); + } +} + +/* static */ bool +ShutdownTracker::ShutdownHasStarted() +{ + return sShutdownHasStarted; +} + +} // namespace image +} // namespace mozilla diff --git a/image/ShutdownTracker.h b/image/ShutdownTracker.h new file mode 100644 index 000000000..9a38735db --- /dev/null +++ b/image/ShutdownTracker.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * ShutdownTracker is an imagelib-global service that allows callers to check + * whether shutdown has started. + */ + +#ifndef mozilla_image_ShutdownTracker_h +#define mozilla_image_ShutdownTracker_h + +namespace mozilla { +namespace image { + +/** + * ShutdownTracker is an imagelib-global service that allows callers to check + * whether shutdown has started. It exists to avoid the need for registering + * many 'xpcom-will-shutdown' notification observers on short-lived objects, + * which would have an unnecessary performance cost. + */ +struct ShutdownTracker +{ + /** + * Initialize static data. Called during imagelib module initialization. + */ + static void Initialize(); + + /** + * Check whether shutdown has started. Callers can use this to check whether + * it's safe to access XPCOM services; if shutdown has started, such calls + * must be avoided. + * + * @return true if shutdown has already started. + */ + static bool ShutdownHasStarted(); + +private: + virtual ~ShutdownTracker() = 0; // Forbid instantiation. +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_ShutdownTracker_h diff --git a/image/SourceBuffer.cpp b/image/SourceBuffer.cpp new file mode 100644 index 000000000..de0719d45 --- /dev/null +++ b/image/SourceBuffer.cpp @@ -0,0 +1,682 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SourceBuffer.h" + +#include +#include +#include +#include "mozilla/Likely.h" +#include "nsIInputStream.h" +#include "MainThreadUtils.h" +#include "SurfaceCache.h" + +using std::max; +using std::min; + +namespace mozilla { +namespace image { + +////////////////////////////////////////////////////////////////////////////// +// SourceBufferIterator implementation. +////////////////////////////////////////////////////////////////////////////// + +SourceBufferIterator::~SourceBufferIterator() +{ + if (mOwner) { + mOwner->OnIteratorRelease(); + } +} + +SourceBufferIterator& +SourceBufferIterator::operator=(SourceBufferIterator&& aOther) +{ + if (mOwner) { + mOwner->OnIteratorRelease(); + } + + mOwner = Move(aOther.mOwner); + mState = aOther.mState; + mData = aOther.mData; + mChunkCount = aOther.mChunkCount; + mByteCount = aOther.mByteCount; + + return *this; +} + +SourceBufferIterator::State +SourceBufferIterator::AdvanceOrScheduleResume(size_t aRequestedBytes, + IResumable* aConsumer) +{ + MOZ_ASSERT(mOwner); + + if (MOZ_UNLIKELY(!HasMore())) { + MOZ_ASSERT_UNREACHABLE("Should not advance a completed iterator"); + return COMPLETE; + } + + // The range of data [mOffset, mOffset + mNextReadLength) has just been read + // by the caller (or at least they don't have any interest in it), so consume + // that data. + MOZ_ASSERT(mData.mIterating.mNextReadLength <= mData.mIterating.mAvailableLength); + mData.mIterating.mOffset += mData.mIterating.mNextReadLength; + mData.mIterating.mAvailableLength -= mData.mIterating.mNextReadLength; + mData.mIterating.mNextReadLength = 0; + + if (MOZ_LIKELY(mState == READY)) { + // If the caller wants zero bytes of data, that's easy enough; we just + // configured ourselves for a zero-byte read above! In theory we could do + // this even in the START state, but it's not important for performance and + // breaking the ability of callers to assert that the pointer returned by + // Data() is non-null doesn't seem worth it. + if (aRequestedBytes == 0) { + MOZ_ASSERT(mData.mIterating.mNextReadLength == 0); + return READY; + } + + // Try to satisfy the request out of our local buffer. This is potentially + // much faster than requesting data from our owning SourceBuffer because we + // don't have to take the lock. Note that if we have anything at all in our + // local buffer, we use it to satisfy the request; @aRequestedBytes is just + // the *maximum* number of bytes we can return. + if (mData.mIterating.mAvailableLength > 0) { + return AdvanceFromLocalBuffer(aRequestedBytes); + } + } + + // Our local buffer is empty, so we'll have to request data from our owning + // SourceBuffer. + return mOwner->AdvanceIteratorOrScheduleResume(*this, + aRequestedBytes, + aConsumer); +} + +bool +SourceBufferIterator::RemainingBytesIsNoMoreThan(size_t aBytes) const +{ + MOZ_ASSERT(mOwner); + return mOwner->RemainingBytesIsNoMoreThan(*this, aBytes); +} + + +////////////////////////////////////////////////////////////////////////////// +// SourceBuffer implementation. +////////////////////////////////////////////////////////////////////////////// + +const size_t SourceBuffer::MIN_CHUNK_CAPACITY; + +SourceBuffer::SourceBuffer() + : mMutex("image::SourceBuffer") + , mConsumerCount(0) +{ } + +SourceBuffer::~SourceBuffer() +{ + MOZ_ASSERT(mConsumerCount == 0, + "SourceBuffer destroyed with active consumers"); +} + +nsresult +SourceBuffer::AppendChunk(Maybe&& aChunk) +{ + mMutex.AssertCurrentThreadOwns(); + +#ifdef DEBUG + if (mChunks.Length() > 0) { + NS_WARNING("Appending an extra chunk for SourceBuffer"); + } +#endif + + if (MOZ_UNLIKELY(!aChunk)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (MOZ_UNLIKELY(aChunk->AllocationFailed())) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (MOZ_UNLIKELY(!mChunks.AppendElement(Move(*aChunk), fallible))) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +Maybe +SourceBuffer::CreateChunk(size_t aCapacity, bool aRoundUp /* = true */) +{ + if (MOZ_UNLIKELY(aCapacity == 0)) { + MOZ_ASSERT_UNREACHABLE("Appending a chunk of zero size?"); + return Nothing(); + } + + // Round up if requested. + size_t finalCapacity = aRoundUp ? RoundedUpCapacity(aCapacity) + : aCapacity; + + // Use the size of the SurfaceCache as an additional heuristic to avoid + // allocating huge buffers. Generally images do not get smaller when decoded, + // so if we could store the source data in the SurfaceCache, we assume that + // there's no way we'll be able to store the decoded version. + if (MOZ_UNLIKELY(!SurfaceCache::CanHold(finalCapacity))) { + NS_WARNING("SourceBuffer refused to create chunk too large for SurfaceCache"); + return Nothing(); + } + + return Some(Chunk(finalCapacity)); +} + +nsresult +SourceBuffer::Compact() +{ + mMutex.AssertCurrentThreadOwns(); + + MOZ_ASSERT(mConsumerCount == 0, "Should have no consumers here"); + MOZ_ASSERT(mWaitingConsumers.Length() == 0, "Shouldn't have waiters"); + MOZ_ASSERT(mStatus, "Should be complete here"); + + // Compact our waiting consumers list, since we're complete and no future + // consumer will ever have to wait. + mWaitingConsumers.Compact(); + + // If we have no chunks, then there's nothing to compact. + if (mChunks.Length() < 1) { + return NS_OK; + } + + // If we have one chunk, then we can compact if it has excess capacity. + if (mChunks.Length() == 1 && mChunks[0].Length() == mChunks[0].Capacity()) { + return NS_OK; + } + + // We can compact our buffer. Determine the total length. + size_t length = 0; + for (uint32_t i = 0 ; i < mChunks.Length() ; ++i) { + length += mChunks[i].Length(); + } + + // If our total length is zero (which means ExpectLength() got called, but no + // data ever actually got written) then just empty our chunk list. + if (MOZ_UNLIKELY(length == 0)) { + mChunks.Clear(); + return NS_OK; + } + + Maybe newChunk = CreateChunk(length, /* aRoundUp = */ false); + if (MOZ_UNLIKELY(!newChunk || newChunk->AllocationFailed())) { + NS_WARNING("Failed to allocate chunk for SourceBuffer compacting - OOM?"); + return NS_OK; + } + + // Copy our old chunks into the new chunk. + for (uint32_t i = 0 ; i < mChunks.Length() ; ++i) { + size_t offset = newChunk->Length(); + MOZ_ASSERT(offset < newChunk->Capacity()); + MOZ_ASSERT(offset + mChunks[i].Length() <= newChunk->Capacity()); + + memcpy(newChunk->Data() + offset, mChunks[i].Data(), mChunks[i].Length()); + newChunk->AddLength(mChunks[i].Length()); + } + + MOZ_ASSERT(newChunk->Length() == newChunk->Capacity(), + "Compacted chunk has slack space"); + + // Replace the old chunks with the new, compact chunk. + mChunks.Clear(); + if (MOZ_UNLIKELY(NS_FAILED(AppendChunk(Move(newChunk))))) { + return HandleError(NS_ERROR_OUT_OF_MEMORY); + } + mChunks.Compact(); + + return NS_OK; +} + +/* static */ size_t +SourceBuffer::RoundedUpCapacity(size_t aCapacity) +{ + // Protect against overflow. + if (MOZ_UNLIKELY(SIZE_MAX - aCapacity < MIN_CHUNK_CAPACITY)) { + return aCapacity; + } + + // Round up to the next multiple of MIN_CHUNK_CAPACITY (which should be the + // size of a page). + size_t roundedCapacity = + (aCapacity + MIN_CHUNK_CAPACITY - 1) & ~(MIN_CHUNK_CAPACITY - 1); + MOZ_ASSERT(roundedCapacity >= aCapacity, "Bad math?"); + MOZ_ASSERT(roundedCapacity - aCapacity < MIN_CHUNK_CAPACITY, "Bad math?"); + + return roundedCapacity; +} + +size_t +SourceBuffer::FibonacciCapacityWithMinimum(size_t aMinCapacity) +{ + mMutex.AssertCurrentThreadOwns(); + + // We grow the source buffer using a Fibonacci growth rate. + + size_t length = mChunks.Length(); + + if (length == 0) { + return aMinCapacity; + } + + if (length == 1) { + return max(2 * mChunks[0].Capacity(), aMinCapacity); + } + + return max(mChunks[length - 1].Capacity() + mChunks[length - 2].Capacity(), + aMinCapacity); +} + +void +SourceBuffer::AddWaitingConsumer(IResumable* aConsumer) +{ + mMutex.AssertCurrentThreadOwns(); + + MOZ_ASSERT(!mStatus, "Waiting when we're complete?"); + + if (aConsumer) { + mWaitingConsumers.AppendElement(aConsumer); + } +} + +void +SourceBuffer::ResumeWaitingConsumers() +{ + mMutex.AssertCurrentThreadOwns(); + + if (mWaitingConsumers.Length() == 0) { + return; + } + + for (uint32_t i = 0 ; i < mWaitingConsumers.Length() ; ++i) { + mWaitingConsumers[i]->Resume(); + } + + mWaitingConsumers.Clear(); +} + +nsresult +SourceBuffer::ExpectLength(size_t aExpectedLength) +{ + MOZ_ASSERT(aExpectedLength > 0, "Zero expected size?"); + + MutexAutoLock lock(mMutex); + + if (MOZ_UNLIKELY(mStatus)) { + MOZ_ASSERT_UNREACHABLE("ExpectLength after SourceBuffer is complete"); + return NS_OK; + } + + if (MOZ_UNLIKELY(mChunks.Length() > 0)) { + MOZ_ASSERT_UNREACHABLE("Duplicate or post-Append call to ExpectLength"); + return NS_OK; + } + + if (MOZ_UNLIKELY(NS_FAILED(AppendChunk(CreateChunk(aExpectedLength))))) { + return HandleError(NS_ERROR_OUT_OF_MEMORY); + } + + return NS_OK; +} + +nsresult +SourceBuffer::Append(const char* aData, size_t aLength) +{ + MOZ_ASSERT(aData, "Should have a buffer"); + MOZ_ASSERT(aLength > 0, "Writing a zero-sized chunk"); + + size_t currentChunkCapacity = 0; + size_t currentChunkLength = 0; + char* currentChunkData = nullptr; + size_t currentChunkRemaining = 0; + size_t forCurrentChunk = 0; + size_t forNextChunk = 0; + size_t nextChunkCapacity = 0; + + { + MutexAutoLock lock(mMutex); + + if (MOZ_UNLIKELY(mStatus)) { + // This SourceBuffer is already complete; ignore further data. + return NS_ERROR_FAILURE; + } + + if (MOZ_UNLIKELY(mChunks.Length() == 0)) { + if (MOZ_UNLIKELY(NS_FAILED(AppendChunk(CreateChunk(aLength))))) { + return HandleError(NS_ERROR_OUT_OF_MEMORY); + } + } + + // Copy out the current chunk's information so we can release the lock. + // Note that this wouldn't be safe if multiple producers were allowed! + Chunk& currentChunk = mChunks.LastElement(); + currentChunkCapacity = currentChunk.Capacity(); + currentChunkLength = currentChunk.Length(); + currentChunkData = currentChunk.Data(); + + // Partition this data between the current chunk and the next chunk. + // (Because we always allocate a chunk big enough to fit everything passed + // to Append, we'll never need more than those two chunks to store + // everything.) + currentChunkRemaining = currentChunkCapacity - currentChunkLength; + forCurrentChunk = min(aLength, currentChunkRemaining); + forNextChunk = aLength - forCurrentChunk; + + // If we'll need another chunk, determine what its capacity should be while + // we still hold the lock. + nextChunkCapacity = forNextChunk > 0 + ? FibonacciCapacityWithMinimum(forNextChunk) + : 0; + } + + // Write everything we can fit into the current chunk. + MOZ_ASSERT(currentChunkLength + forCurrentChunk <= currentChunkCapacity); + memcpy(currentChunkData + currentChunkLength, aData, forCurrentChunk); + + // If there's something left, create a new chunk and write it there. + Maybe nextChunk; + if (forNextChunk > 0) { + MOZ_ASSERT(nextChunkCapacity >= forNextChunk, "Next chunk too small?"); + nextChunk = CreateChunk(nextChunkCapacity); + if (MOZ_LIKELY(nextChunk && !nextChunk->AllocationFailed())) { + memcpy(nextChunk->Data(), aData + forCurrentChunk, forNextChunk); + nextChunk->AddLength(forNextChunk); + } + } + + // Update shared data structures. + { + MutexAutoLock lock(mMutex); + + // Update the length of the current chunk. + Chunk& currentChunk = mChunks.LastElement(); + MOZ_ASSERT(currentChunk.Data() == currentChunkData, "Multiple producers?"); + MOZ_ASSERT(currentChunk.Length() == currentChunkLength, + "Multiple producers?"); + + currentChunk.AddLength(forCurrentChunk); + + // If we created a new chunk, add it to the series. + if (forNextChunk > 0) { + if (MOZ_UNLIKELY(!nextChunk)) { + return HandleError(NS_ERROR_OUT_OF_MEMORY); + } + + if (MOZ_UNLIKELY(NS_FAILED(AppendChunk(Move(nextChunk))))) { + return HandleError(NS_ERROR_OUT_OF_MEMORY); + } + } + + // Resume any waiting readers now that there's new data. + ResumeWaitingConsumers(); + } + + return NS_OK; +} + +static nsresult +AppendToSourceBuffer(nsIInputStream*, + void* aClosure, + const char* aFromRawSegment, + uint32_t, + uint32_t aCount, + uint32_t* aWriteCount) +{ + SourceBuffer* sourceBuffer = static_cast(aClosure); + + // Copy the source data. Unless we hit OOM, we squelch the return value here, + // because returning an error means that ReadSegments stops reading data, and + // we want to ensure that we read everything we get. If we hit OOM then we + // return a failed status to the caller. + nsresult rv = sourceBuffer->Append(aFromRawSegment, aCount); + if (rv == NS_ERROR_OUT_OF_MEMORY) { + return rv; + } + + // Report that we wrote everything we got. + *aWriteCount = aCount; + + return NS_OK; +} + +nsresult +SourceBuffer::AppendFromInputStream(nsIInputStream* aInputStream, + uint32_t aCount) +{ + uint32_t bytesRead; + nsresult rv = aInputStream->ReadSegments(AppendToSourceBuffer, this, + aCount, &bytesRead); + if (!NS_WARN_IF(NS_FAILED(rv))) { + MOZ_ASSERT(bytesRead == aCount, + "AppendToSourceBuffer should consume everything"); + } + return rv; +} + +void +SourceBuffer::Complete(nsresult aStatus) +{ + MutexAutoLock lock(mMutex); + + if (MOZ_UNLIKELY(mStatus)) { + MOZ_ASSERT_UNREACHABLE("Called Complete more than once"); + return; + } + + if (MOZ_UNLIKELY(NS_SUCCEEDED(aStatus) && IsEmpty())) { + // It's illegal to succeed without writing anything. + aStatus = NS_ERROR_FAILURE; + } + + mStatus = Some(aStatus); + + // Resume any waiting consumers now that we're complete. + ResumeWaitingConsumers(); + + // If we still have active consumers, just return. + if (mConsumerCount > 0) { + return; + } + + // Attempt to compact our buffer down to a single chunk. + Compact(); +} + +bool +SourceBuffer::IsComplete() +{ + MutexAutoLock lock(mMutex); + return bool(mStatus); +} + +size_t +SourceBuffer::SizeOfIncludingThisWithComputedFallback(MallocSizeOf + aMallocSizeOf) const +{ + MutexAutoLock lock(mMutex); + + size_t n = aMallocSizeOf(this); + n += mChunks.ShallowSizeOfExcludingThis(aMallocSizeOf); + + for (uint32_t i = 0 ; i < mChunks.Length() ; ++i) { + size_t chunkSize = aMallocSizeOf(mChunks[i].Data()); + + if (chunkSize == 0) { + // We're on a platform where moz_malloc_size_of always returns 0. + chunkSize = mChunks[i].Capacity(); + } + + n += chunkSize; + } + + return n; +} + +SourceBufferIterator +SourceBuffer::Iterator() +{ + { + MutexAutoLock lock(mMutex); + mConsumerCount++; + } + + return SourceBufferIterator(this); +} + +void +SourceBuffer::OnIteratorRelease() +{ + MutexAutoLock lock(mMutex); + + MOZ_ASSERT(mConsumerCount > 0, "Consumer count doesn't add up"); + mConsumerCount--; + + // If we still have active consumers, or we're not complete yet, then return. + if (mConsumerCount > 0 || !mStatus) { + return; + } + + // Attempt to compact our buffer down to a single chunk. + Compact(); +} + +bool +SourceBuffer::RemainingBytesIsNoMoreThan(const SourceBufferIterator& aIterator, + size_t aBytes) const +{ + MutexAutoLock lock(mMutex); + + // If we're not complete, we always say no. + if (!mStatus) { + return false; + } + + // If the iterator's at the end, the answer is trivial. + if (!aIterator.HasMore()) { + return true; + } + + uint32_t iteratorChunk = aIterator.mData.mIterating.mChunk; + size_t iteratorOffset = aIterator.mData.mIterating.mOffset; + size_t iteratorLength = aIterator.mData.mIterating.mAvailableLength; + + // Include the bytes the iterator is currently pointing to in the limit, so + // that the current chunk doesn't have to be a special case. + size_t bytes = aBytes + iteratorOffset + iteratorLength; + + // Count the length over all of our chunks, starting with the one that the + // iterator is currently pointing to. (This is O(N), but N is expected to be + // ~1, so it doesn't seem worth caching the length separately.) + size_t lengthSoFar = 0; + for (uint32_t i = iteratorChunk ; i < mChunks.Length() ; ++i) { + lengthSoFar += mChunks[i].Length(); + if (lengthSoFar > bytes) { + return false; + } + } + + return true; +} + +SourceBufferIterator::State +SourceBuffer::AdvanceIteratorOrScheduleResume(SourceBufferIterator& aIterator, + size_t aRequestedBytes, + IResumable* aConsumer) +{ + MutexAutoLock lock(mMutex); + + MOZ_ASSERT(aIterator.HasMore(), "Advancing a completed iterator and " + "AdvanceOrScheduleResume didn't catch it"); + + if (MOZ_UNLIKELY(mStatus && NS_FAILED(*mStatus))) { + // This SourceBuffer is complete due to an error; all reads fail. + return aIterator.SetComplete(*mStatus); + } + + if (MOZ_UNLIKELY(mChunks.Length() == 0)) { + // We haven't gotten an initial chunk yet. + AddWaitingConsumer(aConsumer); + return aIterator.SetWaiting(); + } + + uint32_t iteratorChunkIdx = aIterator.mData.mIterating.mChunk; + MOZ_ASSERT(iteratorChunkIdx < mChunks.Length()); + + const Chunk& currentChunk = mChunks[iteratorChunkIdx]; + size_t iteratorEnd = aIterator.mData.mIterating.mOffset + + aIterator.mData.mIterating.mAvailableLength; + MOZ_ASSERT(iteratorEnd <= currentChunk.Length()); + MOZ_ASSERT(iteratorEnd <= currentChunk.Capacity()); + + if (iteratorEnd < currentChunk.Length()) { + // There's more data in the current chunk. + return aIterator.SetReady(iteratorChunkIdx, currentChunk.Data(), + iteratorEnd, currentChunk.Length() - iteratorEnd, + aRequestedBytes); + } + + if (iteratorEnd == currentChunk.Capacity() && + !IsLastChunk(iteratorChunkIdx)) { + // Advance to the next chunk. + const Chunk& nextChunk = mChunks[iteratorChunkIdx + 1]; + return aIterator.SetReady(iteratorChunkIdx + 1, nextChunk.Data(), 0, + nextChunk.Length(), aRequestedBytes); + } + + MOZ_ASSERT(IsLastChunk(iteratorChunkIdx), "Should've advanced"); + + if (mStatus) { + // There's no more data and this SourceBuffer completed successfully. + MOZ_ASSERT(NS_SUCCEEDED(*mStatus), "Handled failures earlier"); + return aIterator.SetComplete(*mStatus); + } + + // We're not complete, but there's no more data right now. Arrange to wake up + // the consumer when we get more data. + AddWaitingConsumer(aConsumer); + return aIterator.SetWaiting(); +} + +nsresult +SourceBuffer::HandleError(nsresult aError) +{ + MOZ_ASSERT(NS_FAILED(aError), "Should have an error here"); + MOZ_ASSERT(aError == NS_ERROR_OUT_OF_MEMORY, + "Unexpected error; may want to notify waiting readers, which " + "HandleError currently doesn't do"); + + mMutex.AssertCurrentThreadOwns(); + + NS_WARNING("SourceBuffer encountered an unrecoverable error"); + + // Record the error. + mStatus = Some(aError); + + // Drop our references to waiting readers. + mWaitingConsumers.Clear(); + + return *mStatus; +} + +bool +SourceBuffer::IsEmpty() +{ + mMutex.AssertCurrentThreadOwns(); + return mChunks.Length() == 0 || + mChunks[0].Length() == 0; +} + +bool +SourceBuffer::IsLastChunk(uint32_t aChunk) +{ + mMutex.AssertCurrentThreadOwns(); + return aChunk + 1 == mChunks.Length(); +} + +} // namespace image +} // namespace mozilla diff --git a/image/SourceBuffer.h b/image/SourceBuffer.h new file mode 100644 index 000000000..64727e65e --- /dev/null +++ b/image/SourceBuffer.h @@ -0,0 +1,459 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * SourceBuffer is a single producer, multiple consumer data structure used for + * storing image source (compressed) data. + */ + +#ifndef mozilla_image_sourcebuffer_h +#define mozilla_image_sourcebuffer_h + +#include +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" +#include "mozilla/Move.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/RefPtr.h" +#include "mozilla/RefCounted.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/RefPtr.h" +#include "nsTArray.h" + +class nsIInputStream; + +namespace mozilla { +namespace image { + +class SourceBuffer; + +/** + * IResumable is an interface for classes that can schedule themselves to resume + * their work later. An implementation of IResumable generally should post a + * runnable to some event target which continues the work of the task. + */ +struct IResumable +{ + MOZ_DECLARE_REFCOUNTED_TYPENAME(IResumable) + + // Subclasses may or may not be XPCOM classes, so we just require that they + // implement AddRef and Release. + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0; + NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0; + + virtual void Resume() = 0; + +protected: + virtual ~IResumable() { } +}; + +/** + * SourceBufferIterator is a class that allows consumers of image source data to + * read the contents of a SourceBuffer sequentially. + * + * Consumers can advance through the SourceBuffer by calling + * AdvanceOrScheduleResume() repeatedly. After every advance, they should call + * check the return value, which will tell them the iterator's new state. + * + * If WAITING is returned, AdvanceOrScheduleResume() has arranged + * to call the consumer's Resume() method later, so the consumer should save its + * state if needed and stop running. + * + * If the iterator's new state is READY, then the consumer can call Data() and + * Length() to read new data from the SourceBuffer. + * + * Finally, in the COMPLETE state the consumer can call CompletionStatus() to + * get the status passed to SourceBuffer::Complete(). + */ +class SourceBufferIterator final +{ +public: + enum State { + START, // The iterator is at the beginning of the buffer. + READY, // The iterator is pointing to new data. + WAITING, // The iterator is blocked and the caller must yield. + COMPLETE // The iterator is pointing to the end of the buffer. + }; + + explicit SourceBufferIterator(SourceBuffer* aOwner) + : mOwner(aOwner) + , mState(START) + , mChunkCount(0) + , mByteCount(0) + { + MOZ_ASSERT(aOwner); + mData.mIterating.mChunk = 0; + mData.mIterating.mData = nullptr; + mData.mIterating.mOffset = 0; + mData.mIterating.mAvailableLength = 0; + mData.mIterating.mNextReadLength = 0; + } + + SourceBufferIterator(SourceBufferIterator&& aOther) + : mOwner(Move(aOther.mOwner)) + , mState(aOther.mState) + , mData(aOther.mData) + , mChunkCount(aOther.mChunkCount) + , mByteCount(aOther.mByteCount) + { } + + ~SourceBufferIterator(); + + SourceBufferIterator& operator=(SourceBufferIterator&& aOther); + + /** + * Returns true if there are no more than @aBytes remaining in the + * SourceBuffer. If the SourceBuffer is not yet complete, returns false. + */ + bool RemainingBytesIsNoMoreThan(size_t aBytes) const; + + /** + * Advances the iterator through the SourceBuffer if possible. Advances no + * more than @aRequestedBytes bytes. (Use SIZE_MAX to advance as much as + * possible.) + * + * This is a wrapper around AdvanceOrScheduleResume() that makes it clearer at + * the callsite when the no resuming is intended. + * + * @return State::READY if the iterator was successfully advanced. + * State::WAITING if the iterator could not be advanced because it's + * at the end of the underlying SourceBuffer, but the SourceBuffer + * may still receive additional data. + * State::COMPLETE if the iterator could not be advanced because it's + * at the end of the underlying SourceBuffer and the SourceBuffer is + * marked complete (i.e., it will never receive any additional + * data). + */ + State Advance(size_t aRequestedBytes) + { + return AdvanceOrScheduleResume(aRequestedBytes, nullptr); + } + + /** + * Advances the iterator through the SourceBuffer if possible. Advances no + * more than @aRequestedBytes bytes. (Use SIZE_MAX to advance as much as + * possible.) If advancing is not possible and @aConsumer is not null, + * arranges to call the @aConsumer's Resume() method when more data is + * available. + * + * @return State::READY if the iterator was successfully advanced. + * State::WAITING if the iterator could not be advanced because it's + * at the end of the underlying SourceBuffer, but the SourceBuffer + * may still receive additional data. @aConsumer's Resume() method + * will be called when additional data is available. + * State::COMPLETE if the iterator could not be advanced because it's + * at the end of the underlying SourceBuffer and the SourceBuffer is + * marked complete (i.e., it will never receive any additional + * data). + */ + State AdvanceOrScheduleResume(size_t aRequestedBytes, IResumable* aConsumer); + + /// If at the end, returns the status passed to SourceBuffer::Complete(). + nsresult CompletionStatus() const + { + MOZ_ASSERT(mState == COMPLETE, + "Calling CompletionStatus() in the wrong state"); + return mState == COMPLETE ? mData.mAtEnd.mStatus : NS_OK; + } + + /// If we're ready to read, returns a pointer to the new data. + const char* Data() const + { + MOZ_ASSERT(mState == READY, "Calling Data() in the wrong state"); + return mState == READY ? mData.mIterating.mData + mData.mIterating.mOffset + : nullptr; + } + + /// If we're ready to read, returns the length of the new data. + size_t Length() const + { + MOZ_ASSERT(mState == READY, "Calling Length() in the wrong state"); + return mState == READY ? mData.mIterating.mNextReadLength : 0; + } + + /// @return a count of the chunks we've advanced through. + uint32_t ChunkCount() const { return mChunkCount; } + + /// @return a count of the bytes in all chunks we've advanced through. + size_t ByteCount() const { return mByteCount; } + +private: + friend class SourceBuffer; + + SourceBufferIterator(const SourceBufferIterator&) = delete; + SourceBufferIterator& operator=(const SourceBufferIterator&) = delete; + + bool HasMore() const { return mState != COMPLETE; } + + State AdvanceFromLocalBuffer(size_t aRequestedBytes) + { + MOZ_ASSERT(mState == READY, "Advancing in the wrong state"); + MOZ_ASSERT(mData.mIterating.mAvailableLength > 0, + "The local buffer shouldn't be empty"); + MOZ_ASSERT(mData.mIterating.mNextReadLength == 0, + "Advancing without consuming previous data"); + + mData.mIterating.mNextReadLength = + std::min(mData.mIterating.mAvailableLength, aRequestedBytes); + + return READY; + } + + State SetReady(uint32_t aChunk, const char* aData, + size_t aOffset, size_t aAvailableLength, + size_t aRequestedBytes) + { + MOZ_ASSERT(mState != COMPLETE); + mState = READY; + + // Update state. + mData.mIterating.mChunk = aChunk; + mData.mIterating.mData = aData; + mData.mIterating.mOffset = aOffset; + mData.mIterating.mAvailableLength = aAvailableLength; + + // Update metrics. + mChunkCount++; + mByteCount += aAvailableLength; + + // Attempt to advance by the requested number of bytes. + return AdvanceFromLocalBuffer(aRequestedBytes); + } + + State SetWaiting() + { + MOZ_ASSERT(mState != COMPLETE); + MOZ_ASSERT(mState != WAITING, "Did we get a spurious wakeup somehow?"); + return mState = WAITING; + } + + State SetComplete(nsresult aStatus) + { + mData.mAtEnd.mStatus = aStatus; + return mState = COMPLETE; + } + + RefPtr mOwner; + + State mState; + + /** + * This union contains our iteration state if we're still iterating (for + * states START, READY, and WAITING) and the status the SourceBuffer was + * completed with if we're in state COMPLETE. + */ + union { + struct { + uint32_t mChunk; + const char* mData; + size_t mOffset; + size_t mAvailableLength; + size_t mNextReadLength; + } mIterating; + struct { + nsresult mStatus; + } mAtEnd; + } mData; + + uint32_t mChunkCount; // Count of chunks we've advanced through. + size_t mByteCount; // Count of bytes in all chunks we've advanced through. +}; + +/** + * SourceBuffer is a parallel data structure used for storing image source + * (compressed) data. + * + * SourceBuffer is a single producer, multiple consumer data structure. The + * single producer calls Append() to append data to the buffer. In parallel, + * multiple consumers can call Iterator(), which returns a SourceBufferIterator + * that they can use to iterate through the buffer. The SourceBufferIterator + * returns a series of pointers which remain stable for lifetime of the + * SourceBuffer, and the data they point to is immutable, ensuring that the + * producer never interferes with the consumers. + * + * In order to avoid blocking, SourceBuffer works with SourceBufferIterator to + * keep a list of consumers which are waiting for new data, and to resume them + * when the producer appends more. All consumers must implement the IResumable + * interface to make this possible. + */ +class SourceBuffer final +{ +public: + MOZ_DECLARE_REFCOUNTED_TYPENAME(image::SourceBuffer) + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(image::SourceBuffer) + + SourceBuffer(); + + ////////////////////////////////////////////////////////////////////////////// + // Producer methods. + ////////////////////////////////////////////////////////////////////////////// + + /** + * If the producer knows how long the source data will be, it should call + * ExpectLength, which enables SourceBuffer to preallocate its buffer. + */ + nsresult ExpectLength(size_t aExpectedLength); + + /// Append the provided data to the buffer. + nsresult Append(const char* aData, size_t aLength); + + /// Append the data available on the provided nsIInputStream to the buffer. + nsresult AppendFromInputStream(nsIInputStream* aInputStream, uint32_t aCount); + + /** + * Mark the buffer complete, with a status that will be available to + * consumers. Further calls to Append() are forbidden after Complete(). + */ + void Complete(nsresult aStatus); + + /// Returns true if the buffer is complete. + bool IsComplete(); + + /// Memory reporting. + size_t SizeOfIncludingThisWithComputedFallback(MallocSizeOf) const; + + + ////////////////////////////////////////////////////////////////////////////// + // Consumer methods. + ////////////////////////////////////////////////////////////////////////////// + + /// Returns an iterator to this SourceBuffer. + SourceBufferIterator Iterator(); + + + ////////////////////////////////////////////////////////////////////////////// + // Consumer methods. + ////////////////////////////////////////////////////////////////////////////// + + /** + * The minimum chunk capacity we'll allocate, if we don't know the correct + * capacity (which would happen because ExpectLength() wasn't called or gave + * us the wrong value). This is only exposed for use by tests; if normal code + * is using this, it's doing something wrong. + */ + static const size_t MIN_CHUNK_CAPACITY = 4096; + +private: + friend class SourceBufferIterator; + + ~SourceBuffer(); + + ////////////////////////////////////////////////////////////////////////////// + // Chunk type and chunk-related methods. + ////////////////////////////////////////////////////////////////////////////// + + class Chunk + { + public: + explicit Chunk(size_t aCapacity) + : mCapacity(aCapacity) + , mLength(0) + { + MOZ_ASSERT(aCapacity > 0, "Creating zero-capacity chunk"); + mData.reset(new (fallible) char[mCapacity]); + } + + Chunk(Chunk&& aOther) + : mCapacity(aOther.mCapacity) + , mLength(aOther.mLength) + , mData(Move(aOther.mData)) + { + aOther.mCapacity = aOther.mLength = 0; + aOther.mData = nullptr; + } + + Chunk& operator=(Chunk&& aOther) + { + mCapacity = aOther.mCapacity; + mLength = aOther.mLength; + mData = Move(aOther.mData); + aOther.mCapacity = aOther.mLength = 0; + aOther.mData = nullptr; + return *this; + } + + bool AllocationFailed() const { return !mData; } + size_t Capacity() const { return mCapacity; } + size_t Length() const { return mLength; } + + char* Data() const + { + MOZ_ASSERT(mData, "Allocation failed but nobody checked for it"); + return mData.get(); + } + + void AddLength(size_t aAdditionalLength) + { + MOZ_ASSERT(mLength + aAdditionalLength <= mCapacity); + mLength += aAdditionalLength; + } + + private: + Chunk(const Chunk&) = delete; + Chunk& operator=(const Chunk&) = delete; + + size_t mCapacity; + size_t mLength; + UniquePtr mData; + }; + + nsresult AppendChunk(Maybe&& aChunk); + Maybe CreateChunk(size_t aCapacity, bool aRoundUp = true); + nsresult Compact(); + static size_t RoundedUpCapacity(size_t aCapacity); + size_t FibonacciCapacityWithMinimum(size_t aMinCapacity); + + + ////////////////////////////////////////////////////////////////////////////// + // Iterator / consumer methods. + ////////////////////////////////////////////////////////////////////////////// + + void AddWaitingConsumer(IResumable* aConsumer); + void ResumeWaitingConsumers(); + + typedef SourceBufferIterator::State State; + + State AdvanceIteratorOrScheduleResume(SourceBufferIterator& aIterator, + size_t aRequestedBytes, + IResumable* aConsumer); + bool RemainingBytesIsNoMoreThan(const SourceBufferIterator& aIterator, + size_t aBytes) const; + + void OnIteratorRelease(); + + ////////////////////////////////////////////////////////////////////////////// + // Helper methods. + ////////////////////////////////////////////////////////////////////////////// + + nsresult HandleError(nsresult aError); + bool IsEmpty(); + bool IsLastChunk(uint32_t aChunk); + + + ////////////////////////////////////////////////////////////////////////////// + // Member variables. + ////////////////////////////////////////////////////////////////////////////// + + /// All private members are protected by mMutex. + mutable Mutex mMutex; + + /// The data in this SourceBuffer, stored as a series of Chunks. + FallibleTArray mChunks; + + /// Consumers which are waiting to be notified when new data is available. + nsTArray> mWaitingConsumers; + + /// If present, marks this SourceBuffer complete with the given final status. + Maybe mStatus; + + /// Count of active consumers. + uint32_t mConsumerCount; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_sourcebuffer_h diff --git a/image/StreamingLexer.h b/image/StreamingLexer.h new file mode 100644 index 000000000..e4abdb718 --- /dev/null +++ b/image/StreamingLexer.h @@ -0,0 +1,750 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * StreamingLexer is a lexing framework designed to make it simple to write + * image decoders without worrying about the details of how the data is arriving + * from the network. + */ + +#ifndef mozilla_image_StreamingLexer_h +#define mozilla_image_StreamingLexer_h + +#include +#include +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "mozilla/Move.h" +#include "mozilla/Variant.h" +#include "mozilla/Vector.h" + +namespace mozilla { +namespace image { + +/// Buffering behaviors for StreamingLexer transitions. +enum class BufferingStrategy +{ + BUFFERED, // Data will be buffered and processed in one chunk. + UNBUFFERED // Data will be processed as it arrives, in multiple chunks. +}; + +/// Control flow behaviors for StreamingLexer transitions. +enum class ControlFlowStrategy +{ + CONTINUE, // If there's enough data, proceed to the next state immediately. + YIELD // Yield to the caller before proceeding to the next state. +}; + +/// Possible terminal states for the lexer. +enum class TerminalState +{ + SUCCESS, + FAILURE +}; + +/// Possible yield reasons for the lexer. +enum class Yield +{ + NEED_MORE_DATA, // The lexer cannot continue without more data. + OUTPUT_AVAILABLE // There is output available for the caller to consume. +}; + +/// The result of a call to StreamingLexer::Lex(). +typedef Variant LexerResult; + +/** + * LexerTransition is a type used to give commands to the lexing framework. + * Code that uses StreamingLexer can create LexerTransition values using the + * static methods on Transition, and then return them to the lexing framework + * for execution. + */ +template +class LexerTransition +{ +public: + // This is implicit so that Terminate{Success,Failure}() can return a + // TerminalState and have it implicitly converted to a + // LexerTransition, which avoids the need for a "" + // qualification to the Terminate{Success,Failure}() callsite. + MOZ_IMPLICIT LexerTransition(TerminalState aFinalState) + : mNextState(aFinalState) + {} + + bool NextStateIsTerminal() const + { + return mNextState.template is(); + } + + TerminalState NextStateAsTerminal() const + { + return mNextState.template as(); + } + + State NextState() const + { + return mNextState.template as().mState; + } + + State UnbufferedState() const + { + return *mNextState.template as().mUnbufferedState; + } + + size_t Size() const + { + return mNextState.template as().mSize; + } + + BufferingStrategy Buffering() const + { + return mNextState.template as().mBufferingStrategy; + } + + ControlFlowStrategy ControlFlow() const + { + return mNextState.template as().mControlFlowStrategy; + } + +private: + friend struct Transition; + + LexerTransition(State aNextState, + const Maybe& aUnbufferedState, + size_t aSize, + BufferingStrategy aBufferingStrategy, + ControlFlowStrategy aControlFlowStrategy) + : mNextState(NonTerminalState(aNextState, aUnbufferedState, aSize, + aBufferingStrategy, aControlFlowStrategy)) + {} + + struct NonTerminalState + { + State mState; + Maybe mUnbufferedState; + size_t mSize; + BufferingStrategy mBufferingStrategy; + ControlFlowStrategy mControlFlowStrategy; + + NonTerminalState(State aState, + const Maybe& aUnbufferedState, + size_t aSize, + BufferingStrategy aBufferingStrategy, + ControlFlowStrategy aControlFlowStrategy) + : mState(aState) + , mUnbufferedState(aUnbufferedState) + , mSize(aSize) + , mBufferingStrategy(aBufferingStrategy) + , mControlFlowStrategy(aControlFlowStrategy) + { + MOZ_ASSERT_IF(mBufferingStrategy == BufferingStrategy::UNBUFFERED, + mUnbufferedState); + MOZ_ASSERT_IF(mUnbufferedState, + mBufferingStrategy == BufferingStrategy::UNBUFFERED); + } + }; + + Variant mNextState; +}; + +struct Transition +{ + /// Transition to @aNextState, buffering @aSize bytes of data. + template + static LexerTransition + To(const State& aNextState, size_t aSize) + { + return LexerTransition(aNextState, Nothing(), aSize, + BufferingStrategy::BUFFERED, + ControlFlowStrategy::CONTINUE); + } + + /// Yield to the caller, transitioning to @aNextState when Lex() is next + /// invoked. The same data that was delivered for the current state will be + /// delivered again. + template + static LexerTransition + ToAfterYield(const State& aNextState) + { + return LexerTransition(aNextState, Nothing(), 0, + BufferingStrategy::BUFFERED, + ControlFlowStrategy::YIELD); + } + + /** + * Transition to @aNextState via @aUnbufferedState, reading @aSize bytes of + * data unbuffered. + * + * The unbuffered data will be delivered in state @aUnbufferedState, which may + * be invoked repeatedly until all @aSize bytes have been delivered. Then, + * @aNextState will be invoked with no data. No state transitions are allowed + * from @aUnbufferedState except for transitions to a terminal state, so + * @aNextState will always be reached unless lexing terminates early. + */ + template + static LexerTransition + ToUnbuffered(const State& aNextState, + const State& aUnbufferedState, + size_t aSize) + { + return LexerTransition(aNextState, Some(aUnbufferedState), aSize, + BufferingStrategy::UNBUFFERED, + ControlFlowStrategy::CONTINUE); + } + + /** + * Continue receiving unbuffered data. @aUnbufferedState should be the same + * state as the @aUnbufferedState specified in the preceding call to + * ToUnbuffered(). + * + * This should be used during an unbuffered read initiated by ToUnbuffered(). + */ + template + static LexerTransition + ContinueUnbuffered(const State& aUnbufferedState) + { + return LexerTransition(aUnbufferedState, Nothing(), 0, + BufferingStrategy::BUFFERED, + ControlFlowStrategy::CONTINUE); + } + + /** + * Continue receiving unbuffered data. @aUnbufferedState should be the same + * state as the @aUnbufferedState specified in the preceding call to + * ToUnbuffered(). @aSize indicates the amount of data that has already been + * consumed; the next state will receive the same data that was delivered to + * the current state, without the first @aSize bytes. + * + * This should be used during an unbuffered read initiated by ToUnbuffered(). + */ + template + static LexerTransition + ContinueUnbufferedAfterYield(const State& aUnbufferedState, size_t aSize) + { + return LexerTransition(aUnbufferedState, Nothing(), aSize, + BufferingStrategy::BUFFERED, + ControlFlowStrategy::YIELD); + } + + /** + * Terminate lexing, ending up in terminal state SUCCESS. (The implicit + * LexerTransition constructor will convert the result to a LexerTransition + * as needed.) + * + * No more data will be delivered after this function is used. + */ + static TerminalState + TerminateSuccess() + { + return TerminalState::SUCCESS; + } + + /** + * Terminate lexing, ending up in terminal state FAILURE. (The implicit + * LexerTransition constructor will convert the result to a LexerTransition + * as needed.) + * + * No more data will be delivered after this function is used. + */ + static TerminalState + TerminateFailure() + { + return TerminalState::FAILURE; + } + +private: + Transition(); +}; + +/** + * StreamingLexer is a lexing framework designed to make it simple to write + * image decoders without worrying about the details of how the data is arriving + * from the network. + * + * To use StreamingLexer: + * + * - Create a State type. This should be an |enum class| listing all of the + * states that you can be in while lexing the image format you're trying to + * read. + * + * - Add an instance of StreamingLexer to your decoder class. Initialize + * it with a Transition::To() the state that you want to start lexing in, and + * a Transition::To() the state you'd like to use to handle truncated data. + * + * - In your decoder's DoDecode() method, call Lex(), passing in the input + * data and length that are passed to DoDecode(). You also need to pass + * a lambda which dispatches to lexing code for each state based on the State + * value that's passed in. The lambda generally should just continue a + * |switch| statement that calls different methods for each State value. Each + * method should return a LexerTransition, which the lambda should + * return in turn. + * + * - Write the methods that actually implement lexing for your image format. + * These methods should return either Transition::To(), to move on to another + * state, or Transition::Terminate{Success,Failure}(), if lexing has + * terminated in either success or failure. (There are also additional + * transitions for unbuffered reads; see below.) + * + * That's the basics. The StreamingLexer will track your position in the input + * and buffer enough data so that your lexing methods can process everything in + * one pass. Lex() returns Yield::NEED_MORE_DATA if more data is needed, in + * which case you should just return from DoDecode(). If lexing reaches a + * terminal state, Lex() returns TerminalState::SUCCESS or + * TerminalState::FAILURE, and you can check which one to determine if lexing + * succeeded or failed and do any necessary cleanup. + * + * Sometimes, the input data is truncated. StreamingLexer will notify you when + * this happens by invoking the truncated data state you passed to the + * constructor. At this point you can attempt to recover and return + * TerminalState::SUCCESS or TerminalState::FAILURE, depending on whether you + * were successful. Note that you can't return anything other than a terminal + * state in this situation, since there's no more data to read. For the same + * reason, your truncated data state shouldn't require any data. (That is, the + * @aSize argument you pass to Transition::To() must be zero.) Violating these + * requirements will trigger assertions and an immediate transition to + * TerminalState::FAILURE. + * + * Some lexers may want to *avoid* buffering in some cases, and just process the + * data as it comes in. This is useful if, for example, you just want to skip + * over a large section of data; there's no point in buffering data you're just + * going to ignore. + * + * You can begin an unbuffered read with Transition::ToUnbuffered(). This works + * a little differently than Transition::To() in that you specify *two* states. + * The @aUnbufferedState argument specifies a state that will be called + * repeatedly with unbuffered data, as soon as it arrives. The implementation + * for that state should return either a transition to a terminal state, or a + * Transition::ContinueUnbuffered() to the same @aUnbufferedState. (From a + * technical perspective, it's not necessary to specify the state again, but + * it's helpful to human readers.) Once the amount of data requested in the + * original call to Transition::ToUnbuffered() has been delivered, Lex() will + * transition to the @aNextState state specified via Transition::ToUnbuffered(). + * That state will be invoked with *no* data; it's just called to signal that + * the unbuffered read is over. + * + * It's sometimes useful for a lexer to provide incremental results, rather + * than simply running to completion and presenting all its output at once. For + * example, when decoding animated images, it may be useful to produce each + * frame incrementally. StreamingLexer supports this by allowing a lexer to + * yield. + * + * To yield back to the caller, a state implementation can simply return + * Transition::ToAfterYield(). ToAfterYield()'s @aNextState argument specifies + * the next state that the lexer should transition to, just like when using + * Transition::To(), but there are two differences. One is that Lex() will + * return to the caller before processing any more data when it encounters a + * yield transition. This provides an opportunity for the caller to interact with the + * lexer's intermediate results. The second difference is that @aNextState + * will be called with *the same data as the state that you returned + * Transition::ToAfterYield() from*. This allows a lexer to partially consume + * the data, return intermediate results, and then finish consuming the data + * when @aNextState is called. + * + * It's also possible to yield during an unbuffered read. Just return a + * Transition::ContinueUnbufferedAfterYield(). Just like with + * Transition::ContinueUnbuffered(), the @aUnbufferedState must be the same as + * the one originally passed to Transition::ToUnbuffered(). The second argument, + * @aSize, specifies the amount of data that the lexer has already consumed. + * When @aUnbufferedState is next invoked, it will get the same data that it + * received previously, except that the first @aSize bytes will be excluded. + * This makes it easy to consume unbuffered data incrementally. + * + * XXX(seth): We should be able to get of the |State| stuff totally once bug + * 1198451 lands, since we can then just return a function representing the next + * state directly. + */ +template +class StreamingLexer +{ +public: + StreamingLexer(LexerTransition aStartState, + LexerTransition aTruncatedState) + : mTransition(TerminalState::FAILURE) + , mTruncatedTransition(aTruncatedState) + { + if (!aStartState.NextStateIsTerminal() && + aStartState.ControlFlow() == ControlFlowStrategy::YIELD) { + // Allowing a StreamingLexer to start in a yield state doesn't make sense + // semantically (since yield states are supposed to deliver the same data + // as previous states, and there's no previous state here), but more + // importantly, it's necessary to advance a SourceBufferIterator at least + // once before you can read from it, and adding the necessary checks to + // Lex() to avoid that issue has the potential to mask real bugs. So + // instead, it's better to forbid starting in a yield state. + MOZ_ASSERT_UNREACHABLE("Starting in a yield state"); + return; + } + + if (!aTruncatedState.NextStateIsTerminal() && + (aTruncatedState.ControlFlow() == ControlFlowStrategy::YIELD || + aTruncatedState.Buffering() == BufferingStrategy::UNBUFFERED || + aTruncatedState.Size() != 0)) { + // The truncated state can't receive any data because, by definition, + // there is no more data to receive. That means that yielding or an + // unbuffered read would not make sense, and that the state must require + // zero bytes. + MOZ_ASSERT_UNREACHABLE("Truncated state makes no sense"); + return; + } + + SetTransition(aStartState); + } + + template + LexerResult Lex(SourceBufferIterator& aIterator, + IResumable* aOnResume, + Func aFunc) + { + if (mTransition.NextStateIsTerminal()) { + // We've already reached a terminal state. We never deliver any more data + // in this case; just return the terminal state again immediately. + return LexerResult(mTransition.NextStateAsTerminal()); + } + + Maybe result; + + // If the lexer requested a yield last time, we deliver the same data again + // before we read anything else from |aIterator|. Note that although to the + // callers of Lex(), Yield::NEED_MORE_DATA is just another type of yield, + // internally they're different in that we don't redeliver the same data in + // the Yield::NEED_MORE_DATA case, and |mYieldingToState| is not set. This + // means that for Yield::NEED_MORE_DATA, we go directly to the loop below. + if (mYieldingToState) { + result = mTransition.Buffering() == BufferingStrategy::UNBUFFERED + ? UnbufferedReadAfterYield(aIterator, aFunc) + : BufferedReadAfterYield(aIterator, aFunc); + } + + while (!result) { + MOZ_ASSERT_IF(mTransition.Buffering() == BufferingStrategy::UNBUFFERED, + mUnbufferedState); + + // Figure out how much we need to read. + const size_t toRead = mTransition.Buffering() == BufferingStrategy::UNBUFFERED + ? mUnbufferedState->mBytesRemaining + : mTransition.Size() - mBuffer.length(); + + // Attempt to advance the iterator by |toRead| bytes. + switch (aIterator.AdvanceOrScheduleResume(toRead, aOnResume)) { + case SourceBufferIterator::WAITING: + // We can't continue because the rest of the data hasn't arrived from + // the network yet. We don't have to do anything special; the + // SourceBufferIterator will ensure that |aOnResume| gets called when + // more data is available. + result = Some(LexerResult(Yield::NEED_MORE_DATA)); + break; + + case SourceBufferIterator::COMPLETE: + // The data is truncated; if not, the lexer would've reached a + // terminal state by now. We only get to + // SourceBufferIterator::COMPLETE after every byte of data has been + // delivered to the lexer. + result = Truncated(aIterator, aFunc); + break; + + case SourceBufferIterator::READY: + // Process the new data that became available. + MOZ_ASSERT(aIterator.Data()); + + result = mTransition.Buffering() == BufferingStrategy::UNBUFFERED + ? UnbufferedRead(aIterator, aFunc) + : BufferedRead(aIterator, aFunc); + break; + + default: + MOZ_ASSERT_UNREACHABLE("Unknown SourceBufferIterator state"); + result = SetTransition(Transition::TerminateFailure()); + } + }; + + return *result; + } + +private: + template + Maybe UnbufferedRead(SourceBufferIterator& aIterator, Func aFunc) + { + MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::UNBUFFERED); + MOZ_ASSERT(mUnbufferedState); + MOZ_ASSERT(!mYieldingToState); + MOZ_ASSERT(mBuffer.empty(), + "Buffered read at the same time as unbuffered read?"); + MOZ_ASSERT(aIterator.Length() <= mUnbufferedState->mBytesRemaining, + "Read too much data during unbuffered read?"); + MOZ_ASSERT(mUnbufferedState->mBytesConsumedInCurrentChunk == 0, + "Already consumed data in the current chunk, but not yielding?"); + + if (mUnbufferedState->mBytesRemaining == 0) { + // We're done with the unbuffered read, so transition to the next state. + return SetTransition(aFunc(mTransition.NextState(), nullptr, 0)); + } + + return ContinueUnbufferedRead(aIterator.Data(), aIterator.Length(), + aIterator.Length(), aFunc); + } + + template + Maybe UnbufferedReadAfterYield(SourceBufferIterator& aIterator, Func aFunc) + { + MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::UNBUFFERED); + MOZ_ASSERT(mUnbufferedState); + MOZ_ASSERT(mYieldingToState); + MOZ_ASSERT(mBuffer.empty(), + "Buffered read at the same time as unbuffered read?"); + MOZ_ASSERT(aIterator.Length() <= mUnbufferedState->mBytesRemaining, + "Read too much data during unbuffered read?"); + MOZ_ASSERT(mUnbufferedState->mBytesConsumedInCurrentChunk <= aIterator.Length(), + "Consumed more data than the current chunk holds?"); + MOZ_ASSERT(mTransition.UnbufferedState() == *mYieldingToState); + + mYieldingToState = Nothing(); + + if (mUnbufferedState->mBytesRemaining == 0) { + // We're done with the unbuffered read, so transition to the next state. + return SetTransition(aFunc(mTransition.NextState(), nullptr, 0)); + } + + // Since we've yielded, we may have already consumed some data in this + // chunk. Make the necessary adjustments. (Note that the std::min call is + // just belt-and-suspenders to keep this code memory safe even if there's + // a bug somewhere.) + const size_t toSkip = + std::min(mUnbufferedState->mBytesConsumedInCurrentChunk, aIterator.Length()); + const char* data = aIterator.Data() + toSkip; + const size_t length = aIterator.Length() - toSkip; + + // If |length| is zero, we've hit the end of the current chunk. This only + // happens if we yield right at the end of a chunk. Rather than call |aFunc| + // with a |length| of zero bytes (which seems potentially surprising to + // decoder authors), we go ahead and read more data. + if (length == 0) { + return FinishCurrentChunkOfUnbufferedRead(aIterator.Length()); + } + + return ContinueUnbufferedRead(data, length, aIterator.Length(), aFunc); + } + + template + Maybe ContinueUnbufferedRead(const char* aData, + size_t aLength, + size_t aChunkLength, + Func aFunc) + { + // Call aFunc with the unbuffered state to indicate that we're in the + // middle of an unbuffered read. We enforce that any state transition + // passed back to us is either a terminal state or takes us back to the + // unbuffered state. + LexerTransition unbufferedTransition = + aFunc(mTransition.UnbufferedState(), aData, aLength); + + // If we reached a terminal state, we're done. + if (unbufferedTransition.NextStateIsTerminal()) { + return SetTransition(unbufferedTransition); + } + + MOZ_ASSERT(mTransition.UnbufferedState() == + unbufferedTransition.NextState()); + + // Perform bookkeeping. + if (unbufferedTransition.ControlFlow() == ControlFlowStrategy::YIELD) { + mUnbufferedState->mBytesConsumedInCurrentChunk += unbufferedTransition.Size(); + return SetTransition(unbufferedTransition); + } + + MOZ_ASSERT(unbufferedTransition.Size() == 0); + return FinishCurrentChunkOfUnbufferedRead(aChunkLength); + } + + Maybe FinishCurrentChunkOfUnbufferedRead(size_t aChunkLength) + { + // We've finished an unbuffered read of a chunk of length |aChunkLength|, so + // update |myBytesRemaining| to reflect that we're |aChunkLength| closer to + // the end of the unbuffered read. (The std::min call is just + // belt-and-suspenders to keep this code memory safe even if there's a bug + // somewhere.) + mUnbufferedState->mBytesRemaining -= + std::min(mUnbufferedState->mBytesRemaining, aChunkLength); + + // Since we're moving on to a new chunk, we can forget about the count of + // bytes consumed by yielding in the current chunk. + mUnbufferedState->mBytesConsumedInCurrentChunk = 0; + + return Nothing(); // Keep processing. + } + + template + Maybe BufferedRead(SourceBufferIterator& aIterator, Func aFunc) + { + MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::BUFFERED); + MOZ_ASSERT(!mYieldingToState); + MOZ_ASSERT(!mUnbufferedState, + "Buffered read at the same time as unbuffered read?"); + MOZ_ASSERT(mBuffer.length() < mTransition.Size() || + (mBuffer.length() == 0 && mTransition.Size() == 0), + "Buffered more than we needed?"); + + // If we have all the data, we don't actually need to buffer anything. + if (mBuffer.empty() && aIterator.Length() == mTransition.Size()) { + return SetTransition(aFunc(mTransition.NextState(), + aIterator.Data(), + aIterator.Length())); + } + + // We do need to buffer, so make sure the buffer has enough capacity. We + // deliberately wait until we know for sure we need to buffer to call + // reserve() since it could require memory allocation. + if (!mBuffer.reserve(mTransition.Size())) { + return SetTransition(Transition::TerminateFailure()); + } + + // Append the new data we just got to the buffer. + if (!mBuffer.append(aIterator.Data(), aIterator.Length())) { + return SetTransition(Transition::TerminateFailure()); + } + + if (mBuffer.length() != mTransition.Size()) { + return Nothing(); // Keep processing. + } + + // We've buffered everything, so transition to the next state. + return SetTransition(aFunc(mTransition.NextState(), + mBuffer.begin(), + mBuffer.length())); + } + + template + Maybe BufferedReadAfterYield(SourceBufferIterator& aIterator, + Func aFunc) + { + MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::BUFFERED); + MOZ_ASSERT(mYieldingToState); + MOZ_ASSERT(!mUnbufferedState, + "Buffered read at the same time as unbuffered read?"); + MOZ_ASSERT(mBuffer.length() <= mTransition.Size(), + "Buffered more than we needed?"); + + State nextState = Move(*mYieldingToState); + + // After a yield, we need to take the same data that we delivered to the + // last state, and deliver it again to the new state. We know that this is + // happening right at a state transition, and that the last state was a + // buffered read, so there are two cases: + + // 1. We got the data from the SourceBufferIterator directly. + if (mBuffer.empty() && aIterator.Length() == mTransition.Size()) { + return SetTransition(aFunc(nextState, + aIterator.Data(), + aIterator.Length())); + } + + // 2. We got the data from the buffer. + if (mBuffer.length() == mTransition.Size()) { + return SetTransition(aFunc(nextState, + mBuffer.begin(), + mBuffer.length())); + } + + // Anything else indicates a bug. + MOZ_ASSERT_UNREACHABLE("Unexpected state encountered during yield"); + return SetTransition(Transition::TerminateFailure()); + } + + template + Maybe Truncated(SourceBufferIterator& aIterator, + Func aFunc) + { + // The data is truncated. Let the lexer clean up and decide which terminal + // state we should end up in. + LexerTransition transition + = mTruncatedTransition.NextStateIsTerminal() + ? mTruncatedTransition + : aFunc(mTruncatedTransition.NextState(), nullptr, 0); + + if (!transition.NextStateIsTerminal()) { + MOZ_ASSERT_UNREACHABLE("Truncated state didn't lead to terminal state?"); + return SetTransition(Transition::TerminateFailure()); + } + + // If the SourceBuffer was completed with a failing state, we end in + // TerminalState::FAILURE no matter what. This only happens in exceptional + // situations like SourceBuffer itself encountering a failure due to OOM. + if (NS_FAILED(aIterator.CompletionStatus())) { + return SetTransition(Transition::TerminateFailure()); + } + + return SetTransition(transition); + } + + Maybe SetTransition(const LexerTransition& aTransition) + { + // There should be no transitions while we're buffering for a buffered read + // unless they're to terminal states. (The terminal state transitions would + // generally be triggered by error handling code.) + MOZ_ASSERT_IF(!mBuffer.empty(), + aTransition.NextStateIsTerminal() || + mBuffer.length() == mTransition.Size()); + + // Similarly, the only transitions allowed in the middle of an unbuffered + // read are to a terminal state, or a yield to the same state. Otherwise, we + // should remain in the same state until the unbuffered read completes. + MOZ_ASSERT_IF(mUnbufferedState, + aTransition.NextStateIsTerminal() || + (aTransition.ControlFlow() == ControlFlowStrategy::YIELD && + aTransition.NextState() == mTransition.UnbufferedState()) || + mUnbufferedState->mBytesRemaining == 0); + + // If this transition is a yield, save the next state and return. We'll + // handle the rest when Lex() gets called again. + if (!aTransition.NextStateIsTerminal() && + aTransition.ControlFlow() == ControlFlowStrategy::YIELD) { + mYieldingToState = Some(aTransition.NextState()); + return Some(LexerResult(Yield::OUTPUT_AVAILABLE)); + } + + // Update our transition. + mTransition = aTransition; + + // Get rid of anything left over from the previous state. + mBuffer.clear(); + mYieldingToState = Nothing(); + mUnbufferedState = Nothing(); + + // If we reached a terminal state, let the caller know. + if (mTransition.NextStateIsTerminal()) { + return Some(LexerResult(mTransition.NextStateAsTerminal())); + } + + // If we're entering an unbuffered state, record how long we'll stay in it. + if (mTransition.Buffering() == BufferingStrategy::UNBUFFERED) { + mUnbufferedState.emplace(mTransition.Size()); + } + + return Nothing(); // Keep processing. + } + + // State that tracks our position within an unbuffered read. + struct UnbufferedState + { + explicit UnbufferedState(size_t aBytesRemaining) + : mBytesRemaining(aBytesRemaining) + , mBytesConsumedInCurrentChunk(0) + { } + + size_t mBytesRemaining; + size_t mBytesConsumedInCurrentChunk; + }; + + Vector mBuffer; + LexerTransition mTransition; + const LexerTransition mTruncatedTransition; + Maybe mYieldingToState; + Maybe mUnbufferedState; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_StreamingLexer_h diff --git a/image/SurfaceCache.cpp b/image/SurfaceCache.cpp new file mode 100644 index 000000000..66fdfcca0 --- /dev/null +++ b/image/SurfaceCache.cpp @@ -0,0 +1,1164 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * SurfaceCache is a service for caching temporary surfaces in imagelib. + */ + +#include "SurfaceCache.h" + +#include +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Likely.h" +#include "mozilla/Move.h" +#include "mozilla/Pair.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Tuple.h" +#include "nsIMemoryReporter.h" +#include "gfx2DGlue.h" +#include "gfxPlatform.h" +#include "gfxPrefs.h" +#include "imgFrame.h" +#include "Image.h" +#include "ISurfaceProvider.h" +#include "LookupResult.h" +#include "nsExpirationTracker.h" +#include "nsHashKeys.h" +#include "nsRefPtrHashtable.h" +#include "nsSize.h" +#include "nsTArray.h" +#include "prsystem.h" +#include "ShutdownTracker.h" + +using std::max; +using std::min; + +namespace mozilla { + +using namespace gfx; + +namespace image { + +class CachedSurface; +class SurfaceCacheImpl; + +/////////////////////////////////////////////////////////////////////////////// +// Static Data +/////////////////////////////////////////////////////////////////////////////// + +// The single surface cache instance. +static StaticRefPtr sInstance; + +// The mutex protecting the surface cache. +static StaticMutex sInstanceMutex; + +/////////////////////////////////////////////////////////////////////////////// +// SurfaceCache Implementation +/////////////////////////////////////////////////////////////////////////////// + +/** + * Cost models the cost of storing a surface in the cache. Right now, this is + * simply an estimate of the size of the surface in bytes, but in the future it + * may be worth taking into account the cost of rematerializing the surface as + * well. + */ +typedef size_t Cost; + +static Cost +ComputeCost(const IntSize& aSize, uint32_t aBytesPerPixel) +{ + MOZ_ASSERT(aBytesPerPixel == 1 || aBytesPerPixel == 4); + return aSize.width * aSize.height * aBytesPerPixel; +} + +/** + * Since we want to be able to make eviction decisions based on cost, we need to + * be able to look up the CachedSurface which has a certain cost as well as the + * cost associated with a certain CachedSurface. To make this possible, in data + * structures we actually store a CostEntry, which contains a weak pointer to + * its associated surface. + * + * To make usage of the weak pointer safe, SurfaceCacheImpl always calls + * StartTracking after a surface is stored in the cache and StopTracking before + * it is removed. + */ +class CostEntry +{ +public: + CostEntry(NotNull aSurface, Cost aCost) + : mSurface(aSurface) + , mCost(aCost) + { } + + NotNull Surface() const { return mSurface; } + Cost GetCost() const { return mCost; } + + bool operator==(const CostEntry& aOther) const + { + return mSurface == aOther.mSurface && + mCost == aOther.mCost; + } + + bool operator<(const CostEntry& aOther) const + { + return mCost < aOther.mCost || + (mCost == aOther.mCost && mSurface < aOther.mSurface); + } + +private: + NotNull mSurface; + Cost mCost; +}; + +/** + * A CachedSurface associates a surface with a key that uniquely identifies that + * surface. + */ +class CachedSurface +{ + ~CachedSurface() { } +public: + MOZ_DECLARE_REFCOUNTED_TYPENAME(CachedSurface) + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CachedSurface) + + explicit CachedSurface(NotNull aProvider) + : mProvider(aProvider) + , mIsLocked(false) + { } + + DrawableSurface GetDrawableSurface() const + { + if (MOZ_UNLIKELY(IsPlaceholder())) { + MOZ_ASSERT_UNREACHABLE("Called GetDrawableSurface() on a placeholder"); + return DrawableSurface(); + } + + return mProvider->Surface(); + } + + void SetLocked(bool aLocked) + { + if (IsPlaceholder()) { + return; // Can't lock a placeholder. + } + + // Update both our state and our provider's state. Some surface providers + // are permanently locked; maintaining our own locking state enables us to + // respect SetLocked() even when it's meaningless from the provider's + // perspective. + mIsLocked = aLocked; + mProvider->SetLocked(aLocked); + } + + bool IsLocked() const + { + return !IsPlaceholder() && mIsLocked && mProvider->IsLocked(); + } + + bool IsPlaceholder() const { return mProvider->Availability().IsPlaceholder(); } + bool IsDecoded() const { return !IsPlaceholder() && mProvider->IsFinished(); } + + ImageKey GetImageKey() const { return mProvider->GetImageKey(); } + SurfaceKey GetSurfaceKey() const { return mProvider->GetSurfaceKey(); } + nsExpirationState* GetExpirationState() { return &mExpirationState; } + + CostEntry GetCostEntry() + { + return image::CostEntry(WrapNotNull(this), mProvider->LogicalSizeInBytes()); + } + + // A helper type used by SurfaceCacheImpl::CollectSizeOfSurfaces. + struct MOZ_STACK_CLASS SurfaceMemoryReport + { + SurfaceMemoryReport(nsTArray& aCounters, + MallocSizeOf aMallocSizeOf) + : mCounters(aCounters) + , mMallocSizeOf(aMallocSizeOf) + { } + + void Add(NotNull aCachedSurface) + { + SurfaceMemoryCounter counter(aCachedSurface->GetSurfaceKey(), + aCachedSurface->IsLocked()); + + if (aCachedSurface->IsPlaceholder()) { + return; + } + + // Record the memory used by the ISurfaceProvider. This may not have a + // straightforward relationship to the size of the surface that + // DrawableRef() returns if the surface is generated dynamically. (i.e., + // for surfaces with PlaybackType::eAnimated.) + size_t heap = 0; + size_t nonHeap = 0; + aCachedSurface->mProvider + ->AddSizeOfExcludingThis(mMallocSizeOf, heap, nonHeap); + counter.Values().SetDecodedHeap(heap); + counter.Values().SetDecodedNonHeap(nonHeap); + + mCounters.AppendElement(counter); + } + + private: + nsTArray& mCounters; + MallocSizeOf mMallocSizeOf; + }; + +private: + nsExpirationState mExpirationState; + NotNull> mProvider; + bool mIsLocked; +}; + +static int64_t +AreaOfIntSize(const IntSize& aSize) { + return static_cast(aSize.width) * static_cast(aSize.height); +} + +/** + * An ImageSurfaceCache is a per-image surface cache. For correctness we must be + * able to remove all surfaces associated with an image when the image is + * destroyed or invalidated. Since this will happen frequently, it makes sense + * to make it cheap by storing the surfaces for each image separately. + * + * ImageSurfaceCache also keeps track of whether its associated image is locked + * or unlocked. + */ +class ImageSurfaceCache +{ + ~ImageSurfaceCache() { } +public: + ImageSurfaceCache() : mLocked(false) { } + + MOZ_DECLARE_REFCOUNTED_TYPENAME(ImageSurfaceCache) + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ImageSurfaceCache) + + typedef + nsRefPtrHashtable, CachedSurface> SurfaceTable; + + bool IsEmpty() const { return mSurfaces.Count() == 0; } + + void Insert(NotNull aSurface) + { + MOZ_ASSERT(!mLocked || aSurface->IsPlaceholder() || aSurface->IsLocked(), + "Inserting an unlocked surface for a locked image"); + mSurfaces.Put(aSurface->GetSurfaceKey(), aSurface); + } + + void Remove(NotNull aSurface) + { + MOZ_ASSERT(mSurfaces.GetWeak(aSurface->GetSurfaceKey()), + "Should not be removing a surface we don't have"); + + mSurfaces.Remove(aSurface->GetSurfaceKey()); + } + + already_AddRefed Lookup(const SurfaceKey& aSurfaceKey) + { + RefPtr surface; + mSurfaces.Get(aSurfaceKey, getter_AddRefs(surface)); + return surface.forget(); + } + + Pair, MatchType> + LookupBestMatch(const SurfaceKey& aIdealKey) + { + // Try for an exact match first. + RefPtr exactMatch; + mSurfaces.Get(aIdealKey, getter_AddRefs(exactMatch)); + if (exactMatch && exactMatch->IsDecoded()) { + return MakePair(exactMatch.forget(), MatchType::EXACT); + } + + // There's no perfect match, so find the best match we can. + RefPtr bestMatch; + for (auto iter = ConstIter(); !iter.Done(); iter.Next()) { + NotNull current = WrapNotNull(iter.UserData()); + const SurfaceKey& currentKey = current->GetSurfaceKey(); + + // We never match a placeholder. + if (current->IsPlaceholder()) { + continue; + } + // Matching the playback type and SVG context is required. + if (currentKey.Playback() != aIdealKey.Playback() || + currentKey.SVGContext() != aIdealKey.SVGContext()) { + continue; + } + // Matching the flags is required. + if (currentKey.Flags() != aIdealKey.Flags()) { + continue; + } + // Anything is better than nothing! (Within the constraints we just + // checked, of course.) + if (!bestMatch) { + bestMatch = current; + continue; + } + + MOZ_ASSERT(bestMatch, "Should have a current best match"); + + // Always prefer completely decoded surfaces. + bool bestMatchIsDecoded = bestMatch->IsDecoded(); + if (bestMatchIsDecoded && !current->IsDecoded()) { + continue; + } + if (!bestMatchIsDecoded && current->IsDecoded()) { + bestMatch = current; + continue; + } + + SurfaceKey bestMatchKey = bestMatch->GetSurfaceKey(); + + // Compare sizes. We use an area-based heuristic here instead of computing a + // truly optimal answer, since it seems very unlikely to make a difference + // for realistic sizes. + int64_t idealArea = AreaOfIntSize(aIdealKey.Size()); + int64_t currentArea = AreaOfIntSize(currentKey.Size()); + int64_t bestMatchArea = AreaOfIntSize(bestMatchKey.Size()); + + // If the best match is smaller than the ideal size, prefer bigger sizes. + if (bestMatchArea < idealArea) { + if (currentArea > bestMatchArea) { + bestMatch = current; + } + continue; + } + // Other, prefer sizes closer to the ideal size, but still not smaller. + if (idealArea <= currentArea && currentArea < bestMatchArea) { + bestMatch = current; + continue; + } + // This surface isn't an improvement over the current best match. + } + + MatchType matchType; + if (bestMatch) { + if (!exactMatch) { + // No exact match, but we found a substitute. + matchType = MatchType::SUBSTITUTE_BECAUSE_NOT_FOUND; + } else if (exactMatch != bestMatch) { + // The exact match is still decoding, but we found a substitute. + matchType = MatchType::SUBSTITUTE_BECAUSE_PENDING; + } else { + // The exact match is still decoding, but it's the best we've got. + matchType = MatchType::EXACT; + } + } else { + if (exactMatch) { + // We found an "exact match"; it must have been a placeholder. + MOZ_ASSERT(exactMatch->IsPlaceholder()); + matchType = MatchType::PENDING; + } else { + // We couldn't find an exact match *or* a substitute. + matchType = MatchType::NOT_FOUND; + } + } + + return MakePair(bestMatch.forget(), matchType); + } + + SurfaceTable::Iterator ConstIter() const + { + return mSurfaces.ConstIter(); + } + + void SetLocked(bool aLocked) { mLocked = aLocked; } + bool IsLocked() const { return mLocked; } + +private: + SurfaceTable mSurfaces; + bool mLocked; +}; + +/** + * SurfaceCacheImpl is responsible for determining which surfaces will be cached + * and managing the surface cache data structures. Rather than interact with + * SurfaceCacheImpl directly, client code interacts with SurfaceCache, which + * maintains high-level invariants and encapsulates the details of the surface + * cache's implementation. + */ +class SurfaceCacheImpl final : public nsIMemoryReporter +{ +public: + NS_DECL_ISUPPORTS + + SurfaceCacheImpl(uint32_t aSurfaceCacheExpirationTimeMS, + uint32_t aSurfaceCacheDiscardFactor, + uint32_t aSurfaceCacheSize) + : mExpirationTracker(aSurfaceCacheExpirationTimeMS) + , mMemoryPressureObserver(new MemoryPressureObserver) + , mDiscardFactor(aSurfaceCacheDiscardFactor) + , mMaxCost(aSurfaceCacheSize) + , mAvailableCost(aSurfaceCacheSize) + , mLockedCost(0) + , mOverflowCount(0) + { + nsCOMPtr os = services::GetObserverService(); + if (os) { + os->AddObserver(mMemoryPressureObserver, "memory-pressure", false); + } + } + +private: + virtual ~SurfaceCacheImpl() + { + nsCOMPtr os = services::GetObserverService(); + if (os) { + os->RemoveObserver(mMemoryPressureObserver, "memory-pressure"); + } + + UnregisterWeakMemoryReporter(this); + } + +public: + void InitMemoryReporter() { RegisterWeakMemoryReporter(this); } + + InsertOutcome Insert(NotNull aProvider, + bool aSetAvailable, + const StaticMutexAutoLock& aAutoLock) + { + // If this is a duplicate surface, refuse to replace the original. + // XXX(seth): Calling Lookup() and then RemoveEntry() does the lookup + // twice. We'll make this more efficient in bug 1185137. + LookupResult result = Lookup(aProvider->GetImageKey(), + aProvider->GetSurfaceKey(), + aAutoLock, + /* aMarkUsed = */ false); + if (MOZ_UNLIKELY(result)) { + return InsertOutcome::FAILURE_ALREADY_PRESENT; + } + + if (result.Type() == MatchType::PENDING) { + RemoveEntry(aProvider->GetImageKey(), aProvider->GetSurfaceKey(), aAutoLock); + } + + MOZ_ASSERT(result.Type() == MatchType::NOT_FOUND || + result.Type() == MatchType::PENDING, + "A LookupResult with no surface should be NOT_FOUND or PENDING"); + + // If this is bigger than we can hold after discarding everything we can, + // refuse to cache it. + Cost cost = aProvider->LogicalSizeInBytes(); + if (MOZ_UNLIKELY(!CanHoldAfterDiscarding(cost))) { + mOverflowCount++; + return InsertOutcome::FAILURE; + } + + // Remove elements in order of cost until we can fit this in the cache. Note + // that locked surfaces aren't in mCosts, so we never remove them here. + while (cost > mAvailableCost) { + MOZ_ASSERT(!mCosts.IsEmpty(), + "Removed everything and it still won't fit"); + Remove(mCosts.LastElement().Surface(), aAutoLock); + } + + // Locate the appropriate per-image cache. If there's not an existing cache + // for this image, create it. + RefPtr cache = GetImageCache(aProvider->GetImageKey()); + if (!cache) { + cache = new ImageSurfaceCache; + mImageCaches.Put(aProvider->GetImageKey(), cache); + } + + // If we were asked to mark the cache entry available, do so. + if (aSetAvailable) { + aProvider->Availability().SetAvailable(); + } + + NotNull> surface = + WrapNotNull(new CachedSurface(aProvider)); + + // We require that locking succeed if the image is locked and we're not + // inserting a placeholder; the caller may need to know this to handle + // errors correctly. + if (cache->IsLocked() && !surface->IsPlaceholder()) { + surface->SetLocked(true); + if (!surface->IsLocked()) { + return InsertOutcome::FAILURE; + } + } + + // Insert. + MOZ_ASSERT(cost <= mAvailableCost, "Inserting despite too large a cost"); + cache->Insert(surface); + StartTracking(surface, aAutoLock); + + return InsertOutcome::SUCCESS; + } + + void Remove(NotNull aSurface, + const StaticMutexAutoLock& aAutoLock) + { + ImageKey imageKey = aSurface->GetImageKey(); + + RefPtr cache = GetImageCache(imageKey); + MOZ_ASSERT(cache, "Shouldn't try to remove a surface with no image cache"); + + // If the surface was not a placeholder, tell its image that we discarded it. + if (!aSurface->IsPlaceholder()) { + static_cast(imageKey)->OnSurfaceDiscarded(); + } + + StopTracking(aSurface, aAutoLock); + cache->Remove(aSurface); + + // Remove the per-image cache if it's unneeded now. (Keep it if the image is + // locked, since the per-image cache is where we store that state.) + if (cache->IsEmpty() && !cache->IsLocked()) { + mImageCaches.Remove(imageKey); + } + } + + void StartTracking(NotNull aSurface, + const StaticMutexAutoLock& aAutoLock) + { + CostEntry costEntry = aSurface->GetCostEntry(); + MOZ_ASSERT(costEntry.GetCost() <= mAvailableCost, + "Cost too large and the caller didn't catch it"); + + mAvailableCost -= costEntry.GetCost(); + + if (aSurface->IsLocked()) { + mLockedCost += costEntry.GetCost(); + MOZ_ASSERT(mLockedCost <= mMaxCost, "Locked more than we can hold?"); + } else { + mCosts.InsertElementSorted(costEntry); + // This may fail during XPCOM shutdown, so we need to ensure the object is + // tracked before calling RemoveObject in StopTracking. + mExpirationTracker.AddObjectLocked(aSurface, aAutoLock); + } + } + + void StopTracking(NotNull aSurface, + const StaticMutexAutoLock& aAutoLock) + { + CostEntry costEntry = aSurface->GetCostEntry(); + + if (aSurface->IsLocked()) { + MOZ_ASSERT(mLockedCost >= costEntry.GetCost(), "Costs don't balance"); + mLockedCost -= costEntry.GetCost(); + // XXX(seth): It'd be nice to use an O(log n) lookup here. This is O(n). + MOZ_ASSERT(!mCosts.Contains(costEntry), + "Shouldn't have a cost entry for a locked surface"); + } else { + if (MOZ_LIKELY(aSurface->GetExpirationState()->IsTracked())) { + mExpirationTracker.RemoveObjectLocked(aSurface, aAutoLock); + } else { + // Our call to AddObject must have failed in StartTracking; most likely + // we're in XPCOM shutdown right now. + NS_ASSERTION(ShutdownTracker::ShutdownHasStarted(), + "Not expiration-tracking an unlocked surface!"); + } + + DebugOnly foundInCosts = mCosts.RemoveElementSorted(costEntry); + MOZ_ASSERT(foundInCosts, "Lost track of costs for this surface"); + } + + mAvailableCost += costEntry.GetCost(); + MOZ_ASSERT(mAvailableCost <= mMaxCost, + "More available cost than we started with"); + } + + LookupResult Lookup(const ImageKey aImageKey, + const SurfaceKey& aSurfaceKey, + const StaticMutexAutoLock& aAutoLock, + bool aMarkUsed = true) + { + RefPtr cache = GetImageCache(aImageKey); + if (!cache) { + // No cached surfaces for this image. + return LookupResult(MatchType::NOT_FOUND); + } + + RefPtr surface = cache->Lookup(aSurfaceKey); + if (!surface) { + // Lookup in the per-image cache missed. + return LookupResult(MatchType::NOT_FOUND); + } + + if (surface->IsPlaceholder()) { + return LookupResult(MatchType::PENDING); + } + + DrawableSurface drawableSurface = surface->GetDrawableSurface(); + if (!drawableSurface) { + // The surface was released by the operating system. Remove the cache + // entry as well. + Remove(WrapNotNull(surface), aAutoLock); + return LookupResult(MatchType::NOT_FOUND); + } + + if (aMarkUsed) { + MarkUsed(WrapNotNull(surface), WrapNotNull(cache), aAutoLock); + } + + MOZ_ASSERT(surface->GetSurfaceKey() == aSurfaceKey, + "Lookup() not returning an exact match?"); + return LookupResult(Move(drawableSurface), MatchType::EXACT); + } + + LookupResult LookupBestMatch(const ImageKey aImageKey, + const SurfaceKey& aSurfaceKey, + const StaticMutexAutoLock& aAutoLock) + { + RefPtr cache = GetImageCache(aImageKey); + if (!cache) { + // No cached surfaces for this image. + return LookupResult(MatchType::NOT_FOUND); + } + + // Repeatedly look up the best match, trying again if the resulting surface + // has been freed by the operating system, until we can either lock a + // surface for drawing or there are no matching surfaces left. + // XXX(seth): This is O(N^2), but N is expected to be very small. If we + // encounter a performance problem here we can revisit this. + + RefPtr surface; + DrawableSurface drawableSurface; + MatchType matchType = MatchType::NOT_FOUND; + while (true) { + Tie(surface, matchType) = cache->LookupBestMatch(aSurfaceKey); + + if (!surface) { + return LookupResult(matchType); // Lookup in the per-image cache missed. + } + + drawableSurface = surface->GetDrawableSurface(); + if (drawableSurface) { + break; + } + + // The surface was released by the operating system. Remove the cache + // entry as well. + Remove(WrapNotNull(surface), aAutoLock); + } + + MOZ_ASSERT_IF(matchType == MatchType::EXACT, + surface->GetSurfaceKey() == aSurfaceKey); + MOZ_ASSERT_IF(matchType == MatchType::SUBSTITUTE_BECAUSE_NOT_FOUND || + matchType == MatchType::SUBSTITUTE_BECAUSE_PENDING, + surface->GetSurfaceKey().SVGContext() == aSurfaceKey.SVGContext() && + surface->GetSurfaceKey().Playback() == aSurfaceKey.Playback() && + surface->GetSurfaceKey().Flags() == aSurfaceKey.Flags()); + + if (matchType == MatchType::EXACT) { + MarkUsed(WrapNotNull(surface), WrapNotNull(cache), aAutoLock); + } + + return LookupResult(Move(drawableSurface), matchType); + } + + bool CanHold(const Cost aCost) const + { + return aCost <= mMaxCost; + } + + size_t MaximumCapacity() const + { + return size_t(mMaxCost); + } + + void SurfaceAvailable(NotNull aProvider, + const StaticMutexAutoLock& aAutoLock) + { + if (!aProvider->Availability().IsPlaceholder()) { + MOZ_ASSERT_UNREACHABLE("Calling SurfaceAvailable on non-placeholder"); + return; + } + + // Reinsert the provider, requesting that Insert() mark it available. This + // may or may not succeed, depending on whether some other decoder has + // beaten us to the punch and inserted a non-placeholder version of this + // surface first, but it's fine either way. + // XXX(seth): This could be implemented more efficiently; we should be able + // to just update our data structures without reinserting. + Insert(aProvider, /* aSetAvailable = */ true, aAutoLock); + } + + void LockImage(const ImageKey aImageKey) + { + RefPtr cache = GetImageCache(aImageKey); + if (!cache) { + cache = new ImageSurfaceCache; + mImageCaches.Put(aImageKey, cache); + } + + cache->SetLocked(true); + + // We don't relock this image's existing surfaces right away; instead, the + // image should arrange for Lookup() to touch them if they are still useful. + } + + void UnlockImage(const ImageKey aImageKey, const StaticMutexAutoLock& aAutoLock) + { + RefPtr cache = GetImageCache(aImageKey); + if (!cache || !cache->IsLocked()) { + return; // Already unlocked. + } + + cache->SetLocked(false); + DoUnlockSurfaces(WrapNotNull(cache), aAutoLock); + } + + void UnlockEntries(const ImageKey aImageKey, const StaticMutexAutoLock& aAutoLock) + { + RefPtr cache = GetImageCache(aImageKey); + if (!cache || !cache->IsLocked()) { + return; // Already unlocked. + } + + // (Note that we *don't* unlock the per-image cache here; that's the + // difference between this and UnlockImage.) + DoUnlockSurfaces(WrapNotNull(cache), aAutoLock); + } + + void RemoveImage(const ImageKey aImageKey, const StaticMutexAutoLock& aAutoLock) + { + RefPtr cache = GetImageCache(aImageKey); + if (!cache) { + return; // No cached surfaces for this image, so nothing to do. + } + + // Discard all of the cached surfaces for this image. + // XXX(seth): This is O(n^2) since for each item in the cache we are + // removing an element from the costs array. Since n is expected to be + // small, performance should be good, but if usage patterns change we should + // change the data structure used for mCosts. + for (auto iter = cache->ConstIter(); !iter.Done(); iter.Next()) { + StopTracking(WrapNotNull(iter.UserData()), aAutoLock); + } + + // The per-image cache isn't needed anymore, so remove it as well. + // This implicitly unlocks the image if it was locked. + mImageCaches.Remove(aImageKey); + } + + void DiscardAll(const StaticMutexAutoLock& aAutoLock) + { + // Remove in order of cost because mCosts is an array and the other data + // structures are all hash tables. Note that locked surfaces are not + // removed, since they aren't present in mCosts. + while (!mCosts.IsEmpty()) { + Remove(mCosts.LastElement().Surface(), aAutoLock); + } + } + + void DiscardForMemoryPressure(const StaticMutexAutoLock& aAutoLock) + { + // Compute our discardable cost. Since locked surfaces aren't discardable, + // we exclude them. + const Cost discardableCost = (mMaxCost - mAvailableCost) - mLockedCost; + MOZ_ASSERT(discardableCost <= mMaxCost, "Discardable cost doesn't add up"); + + // Our target is to raise our available cost by (1 / mDiscardFactor) of our + // discardable cost - in other words, we want to end up with about + // (discardableCost / mDiscardFactor) fewer bytes stored in the surface + // cache after we're done. + const Cost targetCost = mAvailableCost + (discardableCost / mDiscardFactor); + + if (targetCost > mMaxCost - mLockedCost) { + MOZ_ASSERT_UNREACHABLE("Target cost is more than we can discard"); + DiscardAll(aAutoLock); + return; + } + + // Discard surfaces until we've reduced our cost to our target cost. + while (mAvailableCost < targetCost) { + MOZ_ASSERT(!mCosts.IsEmpty(), "Removed everything and still not done"); + Remove(mCosts.LastElement().Surface(), aAutoLock); + } + } + + void LockSurface(NotNull aSurface, + const StaticMutexAutoLock& aAutoLock) + { + if (aSurface->IsPlaceholder() || aSurface->IsLocked()) { + return; + } + + StopTracking(aSurface, aAutoLock); + + // Lock the surface. This can fail. + aSurface->SetLocked(true); + StartTracking(aSurface, aAutoLock); + } + + NS_IMETHOD + CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, + bool aAnonymize) override + { + StaticMutexAutoLock lock(sInstanceMutex); + + // We have explicit memory reporting for the surface cache which is more + // accurate than the cost metrics we report here, but these metrics are + // still useful to report, since they control the cache's behavior. + MOZ_COLLECT_REPORT( + "imagelib-surface-cache-estimated-total", + KIND_OTHER, UNITS_BYTES, (mMaxCost - mAvailableCost), +"Estimated total memory used by the imagelib surface cache."); + + MOZ_COLLECT_REPORT( + "imagelib-surface-cache-estimated-locked", + KIND_OTHER, UNITS_BYTES, mLockedCost, +"Estimated memory used by locked surfaces in the imagelib surface cache."); + + MOZ_COLLECT_REPORT( + "imagelib-surface-cache-overflow-count", + KIND_OTHER, UNITS_COUNT, mOverflowCount, +"Count of how many times the surface cache has hit its capacity and been " +"unable to insert a new surface."); + + return NS_OK; + } + + void CollectSizeOfSurfaces(const ImageKey aImageKey, + nsTArray& aCounters, + MallocSizeOf aMallocSizeOf) + { + RefPtr cache = GetImageCache(aImageKey); + if (!cache) { + return; // No surfaces for this image. + } + + // Report all surfaces in the per-image cache. + CachedSurface::SurfaceMemoryReport report(aCounters, aMallocSizeOf); + for (auto iter = cache->ConstIter(); !iter.Done(); iter.Next()) { + report.Add(WrapNotNull(iter.UserData())); + } + } + +private: + already_AddRefed GetImageCache(const ImageKey aImageKey) + { + RefPtr imageCache; + mImageCaches.Get(aImageKey, getter_AddRefs(imageCache)); + return imageCache.forget(); + } + + // This is similar to CanHold() except that it takes into account the costs of + // locked surfaces. It's used internally in Insert(), but it's not exposed + // publicly because we permit multithreaded access to the surface cache, which + // means that the result would be meaningless: another thread could insert a + // surface or lock an image at any time. + bool CanHoldAfterDiscarding(const Cost aCost) const + { + return aCost <= mMaxCost - mLockedCost; + } + + void MarkUsed(NotNull aSurface, + NotNull aCache, + const StaticMutexAutoLock& aAutoLock) + { + if (aCache->IsLocked()) { + LockSurface(aSurface, aAutoLock); + } else { + mExpirationTracker.MarkUsedLocked(aSurface, aAutoLock); + } + } + + void DoUnlockSurfaces(NotNull aCache, + const StaticMutexAutoLock& aAutoLock) + { + // Unlock all the surfaces the per-image cache is holding. + for (auto iter = aCache->ConstIter(); !iter.Done(); iter.Next()) { + NotNull surface = WrapNotNull(iter.UserData()); + if (surface->IsPlaceholder() || !surface->IsLocked()) { + continue; + } + StopTracking(surface, aAutoLock); + surface->SetLocked(false); + StartTracking(surface, aAutoLock); + } + } + + void RemoveEntry(const ImageKey aImageKey, + const SurfaceKey& aSurfaceKey, + const StaticMutexAutoLock& aAutoLock) + { + RefPtr cache = GetImageCache(aImageKey); + if (!cache) { + return; // No cached surfaces for this image. + } + + RefPtr surface = cache->Lookup(aSurfaceKey); + if (!surface) { + return; // Lookup in the per-image cache missed. + } + + Remove(WrapNotNull(surface), aAutoLock); + } + + struct SurfaceTracker : public ExpirationTrackerImpl + { + explicit SurfaceTracker(uint32_t aSurfaceCacheExpirationTimeMS) + : ExpirationTrackerImpl( + aSurfaceCacheExpirationTimeMS, "SurfaceTracker") + { } + + protected: + void NotifyExpiredLocked(CachedSurface* aSurface, + const StaticMutexAutoLock& aAutoLock) override + { + sInstance->Remove(WrapNotNull(aSurface), aAutoLock); + } + + StaticMutex& GetMutex() override + { + return sInstanceMutex; + } + }; + + struct MemoryPressureObserver : public nsIObserver + { + NS_DECL_ISUPPORTS + + NS_IMETHOD Observe(nsISupports*, + const char* aTopic, + const char16_t*) override + { + StaticMutexAutoLock lock(sInstanceMutex); + if (sInstance && strcmp(aTopic, "memory-pressure") == 0) { + sInstance->DiscardForMemoryPressure(lock); + } + return NS_OK; + } + + private: + virtual ~MemoryPressureObserver() { } + }; + + nsTArray mCosts; + nsRefPtrHashtable, + ImageSurfaceCache> mImageCaches; + SurfaceTracker mExpirationTracker; + RefPtr mMemoryPressureObserver; + const uint32_t mDiscardFactor; + const Cost mMaxCost; + Cost mAvailableCost; + Cost mLockedCost; + size_t mOverflowCount; +}; + +NS_IMPL_ISUPPORTS(SurfaceCacheImpl, nsIMemoryReporter) +NS_IMPL_ISUPPORTS(SurfaceCacheImpl::MemoryPressureObserver, nsIObserver) + +/////////////////////////////////////////////////////////////////////////////// +// Public API +/////////////////////////////////////////////////////////////////////////////// + +/* static */ void +SurfaceCache::Initialize() +{ + // Initialize preferences. + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!sInstance, "Shouldn't initialize more than once"); + + // See gfxPrefs for the default values of these preferences. + + // Length of time before an unused surface is removed from the cache, in + // milliseconds. + uint32_t surfaceCacheExpirationTimeMS = + gfxPrefs::ImageMemSurfaceCacheMinExpirationMS(); + + // What fraction of the memory used by the surface cache we should discard + // when we get a memory pressure notification. This value is interpreted as + // 1/N, so 1 means to discard everything, 2 means to discard about half of the + // memory we're using, and so forth. We clamp it to avoid division by zero. + uint32_t surfaceCacheDiscardFactor = + max(gfxPrefs::ImageMemSurfaceCacheDiscardFactor(), 1u); + + // Maximum size of the surface cache, in kilobytes. + uint64_t surfaceCacheMaxSizeKB = gfxPrefs::ImageMemSurfaceCacheMaxSizeKB(); + + // A knob determining the actual size of the surface cache. Currently the + // cache is (size of main memory) / (surface cache size factor) KB + // or (surface cache max size) KB, whichever is smaller. The formula + // may change in the future, though. + // For example, a value of 4 would yield a 256MB cache on a 1GB machine. + // The smallest machines we are likely to run this code on have 256MB + // of memory, which would yield a 64MB cache on this setting. + // We clamp this value to avoid division by zero. + uint32_t surfaceCacheSizeFactor = + max(gfxPrefs::ImageMemSurfaceCacheSizeFactor(), 1u); + + // Compute the size of the surface cache. + uint64_t memorySize = PR_GetPhysicalMemorySize(); + if (memorySize == 0) { + MOZ_ASSERT_UNREACHABLE("PR_GetPhysicalMemorySize not implemented here"); + memorySize = 256 * 1024 * 1024; // Fall back to 256MB. + } + uint64_t proposedSize = memorySize / surfaceCacheSizeFactor; + uint64_t surfaceCacheSizeBytes = min(proposedSize, + surfaceCacheMaxSizeKB * 1024); + uint32_t finalSurfaceCacheSizeBytes = + min(surfaceCacheSizeBytes, uint64_t(UINT32_MAX)); + + // Create the surface cache singleton with the requested settings. Note that + // the size is a limit that the cache may not grow beyond, but we do not + // actually allocate any storage for surfaces at this time. + sInstance = new SurfaceCacheImpl(surfaceCacheExpirationTimeMS, + surfaceCacheDiscardFactor, + finalSurfaceCacheSizeBytes); + sInstance->InitMemoryReporter(); +} + +/* static */ void +SurfaceCache::Shutdown() +{ + StaticMutexAutoLock lock(sInstanceMutex); + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(sInstance, "No singleton - was Shutdown() called twice?"); + sInstance = nullptr; +} + +/* static */ LookupResult +SurfaceCache::Lookup(const ImageKey aImageKey, + const SurfaceKey& aSurfaceKey) +{ + StaticMutexAutoLock lock(sInstanceMutex); + if (!sInstance) { + return LookupResult(MatchType::NOT_FOUND); + } + + return sInstance->Lookup(aImageKey, aSurfaceKey, lock); +} + +/* static */ LookupResult +SurfaceCache::LookupBestMatch(const ImageKey aImageKey, + const SurfaceKey& aSurfaceKey) +{ + StaticMutexAutoLock lock(sInstanceMutex); + if (!sInstance) { + return LookupResult(MatchType::NOT_FOUND); + } + + return sInstance->LookupBestMatch(aImageKey, aSurfaceKey, lock); +} + +/* static */ InsertOutcome +SurfaceCache::Insert(NotNull aProvider) +{ + StaticMutexAutoLock lock(sInstanceMutex); + if (!sInstance) { + return InsertOutcome::FAILURE; + } + + return sInstance->Insert(aProvider, /* aSetAvailable = */ false, lock); +} + +/* static */ bool +SurfaceCache::CanHold(const IntSize& aSize, uint32_t aBytesPerPixel /* = 4 */) +{ + StaticMutexAutoLock lock(sInstanceMutex); + if (!sInstance) { + return false; + } + + Cost cost = ComputeCost(aSize, aBytesPerPixel); + return sInstance->CanHold(cost); +} + +/* static */ bool +SurfaceCache::CanHold(size_t aSize) +{ + StaticMutexAutoLock lock(sInstanceMutex); + if (!sInstance) { + return false; + } + + return sInstance->CanHold(aSize); +} + +/* static */ void +SurfaceCache::SurfaceAvailable(NotNull aProvider) +{ + StaticMutexAutoLock lock(sInstanceMutex); + if (!sInstance) { + return; + } + + sInstance->SurfaceAvailable(aProvider, lock); +} + +/* static */ void +SurfaceCache::LockImage(const ImageKey aImageKey) +{ + StaticMutexAutoLock lock(sInstanceMutex); + if (sInstance) { + return sInstance->LockImage(aImageKey); + } +} + +/* static */ void +SurfaceCache::UnlockImage(const ImageKey aImageKey) +{ + StaticMutexAutoLock lock(sInstanceMutex); + if (sInstance) { + return sInstance->UnlockImage(aImageKey, lock); + } +} + +/* static */ void +SurfaceCache::UnlockEntries(const ImageKey aImageKey) +{ + StaticMutexAutoLock lock(sInstanceMutex); + if (sInstance) { + return sInstance->UnlockEntries(aImageKey, lock); + } +} + +/* static */ void +SurfaceCache::RemoveImage(const ImageKey aImageKey) +{ + StaticMutexAutoLock lock(sInstanceMutex); + if (sInstance) { + sInstance->RemoveImage(aImageKey, lock); + } +} + +/* static */ void +SurfaceCache::DiscardAll() +{ + StaticMutexAutoLock lock(sInstanceMutex); + if (sInstance) { + sInstance->DiscardAll(lock); + } +} + +/* static */ void +SurfaceCache::CollectSizeOfSurfaces(const ImageKey aImageKey, + nsTArray& aCounters, + MallocSizeOf aMallocSizeOf) +{ + StaticMutexAutoLock lock(sInstanceMutex); + if (!sInstance) { + return; + } + + return sInstance->CollectSizeOfSurfaces(aImageKey, aCounters, aMallocSizeOf); +} + +/* static */ size_t +SurfaceCache::MaximumCapacity() +{ + StaticMutexAutoLock lock(sInstanceMutex); + if (!sInstance) { + return 0; + } + + return sInstance->MaximumCapacity(); +} + +} // namespace image +} // namespace mozilla diff --git a/image/SurfaceCache.h b/image/SurfaceCache.h new file mode 100644 index 000000000..e0c22c999 --- /dev/null +++ b/image/SurfaceCache.h @@ -0,0 +1,427 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * SurfaceCache is a service for caching temporary surfaces and decoded image + * data in imagelib. + */ + +#ifndef mozilla_image_SurfaceCache_h +#define mozilla_image_SurfaceCache_h + +#include "mozilla/Maybe.h" // for Maybe +#include "mozilla/NotNull.h" +#include "mozilla/MemoryReporting.h" // for MallocSizeOf +#include "mozilla/HashFunctions.h" // for HashGeneric and AddToHash +#include "gfx2DGlue.h" +#include "gfxPoint.h" // for gfxSize +#include "nsCOMPtr.h" // for already_AddRefed +#include "mozilla/gfx/Point.h" // for mozilla::gfx::IntSize +#include "mozilla/gfx/2D.h" // for SourceSurface +#include "PlaybackType.h" +#include "SurfaceFlags.h" +#include "SVGImageContext.h" // for SVGImageContext + +namespace mozilla { +namespace image { + +class Image; +class ISurfaceProvider; +class LookupResult; +class SurfaceCacheImpl; +struct SurfaceMemoryCounter; + +/* + * ImageKey contains the information we need to look up all SurfaceCache entries + * for a particular image. + */ +typedef Image* ImageKey; + +/* + * SurfaceKey contains the information we need to look up a specific + * SurfaceCache entry. Together with an ImageKey, this uniquely identifies the + * surface. + * + * Callers should construct a SurfaceKey using the appropriate helper function + * for their image type - either RasterSurfaceKey or VectorSurfaceKey. + */ +class SurfaceKey +{ + typedef gfx::IntSize IntSize; + +public: + bool operator==(const SurfaceKey& aOther) const + { + return aOther.mSize == mSize && + aOther.mSVGContext == mSVGContext && + aOther.mPlayback == mPlayback && + aOther.mFlags == mFlags; + } + + uint32_t Hash() const + { + uint32_t hash = HashGeneric(mSize.width, mSize.height); + hash = AddToHash(hash, mSVGContext.map(HashSIC).valueOr(0)); + hash = AddToHash(hash, uint8_t(mPlayback), uint32_t(mFlags)); + return hash; + } + + const IntSize& Size() const { return mSize; } + Maybe SVGContext() const { return mSVGContext; } + PlaybackType Playback() const { return mPlayback; } + SurfaceFlags Flags() const { return mFlags; } + +private: + SurfaceKey(const IntSize& aSize, + const Maybe& aSVGContext, + PlaybackType aPlayback, + SurfaceFlags aFlags) + : mSize(aSize) + , mSVGContext(aSVGContext) + , mPlayback(aPlayback) + , mFlags(aFlags) + { } + + static uint32_t HashSIC(const SVGImageContext& aSIC) { + return aSIC.Hash(); + } + + friend SurfaceKey RasterSurfaceKey(const IntSize&, SurfaceFlags, PlaybackType); + friend SurfaceKey VectorSurfaceKey(const IntSize&, + const Maybe&); + + IntSize mSize; + Maybe mSVGContext; + PlaybackType mPlayback; + SurfaceFlags mFlags; +}; + +inline SurfaceKey +RasterSurfaceKey(const gfx::IntSize& aSize, + SurfaceFlags aFlags, + PlaybackType aPlayback) +{ + return SurfaceKey(aSize, Nothing(), aPlayback, aFlags); +} + +inline SurfaceKey +VectorSurfaceKey(const gfx::IntSize& aSize, + const Maybe& aSVGContext) +{ + // We don't care about aFlags for VectorImage because none of the flags we + // have right now influence VectorImage's rendering. If we add a new flag that + // *does* affect how a VectorImage renders, we'll have to change this. + // Similarly, we don't accept a PlaybackType parameter because we don't + // currently cache frames of animated SVG images. + return SurfaceKey(aSize, aSVGContext, PlaybackType::eStatic, + DefaultSurfaceFlags()); +} + + +/** + * AvailabilityState is used to track whether an ISurfaceProvider has a surface + * available or is just a placeholder. + * + * To ensure that availability changes are atomic (and especially that internal + * SurfaceCache code doesn't have to deal with asynchronous availability + * changes), an ISurfaceProvider which starts as a placeholder can only reveal + * the fact that it now has a surface available via a call to + * SurfaceCache::SurfaceAvailable(). + */ +class AvailabilityState +{ +public: + static AvailabilityState StartAvailable() { return AvailabilityState(true); } + static AvailabilityState StartAsPlaceholder() { return AvailabilityState(false); } + + bool IsAvailable() const { return mIsAvailable; } + bool IsPlaceholder() const { return !mIsAvailable; } + +private: + friend class SurfaceCacheImpl; + + explicit AvailabilityState(bool aIsAvailable) : mIsAvailable(aIsAvailable) { } + + void SetAvailable() { mIsAvailable = true; } + + bool mIsAvailable; +}; + +enum class InsertOutcome : uint8_t { + SUCCESS, // Success (but see Insert documentation). + FAILURE, // Couldn't insert (e.g., for capacity reasons). + FAILURE_ALREADY_PRESENT // A surface with the same key is already present. +}; + +/** + * SurfaceCache is an ImageLib-global service that allows caching of decoded + * image surfaces, temporary surfaces (e.g. for caching rotated or clipped + * versions of images), or dynamically generated surfaces (e.g. for animations). + * SurfaceCache entries normally expire from the cache automatically if they go + * too long without being accessed. + * + * Because SurfaceCache must support both normal surfaces and dynamically + * generated surfaces, it does not actually hold surfaces directly. Instead, it + * holds ISurfaceProvider objects which can provide access to a surface when + * requested; SurfaceCache doesn't care about the details of how this is + * accomplished. + * + * Sometime it's useful to temporarily prevent entries from expiring from the + * cache. This is most often because losing the data could harm the user + * experience (for example, we often don't want to allow surfaces that are + * currently visible to expire) or because it's not possible to rematerialize + * the surface. SurfaceCache supports this through the use of image locking; see + * the comments for Insert() and LockImage() for more details. + * + * Any image which stores surfaces in the SurfaceCache *must* ensure that it + * calls RemoveImage() before it is destroyed. See the comments for + * RemoveImage() for more details. + */ +struct SurfaceCache +{ + typedef gfx::IntSize IntSize; + + /** + * Initialize static data. Called during imagelib module initialization. + */ + static void Initialize(); + + /** + * Release static data. Called during imagelib module shutdown. + */ + static void Shutdown(); + + /** + * Looks up the requested cache entry and returns a drawable reference to its + * associated surface. + * + * If the image associated with the cache entry is locked, then the entry will + * be locked before it is returned. + * + * If a matching ISurfaceProvider was found in the cache, but SurfaceCache + * couldn't obtain a surface from it (e.g. because it had stored its surface + * in a volatile buffer which was discarded by the OS) then it is + * automatically removed from the cache and an empty LookupResult is returned. + * Note that this will never happen to ISurfaceProviders associated with a + * locked image; SurfaceCache tells such ISurfaceProviders to keep a strong + * references to their data internally. + * + * @param aImageKey Key data identifying which image the cache entry + * belongs to. + * @param aSurfaceKey Key data which uniquely identifies the requested + * cache entry. + * @return a LookupResult which will contain a DrawableSurface + * if the cache entry was found. + */ + static LookupResult Lookup(const ImageKey aImageKey, + const SurfaceKey& aSurfaceKey); + + /** + * Looks up the best matching cache entry and returns a drawable reference to + * its associated surface. + * + * The result may vary from the requested cache entry only in terms of size. + * + * @param aImageKey Key data identifying which image the cache entry + * belongs to. + * @param aSurfaceKey Key data which uniquely identifies the requested + * cache entry. + * @return a LookupResult which will contain a DrawableSurface + * if a cache entry similar to the one the caller + * requested could be found. Callers can use + * LookupResult::IsExactMatch() to check whether the + * returned surface exactly matches @aSurfaceKey. + */ + static LookupResult LookupBestMatch(const ImageKey aImageKey, + const SurfaceKey& aSurfaceKey); + + /** + * Insert an ISurfaceProvider into the cache. If an entry with the same + * ImageKey and SurfaceKey is already in the cache, Insert returns + * FAILURE_ALREADY_PRESENT. If a matching placeholder is already present, it + * is replaced. + * + * Cache entries will never expire as long as they remain locked, but if they + * become unlocked, they can expire either because the SurfaceCache runs out + * of capacity or because they've gone too long without being used. When it + * is first inserted, a cache entry is locked if its associated image is + * locked. When that image is later unlocked, the cache entry becomes + * unlocked too. To become locked again at that point, two things must happen: + * the image must become locked again (via LockImage()), and the cache entry + * must be touched again (via one of the Lookup() functions). + * + * All of this means that a very particular procedure has to be followed for + * cache entries which cannot be rematerialized. First, they must be inserted + * *after* the image is locked with LockImage(); if you use the other order, + * the cache entry might expire before LockImage() gets called or before the + * entry is touched again by Lookup(). Second, the image they are associated + * with must never be unlocked. + * + * If a cache entry cannot be rematerialized, it may be important to know + * whether it was inserted into the cache successfully. Insert() returns + * FAILURE if it failed to insert the cache entry, which could happen because + * of capacity reasons, or because it was already freed by the OS. If the + * cache entry isn't associated with a locked image, checking for SUCCESS or + * FAILURE is useless: the entry might expire immediately after being + * inserted, even though Insert() returned SUCCESS. Thus, many callers do not + * need to check the result of Insert() at all. + * + * @param aProvider The new cache entry to insert into the cache. + * @return SUCCESS if the cache entry was inserted successfully. (But see above + * for more information about when you should check this.) + * FAILURE if the cache entry could not be inserted, e.g. for capacity + * reasons. (But see above for more information about when you + * should check this.) + * FAILURE_ALREADY_PRESENT if an entry with the same ImageKey and + * SurfaceKey already exists in the cache. + */ + static InsertOutcome Insert(NotNull aProvider); + + /** + * Mark the cache entry @aProvider as having an available surface. This turns + * a placeholder cache entry into a normal cache entry. The cache entry + * becomes locked if the associated image is locked; otherwise, it starts in + * the unlocked state. + * + * If the cache entry containing @aProvider has already been evicted from the + * surface cache, this function has no effect. + * + * It's illegal to call this function if @aProvider is not a placeholder; by + * definition, non-placeholder ISurfaceProviders should have a surface + * available already. + * + * @param aProvider The cache entry that now has a surface available. + */ + static void SurfaceAvailable(NotNull aProvider); + + /** + * Checks if a surface of a given size could possibly be stored in the cache. + * If CanHold() returns false, Insert() will always fail to insert the + * surface, but the inverse is not true: Insert() may take more information + * into account than just image size when deciding whether to cache the + * surface, so Insert() may still fail even if CanHold() returns true. + * + * Use CanHold() to avoid the need to create a temporary surface when we know + * for sure the cache can't hold it. + * + * @param aSize The dimensions of a surface in pixels. + * @param aBytesPerPixel How many bytes each pixel of the surface requires. + * Defaults to 4, which is appropriate for RGBA or RGBX + * images. + * + * @return false if the surface cache can't hold a surface of that size. + */ + static bool CanHold(const IntSize& aSize, uint32_t aBytesPerPixel = 4); + static bool CanHold(size_t aSize); + + /** + * Locks an image. Any of the image's cache entries which are either inserted + * or accessed while the image is locked will not expire. + * + * Locking an image does not automatically lock that image's existing cache + * entries. A call to LockImage() guarantees that entries which are inserted + * afterward will not expire before the next call to UnlockImage() or + * UnlockSurfaces() for that image. Cache entries that are accessed via + * Lookup() or LookupBestMatch() after a LockImage() call will also not expire + * until the next UnlockImage() or UnlockSurfaces() call for that image. Any + * other cache entries owned by the image may expire at any time. + * + * All of an image's cache entries are removed by RemoveImage(), whether the + * image is locked or not. + * + * It's safe to call LockImage() on an image that's already locked; this has + * no effect. + * + * You must always unlock any image you lock. You may do this explicitly by + * calling UnlockImage(), or implicitly by calling RemoveImage(). Since you're + * required to call RemoveImage() when you destroy an image, this doesn't + * impose any additional requirements, but it's preferable to call + * UnlockImage() earlier if it's possible. + * + * @param aImageKey The image to lock. + */ + static void LockImage(const ImageKey aImageKey); + + /** + * Unlocks an image, allowing any of its cache entries to expire at any time. + * + * It's OK to call UnlockImage() on an image that's already unlocked; this has + * no effect. + * + * @param aImageKey The image to unlock. + */ + static void UnlockImage(const ImageKey aImageKey); + + /** + * Unlocks the existing cache entries of an image, allowing them to expire at + * any time. + * + * This does not unlock the image itself, so accessing the cache entries via + * Lookup() or LookupBestMatch() will lock them again, and prevent them from + * expiring. + * + * This is intended to be used in situations where it's no longer clear that + * all of the cache entries owned by an image are needed. Calling + * UnlockSurfaces() and then taking some action that will cause Lookup() to + * touch any cache entries that are still useful will permit the remaining + * entries to expire from the cache. + * + * If the image is unlocked, this has no effect. + * + * @param aImageKey The image which should have its existing cache entries + * unlocked. + */ + static void UnlockEntries(const ImageKey aImageKey); + + /** + * Removes all cache entries (including placeholders) associated with the + * given image from the cache. If the image is locked, it is automatically + * unlocked. + * + * This MUST be called, at a minimum, when an Image which could be storing + * entries in the surface cache is destroyed. If another image were allocated + * at the same address it could result in subtle, difficult-to-reproduce bugs. + * + * @param aImageKey The image which should be removed from the cache. + */ + static void RemoveImage(const ImageKey aImageKey); + + /** + * Evicts all evictable entries from the cache. + * + * All entries are evictable except for entries associated with locked images. + * Non-evictable entries can only be removed by RemoveImage(). + */ + static void DiscardAll(); + + /** + * Collects an accounting of the surfaces contained in the SurfaceCache for + * the given image, along with their size and various other metadata. + * + * This is intended for use with memory reporting. + * + * @param aImageKey The image to report memory usage for. + * @param aCounters An array into which the report for each surface will + * be written. + * @param aMallocSizeOf A fallback malloc memory reporting function. + */ + static void CollectSizeOfSurfaces(const ImageKey aImageKey, + nsTArray& aCounters, + MallocSizeOf aMallocSizeOf); + + /** + * @return maximum capacity of the SurfaceCache in bytes. This is only exposed + * for use by tests; normal code should use CanHold() instead. + */ + static size_t MaximumCapacity(); + +private: + virtual ~SurfaceCache() = 0; // Forbid instantiation. +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_SurfaceCache_h diff --git a/image/SurfaceCacheUtils.cpp b/image/SurfaceCacheUtils.cpp new file mode 100644 index 000000000..f97674c4f --- /dev/null +++ b/image/SurfaceCacheUtils.cpp @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SurfaceCacheUtils.h" + +#include "SurfaceCache.h" + +namespace mozilla { +namespace image { + +/* static */ void +SurfaceCacheUtils::DiscardAll() +{ + SurfaceCache::DiscardAll(); +} + +} // namespace image +} // namespace mozilla diff --git a/image/SurfaceCacheUtils.h b/image/SurfaceCacheUtils.h new file mode 100644 index 000000000..fcce7a334 --- /dev/null +++ b/image/SurfaceCacheUtils.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_SurfaceCacheUtils_h +#define mozilla_image_SurfaceCacheUtils_h + +/** + * SurfaceCacheUtils provides an ImageLib-external API to interact with + * ImageLib's SurfaceCache. + */ + +namespace mozilla { +namespace image { + +class SurfaceCacheUtils +{ +public: + /** + * Evicts all evictable entries from the surface cache. + * + * See the documentation for SurfaceCache::DiscardAll() for the details. + */ + static void DiscardAll(); + +private: + virtual ~SurfaceCacheUtils() = 0; // Forbid instantiation. +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_SurfaceCacheUtils_h diff --git a/image/SurfaceFilters.h b/image/SurfaceFilters.h new file mode 100644 index 000000000..1885661b9 --- /dev/null +++ b/image/SurfaceFilters.h @@ -0,0 +1,893 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This header contains various SurfaceFilter implementations that apply + * transformations to image data, for usage with SurfacePipe. + */ + +#ifndef mozilla_image_SurfaceFilters_h +#define mozilla_image_SurfaceFilters_h + +#include +#include +#include + +#include "mozilla/Likely.h" +#include "mozilla/Maybe.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/gfx/2D.h" + +#include "DownscalingFilter.h" +#include "SurfaceCache.h" +#include "SurfacePipe.h" + +namespace mozilla { +namespace image { + +////////////////////////////////////////////////////////////////////////////// +// DeinterlacingFilter +////////////////////////////////////////////////////////////////////////////// + +template class DeinterlacingFilter; + +/** + * A configuration struct for DeinterlacingFilter. + * + * The 'PixelType' template parameter should be either uint32_t (for output to a + * SurfaceSink) or uint8_t (for output to a PalettedSurfaceSink). + */ +template +struct DeinterlacingConfig +{ + template using Filter = DeinterlacingFilter; + bool mProgressiveDisplay; /// If true, duplicate rows during deinterlacing + /// to make progressive display look better, at + /// the cost of some performance. +}; + +/** + * DeinterlacingFilter performs deinterlacing by reordering the rows that are + * written to it. + * + * The 'PixelType' template parameter should be either uint32_t (for output to a + * SurfaceSink) or uint8_t (for output to a PalettedSurfaceSink). + * + * The 'Next' template parameter specifies the next filter in the chain. + */ +template +class DeinterlacingFilter final : public SurfaceFilter +{ +public: + DeinterlacingFilter() + : mInputRow(0) + , mOutputRow(0) + , mPass(0) + , mProgressiveDisplay(true) + { } + + template + nsresult Configure(const DeinterlacingConfig& aConfig, Rest... aRest) + { + nsresult rv = mNext.Configure(aRest...); + if (NS_FAILED(rv)) { + return rv; + } + + if (sizeof(PixelType) == 1 && !mNext.IsValidPalettedPipe()) { + NS_WARNING("Paletted DeinterlacingFilter used with non-paletted pipe?"); + return NS_ERROR_INVALID_ARG; + } + if (sizeof(PixelType) == 4 && mNext.IsValidPalettedPipe()) { + NS_WARNING("Non-paletted DeinterlacingFilter used with paletted pipe?"); + return NS_ERROR_INVALID_ARG; + } + + gfx::IntSize outputSize = mNext.InputSize(); + mProgressiveDisplay = aConfig.mProgressiveDisplay; + + const uint32_t bufferSize = outputSize.width * + outputSize.height * + sizeof(PixelType); + + // Use the size of the SurfaceCache as a heuristic to avoid gigantic + // allocations. Even if DownscalingFilter allowed us to allocate space for + // the output image, the deinterlacing buffer may still be too big, and + // fallible allocation won't always save us in the presence of overcommit. + if (!SurfaceCache::CanHold(bufferSize)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // Allocate the buffer, which contains deinterlaced scanlines of the image. + // The buffer is necessary so that we can output rows which have already + // been deinterlaced again on subsequent passes. Since a later stage in the + // pipeline may be transforming the rows it receives (for example, by + // downscaling them), the rows may no longer exist in their original form on + // the surface itself. + mBuffer.reset(new (fallible) uint8_t[bufferSize]); + if (MOZ_UNLIKELY(!mBuffer)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // Clear the buffer to avoid writing uninitialized memory to the output. + memset(mBuffer.get(), 0, bufferSize); + + ConfigureFilter(outputSize, sizeof(PixelType)); + return NS_OK; + } + + bool IsValidPalettedPipe() const override + { + return sizeof(PixelType) == 1 && mNext.IsValidPalettedPipe(); + } + + Maybe TakeInvalidRect() override + { + return mNext.TakeInvalidRect(); + } + +protected: + uint8_t* DoResetToFirstRow() override + { + mNext.ResetToFirstRow(); + mPass = 0; + mInputRow = 0; + mOutputRow = InterlaceOffset(mPass); + return GetRowPointer(mOutputRow); + } + + uint8_t* DoAdvanceRow() override + { + if (mPass >= 4) { + return nullptr; // We already finished all passes. + } + if (mInputRow >= InputSize().height) { + return nullptr; // We already got all the input rows we expect. + } + + // Duplicate from the first Haeberli row to the remaining Haeberli rows + // within the buffer. + DuplicateRows(HaeberliOutputStartRow(mPass, mProgressiveDisplay, mOutputRow), + HaeberliOutputUntilRow(mPass, mProgressiveDisplay, + InputSize(), mOutputRow)); + + // Write the current set of Haeberli rows (which contains the current row) + // to the next stage in the pipeline. + OutputRows(HaeberliOutputStartRow(mPass, mProgressiveDisplay, mOutputRow), + HaeberliOutputUntilRow(mPass, mProgressiveDisplay, + InputSize(), mOutputRow)); + + // Determine which output row the next input row corresponds to. + bool advancedPass = false; + uint32_t stride = InterlaceStride(mPass); + int32_t nextOutputRow = mOutputRow + stride; + while (nextOutputRow >= InputSize().height) { + // Copy any remaining rows from the buffer. + if (!advancedPass) { + OutputRows(HaeberliOutputUntilRow(mPass, mProgressiveDisplay, + InputSize(), mOutputRow), + InputSize().height); + } + + // We finished the current pass; advance to the next one. + mPass++; + if (mPass >= 4) { + return nullptr; // Finished all passes. + } + + // Tell the next pipeline stage that we're starting the next pass. + mNext.ResetToFirstRow(); + + // Update our state to reflect the pass change. + advancedPass = true; + stride = InterlaceStride(mPass); + nextOutputRow = InterlaceOffset(mPass); + } + + MOZ_ASSERT(nextOutputRow >= 0); + MOZ_ASSERT(nextOutputRow < InputSize().height); + + MOZ_ASSERT(HaeberliOutputStartRow(mPass, mProgressiveDisplay, + nextOutputRow) >= 0); + MOZ_ASSERT(HaeberliOutputStartRow(mPass, mProgressiveDisplay, + nextOutputRow) < InputSize().height); + MOZ_ASSERT(HaeberliOutputStartRow(mPass, mProgressiveDisplay, + nextOutputRow) <= nextOutputRow); + + MOZ_ASSERT(HaeberliOutputUntilRow(mPass, mProgressiveDisplay, + InputSize(), nextOutputRow) >= 0); + MOZ_ASSERT(HaeberliOutputUntilRow(mPass, mProgressiveDisplay, + InputSize(), nextOutputRow) + <= InputSize().height); + MOZ_ASSERT(HaeberliOutputUntilRow(mPass, mProgressiveDisplay, + InputSize(), nextOutputRow) + > nextOutputRow); + + int32_t nextHaeberliOutputRow = + HaeberliOutputStartRow(mPass, mProgressiveDisplay, nextOutputRow); + + // Copy rows from the buffer until we reach the desired output row. + if (advancedPass) { + OutputRows(0, nextHaeberliOutputRow); + } else { + OutputRows(HaeberliOutputUntilRow(mPass, mProgressiveDisplay, + InputSize(), mOutputRow), + nextHaeberliOutputRow); + } + + // Update our position within the buffer. + mInputRow++; + mOutputRow = nextOutputRow; + + // We'll actually write to the first Haeberli output row, then copy it until + // we reach the last Haeberli output row. The assertions above make sure + // this always includes mOutputRow. + return GetRowPointer(nextHaeberliOutputRow); + } + +private: + static uint32_t InterlaceOffset(uint32_t aPass) + { + MOZ_ASSERT(aPass < 4, "Invalid pass"); + static const uint8_t offset[] = { 0, 4, 2, 1 }; + return offset[aPass]; + } + + static uint32_t InterlaceStride(uint32_t aPass) + { + MOZ_ASSERT(aPass < 4, "Invalid pass"); + static const uint8_t stride[] = { 8, 8, 4, 2 }; + return stride[aPass]; + } + + static int32_t HaeberliOutputStartRow(uint32_t aPass, + bool aProgressiveDisplay, + int32_t aOutputRow) + { + MOZ_ASSERT(aPass < 4, "Invalid pass"); + static const uint8_t firstRowOffset[] = { 3, 1, 0, 0 }; + + if (aProgressiveDisplay) { + return std::max(aOutputRow - firstRowOffset[aPass], 0); + } else { + return aOutputRow; + } + } + + static int32_t HaeberliOutputUntilRow(uint32_t aPass, + bool aProgressiveDisplay, + const gfx::IntSize& aInputSize, + int32_t aOutputRow) + { + MOZ_ASSERT(aPass < 4, "Invalid pass"); + static const uint8_t lastRowOffset[] = { 4, 2, 1, 0 }; + + if (aProgressiveDisplay) { + return std::min(aOutputRow + lastRowOffset[aPass], + aInputSize.height - 1) + + 1; // Add one because this is an open interval on the right. + } else { + return aOutputRow + 1; + } + } + + void DuplicateRows(int32_t aStart, int32_t aUntil) + { + MOZ_ASSERT(aStart >= 0); + MOZ_ASSERT(aUntil >= 0); + + if (aUntil <= aStart || aStart >= InputSize().height) { + return; + } + + // The source row is the first row in the range. + const uint8_t* sourceRowPointer = GetRowPointer(aStart); + + // We duplicate the source row into each subsequent row in the range. + for (int32_t destRow = aStart + 1 ; destRow < aUntil ; ++destRow) { + uint8_t* destRowPointer = GetRowPointer(destRow); + memcpy(destRowPointer, sourceRowPointer, InputSize().width * sizeof(PixelType)); + } + } + + void OutputRows(int32_t aStart, int32_t aUntil) + { + MOZ_ASSERT(aStart >= 0); + MOZ_ASSERT(aUntil >= 0); + + if (aUntil <= aStart || aStart >= InputSize().height) { + return; + } + + for (int32_t rowToOutput = aStart; rowToOutput < aUntil; ++rowToOutput) { + mNext.WriteBuffer(reinterpret_cast(GetRowPointer(rowToOutput))); + } + } + + uint8_t* GetRowPointer(uint32_t aRow) const + { + uint32_t offset = aRow * InputSize().width * sizeof(PixelType); + MOZ_ASSERT(offset < InputSize().width * InputSize().height * sizeof(PixelType), + "Start of row is outside of image"); + MOZ_ASSERT(offset + InputSize().width * sizeof(PixelType) + <= InputSize().width * InputSize().height * sizeof(PixelType), + "End of row is outside of image"); + return mBuffer.get() + offset; + } + + Next mNext; /// The next SurfaceFilter in the chain. + + UniquePtr mBuffer; /// The buffer used to store reordered rows. + int32_t mInputRow; /// The current row we're reading. (0-indexed) + int32_t mOutputRow; /// The current row we're writing. (0-indexed) + uint8_t mPass; /// Which pass we're on. (0-indexed) + bool mProgressiveDisplay; /// If true, duplicate rows to optimize for + /// progressive display. +}; + + +////////////////////////////////////////////////////////////////////////////// +// RemoveFrameRectFilter +////////////////////////////////////////////////////////////////////////////// + +template class RemoveFrameRectFilter; + +/** + * A configuration struct for RemoveFrameRectFilter. + */ +struct RemoveFrameRectConfig +{ + template using Filter = RemoveFrameRectFilter; + gfx::IntRect mFrameRect; /// The surface subrect which contains data. +}; + +/** + * RemoveFrameRectFilter turns an image with a frame rect that does not match + * its logical size into an image with no frame rect. It does this by writing + * transparent pixels into any padding regions and throwing away excess data. + * + * The 'Next' template parameter specifies the next filter in the chain. + */ +template +class RemoveFrameRectFilter final : public SurfaceFilter +{ +public: + RemoveFrameRectFilter() + : mRow(0) + { } + + template + nsresult Configure(const RemoveFrameRectConfig& aConfig, Rest... aRest) + { + nsresult rv = mNext.Configure(aRest...); + if (NS_FAILED(rv)) { + return rv; + } + + if (mNext.IsValidPalettedPipe()) { + NS_WARNING("RemoveFrameRectFilter used with paletted pipe?"); + return NS_ERROR_INVALID_ARG; + } + + mFrameRect = mUnclampedFrameRect = aConfig.mFrameRect; + gfx::IntSize outputSize = mNext.InputSize(); + + // Forbid frame rects with negative size. + if (aConfig.mFrameRect.width < 0 || aConfig.mFrameRect.height < 0) { + return NS_ERROR_INVALID_ARG; + } + + // Clamp mFrameRect to the output size. + gfx::IntRect outputRect(0, 0, outputSize.width, outputSize.height); + mFrameRect = mFrameRect.Intersect(outputRect); + + // If there's no intersection, |mFrameRect| will be an empty rect positioned + // at the maximum of |inputRect|'s and |aFrameRect|'s coordinates, which is + // not what we want. Force it to (0, 0) in that case. + if (mFrameRect.IsEmpty()) { + mFrameRect.MoveTo(0, 0); + } + + // We don't need an intermediate buffer unless the unclamped frame rect + // width is larger than the clamped frame rect width. In that case, the + // caller will end up writing data that won't end up in the final image at + // all, and we'll need a buffer to give that data a place to go. + if (mFrameRect.width < mUnclampedFrameRect.width) { + mBuffer.reset(new (fallible) uint8_t[mUnclampedFrameRect.width * + sizeof(uint32_t)]); + if (MOZ_UNLIKELY(!mBuffer)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + memset(mBuffer.get(), 0, mUnclampedFrameRect.width * sizeof(uint32_t)); + } + + ConfigureFilter(mUnclampedFrameRect.Size(), sizeof(uint32_t)); + return NS_OK; + } + + Maybe TakeInvalidRect() override + { + return mNext.TakeInvalidRect(); + } + +protected: + uint8_t* DoResetToFirstRow() override + { + uint8_t* rowPtr = mNext.ResetToFirstRow(); + if (rowPtr == nullptr) { + mRow = mFrameRect.YMost(); + return nullptr; + } + + mRow = mUnclampedFrameRect.y; + + // Advance the next pipeline stage to the beginning of the frame rect, + // outputting blank rows. + if (mFrameRect.y > 0) { + for (int32_t rowToOutput = 0; rowToOutput < mFrameRect.y ; ++rowToOutput) { + mNext.WriteEmptyRow(); + } + } + + // We're at the beginning of the frame rect now, so return if we're either + // ready for input or we're already done. + rowPtr = mBuffer ? mBuffer.get() : mNext.CurrentRowPointer(); + if (!mFrameRect.IsEmpty() || rowPtr == nullptr) { + // Note that the pointer we're returning is for the next row we're + // actually going to write to, but we may discard writes before that point + // if mRow < mFrameRect.y. + return AdjustRowPointer(rowPtr); + } + + // We've finished the region specified by the frame rect, but the frame rect + // is empty, so we need to output the rest of the image immediately. Advance + // to the end of the next pipeline stage's buffer, outputting blank rows. + while (mNext.WriteEmptyRow() == WriteState::NEED_MORE_DATA) { } + + mRow = mFrameRect.YMost(); + return nullptr; // We're done. + } + + uint8_t* DoAdvanceRow() override + { + uint8_t* rowPtr = nullptr; + + const int32_t currentRow = mRow; + mRow++; + + if (currentRow < mFrameRect.y) { + // This row is outside of the frame rect, so just drop it on the floor. + rowPtr = mBuffer ? mBuffer.get() : mNext.CurrentRowPointer(); + return AdjustRowPointer(rowPtr); + } else if (currentRow >= mFrameRect.YMost()) { + NS_WARNING("RemoveFrameRectFilter: Advancing past end of frame rect"); + return nullptr; + } + + // If we had to buffer, copy the data. Otherwise, just advance the row. + if (mBuffer) { + // We write from the beginning of the buffer unless |mUnclampedFrameRect.x| + // is negative; if that's the case, we have to skip the portion of the + // unclamped frame rect that's outside the row. + uint32_t* source = reinterpret_cast(mBuffer.get()) - + std::min(mUnclampedFrameRect.x, 0); + + // We write |mFrameRect.width| columns starting at |mFrameRect.x|; we've + // already clamped these values to the size of the output, so we don't + // have to worry about bounds checking here (though WriteBuffer() will do + // it for us in any case). + WriteState state = mNext.WriteBuffer(source, mFrameRect.x, mFrameRect.width); + + rowPtr = state == WriteState::NEED_MORE_DATA ? mBuffer.get() + : nullptr; + } else { + rowPtr = mNext.AdvanceRow(); + } + + // If there's still more data coming or we're already done, just adjust the + // pointer and return. + if (mRow < mFrameRect.YMost() || rowPtr == nullptr) { + return AdjustRowPointer(rowPtr); + } + + // We've finished the region specified by the frame rect. Advance to the end + // of the next pipeline stage's buffer, outputting blank rows. + while (mNext.WriteEmptyRow() == WriteState::NEED_MORE_DATA) { } + + mRow = mFrameRect.YMost(); + return nullptr; // We're done. + } + +private: + uint8_t* AdjustRowPointer(uint8_t* aNextRowPointer) const + { + if (mBuffer) { + MOZ_ASSERT(aNextRowPointer == mBuffer.get() || aNextRowPointer == nullptr); + return aNextRowPointer; // No adjustment needed for an intermediate buffer. + } + + if (mFrameRect.IsEmpty() || + mRow >= mFrameRect.YMost() || + aNextRowPointer == nullptr) { + return nullptr; // Nothing left to write. + } + + return aNextRowPointer + mFrameRect.x * sizeof(uint32_t); + } + + Next mNext; /// The next SurfaceFilter in the chain. + + gfx::IntRect mFrameRect; /// The surface subrect which contains data, + /// clamped to the image size. + gfx::IntRect mUnclampedFrameRect; /// The frame rect before clamping. + UniquePtr mBuffer; /// The intermediate buffer, if one is + /// necessary because the frame rect width + /// is larger than the image's logical width. + int32_t mRow; /// The row in unclamped frame rect space + /// that we're currently writing. +}; + + +////////////////////////////////////////////////////////////////////////////// +// ADAM7InterpolatingFilter +////////////////////////////////////////////////////////////////////////////// + +template class ADAM7InterpolatingFilter; + +/** + * A configuration struct for ADAM7InterpolatingFilter. + */ +struct ADAM7InterpolatingConfig +{ + template using Filter = ADAM7InterpolatingFilter; +}; + +/** + * ADAM7InterpolatingFilter performs bilinear interpolation over an ADAM7 + * interlaced image. + * + * ADAM7 breaks up the image into 8x8 blocks. On each of the 7 passes, a new set + * of pixels in each block receives their final values, according to the + * following pattern: + * + * 1 6 4 6 2 6 4 6 + * 7 7 7 7 7 7 7 7 + * 5 6 5 6 5 6 5 6 + * 7 7 7 7 7 7 7 7 + * 3 6 4 6 3 6 4 6 + * 7 7 7 7 7 7 7 7 + * 5 6 5 6 5 6 5 6 + * 7 7 7 7 7 7 7 7 + * + * When rendering the pixels that have not yet received their final values, we + * can get much better intermediate results if we interpolate between + * the pixels we *have* gotten so far. This filter performs bilinear + * interpolation by first performing linear interpolation horizontally for each + * "important" row (which we'll define as a row that has received any pixels + * with final values at all) and then performing linear interpolation vertically + * to produce pixel values for rows which aren't important on the current pass. + * + * Note that this filter totally ignores the data which is written to rows which + * aren't important on the current pass! It's fine to write nothing at all for + * these rows, although doing so won't cause any harm. + * + * XXX(seth): In bug 1280552 we'll add a SIMD implementation for this filter. + * + * The 'Next' template parameter specifies the next filter in the chain. + */ +template +class ADAM7InterpolatingFilter final : public SurfaceFilter +{ +public: + ADAM7InterpolatingFilter() + : mPass(0) // The current pass, in the range 1..7. Starts at 0 so that + // DoResetToFirstRow() doesn't have to special case the first pass. + , mRow(0) + { } + + template + nsresult Configure(const ADAM7InterpolatingConfig& aConfig, Rest... aRest) + { + nsresult rv = mNext.Configure(aRest...); + if (NS_FAILED(rv)) { + return rv; + } + + if (mNext.IsValidPalettedPipe()) { + NS_WARNING("ADAM7InterpolatingFilter used with paletted pipe?"); + return NS_ERROR_INVALID_ARG; + } + + // We have two intermediate buffers, one for the previous row with final + // pixel values and one for the row that the previous filter in the chain is + // currently writing to. + size_t inputWidthInBytes = mNext.InputSize().width * sizeof(uint32_t); + mPreviousRow.reset(new (fallible) uint8_t[inputWidthInBytes]); + if (MOZ_UNLIKELY(!mPreviousRow)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + mCurrentRow.reset(new (fallible) uint8_t[inputWidthInBytes]); + if (MOZ_UNLIKELY(!mCurrentRow)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + memset(mPreviousRow.get(), 0, inputWidthInBytes); + memset(mCurrentRow.get(), 0, inputWidthInBytes); + + ConfigureFilter(mNext.InputSize(), sizeof(uint32_t)); + return NS_OK; + } + + Maybe TakeInvalidRect() override + { + return mNext.TakeInvalidRect(); + } + +protected: + uint8_t* DoResetToFirstRow() override + { + mRow = 0; + mPass = std::min(mPass + 1, 7); + + uint8_t* rowPtr = mNext.ResetToFirstRow(); + if (mPass == 7) { + // Short circuit this filter on the final pass, since all pixels have + // their final values at that point. + return rowPtr; + } + + return mCurrentRow.get(); + } + + uint8_t* DoAdvanceRow() override + { + MOZ_ASSERT(0 < mPass && mPass <= 7, "Invalid pass"); + + int32_t currentRow = mRow; + ++mRow; + + if (mPass == 7) { + // On the final pass we short circuit this filter totally. + return mNext.AdvanceRow(); + } + + const int32_t lastImportantRow = LastImportantRow(InputSize().height, mPass); + if (currentRow > lastImportantRow) { + return nullptr; // This pass is already complete. + } + + if (!IsImportantRow(currentRow, mPass)) { + // We just ignore whatever the caller gives us for these rows. We'll + // interpolate them in later. + return mCurrentRow.get(); + } + + // This is an important row. We need to perform horizontal interpolation for + // these rows. + InterpolateHorizontally(mCurrentRow.get(), InputSize().width, mPass); + + // Interpolate vertically between the previous important row and the current + // important row. We skip this if the current row is 0 (which is always an + // important row), because in that case there is no previous important row + // to interpolate with. + if (currentRow != 0) { + InterpolateVertically(mPreviousRow.get(), mCurrentRow.get(), mPass, mNext); + } + + // Write out the current row itself, which, being an important row, does not + // need vertical interpolation. + uint32_t* currentRowAsPixels = reinterpret_cast(mCurrentRow.get()); + mNext.WriteBuffer(currentRowAsPixels); + + if (currentRow == lastImportantRow) { + // This is the last important row, which completes this pass. Note that + // for very small images, this may be the first row! Since there won't be + // another important row, there's nothing to interpolate with vertically, + // so we just duplicate this row until the end of the image. + while (mNext.WriteBuffer(currentRowAsPixels) == WriteState::NEED_MORE_DATA) { } + + // All of the remaining rows in the image were determined above, so we're done. + return nullptr; + } + + // The current row is now the previous important row; save it. + Swap(mPreviousRow, mCurrentRow); + + MOZ_ASSERT(mRow < InputSize().height, "Reached the end of the surface without " + "hitting the last important row?"); + + return mCurrentRow.get(); + } + +private: + static void InterpolateVertically(uint8_t* aPreviousRow, + uint8_t* aCurrentRow, + uint8_t aPass, + SurfaceFilter& aNext) + { + const float* weights = InterpolationWeights(ImportantRowStride(aPass)); + + // We need to interpolate vertically to generate the rows between the + // previous important row and the next one. Recall that important rows are + // rows which contain at least some final pixels; see + // InterpolateHorizontally() for some additional explanation as to what that + // means. Note that we've already written out the previous important row, so + // we start the iteration at 1. + for (int32_t outRow = 1; outRow < ImportantRowStride(aPass); ++outRow) { + const float weight = weights[outRow]; + + // We iterate through the previous and current important row every time we + // write out an interpolated row, so we need to copy the pointers. + uint8_t* prevRowBytes = aPreviousRow; + uint8_t* currRowBytes = aCurrentRow; + + // Write out the interpolated pixels. Interpolation is componentwise. + aNext.template WritePixelsToRow([&]{ + uint32_t pixel = 0; + auto* component = reinterpret_cast(&pixel); + *component++ = InterpolateByte(*prevRowBytes++, *currRowBytes++, weight); + *component++ = InterpolateByte(*prevRowBytes++, *currRowBytes++, weight); + *component++ = InterpolateByte(*prevRowBytes++, *currRowBytes++, weight); + *component++ = InterpolateByte(*prevRowBytes++, *currRowBytes++, weight); + return AsVariant(pixel); + }); + } + } + + static void InterpolateHorizontally(uint8_t* aRow, int32_t aWidth, uint8_t aPass) + { + // Collect the data we'll need to perform horizontal interpolation. The + // terminology here bears some explanation: a "final pixel" is a pixel which + // has received its final value. On each pass, a new set of pixels receives + // their final value; see the diagram above of the 8x8 pattern that ADAM7 + // uses. Any pixel which hasn't received its final value on this pass + // derives its value from either horizontal or vertical interpolation + // instead. + const size_t finalPixelStride = FinalPixelStride(aPass); + const size_t finalPixelStrideBytes = finalPixelStride * sizeof(uint32_t); + const size_t lastFinalPixel = LastFinalPixel(aWidth, aPass); + const size_t lastFinalPixelBytes = lastFinalPixel * sizeof(uint32_t); + const float* weights = InterpolationWeights(finalPixelStride); + + // Interpolate blocks of pixels which lie between two final pixels. + // Horizontal interpolation is done in place, as we'll need the results + // later when we vertically interpolate. + for (size_t blockBytes = 0; + blockBytes < lastFinalPixelBytes; + blockBytes += finalPixelStrideBytes) { + uint8_t* finalPixelA = aRow + blockBytes; + uint8_t* finalPixelB = aRow + blockBytes + finalPixelStrideBytes; + + MOZ_ASSERT(finalPixelA < aRow + aWidth * sizeof(uint32_t), + "Running off end of buffer"); + MOZ_ASSERT(finalPixelB < aRow + aWidth * sizeof(uint32_t), + "Running off end of buffer"); + + // Interpolate the individual pixels componentwise. Note that we start + // iteration at 1 since we don't need to apply any interpolation to the + // first pixel in the block, which has its final value. + for (size_t pixelIndex = 1; pixelIndex < finalPixelStride; ++pixelIndex) { + const float weight = weights[pixelIndex]; + uint8_t* pixel = aRow + blockBytes + pixelIndex * sizeof(uint32_t); + + MOZ_ASSERT(pixel < aRow + aWidth * sizeof(uint32_t), "Running off end of buffer"); + + for (size_t component = 0; component < sizeof(uint32_t); ++component) { + pixel[component] = + InterpolateByte(finalPixelA[component], finalPixelB[component], weight); + } + } + } + + // For the pixels after the last final pixel in the row, there isn't a + // second final pixel to interpolate with, so just duplicate. + uint32_t* rowPixels = reinterpret_cast(aRow); + uint32_t pixelToDuplicate = rowPixels[lastFinalPixel]; + for (int32_t pixelIndex = lastFinalPixel + 1; + pixelIndex < aWidth; + ++pixelIndex) { + MOZ_ASSERT(pixelIndex < aWidth, "Running off end of buffer"); + rowPixels[pixelIndex] = pixelToDuplicate; + } + } + + static uint8_t InterpolateByte(uint8_t aByteA, uint8_t aByteB, float aWeight) + { + return uint8_t(aByteA * aWeight + aByteB * (1.0f - aWeight)); + } + + static int32_t ImportantRowStride(uint8_t aPass) + { + MOZ_ASSERT(0 < aPass && aPass <= 7, "Invalid pass"); + + // The stride between important rows for each pass, with a dummy value for + // the nonexistent pass 0. + static int32_t strides[] = { 1, 8, 8, 4, 4, 2, 2, 1 }; + + return strides[aPass]; + } + + static bool IsImportantRow(int32_t aRow, uint8_t aPass) + { + MOZ_ASSERT(aRow >= 0); + + // Whether the row is important comes down to divisibility by the stride for + // this pass, which is always a power of 2, so we can check using a mask. + int32_t mask = ImportantRowStride(aPass) - 1; + return (aRow & mask) == 0; + } + + static int32_t LastImportantRow(int32_t aHeight, uint8_t aPass) + { + MOZ_ASSERT(aHeight > 0); + + // We can find the last important row using the same mask trick as above. + int32_t lastRow = aHeight - 1; + int32_t mask = ImportantRowStride(aPass) - 1; + return lastRow - (lastRow & mask); + } + + static size_t FinalPixelStride(uint8_t aPass) + { + MOZ_ASSERT(0 < aPass && aPass <= 7, "Invalid pass"); + + // The stride between the final pixels in important rows for each pass, with + // a dummy value for the nonexistent pass 0. + static size_t strides[] = { 1, 8, 4, 4, 2, 2, 1, 1 }; + + return strides[aPass]; + } + + static size_t LastFinalPixel(int32_t aWidth, uint8_t aPass) + { + MOZ_ASSERT(aWidth >= 0); + + // Again, we can use the mask trick above to find the last important pixel. + int32_t lastColumn = aWidth - 1; + size_t mask = FinalPixelStride(aPass) - 1; + return lastColumn - (lastColumn & mask); + } + + static const float* InterpolationWeights(int32_t aStride) + { + // Precalculated interpolation weights. These are used to interpolate + // between final pixels or between important rows. Although no interpolation + // is actually applied to the previous final pixel or important row value, + // the arrays still start with 1.0f, which is always skipped, primarily + // because otherwise |stride1Weights| would have zero elements. + static float stride8Weights[] = + { 1.0f, 7 / 8.0f, 6 / 8.0f, 5 / 8.0f, 4 / 8.0f, 3 / 8.0f, 2 / 8.0f, 1 / 8.0f }; + static float stride4Weights[] = { 1.0f, 3 / 4.0f, 2 / 4.0f, 1 / 4.0f }; + static float stride2Weights[] = { 1.0f, 1 / 2.0f }; + static float stride1Weights[] = { 1.0f }; + + switch (aStride) { + case 8: return stride8Weights; + case 4: return stride4Weights; + case 2: return stride2Weights; + case 1: return stride1Weights; + default: MOZ_CRASH(); + } + } + + Next mNext; /// The next SurfaceFilter in the chain. + + UniquePtr mPreviousRow; /// The last important row (i.e., row with + /// final pixel values) that got written to. + UniquePtr mCurrentRow; /// The row that's being written to right + /// now. + uint8_t mPass; /// Which ADAM7 pass we're on. Valid passes + /// are 1..7 during processing and 0 prior + /// to configuraiton. + int32_t mRow; /// The row we're currently reading. +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_SurfaceFilters_h diff --git a/image/SurfaceFlags.h b/image/SurfaceFlags.h new file mode 100644 index 000000000..1a0000542 --- /dev/null +++ b/image/SurfaceFlags.h @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_SurfaceFlags_h +#define mozilla_image_SurfaceFlags_h + +#include "imgIContainer.h" +#include "mozilla/TypedEnumBits.h" + +namespace mozilla { +namespace image { + +/** + * Flags that change the output a decoder generates. Because different + * combinations of these flags result in logically different surfaces, these + * flags must be taken into account in SurfaceCache lookups. + */ +enum class SurfaceFlags : uint8_t +{ + NO_PREMULTIPLY_ALPHA = 1 << 0, + NO_COLORSPACE_CONVERSION = 1 << 1 +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SurfaceFlags) + +/** + * @return the default set of surface flags. + */ +inline SurfaceFlags +DefaultSurfaceFlags() +{ + return SurfaceFlags(); +} + +/** + * Given a set of imgIContainer FLAG_* flags, returns a set of SurfaceFlags with + * the corresponding flags set. + */ +inline SurfaceFlags +ToSurfaceFlags(uint32_t aFlags) +{ + SurfaceFlags flags = DefaultSurfaceFlags(); + if (aFlags & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA) { + flags |= SurfaceFlags::NO_PREMULTIPLY_ALPHA; + } + if (aFlags & imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION) { + flags |= SurfaceFlags::NO_COLORSPACE_CONVERSION; + } + return flags; +} + +/** + * Given a set of SurfaceFlags, returns a set of imgIContainer FLAG_* flags with + * the corresponding flags set. + */ +inline uint32_t +FromSurfaceFlags(SurfaceFlags aFlags) +{ + uint32_t flags = imgIContainer::DECODE_FLAGS_DEFAULT; + if (aFlags & SurfaceFlags::NO_PREMULTIPLY_ALPHA) { + flags |= imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA; + } + if (aFlags & SurfaceFlags::NO_COLORSPACE_CONVERSION) { + flags |= imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION; + } + return flags; +} + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_SurfaceFlags_h diff --git a/image/SurfacePipe.cpp b/image/SurfacePipe.cpp new file mode 100644 index 000000000..5c306144a --- /dev/null +++ b/image/SurfacePipe.cpp @@ -0,0 +1,199 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SurfacePipe.h" + +#include + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/DebugOnly.h" +#include "Decoder.h" + +namespace mozilla { +namespace image { + +using namespace gfx; + +using std::min; + +/* static */ UniquePtr NullSurfaceSink::sSingleton; + +/* static */ NullSurfaceSink* +NullSurfaceSink::Singleton() +{ + if (!sSingleton) { + MOZ_ASSERT(NS_IsMainThread()); + sSingleton = MakeUnique(); + ClearOnShutdown(&sSingleton); + + DebugOnly rv = sSingleton->Configure(NullSurfaceConfig { }); + MOZ_ASSERT(NS_SUCCEEDED(rv), "Couldn't configure a NullSurfaceSink?"); + } + + return sSingleton.get(); +} + +nsresult +NullSurfaceSink::Configure(const NullSurfaceConfig& aConfig) +{ + // Note that the choice of uint32_t as the pixel size here is more or less + // arbitrary, since you cannot write to a NullSurfaceSink anyway, but uint32_t + // is a natural choice since most SurfacePipes will be for BGRA/BGRX surfaces. + ConfigureFilter(IntSize(), sizeof(uint32_t)); + return NS_OK; +} + +Maybe +AbstractSurfaceSink::TakeInvalidRect() +{ + if (mInvalidRect.IsEmpty()) { + return Nothing(); + } + + SurfaceInvalidRect invalidRect; + invalidRect.mInputSpaceRect = invalidRect.mOutputSpaceRect = mInvalidRect; + + // Forget about the invalid rect we're returning. + mInvalidRect = IntRect(); + + return Some(invalidRect); +} + +uint8_t* +AbstractSurfaceSink::DoResetToFirstRow() +{ + mRow = 0; + return GetRowPointer(); +} + +uint8_t* +AbstractSurfaceSink::DoAdvanceRow() +{ + if (mRow >= uint32_t(InputSize().height)) { + return nullptr; + } + + // If we're vertically flipping the output, we need to flip the invalid rect. Since we're + // dealing with an axis-aligned rect, only the y coordinate needs to change. + int32_t invalidY = mFlipVertically + ? InputSize().height - (mRow + 1) + : mRow; + mInvalidRect.UnionRect(mInvalidRect, + IntRect(0, invalidY, InputSize().width, 1)); + + mRow = min(uint32_t(InputSize().height), mRow + 1); + + return mRow < uint32_t(InputSize().height) ? GetRowPointer() + : nullptr; +} + +nsresult +SurfaceSink::Configure(const SurfaceConfig& aConfig) +{ + // For non-paletted surfaces, the surface size is just the output size. + IntSize surfaceSize = aConfig.mOutputSize; + + // Non-paletted surfaces should not have frame rects, so we just pass + // AllocateFrame() a frame rect which covers the entire surface. + IntRect frameRect(0, 0, surfaceSize.width, surfaceSize.height); + + // Allocate the frame. + // XXX(seth): Once every Decoder subclass uses SurfacePipe, we probably want + // to allocate the frame directly here and get rid of Decoder::AllocateFrame + // altogether. + nsresult rv = aConfig.mDecoder->AllocateFrame(aConfig.mFrameNum, + surfaceSize, + frameRect, + aConfig.mFormat); + if (NS_FAILED(rv)) { + return rv; + } + + mImageData = aConfig.mDecoder->mImageData; + mImageDataLength = aConfig.mDecoder->mImageDataLength; + mFlipVertically = aConfig.mFlipVertically; + + MOZ_ASSERT(mImageData); + MOZ_ASSERT(mImageDataLength == + uint32_t(surfaceSize.width * surfaceSize.height * sizeof(uint32_t))); + + ConfigureFilter(surfaceSize, sizeof(uint32_t)); + return NS_OK; +} + +uint8_t* +SurfaceSink::GetRowPointer() const +{ + // If we're flipping vertically, reverse the order in which we traverse the + // rows. + uint32_t row = mFlipVertically + ? InputSize().height - (mRow + 1) + : mRow; + + uint8_t* rowPtr = mImageData + row * InputSize().width * sizeof(uint32_t); + + MOZ_ASSERT(rowPtr >= mImageData); + MOZ_ASSERT(rowPtr < mImageData + mImageDataLength); + MOZ_ASSERT(rowPtr + InputSize().width * sizeof(uint32_t) <= + mImageData + mImageDataLength); + + return rowPtr; +} + + +nsresult +PalettedSurfaceSink::Configure(const PalettedSurfaceConfig& aConfig) +{ + // For paletted surfaces, the surface size is the size of the frame rect. + IntSize surfaceSize = aConfig.mFrameRect.Size(); + + // Allocate the frame. + // XXX(seth): Once every Decoder subclass uses SurfacePipe, we probably want + // to allocate the frame directly here and get rid of Decoder::AllocateFrame + // altogether. + nsresult rv = aConfig.mDecoder->AllocateFrame(aConfig.mFrameNum, + aConfig.mOutputSize, + aConfig.mFrameRect, + aConfig.mFormat, + aConfig.mPaletteDepth); + if (NS_FAILED(rv)) { + return rv; + } + + mImageData = aConfig.mDecoder->mImageData; + mImageDataLength = aConfig.mDecoder->mImageDataLength; + mFlipVertically = aConfig.mFlipVertically; + mFrameRect = aConfig.mFrameRect; + + MOZ_ASSERT(mImageData); + MOZ_ASSERT(mImageDataLength == + uint32_t(mFrameRect.width * mFrameRect.height * sizeof(uint8_t))); + + ConfigureFilter(surfaceSize, sizeof(uint8_t)); + return NS_OK; +} + +uint8_t* +PalettedSurfaceSink::GetRowPointer() const +{ + // If we're flipping vertically, reverse the order in which we traverse the + // rows. + uint32_t row = mFlipVertically + ? InputSize().height - (mRow + 1) + : mRow; + + uint8_t* rowPtr = mImageData + row * InputSize().width * sizeof(uint8_t); + + MOZ_ASSERT(rowPtr >= mImageData); + MOZ_ASSERT(rowPtr < mImageData + mImageDataLength); + MOZ_ASSERT(rowPtr + InputSize().width * sizeof(uint8_t) <= + mImageData + mImageDataLength); + + return rowPtr; +} + +} // namespace image +} // namespace mozilla diff --git a/image/SurfacePipe.h b/image/SurfacePipe.h new file mode 100644 index 000000000..f046afa56 --- /dev/null +++ b/image/SurfacePipe.h @@ -0,0 +1,793 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * A SurfacePipe is a pipeline that consists of a series of SurfaceFilters + * terminating in a SurfaceSink. Each SurfaceFilter transforms the image data in + * some way before the SurfaceSink ultimately writes it to the surface. This + * design allows for each transformation to be tested independently, for the + * transformations to be combined as needed to meet the needs of different + * situations, and for all image decoders to share the same code for these + * transformations. + * + * Writing to the SurfacePipe is done using lambdas that act as generator + * functions. Because the SurfacePipe machinery controls where the writes take + * place, a bug in an image decoder cannot cause a buffer overflow of the + * underlying surface. + */ + +#ifndef mozilla_image_SurfacePipe_h +#define mozilla_image_SurfacePipe_h + +#include + +#include "nsDebug.h" + +#include "mozilla/Likely.h" +#include "mozilla/Maybe.h" +#include "mozilla/Move.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" +#include "mozilla/Variant.h" +#include "mozilla/gfx/2D.h" + +namespace mozilla { +namespace image { + +class Decoder; + +/** + * An invalid rect for a surface. Results are given both in the space of the + * input image (i.e., before any SurfaceFilters are applied) and in the space + * of the output surface (after all SurfaceFilters). + */ +struct SurfaceInvalidRect +{ + gfx::IntRect mInputSpaceRect; /// The invalid rect in pre-SurfacePipe space. + gfx::IntRect mOutputSpaceRect; /// The invalid rect in post-SurfacePipe space. +}; + +/** + * An enum used to allow the lambdas passed to WritePixels() to communicate + * their state to the caller. + */ +enum class WriteState : uint8_t +{ + NEED_MORE_DATA, /// The lambda ran out of data. + + FINISHED, /// The lambda is done writing to the surface; future writes + /// will fail. + + FAILURE /// The lambda encountered an error. The caller may recover + /// if possible and continue to write. (This never indicates + /// an error in the SurfacePipe machinery itself; it's only + /// generated by the lambdas.) +}; + +/** + * A template alias used to make the return value of WritePixels() lambdas + * (which may return either a pixel value or a WriteState) easier to specify. + */ +template +using NextPixel = Variant; + +/** + * SurfaceFilter is the abstract superclass of SurfacePipe pipeline stages. It + * implements the the code that actually writes to the surface - WritePixels() + * and the other Write*() methods - which are non-virtual for efficiency. + * + * SurfaceFilter's API is nonpublic; only SurfacePipe and other SurfaceFilters + * should use it. Non-SurfacePipe code should use the methods on SurfacePipe. + * + * To implement a SurfaceFilter, it's necessary to subclass SurfaceFilter and + * implement, at a minimum, the pure virtual methods. It's also necessary to + * define a Config struct with a Filter typedef member that identifies the + * matching SurfaceFilter class, and a Configure() template method. See an + * existing SurfaceFilter subclass, such as RemoveFrameRectFilter, for an + * example of how the Configure() method must be implemented. It takes a list of + * Config structs, passes the tail of the list to the next filter in the chain's + * Configure() method, and then uses the head of the list to configure itself. A + * SurfaceFilter's Configure() method must also call + * SurfaceFilter::ConfigureFilter() to provide the Write*() methods with the + * information they need to do their jobs. + */ +class SurfaceFilter +{ +public: + SurfaceFilter() + : mRowPointer(nullptr) + , mCol(0) + , mPixelSize(0) + { } + + virtual ~SurfaceFilter() { } + + /** + * Reset this surface to the first row. It's legal for this filter to throw + * away any previously written data at this point, as all rows must be written + * to on every pass. + * + * @return a pointer to the buffer for the first row. + */ + uint8_t* ResetToFirstRow() + { + mCol = 0; + mRowPointer = DoResetToFirstRow(); + return mRowPointer; + } + + /** + * Called by WritePixels() to advance this filter to the next row. + * + * @return a pointer to the buffer for the next row, or nullptr to indicate + * that we've finished the entire surface. + */ + uint8_t* AdvanceRow() + { + mCol = 0; + mRowPointer = DoAdvanceRow(); + return mRowPointer; + } + + /// @return a pointer to the buffer for the current row. + uint8_t* CurrentRowPointer() const { return mRowPointer; } + + /// @return true if we've finished writing to the surface. + bool IsSurfaceFinished() const { return mRowPointer == nullptr; } + + /// @return the input size this filter expects. + gfx::IntSize InputSize() const { return mInputSize; } + + /** + * Write pixels to the surface one at a time by repeatedly calling a lambda + * that yields pixels. WritePixels() is completely memory safe. + * + * Writing continues until every pixel in the surface has been written to + * (i.e., IsSurfaceFinished() returns true) or the lambda returns a WriteState + * which WritePixels() will return to the caller. + * + * The template parameter PixelType must be uint8_t (for paletted surfaces) or + * uint32_t (for BGRA/BGRX surfaces) and must be in agreement with the pixel + * size passed to ConfigureFilter(). + * + * XXX(seth): We'll remove all support for paletted surfaces in bug 1247520, + * which means we can remove the PixelType template parameter from this + * method. + * + * @param aFunc A lambda that functions as a generator, yielding the next + * pixel in the surface each time it's called. The lambda must + * return a NextPixel value. + * + * @return A WriteState value indicating the lambda generator's state. + * WritePixels() itself will return WriteState::FINISHED if writing + * has finished, regardless of the lambda's internal state. + */ + template + WriteState WritePixels(Func aFunc) + { + Maybe result; + while (!(result = DoWritePixelsToRow(Forward(aFunc)))) { } + + return *result; + } + + /** + * A variant of WritePixels() that writes a single row of pixels to the + * surface one at a time by repeatedly calling a lambda that yields pixels. + * WritePixelsToRow() is completely memory safe. + * + * Writing continues until every pixel in the row has been written to. If the + * surface is complete at that pointer, WriteState::FINISHED is returned; + * otherwise, WritePixelsToRow() returns WriteState::NEED_MORE_DATA. The + * lambda can terminate writing early by returning a WriteState itself, which + * WritePixelsToRow() will return to the caller. + * + * The template parameter PixelType must be uint8_t (for paletted surfaces) or + * uint32_t (for BGRA/BGRX surfaces) and must be in agreement with the pixel + * size passed to ConfigureFilter(). + * + * XXX(seth): We'll remove all support for paletted surfaces in bug 1247520, + * which means we can remove the PixelType template parameter from this + * method. + * + * @param aFunc A lambda that functions as a generator, yielding the next + * pixel in the surface each time it's called. The lambda must + * return a NextPixel value. + * + * @return A WriteState value indicating the lambda generator's state. + * WritePixels() itself will return WriteState::FINISHED if writing + * the entire surface has finished, or WriteState::NEED_MORE_DATA if + * writing the row has finished, regardless of the lambda's internal + * state. + */ + template + WriteState WritePixelsToRow(Func aFunc) + { + return DoWritePixelsToRow(Forward(aFunc)) + .valueOr(WriteState::NEED_MORE_DATA); + } + + /** + * Write a row to the surface by copying from a buffer. This is bounds checked + * and memory safe with respect to the surface, but care must still be taken + * by the caller not to overread the source buffer. This variant of + * WriteBuffer() requires a source buffer which contains |mInputSize.width| + * pixels. + * + * The template parameter PixelType must be uint8_t (for paletted surfaces) or + * uint32_t (for BGRA/BGRX surfaces) and must be in agreement with the pixel + * size passed to ConfigureFilter(). + * + * XXX(seth): We'll remove all support for paletted surfaces in bug 1247520, + * which means we can remove the PixelType template parameter from this + * method. + * + * @param aSource A buffer to copy from. This buffer must be + * |mInputSize.width| pixels wide, which means + * |mInputSize.width * sizeof(PixelType)| bytes. May not be + * null. + * + * @return WriteState::FINISHED if the entire surface has been written to. + * Otherwise, returns WriteState::NEED_MORE_DATA. If a null |aSource| + * value is passed, returns WriteState::FAILURE. + */ + template + WriteState WriteBuffer(const PixelType* aSource) + { + return WriteBuffer(aSource, 0, mInputSize.width); + } + + /** + * Write a row to the surface by copying from a buffer. This is bounds checked + * and memory safe with respect to the surface, but care must still be taken + * by the caller not to overread the source buffer. This variant of + * WriteBuffer() reads at most @aLength pixels from the buffer and writes them + * to the row starting at @aStartColumn. Any pixels in columns before + * @aStartColumn or after the pixels copied from the buffer are cleared. + * + * Bounds checking failures produce warnings in debug builds because although + * the bounds checking maintains safety, this kind of failure could indicate a + * bug in the calling code. + * + * The template parameter PixelType must be uint8_t (for paletted surfaces) or + * uint32_t (for BGRA/BGRX surfaces) and must be in agreement with the pixel + * size passed to ConfigureFilter(). + * + * XXX(seth): We'll remove all support for paletted surfaces in bug 1247520, + * which means we can remove the PixelType template parameter from this + * method. + * + * @param aSource A buffer to copy from. This buffer must be @aLength pixels + * wide, which means |aLength * sizeof(PixelType)| bytes. May + * not be null. + * @param aStartColumn The column to start writing to in the row. Columns + * before this are cleared. + * @param aLength The number of bytes, at most, which may be copied from + * @aSource. Fewer bytes may be copied in practice due to + * bounds checking. + * + * @return WriteState::FINISHED if the entire surface has been written to. + * Otherwise, returns WriteState::NEED_MORE_DATA. If a null |aSource| + * value is passed, returns WriteState::FAILURE. + */ + template + WriteState WriteBuffer(const PixelType* aSource, + const size_t aStartColumn, + const size_t aLength) + { + MOZ_ASSERT(mPixelSize == 1 || mPixelSize == 4); + MOZ_ASSERT_IF(mPixelSize == 1, sizeof(PixelType) == sizeof(uint8_t)); + MOZ_ASSERT_IF(mPixelSize == 4, sizeof(PixelType) == sizeof(uint32_t)); + + if (IsSurfaceFinished()) { + return WriteState::FINISHED; // Already done. + } + + if (MOZ_UNLIKELY(!aSource)) { + NS_WARNING("Passed a null pointer to WriteBuffer"); + return WriteState::FAILURE; + } + + PixelType* dest = reinterpret_cast(mRowPointer); + + // Clear the area before |aStartColumn|. + const size_t prefixLength = std::min(mInputSize.width, aStartColumn); + if (MOZ_UNLIKELY(prefixLength != aStartColumn)) { + NS_WARNING("Provided starting column is out-of-bounds in WriteBuffer"); + } + + memset(dest, 0, mInputSize.width * sizeof(PixelType)); + dest += prefixLength; + + // Write |aLength| pixels from |aSource| into the row, with bounds checking. + const size_t bufferLength = + std::min(mInputSize.width - prefixLength, aLength); + if (MOZ_UNLIKELY(bufferLength != aLength)) { + NS_WARNING("Provided buffer length is out-of-bounds in WriteBuffer"); + } + + memcpy(dest, aSource, bufferLength * sizeof(PixelType)); + dest += bufferLength; + + // Clear the rest of the row. + const size_t suffixLength = mInputSize.width - (prefixLength + bufferLength); + memset(dest, 0, suffixLength * sizeof(PixelType)); + + AdvanceRow(); + + return IsSurfaceFinished() ? WriteState::FINISHED + : WriteState::NEED_MORE_DATA; + } + + /** + * Write an empty row to the surface. If some pixels have already been written + * to this row, they'll be discarded. + * + * @return WriteState::FINISHED if the entire surface has been written to. + * Otherwise, returns WriteState::NEED_MORE_DATA. + */ + WriteState WriteEmptyRow() + { + if (IsSurfaceFinished()) { + return WriteState::FINISHED; // Already done. + } + + memset(mRowPointer, 0, mInputSize.width * mPixelSize); + AdvanceRow(); + + return IsSurfaceFinished() ? WriteState::FINISHED + : WriteState::NEED_MORE_DATA; + } + + /** + * Write a row to the surface by calling a lambda that uses a pointer to + * directly write to the row. This is unsafe because SurfaceFilter can't + * provide any bounds checking; that's up to the lambda itself. For this + * reason, the other Write*() methods should be preferred whenever it's + * possible to use them; WriteUnsafeComputedRow() should be used only when + * it's absolutely necessary to avoid extra copies or other performance + * penalties. + * + * This method should never be exposed to SurfacePipe consumers; it's strictly + * for use in SurfaceFilters. If external code needs this method, it should + * probably be turned into a SurfaceFilter. + * + * The template parameter PixelType must be uint8_t (for paletted surfaces) or + * uint32_t (for BGRA/BGRX surfaces) and must be in agreement with the pixel + * size passed to ConfigureFilter(). + * + * XXX(seth): We'll remove all support for paletted surfaces in bug 1247520, + * which means we can remove the PixelType template parameter from this + * method. + * + * @param aFunc A lambda that writes directly to the row. + * + * @return WriteState::FINISHED if the entire surface has been written to. + * Otherwise, returns WriteState::NEED_MORE_DATA. + */ + template + WriteState WriteUnsafeComputedRow(Func aFunc) + { + MOZ_ASSERT(mPixelSize == 1 || mPixelSize == 4); + MOZ_ASSERT_IF(mPixelSize == 1, sizeof(PixelType) == sizeof(uint8_t)); + MOZ_ASSERT_IF(mPixelSize == 4, sizeof(PixelType) == sizeof(uint32_t)); + + if (IsSurfaceFinished()) { + return WriteState::FINISHED; // Already done. + } + + // Call the provided lambda with a pointer to the buffer for the current + // row. This is unsafe because we can't do any bounds checking; the lambda + // itself has to be responsible for that. + PixelType* rowPtr = reinterpret_cast(mRowPointer); + aFunc(rowPtr, mInputSize.width); + AdvanceRow(); + + return IsSurfaceFinished() ? WriteState::FINISHED + : WriteState::NEED_MORE_DATA; + } + + ////////////////////////////////////////////////////////////////////////////// + // Methods Subclasses Should Override + ////////////////////////////////////////////////////////////////////////////// + + /// @return true if this SurfaceFilter can be used with paletted surfaces. + virtual bool IsValidPalettedPipe() const { return false; } + + /** + * @return a SurfaceInvalidRect representing the region of the surface that + * has been written to since the last time TakeInvalidRect() was + * called, or Nothing() if the region is empty (i.e. nothing has been + * written). + */ + virtual Maybe TakeInvalidRect() = 0; + +protected: + + /** + * Called by ResetToFirstRow() to actually perform the reset. It's legal to + * throw away any previously written data at this point, as all rows must be + * written to on every pass. + */ + virtual uint8_t* DoResetToFirstRow() = 0; + + /** + * Called by AdvanceRow() to actually advance this filter to the next row. + * + * @return a pointer to the buffer for the next row, or nullptr to indicate + * that we've finished the entire surface. + */ + virtual uint8_t* DoAdvanceRow() = 0; + + + ////////////////////////////////////////////////////////////////////////////// + // Methods For Internal Use By Subclasses + ////////////////////////////////////////////////////////////////////////////// + + /** + * Called by subclasses' Configure() methods to initialize the configuration + * of this filter. After the filter is configured, calls ResetToFirstRow(). + * + * @param aInputSize The input size of this filter, in pixels. The previous + * filter in the chain will expect to write into rows + * |aInputSize.width| pixels wide. + * @param aPixelSize How large, in bytes, each pixel in the surface is. This + * should be either 1 for paletted images or 4 for BGRA/BGRX + * images. + */ + void ConfigureFilter(gfx::IntSize aInputSize, uint8_t aPixelSize) + { + mInputSize = aInputSize; + mPixelSize = aPixelSize; + + ResetToFirstRow(); + } + +private: + + /** + * An internal method used to implement both WritePixels() and + * WritePixelsToRow(). Those methods differ only in their behavior after a row + * is successfully written - WritePixels() continues to write another row, + * while WritePixelsToRow() returns to the caller. This method writes a single + * row and returns Some() if we either finished the entire surface or the + * lambda returned a WriteState indicating that we should return to the + * caller. If the row was successfully written without either of those things + * happening, it returns Nothing(), allowing WritePixels() and + * WritePixelsToRow() to implement their respective behaviors. + */ + template + Maybe DoWritePixelsToRow(Func aFunc) + { + MOZ_ASSERT(mPixelSize == 1 || mPixelSize == 4); + MOZ_ASSERT_IF(mPixelSize == 1, sizeof(PixelType) == sizeof(uint8_t)); + MOZ_ASSERT_IF(mPixelSize == 4, sizeof(PixelType) == sizeof(uint32_t)); + + if (IsSurfaceFinished()) { + return Some(WriteState::FINISHED); // We're already done. + } + + PixelType* rowPtr = reinterpret_cast(mRowPointer); + + for (; mCol < mInputSize.width; ++mCol) { + NextPixel result = aFunc(); + if (result.template is()) { + rowPtr[mCol] = result.template as(); + continue; + } + + switch (result.template as()) { + case WriteState::NEED_MORE_DATA: + return Some(WriteState::NEED_MORE_DATA); + + case WriteState::FINISHED: + ZeroOutRestOfSurface(); + return Some(WriteState::FINISHED); + + case WriteState::FAILURE: + // Note that we don't need to record this anywhere, because this + // indicates an error in aFunc, and there's nothing wrong with our + // machinery. The caller can recover as needed and continue writing to + // the row. + return Some(WriteState::FAILURE); + } + } + + AdvanceRow(); // We've finished the row. + + return IsSurfaceFinished() ? Some(WriteState::FINISHED) + : Nothing(); + } + + template + void ZeroOutRestOfSurface() + { + WritePixels([]{ return AsVariant(PixelType(0)); }); + } + + gfx::IntSize mInputSize; /// The size of the input this filter expects. + uint8_t* mRowPointer; /// Pointer to the current row or null if finished. + int32_t mCol; /// The current column we're writing to. (0-indexed) + uint8_t mPixelSize; /// How large each pixel in the surface is, in bytes. +}; + +class NullSurfaceSink; + +/// A trivial configuration struct for NullSurfaceSink. +struct NullSurfaceConfig +{ + using Filter = NullSurfaceSink; +}; + +/** + * NullSurfaceSink is a trivial SurfaceFilter implementation that behaves as if + * it were a zero-size SurfaceSink. It's used as the default filter chain for an + * uninitialized SurfacePipe. + * + * To avoid unnecessary allocations when creating SurfacePipe objects, + * NullSurfaceSink is a singleton. (This implies that the implementation must be + * stateless.) + */ +class NullSurfaceSink final : public SurfaceFilter +{ +public: + /// Returns the singleton instance of NullSurfaceSink. + static NullSurfaceSink* Singleton(); + + virtual ~NullSurfaceSink() { } + + nsresult Configure(const NullSurfaceConfig& aConfig); + + Maybe TakeInvalidRect() override { return Nothing(); } + +protected: + uint8_t* DoResetToFirstRow() override { return nullptr; } + uint8_t* DoAdvanceRow() override { return nullptr; } + +private: + static UniquePtr sSingleton; /// The singleton instance. +}; + + +/** + * SurfacePipe is the public API that decoders should use to interact with a + * SurfaceFilter pipeline. + */ +class SurfacePipe +{ +public: + /// Initialize global state used by all SurfacePipes. + static void Initialize() { NullSurfaceSink::Singleton(); } + + SurfacePipe() + : mHead(NullSurfaceSink::Singleton()) + { } + + SurfacePipe(SurfacePipe&& aOther) + : mHead(Move(aOther.mHead)) + { } + + ~SurfacePipe() + { + // Ensure that we don't free the NullSurfaceSink singleton. + if (mHead.get() == NullSurfaceSink::Singleton()) { + Unused << mHead.release(); + } + } + + SurfacePipe& operator=(SurfacePipe&& aOther) + { + MOZ_ASSERT(this != &aOther); + + // Ensure that we don't free the NullSurfaceSink singleton. + if (mHead.get() == NullSurfaceSink::Singleton()) { + Unused << mHead.release(); + } + + mHead = Move(aOther.mHead); + return *this; + } + + /// Begins a new pass, seeking to the first row of the surface. + void ResetToFirstRow() { mHead->ResetToFirstRow(); } + + /** + * Write pixels to the surface one at a time by repeatedly calling a lambda + * that yields pixels. WritePixels() is completely memory safe. + * + * @see SurfaceFilter::WritePixels() for the canonical documentation. + */ + template + WriteState WritePixels(Func aFunc) + { + return mHead->WritePixels(Forward(aFunc)); + } + + /** + * A variant of WritePixels() that writes a single row of pixels to the + * surface one at a time by repeatedly calling a lambda that yields pixels. + * WritePixelsToRow() is completely memory safe. + * + * @see SurfaceFilter::WritePixelsToRow() for the canonical documentation. + */ + template + WriteState WritePixelsToRow(Func aFunc) + { + return mHead->WritePixelsToRow(Forward(aFunc)); + } + + /** + * Write a row to the surface by copying from a buffer. This is bounds checked + * and memory safe with respect to the surface, but care must still be taken + * by the caller not to overread the source buffer. This variant of + * WriteBuffer() requires a source buffer which contains |mInputSize.width| + * pixels. + * + * @see SurfaceFilter::WriteBuffer() for the canonical documentation. + */ + template + WriteState WriteBuffer(const PixelType* aSource) + { + return mHead->WriteBuffer(aSource); + } + + /** + * Write a row to the surface by copying from a buffer. This is bounds checked + * and memory safe with respect to the surface, but care must still be taken + * by the caller not to overread the source buffer. This variant of + * WriteBuffer() reads at most @aLength pixels from the buffer and writes them + * to the row starting at @aStartColumn. Any pixels in columns before + * @aStartColumn or after the pixels copied from the buffer are cleared. + * + * @see SurfaceFilter::WriteBuffer() for the canonical documentation. + */ + template + WriteState WriteBuffer(const PixelType* aSource, + const size_t aStartColumn, + const size_t aLength) + { + return mHead->WriteBuffer(aSource, aStartColumn, aLength); + } + + /** + * Write an empty row to the surface. If some pixels have already been written + * to this row, they'll be discarded. + * + * @see SurfaceFilter::WriteEmptyRow() for the canonical documentation. + */ + WriteState WriteEmptyRow() + { + return mHead->WriteEmptyRow(); + } + + /// @return true if we've finished writing to the surface. + bool IsSurfaceFinished() const { return mHead->IsSurfaceFinished(); } + + /// @see SurfaceFilter::TakeInvalidRect() for the canonical documentation. + Maybe TakeInvalidRect() const + { + return mHead->TakeInvalidRect(); + } + +private: + friend class SurfacePipeFactory; + friend class TestSurfacePipeFactory; + + explicit SurfacePipe(UniquePtr&& aHead) + : mHead(Move(aHead)) + { } + + SurfacePipe(const SurfacePipe&) = delete; + SurfacePipe& operator=(const SurfacePipe&) = delete; + + UniquePtr mHead; /// The first filter in the chain. +}; + +/** + * AbstractSurfaceSink contains shared implementation for both SurfaceSink and + * PalettedSurfaceSink. + */ +class AbstractSurfaceSink : public SurfaceFilter +{ +public: + AbstractSurfaceSink() + : mImageData(nullptr) + , mImageDataLength(0) + , mRow(0) + , mFlipVertically(false) + { } + + Maybe TakeInvalidRect() override final; + +protected: + uint8_t* DoResetToFirstRow() override final; + uint8_t* DoAdvanceRow() override final; + virtual uint8_t* GetRowPointer() const = 0; + + gfx::IntRect mInvalidRect; /// The region of the surface that has been written + /// to since the last call to TakeInvalidRect(). + uint8_t* mImageData; /// A pointer to the beginning of the surface data. + uint32_t mImageDataLength; /// The length of the surface data. + uint32_t mRow; /// The row to which we're writing. (0-indexed) + bool mFlipVertically; /// If true, write the rows from top to bottom. +}; + +class SurfaceSink; + +/// A configuration struct for SurfaceSink. +struct SurfaceConfig +{ + using Filter = SurfaceSink; + Decoder* mDecoder; /// Which Decoder to use to allocate the surface. + uint32_t mFrameNum; /// Which frame of animation this surface is for. + gfx::IntSize mOutputSize; /// The size of the surface. + gfx::SurfaceFormat mFormat; /// The surface format (BGRA or BGRX). + bool mFlipVertically; /// If true, write the rows from bottom to top. +}; + +/** + * A sink for normal (i.e., non-paletted) surfaces. It handles the allocation of + * the surface and protects against buffer overflow. This sink should be used + * for all non-animated images and for the first frame of animated images. + * + * Sinks must always be at the end of the SurfaceFilter chain. + */ +class SurfaceSink final : public AbstractSurfaceSink +{ +public: + nsresult Configure(const SurfaceConfig& aConfig); + +protected: + uint8_t* GetRowPointer() const override; +}; + +class PalettedSurfaceSink; + +struct PalettedSurfaceConfig +{ + using Filter = PalettedSurfaceSink; + Decoder* mDecoder; /// Which Decoder to use to allocate the surface. + uint32_t mFrameNum; /// Which frame of animation this surface is for. + gfx::IntSize mOutputSize; /// The logical size of the surface. + gfx::IntRect mFrameRect; /// The surface subrect which contains data. + gfx::SurfaceFormat mFormat; /// The surface format (BGRA or BGRX). + uint8_t mPaletteDepth; /// The palette depth of this surface. + bool mFlipVertically; /// If true, write the rows from bottom to top. +}; + +/** + * A sink for paletted surfaces. It handles the allocation of the surface and + * protects against buffer overflow. This sink can be used for frames of + * animated images except the first. + * + * Sinks must always be at the end of the SurfaceFilter chain. + * + * XXX(seth): We'll remove all support for paletted surfaces in bug 1247520, + * which means we can remove PalettedSurfaceSink entirely. + */ +class PalettedSurfaceSink final : public AbstractSurfaceSink +{ +public: + bool IsValidPalettedPipe() const override { return true; } + + nsresult Configure(const PalettedSurfaceConfig& aConfig); + +protected: + uint8_t* GetRowPointer() const override; + +private: + /** + * The surface subrect which contains data. Note that the surface size we + * actually allocate is the size of the frame rect, not the logical size of + * the surface. + */ + gfx::IntRect mFrameRect; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_SurfacePipe_h diff --git a/image/SurfacePipeFactory.h b/image/SurfacePipeFactory.h new file mode 100644 index 000000000..ff53fec5c --- /dev/null +++ b/image/SurfacePipeFactory.h @@ -0,0 +1,249 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_SurfacePipeFactory_h +#define mozilla_image_SurfacePipeFactory_h + +#include "SurfacePipe.h" +#include "SurfaceFilters.h" + +namespace mozilla { +namespace image { + +namespace detail { + +/** + * FilterPipeline is a helper template for SurfacePipeFactory that determines + * the full type of the sequence of SurfaceFilters that a sequence of + * configuration structs corresponds to. To make this work, all configuration + * structs must include a typedef 'Filter' that identifies the SurfaceFilter + * they configure. + */ +template +struct FilterPipeline; + +template +struct FilterPipeline +{ + typedef typename Config::template Filter::Type> Type; +}; + +template +struct FilterPipeline +{ + typedef typename Config::Filter Type; +}; + +} // namespace detail + +/** + * Flags for SurfacePipeFactory, used in conjuction with the factory functions + * in SurfacePipeFactory to enable or disable various SurfacePipe + * functionality. + */ +enum class SurfacePipeFlags +{ + DEINTERLACE = 1 << 0, // If set, deinterlace the image. + + ADAM7_INTERPOLATE = 1 << 1, // If set, the caller is deinterlacing the + // image using ADAM7, and we may want to + // interpolate it for better intermediate results. + + FLIP_VERTICALLY = 1 << 2, // If set, flip the image vertically. + + PROGRESSIVE_DISPLAY = 1 << 3 // If set, we expect the image to be displayed + // progressively. This enables features that + // result in a better user experience for + // progressive display but which may be more + // computationally expensive. +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SurfacePipeFlags) + +class SurfacePipeFactory +{ +public: + /** + * Creates and initializes a normal (i.e., non-paletted) SurfacePipe. + * + * @param aDecoder The decoder whose current frame the SurfacePipe will write + * to. + * @param aFrameNum Which frame the SurfacePipe will write to. This will be 0 + * for non-animated images. + * @param aInputSize The original size of the image. + * @param aOutputSize The size the SurfacePipe should output. Must be the same + * as @aInputSize or smaller. If smaller, the image will be + * downscaled during decoding. + * @param aFrameRect The portion of the image that actually contains data. + * @param aFormat The surface format of the image; generally B8G8R8A8 or + * B8G8R8X8. + * @param aFlags Flags enabling or disabling various functionality for the + * SurfacePipe; see the SurfacePipeFlags documentation for more + * information. + * + * @return A SurfacePipe if the parameters allowed one to be created + * successfully, or Nothing() if the SurfacePipe could not be + * initialized. + */ + static Maybe + CreateSurfacePipe(Decoder* aDecoder, + uint32_t aFrameNum, + const nsIntSize& aInputSize, + const nsIntSize& aOutputSize, + const nsIntRect& aFrameRect, + gfx::SurfaceFormat aFormat, + SurfacePipeFlags aFlags) + { + const bool deinterlace = bool(aFlags & SurfacePipeFlags::DEINTERLACE); + const bool flipVertically = bool(aFlags & SurfacePipeFlags::FLIP_VERTICALLY); + const bool progressiveDisplay = bool(aFlags & SurfacePipeFlags::PROGRESSIVE_DISPLAY); + const bool downscale = aInputSize != aOutputSize; + const bool removeFrameRect = + !aFrameRect.IsEqualEdges(nsIntRect(0, 0, aInputSize.width, aInputSize.height)); + + // Don't interpolate if we're sure we won't show this surface to the user + // until it's completely decoded. The final pass of an ADAM7 image doesn't + // need interpolation, so we only need to interpolate if we'll be displaying + // the image while it's still being decoded. + const bool adam7Interpolate = bool(aFlags & SurfacePipeFlags::ADAM7_INTERPOLATE) && + progressiveDisplay; + + if (deinterlace && adam7Interpolate) { + MOZ_ASSERT_UNREACHABLE("ADAM7 deinterlacing is handled by libpng"); + return Nothing(); + } + + // Construct configurations for the SurfaceFilters. Note that the order of + // these filters is significant. We want to deinterlace or interpolate raw + // input rows, before any other transformations, and we want to remove the + // frame rect (which may involve adding blank rows or columns to the image) + // before any downscaling, so that the new rows and columns are taken into + // account. + DeinterlacingConfig deinterlacingConfig { progressiveDisplay }; + ADAM7InterpolatingConfig interpolatingConfig; + RemoveFrameRectConfig removeFrameRectConfig { aFrameRect }; + DownscalingConfig downscalingConfig { aInputSize, aFormat }; + SurfaceConfig surfaceConfig { aDecoder, aFrameNum, aOutputSize, + aFormat, flipVertically }; + + Maybe pipe; + + if (downscale) { + if (removeFrameRect) { + if (deinterlace) { + pipe = MakePipe(deinterlacingConfig, removeFrameRectConfig, + downscalingConfig, surfaceConfig); + } else if (adam7Interpolate) { + pipe = MakePipe(interpolatingConfig, removeFrameRectConfig, + downscalingConfig, surfaceConfig); + } else { // (deinterlace and adam7Interpolate are false) + pipe = MakePipe(removeFrameRectConfig, downscalingConfig, surfaceConfig); + } + } else { // (removeFrameRect is false) + if (deinterlace) { + pipe = MakePipe(deinterlacingConfig, downscalingConfig, surfaceConfig); + } else if (adam7Interpolate) { + pipe = MakePipe(interpolatingConfig, downscalingConfig, surfaceConfig); + } else { // (deinterlace and adam7Interpolate are false) + pipe = MakePipe(downscalingConfig, surfaceConfig); + } + } + } else { // (downscale is false) + if (removeFrameRect) { + if (deinterlace) { + pipe = MakePipe(deinterlacingConfig, removeFrameRectConfig, surfaceConfig); + } else if (adam7Interpolate) { + pipe = MakePipe(interpolatingConfig, removeFrameRectConfig, surfaceConfig); + } else { // (deinterlace and adam7Interpolate are false) + pipe = MakePipe(removeFrameRectConfig, surfaceConfig); + } + } else { // (removeFrameRect is false) + if (deinterlace) { + pipe = MakePipe(deinterlacingConfig, surfaceConfig); + } else if (adam7Interpolate) { + pipe = MakePipe(interpolatingConfig, surfaceConfig); + } else { // (deinterlace and adam7Interpolate are false) + pipe = MakePipe(surfaceConfig); + } + } + } + + return pipe; + } + + /** + * Creates and initializes a paletted SurfacePipe. + * + * XXX(seth): We'll remove all support for paletted surfaces in bug 1247520, + * which means we can remove CreatePalettedSurfacePipe() entirely. + * + * @param aDecoder The decoder whose current frame the SurfacePipe will write + * to. + * @param aFrameNum Which frame the SurfacePipe will write to. This will be 0 + * for non-animated images. + * @param aInputSize The original size of the image. + * @param aFrameRect The portion of the image that actually contains data. + * @param aFormat The surface format of the image; generally B8G8R8A8 or + * B8G8R8X8. + * @param aPaletteDepth The palette depth of the image. + * @param aFlags Flags enabling or disabling various functionality for the + * SurfacePipe; see the SurfacePipeFlags documentation for more + * information. + * + * @return A SurfacePipe if the parameters allowed one to be created + * successfully, or Nothing() if the SurfacePipe could not be + * initialized. + */ + static Maybe + CreatePalettedSurfacePipe(Decoder* aDecoder, + uint32_t aFrameNum, + const nsIntSize& aInputSize, + const nsIntRect& aFrameRect, + gfx::SurfaceFormat aFormat, + uint8_t aPaletteDepth, + SurfacePipeFlags aFlags) + { + const bool deinterlace = bool(aFlags & SurfacePipeFlags::DEINTERLACE); + const bool flipVertically = bool(aFlags & SurfacePipeFlags::FLIP_VERTICALLY); + const bool progressiveDisplay = bool(aFlags & SurfacePipeFlags::PROGRESSIVE_DISPLAY); + + // Construct configurations for the SurfaceFilters. + DeinterlacingConfig deinterlacingConfig { progressiveDisplay }; + PalettedSurfaceConfig palettedSurfaceConfig { aDecoder, aFrameNum, aInputSize, + aFrameRect, aFormat, aPaletteDepth, + flipVertically }; + + Maybe pipe; + + if (deinterlace) { + pipe = MakePipe(deinterlacingConfig, palettedSurfaceConfig); + } else { + pipe = MakePipe(palettedSurfaceConfig); + } + + return pipe; + } + +private: + template + static Maybe + MakePipe(Configs... aConfigs) + { + auto pipe = MakeUnique::Type>(); + nsresult rv = pipe->Configure(aConfigs...); + if (NS_FAILED(rv)) { + return Nothing(); + } + + return Some(SurfacePipe { Move(pipe) } ); + } + + virtual ~SurfacePipeFactory() = 0; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_SurfacePipeFactory_h diff --git a/image/VectorImage.cpp b/image/VectorImage.cpp new file mode 100644 index 000000000..6e3928362 --- /dev/null +++ b/image/VectorImage.cpp @@ -0,0 +1,1350 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "VectorImage.h" + +#include "gfx2DGlue.h" +#include "gfxContext.h" +#include "gfxDrawable.h" +#include "gfxPlatform.h" +#include "gfxUtils.h" +#include "imgFrame.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/dom/SVGSVGElement.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/RefPtr.h" +#include "nsIDOMEvent.h" +#include "nsIPresShell.h" +#include "nsIStreamListener.h" +#include "nsMimeTypes.h" +#include "nsPresContext.h" +#include "nsRect.h" +#include "nsString.h" +#include "nsStubDocumentObserver.h" +#include "nsSVGEffects.h" // for nsSVGRenderingObserver +#include "nsWindowMemoryReporter.h" +#include "ImageRegion.h" +#include "ISurfaceProvider.h" +#include "LookupResult.h" +#include "Orientation.h" +#include "SVGDocumentWrapper.h" +#include "nsIDOMEventListener.h" +#include "SurfaceCache.h" +#include "nsDocument.h" + +// undef the GetCurrentTime macro defined in WinBase.h from the MS Platform SDK +#undef GetCurrentTime + +namespace mozilla { + +using namespace dom; +using namespace gfx; +using namespace layers; + +namespace image { + +// Helper-class: SVGRootRenderingObserver +class SVGRootRenderingObserver final : public nsSVGRenderingObserver { +public: + NS_DECL_ISUPPORTS + + SVGRootRenderingObserver(SVGDocumentWrapper* aDocWrapper, + VectorImage* aVectorImage) + : nsSVGRenderingObserver() + , mDocWrapper(aDocWrapper) + , mVectorImage(aVectorImage) + , mHonoringInvalidations(true) + { + MOZ_ASSERT(mDocWrapper, "Need a non-null SVG document wrapper"); + MOZ_ASSERT(mVectorImage, "Need a non-null VectorImage"); + + StartListening(); + Element* elem = GetTarget(); + MOZ_ASSERT(elem, "no root SVG node for us to observe"); + + nsSVGEffects::AddRenderingObserver(elem, this); + mInObserverList = true; + } + + + void ResumeHonoringInvalidations() + { + mHonoringInvalidations = true; + } + +protected: + virtual ~SVGRootRenderingObserver() + { + StopListening(); + } + + virtual Element* GetTarget() override + { + return mDocWrapper->GetRootSVGElem(); + } + + virtual void DoUpdate() override + { + Element* elem = GetTarget(); + MOZ_ASSERT(elem, "missing root SVG node"); + + if (mHonoringInvalidations && !mDocWrapper->ShouldIgnoreInvalidation()) { + nsIFrame* frame = elem->GetPrimaryFrame(); + if (!frame || frame->PresContext()->PresShell()->IsDestroying()) { + // We're being destroyed. Bail out. + return; + } + + // Ignore further invalidations until we draw. + mHonoringInvalidations = false; + + mVectorImage->InvalidateObserversOnNextRefreshDriverTick(); + } + + // Our caller might've removed us from rendering-observer list. + // Add ourselves back! + if (!mInObserverList) { + nsSVGEffects::AddRenderingObserver(elem, this); + mInObserverList = true; + } + } + + // Private data + const RefPtr mDocWrapper; + VectorImage* const mVectorImage; // Raw pointer because it owns me. + bool mHonoringInvalidations; +}; + +NS_IMPL_ISUPPORTS(SVGRootRenderingObserver, nsIMutationObserver) + +class SVGParseCompleteListener final : public nsStubDocumentObserver { +public: + NS_DECL_ISUPPORTS + + SVGParseCompleteListener(nsIDocument* aDocument, + VectorImage* aImage) + : mDocument(aDocument) + , mImage(aImage) + { + MOZ_ASSERT(mDocument, "Need an SVG document"); + MOZ_ASSERT(mImage, "Need an image"); + + mDocument->AddObserver(this); + } + +private: + ~SVGParseCompleteListener() + { + if (mDocument) { + // The document must have been destroyed before we got our event. + // Otherwise this can't happen, since documents hold strong references to + // their observers. + Cancel(); + } + } + +public: + void EndLoad(nsIDocument* aDocument) override + { + MOZ_ASSERT(aDocument == mDocument, "Got EndLoad for wrong document?"); + + // OnSVGDocumentParsed will release our owner's reference to us, so ensure + // we stick around long enough to complete our work. + RefPtr kungFuDeathGrip(this); + + mImage->OnSVGDocumentParsed(); + } + + void Cancel() + { + MOZ_ASSERT(mDocument, "Duplicate call to Cancel"); + if (mDocument) { + mDocument->RemoveObserver(this); + mDocument = nullptr; + } + } + +private: + nsCOMPtr mDocument; + VectorImage* const mImage; // Raw pointer to owner. +}; + +NS_IMPL_ISUPPORTS(SVGParseCompleteListener, nsIDocumentObserver) + +class SVGLoadEventListener final : public nsIDOMEventListener { +public: + NS_DECL_ISUPPORTS + + SVGLoadEventListener(nsIDocument* aDocument, + VectorImage* aImage) + : mDocument(aDocument) + , mImage(aImage) + { + MOZ_ASSERT(mDocument, "Need an SVG document"); + MOZ_ASSERT(mImage, "Need an image"); + + mDocument->AddEventListener(NS_LITERAL_STRING("MozSVGAsImageDocumentLoad"), + this, true, false); + mDocument->AddEventListener(NS_LITERAL_STRING("SVGAbort"), this, true, + false); + mDocument->AddEventListener(NS_LITERAL_STRING("SVGError"), this, true, + false); + } + +private: + ~SVGLoadEventListener() + { + if (mDocument) { + // The document must have been destroyed before we got our event. + // Otherwise this can't happen, since documents hold strong references to + // their observers. + Cancel(); + } + } + +public: + NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) override + { + MOZ_ASSERT(mDocument, "Need an SVG document. Received multiple events?"); + + // OnSVGDocumentLoaded/OnSVGDocumentError will release our owner's reference + // to us, so ensure we stick around long enough to complete our work. + RefPtr kungFuDeathGrip(this); + + nsAutoString eventType; + aEvent->GetType(eventType); + MOZ_ASSERT(eventType.EqualsLiteral("MozSVGAsImageDocumentLoad") || + eventType.EqualsLiteral("SVGAbort") || + eventType.EqualsLiteral("SVGError"), + "Received unexpected event"); + + if (eventType.EqualsLiteral("MozSVGAsImageDocumentLoad")) { + mImage->OnSVGDocumentLoaded(); + } else { + mImage->OnSVGDocumentError(); + } + + return NS_OK; + } + + void Cancel() + { + MOZ_ASSERT(mDocument, "Duplicate call to Cancel"); + if (mDocument) { + mDocument + ->RemoveEventListener(NS_LITERAL_STRING("MozSVGAsImageDocumentLoad"), + this, true); + mDocument->RemoveEventListener(NS_LITERAL_STRING("SVGAbort"), this, true); + mDocument->RemoveEventListener(NS_LITERAL_STRING("SVGError"), this, true); + mDocument = nullptr; + } + } + +private: + nsCOMPtr mDocument; + VectorImage* const mImage; // Raw pointer to owner. +}; + +NS_IMPL_ISUPPORTS(SVGLoadEventListener, nsIDOMEventListener) + +// Helper-class: SVGDrawingCallback +class SVGDrawingCallback : public gfxDrawingCallback { +public: + SVGDrawingCallback(SVGDocumentWrapper* aSVGDocumentWrapper, + const IntRect& aViewport, + const IntSize& aSize, + uint32_t aImageFlags) + : mSVGDocumentWrapper(aSVGDocumentWrapper) + , mViewport(aViewport) + , mSize(aSize) + , mImageFlags(aImageFlags) + { } + virtual bool operator()(gfxContext* aContext, + const gfxRect& aFillRect, + const SamplingFilter aSamplingFilter, + const gfxMatrix& aTransform); +private: + RefPtr mSVGDocumentWrapper; + const IntRect mViewport; + const IntSize mSize; + uint32_t mImageFlags; +}; + +// Based loosely on nsSVGIntegrationUtils' PaintFrameCallback::operator() +bool +SVGDrawingCallback::operator()(gfxContext* aContext, + const gfxRect& aFillRect, + const SamplingFilter aSamplingFilter, + const gfxMatrix& aTransform) +{ + MOZ_ASSERT(mSVGDocumentWrapper, "need an SVGDocumentWrapper"); + + // Get (& sanity-check) the helper-doc's presShell + nsCOMPtr presShell; + if (NS_FAILED(mSVGDocumentWrapper->GetPresShell(getter_AddRefs(presShell)))) { + NS_WARNING("Unable to draw -- presShell lookup failed"); + return false; + } + MOZ_ASSERT(presShell, "GetPresShell succeeded but returned null"); + + gfxContextAutoSaveRestore contextRestorer(aContext); + + // Clip to aFillRect so that we don't paint outside. + aContext->NewPath(); + aContext->Rectangle(aFillRect); + aContext->Clip(); + + gfxMatrix matrix = aTransform; + if (!matrix.Invert()) { + return false; + } + aContext->SetMatrix( + aContext->CurrentMatrix().PreMultiply(matrix). + Scale(double(mSize.width) / mViewport.width, + double(mSize.height) / mViewport.height)); + + nsPresContext* presContext = presShell->GetPresContext(); + MOZ_ASSERT(presContext, "pres shell w/out pres context"); + + nsRect svgRect(presContext->DevPixelsToAppUnits(mViewport.x), + presContext->DevPixelsToAppUnits(mViewport.y), + presContext->DevPixelsToAppUnits(mViewport.width), + presContext->DevPixelsToAppUnits(mViewport.height)); + + uint32_t renderDocFlags = nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING; + if (!(mImageFlags & imgIContainer::FLAG_SYNC_DECODE)) { + renderDocFlags |= nsIPresShell::RENDER_ASYNC_DECODE_IMAGES; + } + + presShell->RenderDocument(svgRect, renderDocFlags, + NS_RGBA(0, 0, 0, 0), // transparent + aContext); + + return true; +} + +// Implement VectorImage's nsISupports-inherited methods +NS_IMPL_ISUPPORTS(VectorImage, + imgIContainer, + nsIStreamListener, + nsIRequestObserver) + +//------------------------------------------------------------------------------ +// Constructor / Destructor + +VectorImage::VectorImage(ImageURL* aURI /* = nullptr */) : + ImageResource(aURI), // invoke superclass's constructor + mLockCount(0), + mIsInitialized(false), + mIsFullyLoaded(false), + mIsDrawing(false), + mHaveAnimations(false), + mHasPendingInvalidation(false) +{ } + +VectorImage::~VectorImage() +{ + CancelAllListeners(); + SurfaceCache::RemoveImage(ImageKey(this)); +} + +//------------------------------------------------------------------------------ +// Methods inherited from Image.h + +nsresult +VectorImage::Init(const char* aMimeType, + uint32_t aFlags) +{ + // We don't support re-initialization + if (mIsInitialized) { + return NS_ERROR_ILLEGAL_VALUE; + } + + MOZ_ASSERT(!mIsFullyLoaded && !mHaveAnimations && !mError, + "Flags unexpectedly set before initialization"); + MOZ_ASSERT(!strcmp(aMimeType, IMAGE_SVG_XML), "Unexpected mimetype"); + + mDiscardable = !!(aFlags & INIT_FLAG_DISCARDABLE); + + // Lock this image's surfaces in the SurfaceCache if we're not discardable. + if (!mDiscardable) { + mLockCount++; + SurfaceCache::LockImage(ImageKey(this)); + } + + mIsInitialized = true; + return NS_OK; +} + +size_t +VectorImage::SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const +{ + if (!mSVGDocumentWrapper) { + return 0; // No document, so no memory used for the document. + } + + nsIDocument* doc = mSVGDocumentWrapper->GetDocument(); + if (!doc) { + return 0; // No document, so no memory used for the document. + } + + nsWindowSizes windowSizes(aMallocSizeOf); + doc->DocAddSizeOfIncludingThis(&windowSizes); + + if (windowSizes.getTotalSize() == 0) { + // MallocSizeOf fails on this platform. Because we also use this method for + // determining the size of cache entries, we need to return something + // reasonable here. Unfortunately, there's no way to estimate the document's + // size accurately, so we just use a constant value of 100KB, which will + // generally underestimate the true size. + return 100 * 1024; + } + + return windowSizes.getTotalSize(); +} + +void +VectorImage::CollectSizeOfSurfaces(nsTArray& aCounters, + MallocSizeOf aMallocSizeOf) const +{ + SurfaceCache::CollectSizeOfSurfaces(ImageKey(this), aCounters, aMallocSizeOf); +} + +nsresult +VectorImage::OnImageDataComplete(nsIRequest* aRequest, + nsISupports* aContext, + nsresult aStatus, + bool aLastPart) +{ + // Call our internal OnStopRequest method, which only talks to our embedded + // SVG document. This won't have any effect on our ProgressTracker. + nsresult finalStatus = OnStopRequest(aRequest, aContext, aStatus); + + // Give precedence to Necko failure codes. + if (NS_FAILED(aStatus)) { + finalStatus = aStatus; + } + + Progress loadProgress = LoadCompleteProgress(aLastPart, mError, finalStatus); + + if (mIsFullyLoaded || mError) { + // Our document is loaded, so we're ready to notify now. + mProgressTracker->SyncNotifyProgress(loadProgress); + } else { + // Record our progress so far; we'll actually send the notifications in + // OnSVGDocumentLoaded or OnSVGDocumentError. + mLoadProgress = Some(loadProgress); + } + + return finalStatus; +} + +nsresult +VectorImage::OnImageDataAvailable(nsIRequest* aRequest, + nsISupports* aContext, + nsIInputStream* aInStr, + uint64_t aSourceOffset, + uint32_t aCount) +{ + return OnDataAvailable(aRequest, aContext, aInStr, aSourceOffset, aCount); +} + +nsresult +VectorImage::StartAnimation() +{ + if (mError) { + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(ShouldAnimate(), "Should not animate!"); + + mSVGDocumentWrapper->StartAnimation(); + return NS_OK; +} + +nsresult +VectorImage::StopAnimation() +{ + nsresult rv = NS_OK; + if (mError) { + rv = NS_ERROR_FAILURE; + } else { + MOZ_ASSERT(mIsFullyLoaded && mHaveAnimations, + "Should not have been animating!"); + + mSVGDocumentWrapper->StopAnimation(); + } + + mAnimating = false; + return rv; +} + +bool +VectorImage::ShouldAnimate() +{ + return ImageResource::ShouldAnimate() && mIsFullyLoaded && mHaveAnimations; +} + +NS_IMETHODIMP_(void) +VectorImage::SetAnimationStartTime(const TimeStamp& aTime) +{ + // We don't care about animation start time. +} + +//------------------------------------------------------------------------------ +// imgIContainer methods + +//****************************************************************************** +NS_IMETHODIMP +VectorImage::GetWidth(int32_t* aWidth) +{ + if (mError || !mIsFullyLoaded) { + // XXXdholbert Technically we should leave outparam untouched when we + // fail. But since many callers don't check for failure, we set it to 0 on + // failure, for sane/predictable results. + *aWidth = 0; + return NS_ERROR_FAILURE; + } + + SVGSVGElement* rootElem = mSVGDocumentWrapper->GetRootSVGElem(); + MOZ_ASSERT(rootElem, "Should have a root SVG elem, since we finished " + "loading without errors"); + int32_t rootElemWidth = rootElem->GetIntrinsicWidth(); + if (rootElemWidth < 0) { + *aWidth = 0; + return NS_ERROR_FAILURE; + } + *aWidth = rootElemWidth; + return NS_OK; +} + +//****************************************************************************** +NS_IMETHODIMP_(void) +VectorImage::RequestRefresh(const TimeStamp& aTime) +{ + if (HadRecentRefresh(aTime)) { + return; + } + + PendingAnimationTracker* tracker = + mSVGDocumentWrapper->GetDocument()->GetPendingAnimationTracker(); + if (tracker && ShouldAnimate()) { + tracker->TriggerPendingAnimationsOnNextTick(aTime); + } + + EvaluateAnimation(); + + mSVGDocumentWrapper->TickRefreshDriver(); + + if (mHasPendingInvalidation) { + mHasPendingInvalidation = false; + SendInvalidationNotifications(); + } +} + +void +VectorImage::SendInvalidationNotifications() +{ + // Animated images don't send out invalidation notifications as soon as + // they're generated. Instead, InvalidateObserversOnNextRefreshDriverTick + // records that there are pending invalidations and then returns immediately. + // The notifications are actually sent from RequestRefresh(). We send these + // notifications there to ensure that there is actually a document observing + // us. Otherwise, the notifications are just wasted effort. + // + // Non-animated images call this method directly from + // InvalidateObserversOnNextRefreshDriverTick, because RequestRefresh is never + // called for them. Ordinarily this isn't needed, since we send out + // invalidation notifications in OnSVGDocumentLoaded, but in rare cases the + // SVG document may not be 100% ready to render at that time. In those cases + // we would miss the subsequent invalidations if we didn't send out the + // notifications directly in |InvalidateObservers...|. + + if (mProgressTracker) { + SurfaceCache::RemoveImage(ImageKey(this)); + mProgressTracker->SyncNotifyProgress(FLAG_FRAME_COMPLETE, + GetMaxSizedIntRect()); + } +} + +NS_IMETHODIMP_(IntRect) +VectorImage::GetImageSpaceInvalidationRect(const IntRect& aRect) +{ + return aRect; +} + +//****************************************************************************** +NS_IMETHODIMP +VectorImage::GetHeight(int32_t* aHeight) +{ + if (mError || !mIsFullyLoaded) { + // XXXdholbert Technically we should leave outparam untouched when we + // fail. But since many callers don't check for failure, we set it to 0 on + // failure, for sane/predictable results. + *aHeight = 0; + return NS_ERROR_FAILURE; + } + + SVGSVGElement* rootElem = mSVGDocumentWrapper->GetRootSVGElem(); + MOZ_ASSERT(rootElem, "Should have a root SVG elem, since we finished " + "loading without errors"); + int32_t rootElemHeight = rootElem->GetIntrinsicHeight(); + if (rootElemHeight < 0) { + *aHeight = 0; + return NS_ERROR_FAILURE; + } + *aHeight = rootElemHeight; + return NS_OK; +} + +//****************************************************************************** +NS_IMETHODIMP +VectorImage::GetIntrinsicSize(nsSize* aSize) +{ + if (mError || !mIsFullyLoaded) { + return NS_ERROR_FAILURE; + } + + nsIFrame* rootFrame = mSVGDocumentWrapper->GetRootLayoutFrame(); + if (!rootFrame) { + return NS_ERROR_FAILURE; + } + + *aSize = nsSize(-1, -1); + IntrinsicSize rfSize = rootFrame->GetIntrinsicSize(); + if (rfSize.width.GetUnit() == eStyleUnit_Coord) { + aSize->width = rfSize.width.GetCoordValue(); + } + if (rfSize.height.GetUnit() == eStyleUnit_Coord) { + aSize->height = rfSize.height.GetCoordValue(); + } + + return NS_OK; +} + +//****************************************************************************** +NS_IMETHODIMP +VectorImage::GetIntrinsicRatio(nsSize* aRatio) +{ + if (mError || !mIsFullyLoaded) { + return NS_ERROR_FAILURE; + } + + nsIFrame* rootFrame = mSVGDocumentWrapper->GetRootLayoutFrame(); + if (!rootFrame) { + return NS_ERROR_FAILURE; + } + + *aRatio = rootFrame->GetIntrinsicRatio(); + return NS_OK; +} + +NS_IMETHODIMP_(Orientation) +VectorImage::GetOrientation() +{ + return Orientation(); +} + +//****************************************************************************** +NS_IMETHODIMP +VectorImage::GetType(uint16_t* aType) +{ + NS_ENSURE_ARG_POINTER(aType); + + *aType = imgIContainer::TYPE_VECTOR; + return NS_OK; +} + +//****************************************************************************** +NS_IMETHODIMP +VectorImage::GetAnimated(bool* aAnimated) +{ + if (mError || !mIsFullyLoaded) { + return NS_ERROR_FAILURE; + } + + *aAnimated = mSVGDocumentWrapper->IsAnimated(); + return NS_OK; +} + +//****************************************************************************** +int32_t +VectorImage::GetFirstFrameDelay() +{ + if (mError) { + return -1; + } + + if (!mSVGDocumentWrapper->IsAnimated()) { + return -1; + } + + // We don't really have a frame delay, so just pretend that we constantly + // need updates. + return 0; +} + +NS_IMETHODIMP_(bool) +VectorImage::WillDrawOpaqueNow() +{ + return false; // In general, SVG content is not opaque. +} + +//****************************************************************************** +NS_IMETHODIMP_(already_AddRefed) +VectorImage::GetFrame(uint32_t aWhichFrame, uint32_t aFlags) +{ + if (mError) { + return nullptr; + } + + // Look up height & width + // ---------------------- + SVGSVGElement* svgElem = mSVGDocumentWrapper->GetRootSVGElem(); + MOZ_ASSERT(svgElem, "Should have a root SVG elem, since we finished " + "loading without errors"); + nsIntSize imageIntSize(svgElem->GetIntrinsicWidth(), + svgElem->GetIntrinsicHeight()); + + if (imageIntSize.IsEmpty()) { + // We'll get here if our SVG doc has a percent-valued or negative width or + // height. + return nullptr; + } + + return GetFrameAtSize(imageIntSize, aWhichFrame, aFlags); +} + +NS_IMETHODIMP_(already_AddRefed) +VectorImage::GetFrameAtSize(const IntSize& aSize, + uint32_t aWhichFrame, + uint32_t aFlags) +{ + MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE); + + if (aSize.IsEmpty()) { + return nullptr; + } + + if (aWhichFrame > FRAME_MAX_VALUE) { + return nullptr; + } + + if (mError || !mIsFullyLoaded) { + return nullptr; + } + + // Make our surface the size of what will ultimately be drawn to it. + // (either the full image size, or the restricted region) + RefPtr dt = gfxPlatform::GetPlatform()-> + CreateOffscreenContentDrawTarget(aSize, SurfaceFormat::B8G8R8A8); + if (!dt || !dt->IsValid()) { + NS_ERROR("Could not create a DrawTarget"); + return nullptr; + } + + RefPtr context = gfxContext::CreateOrNull(dt); + MOZ_ASSERT(context); // already checked the draw target above + + auto result = Draw(context, aSize, ImageRegion::Create(aSize), + aWhichFrame, SamplingFilter::POINT, Nothing(), aFlags); + + return result == DrawResult::SUCCESS ? dt->Snapshot() : nullptr; +} + +NS_IMETHODIMP_(bool) +VectorImage::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags) +{ + return false; +} + +//****************************************************************************** +NS_IMETHODIMP_(already_AddRefed) +VectorImage::GetImageContainer(LayerManager* aManager, uint32_t aFlags) +{ + return nullptr; +} + +struct SVGDrawingParameters +{ + SVGDrawingParameters(gfxContext* aContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, + SamplingFilter aSamplingFilter, + const Maybe& aSVGContext, + float aAnimationTime, + uint32_t aFlags) + : context(aContext) + , size(aSize.width, aSize.height) + , region(aRegion) + , samplingFilter(aSamplingFilter) + , svgContext(aSVGContext) + , viewportSize(aSize) + , animationTime(aAnimationTime) + , flags(aFlags) + , opacity(aSVGContext ? aSVGContext->GetGlobalOpacity() : 1.0) + { + if (aSVGContext) { + CSSIntSize sz = aSVGContext->GetViewportSize(); + viewportSize = nsIntSize(sz.width, sz.height); // XXX losing unit + } + } + + gfxContext* context; + IntSize size; + ImageRegion region; + SamplingFilter samplingFilter; + const Maybe& svgContext; + nsIntSize viewportSize; + float animationTime; + uint32_t flags; + gfxFloat opacity; +}; + +//****************************************************************************** +NS_IMETHODIMP_(DrawResult) +VectorImage::Draw(gfxContext* aContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, + uint32_t aWhichFrame, + SamplingFilter aSamplingFilter, + const Maybe& aSVGContext, + uint32_t aFlags) +{ + if (aWhichFrame > FRAME_MAX_VALUE) { + return DrawResult::BAD_ARGS; + } + + if (!aContext) { + return DrawResult::BAD_ARGS; + } + + if (mError) { + return DrawResult::BAD_IMAGE; + } + + if (!mIsFullyLoaded) { + return DrawResult::NOT_READY; + } + + if (mIsDrawing) { + NS_WARNING("Refusing to make re-entrant call to VectorImage::Draw"); + return DrawResult::TEMPORARY_ERROR; + } + + if (mAnimationConsumers == 0 && mProgressTracker) { + mProgressTracker->OnUnlockedDraw(); + } + + AutoRestore autoRestoreIsDrawing(mIsDrawing); + mIsDrawing = true; + + Maybe svgContext; + // If FLAG_FORCE_PRESERVEASPECTRATIO_NONE bit is set, that mean we should + // overwrite SVG preserveAspectRatio attibute of this image with none, and + // always stretch this image to viewport non-uniformly. + // And we can do this only if the caller pass in the the SVG viewport, via + // aSVGContext. + if ((aFlags & FLAG_FORCE_PRESERVEASPECTRATIO_NONE) && aSVGContext.isSome()) { + Maybe aspectRatio = + Some(SVGPreserveAspectRatio(SVG_PRESERVEASPECTRATIO_NONE, + SVG_MEETORSLICE_UNKNOWN)); + svgContext = + Some(SVGImageContext(aSVGContext->GetViewportSize(), + aspectRatio)); + } else { + svgContext = aSVGContext; + } + + float animTime = + (aWhichFrame == FRAME_FIRST) ? 0.0f + : mSVGDocumentWrapper->GetCurrentTime(); + AutoSVGRenderingState autoSVGState(svgContext, animTime, + mSVGDocumentWrapper->GetRootSVGElem()); + + + SVGDrawingParameters params(aContext, aSize, aRegion, aSamplingFilter, + svgContext, animTime, aFlags); + + // If we have an prerasterized version of this image that matches the + // drawing parameters, use that. + RefPtr svgDrawable = LookupCachedSurface(params); + if (svgDrawable) { + Show(svgDrawable, params); + return DrawResult::SUCCESS; + } + + // We didn't get a hit in the surface cache, so we'll need to rerasterize. + CreateSurfaceAndShow(params, aContext->GetDrawTarget()->GetBackendType()); + return DrawResult::SUCCESS; +} + +already_AddRefed +VectorImage::LookupCachedSurface(const SVGDrawingParameters& aParams) +{ + // If we're not allowed to use a cached surface, don't attempt a lookup. + if (aParams.flags & FLAG_BYPASS_SURFACE_CACHE) { + return nullptr; + } + + // We don't do any caching if we have animation, so don't bother with a lookup + // in this case either. + if (mHaveAnimations) { + return nullptr; + } + + LookupResult result = + SurfaceCache::Lookup(ImageKey(this), + VectorSurfaceKey(aParams.size, aParams.svgContext)); + if (!result) { + return nullptr; // No matching surface, or the OS freed the volatile buffer. + } + + RefPtr sourceSurface = result.Surface()->GetSourceSurface(); + if (!sourceSurface) { + // Something went wrong. (Probably a GPU driver crash or device reset.) + // Attempt to recover. + RecoverFromLossOfSurfaces(); + return nullptr; + } + + RefPtr svgDrawable = + new gfxSurfaceDrawable(sourceSurface, result.Surface()->GetSize()); + return svgDrawable.forget(); +} + +void +VectorImage::CreateSurfaceAndShow(const SVGDrawingParameters& aParams, BackendType aBackend) +{ + mSVGDocumentWrapper->UpdateViewportBounds(aParams.viewportSize); + mSVGDocumentWrapper->FlushImageTransformInvalidation(); + + RefPtr cb = + new SVGDrawingCallback(mSVGDocumentWrapper, + IntRect(IntPoint(0, 0), aParams.viewportSize), + aParams.size, + aParams.flags); + + RefPtr svgDrawable = + new gfxCallbackDrawable(cb, aParams.size); + + bool bypassCache = bool(aParams.flags & FLAG_BYPASS_SURFACE_CACHE) || + // Refuse to cache animated images: + // XXX(seth): We may remove this restriction in bug 922893. + mHaveAnimations || + // The image is too big to fit in the cache: + !SurfaceCache::CanHold(aParams.size); + if (bypassCache) { + return Show(svgDrawable, aParams); + } + + // We're about to rerasterize, which may mean that some of the previous + // surfaces we've rasterized aren't useful anymore. We can allow them to + // expire from the cache by unlocking them here, and then sending out an + // invalidation. If this image is locked, any surfaces that are still useful + // will become locked again when Draw touches them, and the remainder will + // eventually expire. + SurfaceCache::UnlockEntries(ImageKey(this)); + + // Try to create an imgFrame, initializing the surface it contains by drawing + // our gfxDrawable into it. (We use FILTER_NEAREST since we never scale here.) + NotNull> frame = WrapNotNull(new imgFrame); + nsresult rv = + frame->InitWithDrawable(svgDrawable, aParams.size, + SurfaceFormat::B8G8R8A8, + SamplingFilter::POINT, aParams.flags, + aBackend); + + // If we couldn't create the frame, it was probably because it would end + // up way too big. Generally it also wouldn't fit in the cache, but the prefs + // could be set such that the cache isn't the limiting factor. + if (NS_FAILED(rv)) { + return Show(svgDrawable, aParams); + } + + // Take a strong reference to the frame's surface and make sure it hasn't + // already been purged by the operating system. + RefPtr surface = frame->GetSourceSurface(); + if (!surface) { + return Show(svgDrawable, aParams); + } + + // Attempt to cache the frame. + SurfaceKey surfaceKey = VectorSurfaceKey(aParams.size, aParams.svgContext); + NotNull> provider = + WrapNotNull(new SimpleSurfaceProvider(ImageKey(this), surfaceKey, frame)); + SurfaceCache::Insert(provider); + + // Draw. + RefPtr drawable = + new gfxSurfaceDrawable(surface, aParams.size); + Show(drawable, aParams); + + // Send out an invalidation so that surfaces that are still in use get + // re-locked. See the discussion of the UnlockSurfaces call above. + mProgressTracker->SyncNotifyProgress(FLAG_FRAME_COMPLETE, + GetMaxSizedIntRect()); +} + + +void +VectorImage::Show(gfxDrawable* aDrawable, const SVGDrawingParameters& aParams) +{ + MOZ_ASSERT(aDrawable, "Should have a gfxDrawable by now"); + gfxUtils::DrawPixelSnapped(aParams.context, aDrawable, + aParams.size, + aParams.region, + SurfaceFormat::B8G8R8A8, + aParams.samplingFilter, + aParams.flags, aParams.opacity); + + MOZ_ASSERT(mRenderingObserver, "Should have a rendering observer by now"); + mRenderingObserver->ResumeHonoringInvalidations(); +} + +void +VectorImage::RecoverFromLossOfSurfaces() +{ + NS_WARNING("An imgFrame became invalid. Attempting to recover..."); + + // Discard all existing frames, since they're probably all now invalid. + SurfaceCache::RemoveImage(ImageKey(this)); +} + +NS_IMETHODIMP +VectorImage::StartDecoding() +{ + // Nothing to do for SVG images + return NS_OK; +} + +NS_IMETHODIMP +VectorImage::RequestDecodeForSize(const nsIntSize& aSize, uint32_t aFlags) +{ + // Nothing to do for SVG images, though in theory we could rasterize to the + // provided size ahead of time if we supported off-main-thread SVG + // rasterization... + return NS_OK; +} + +//****************************************************************************** + +NS_IMETHODIMP +VectorImage::LockImage() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (mError) { + return NS_ERROR_FAILURE; + } + + mLockCount++; + + if (mLockCount == 1) { + // Lock this image's surfaces in the SurfaceCache. + SurfaceCache::LockImage(ImageKey(this)); + } + + return NS_OK; +} + +//****************************************************************************** + +NS_IMETHODIMP +VectorImage::UnlockImage() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (mError) { + return NS_ERROR_FAILURE; + } + + if (mLockCount == 0) { + MOZ_ASSERT_UNREACHABLE("Calling UnlockImage with a zero lock count"); + return NS_ERROR_ABORT; + } + + mLockCount--; + + if (mLockCount == 0) { + // Unlock this image's surfaces in the SurfaceCache. + SurfaceCache::UnlockImage(ImageKey(this)); + } + + return NS_OK; +} + +//****************************************************************************** + +NS_IMETHODIMP +VectorImage::RequestDiscard() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (mDiscardable && mLockCount == 0) { + SurfaceCache::RemoveImage(ImageKey(this)); + mProgressTracker->OnDiscard(); + } + + return NS_OK; +} + +void +VectorImage::OnSurfaceDiscarded() +{ + MOZ_ASSERT(mProgressTracker); + + NS_DispatchToMainThread(NewRunnableMethod(mProgressTracker, &ProgressTracker::OnDiscard)); +} + +//****************************************************************************** +NS_IMETHODIMP +VectorImage::ResetAnimation() +{ + if (mError) { + return NS_ERROR_FAILURE; + } + + if (!mIsFullyLoaded || !mHaveAnimations) { + return NS_OK; // There are no animations to be reset. + } + + mSVGDocumentWrapper->ResetAnimation(); + + return NS_OK; +} + +NS_IMETHODIMP_(float) +VectorImage::GetFrameIndex(uint32_t aWhichFrame) +{ + MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE, "Invalid argument"); + return aWhichFrame == FRAME_FIRST + ? 0.0f + : mSVGDocumentWrapper->GetCurrentTime(); +} + +//------------------------------------------------------------------------------ +// nsIRequestObserver methods + +//****************************************************************************** +NS_IMETHODIMP +VectorImage::OnStartRequest(nsIRequest* aRequest, nsISupports* aCtxt) +{ + MOZ_ASSERT(!mSVGDocumentWrapper, + "Repeated call to OnStartRequest -- can this happen?"); + + mSVGDocumentWrapper = new SVGDocumentWrapper(); + nsresult rv = mSVGDocumentWrapper->OnStartRequest(aRequest, aCtxt); + if (NS_FAILED(rv)) { + mSVGDocumentWrapper = nullptr; + mError = true; + return rv; + } + + // ProgressTracker::SyncNotifyProgress may release us, so ensure we + // stick around long enough to complete our work. + RefPtr kungFuDeathGrip(this); + + // Block page load until the document's ready. (We unblock it in + // OnSVGDocumentLoaded or OnSVGDocumentError.) + if (mProgressTracker) { + mProgressTracker->SyncNotifyProgress(FLAG_ONLOAD_BLOCKED); + } + + // Create a listener to wait until the SVG document is fully loaded, which + // will signal that this image is ready to render. Certain error conditions + // will prevent us from ever getting this notification, so we also create a + // listener that waits for parsing to complete and cancels the + // SVGLoadEventListener if needed. The listeners are automatically attached + // to the document by their constructors. + nsIDocument* document = mSVGDocumentWrapper->GetDocument(); + mLoadEventListener = new SVGLoadEventListener(document, this); + mParseCompleteListener = new SVGParseCompleteListener(document, this); + + return NS_OK; +} + +//****************************************************************************** +NS_IMETHODIMP +VectorImage::OnStopRequest(nsIRequest* aRequest, nsISupports* aCtxt, + nsresult aStatus) +{ + if (mError) { + return NS_ERROR_FAILURE; + } + + return mSVGDocumentWrapper->OnStopRequest(aRequest, aCtxt, aStatus); +} + +void +VectorImage::OnSVGDocumentParsed() +{ + MOZ_ASSERT(mParseCompleteListener, "Should have the parse complete listener"); + MOZ_ASSERT(mLoadEventListener, "Should have the load event listener"); + + if (!mSVGDocumentWrapper->GetRootSVGElem()) { + // This is an invalid SVG document. It may have failed to parse, or it may + // be missing the root element, or the root element may not + // declare the correct namespace. In any of these cases, we'll never be + // notified that the SVG finished loading, so we need to treat this as an + // error. + OnSVGDocumentError(); + } +} + +void +VectorImage::CancelAllListeners() +{ + if (mParseCompleteListener) { + mParseCompleteListener->Cancel(); + mParseCompleteListener = nullptr; + } + if (mLoadEventListener) { + mLoadEventListener->Cancel(); + mLoadEventListener = nullptr; + } +} + +void +VectorImage::OnSVGDocumentLoaded() +{ + MOZ_ASSERT(mSVGDocumentWrapper->GetRootSVGElem(), + "Should have parsed successfully"); + MOZ_ASSERT(!mIsFullyLoaded && !mHaveAnimations, + "These flags shouldn't get set until OnSVGDocumentLoaded. " + "Duplicate calls to OnSVGDocumentLoaded?"); + + CancelAllListeners(); + + // XXX Flushing is wasteful if embedding frame hasn't had initial reflow. + mSVGDocumentWrapper->FlushLayout(); + + mIsFullyLoaded = true; + mHaveAnimations = mSVGDocumentWrapper->IsAnimated(); + + // Start listening to our image for rendering updates. + mRenderingObserver = new SVGRootRenderingObserver(mSVGDocumentWrapper, this); + + // ProgressTracker::SyncNotifyProgress may release us, so ensure we + // stick around long enough to complete our work. + RefPtr kungFuDeathGrip(this); + + // Tell *our* observers that we're done loading. + if (mProgressTracker) { + Progress progress = FLAG_SIZE_AVAILABLE | + FLAG_HAS_TRANSPARENCY | + FLAG_FRAME_COMPLETE | + FLAG_DECODE_COMPLETE | + FLAG_ONLOAD_UNBLOCKED; + + if (mHaveAnimations) { + progress |= FLAG_IS_ANIMATED; + } + + // Merge in any saved progress from OnImageDataComplete. + if (mLoadProgress) { + progress |= *mLoadProgress; + mLoadProgress = Nothing(); + } + + mProgressTracker->SyncNotifyProgress(progress, GetMaxSizedIntRect()); + } + + EvaluateAnimation(); +} + +void +VectorImage::OnSVGDocumentError() +{ + CancelAllListeners(); + + mError = true; + + if (mProgressTracker) { + // Notify observers about the error and unblock page load. + Progress progress = FLAG_ONLOAD_UNBLOCKED | FLAG_HAS_ERROR; + + // Merge in any saved progress from OnImageDataComplete. + if (mLoadProgress) { + progress |= *mLoadProgress; + mLoadProgress = Nothing(); + } + + mProgressTracker->SyncNotifyProgress(progress); + } +} + +//------------------------------------------------------------------------------ +// nsIStreamListener method + +//****************************************************************************** +NS_IMETHODIMP +VectorImage::OnDataAvailable(nsIRequest* aRequest, nsISupports* aCtxt, + nsIInputStream* aInStr, uint64_t aSourceOffset, + uint32_t aCount) +{ + if (mError) { + return NS_ERROR_FAILURE; + } + + return mSVGDocumentWrapper->OnDataAvailable(aRequest, aCtxt, aInStr, + aSourceOffset, aCount); +} + +// -------------------------- +// Invalidation helper method + +void +VectorImage::InvalidateObserversOnNextRefreshDriverTick() +{ + if (mHaveAnimations) { + mHasPendingInvalidation = true; + } else { + SendInvalidationNotifications(); + } +} + +void +VectorImage::PropagateUseCounters(nsIDocument* aParentDocument) +{ + nsIDocument* doc = mSVGDocumentWrapper->GetDocument(); + if (doc) { + doc->PropagateUseCounters(aParentDocument); + } +} + +void +VectorImage::ReportUseCounters() +{ + nsIDocument* doc = mSVGDocumentWrapper->GetDocument(); + if (doc) { + static_cast(doc)->ReportUseCounters(); + } +} + +nsIntSize +VectorImage::OptimalImageSizeForDest(const gfxSize& aDest, + uint32_t aWhichFrame, + SamplingFilter aSamplingFilter, + uint32_t aFlags) +{ + MOZ_ASSERT(aDest.width >= 0 || ceil(aDest.width) <= INT32_MAX || + aDest.height >= 0 || ceil(aDest.height) <= INT32_MAX, + "Unexpected destination size"); + + // We can rescale SVGs freely, so just return the provided destination size. + return nsIntSize::Ceil(aDest.width, aDest.height); +} + +already_AddRefed +VectorImage::Unwrap() +{ + nsCOMPtr self(this); + return self.forget(); +} + +} // namespace image +} // namespace mozilla diff --git a/image/VectorImage.h b/image/VectorImage.h new file mode 100644 index 000000000..e19aa939f --- /dev/null +++ b/image/VectorImage.h @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_VectorImage_h +#define mozilla_image_VectorImage_h + +#include "Image.h" +#include "nsIStreamListener.h" +#include "mozilla/MemoryReporting.h" + +class nsIRequest; +class gfxDrawable; + +namespace mozilla { +namespace image { + +struct SVGDrawingParameters; +class SVGDocumentWrapper; +class SVGRootRenderingObserver; +class SVGLoadEventListener; +class SVGParseCompleteListener; + +class VectorImage final : public ImageResource, + public nsIStreamListener +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_IMGICONTAINER + + // (no public constructor - use ImageFactory) + + // Methods inherited from Image + virtual size_t SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) + const override; + virtual void CollectSizeOfSurfaces(nsTArray& aCounters, + MallocSizeOf aMallocSizeOf) const override; + + virtual nsresult OnImageDataAvailable(nsIRequest* aRequest, + nsISupports* aContext, + nsIInputStream* aInStr, + uint64_t aSourceOffset, + uint32_t aCount) override; + virtual nsresult OnImageDataComplete(nsIRequest* aRequest, + nsISupports* aContext, + nsresult aResult, + bool aLastPart) override; + + virtual void OnSurfaceDiscarded() override; + + /** + * Callback for SVGRootRenderingObserver. + * + * This just sets a dirty flag that we check in VectorImage::RequestRefresh, + * which is called under the ticks of the refresh driver of any observing + * documents that we may have. Only then (after all animations in this image + * have been updated) do we send out "frame changed" notifications, + */ + void InvalidateObserversOnNextRefreshDriverTick(); + + // Callback for SVGParseCompleteListener. + void OnSVGDocumentParsed(); + + // Callbacks for SVGLoadEventListener. + void OnSVGDocumentLoaded(); + void OnSVGDocumentError(); + + virtual void ReportUseCounters() override; + +protected: + explicit VectorImage(ImageURL* aURI = nullptr); + virtual ~VectorImage(); + + virtual nsresult StartAnimation() override; + virtual nsresult StopAnimation() override; + virtual bool ShouldAnimate() override; + +private: + /// Attempt to find a cached surface matching @aParams in the SurfaceCache. + already_AddRefed + LookupCachedSurface(const SVGDrawingParameters& aParams); + + void CreateSurfaceAndShow(const SVGDrawingParameters& aParams, + gfx::BackendType aBackend); + void Show(gfxDrawable* aDrawable, const SVGDrawingParameters& aParams); + + nsresult Init(const char* aMimeType, uint32_t aFlags); + + /** + * In catastrophic circumstances like a GPU driver crash, we may lose our + * surfaces even if they're locked. RecoverFromLossOfSurfaces discards all + * existing surfaces, allowing us to recover. + */ + void RecoverFromLossOfSurfaces(); + + void CancelAllListeners(); + void SendInvalidationNotifications(); + + RefPtr mSVGDocumentWrapper; + RefPtr mRenderingObserver; + RefPtr mLoadEventListener; + RefPtr mParseCompleteListener; + + /// Count of locks on this image (roughly correlated to visible instances). + uint32_t mLockCount; + + // Stored result from the Necko load of the image, which we save in + // OnImageDataComplete if the underlying SVG document isn't loaded. If we save + // this, we actually notify this progress (and clear this value) in + // OnSVGDocumentLoaded or OnSVGDocumentError. + Maybe mLoadProgress; + + bool mIsInitialized; // Have we been initialized? + bool mDiscardable; // Are we discardable? + bool mIsFullyLoaded; // Has the SVG document finished + // loading? + bool mIsDrawing; // Are we currently drawing? + bool mHaveAnimations; // Is our SVG content SMIL-animated? + // (Only set after mIsFullyLoaded.) + bool mHasPendingInvalidation; // Invalidate observers next refresh + // driver tick. + + friend class ImageFactory; +}; + +inline NS_IMETHODIMP VectorImage::GetAnimationMode(uint16_t* aAnimationMode) { + return GetAnimationModeInternal(aAnimationMode); +} + +inline NS_IMETHODIMP VectorImage::SetAnimationMode(uint16_t aAnimationMode) { + return SetAnimationModeInternal(aAnimationMode); +} + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_VectorImage_h diff --git a/image/build/moz.build b/image/build/moz.build new file mode 100644 index 000000000..78db731c7 --- /dev/null +++ b/image/build/moz.build @@ -0,0 +1,22 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + 'nsImageModule.h', +] + +SOURCES += [ + 'nsImageModule.cpp', +] + +FINAL_LIBRARY = 'xul' +LOCAL_INCLUDES += [ + '/image', + '/image/encoders/bmp', + '/image/encoders/ico', + '/image/encoders/jpeg', + '/image/encoders/png', +] diff --git a/image/build/nsImageModule.cpp b/image/build/nsImageModule.cpp new file mode 100644 index 000000000..48d246cd3 --- /dev/null +++ b/image/build/nsImageModule.cpp @@ -0,0 +1,136 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsImageModule.h" + +#include "mozilla/ModuleUtils.h" +#include "nsMimeTypes.h" + +#include "DecodePool.h" +#include "ImageFactory.h" +#include "ShutdownTracker.h" +#include "SurfaceCache.h" +#include "SurfacePipe.h" + +#include "gfxPrefs.h" +#include "imgLoader.h" +#include "imgRequest.h" +#include "imgRequestProxy.h" +#include "imgTools.h" + +#include "nsICOEncoder.h" +#include "nsPNGEncoder.h" +#include "nsJPEGEncoder.h" +#include "nsBMPEncoder.h" + +// objects that just require generic constructors +using namespace mozilla::image; + +// XXX We would like to get rid of the imgLoader factory constructor. See the +// comment documenting the imgLoader constructor. +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(imgLoader, Init) +NS_GENERIC_FACTORY_CONSTRUCTOR(imgRequestProxy) +NS_GENERIC_FACTORY_CONSTRUCTOR(imgTools) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsICOEncoder) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsJPEGEncoder) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsPNGEncoder) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsBMPEncoder) +NS_DEFINE_NAMED_CID(NS_IMGLOADER_CID); +NS_DEFINE_NAMED_CID(NS_IMGREQUESTPROXY_CID); +NS_DEFINE_NAMED_CID(NS_IMGTOOLS_CID); +NS_DEFINE_NAMED_CID(NS_ICOENCODER_CID); +NS_DEFINE_NAMED_CID(NS_JPEGENCODER_CID); +NS_DEFINE_NAMED_CID(NS_PNGENCODER_CID); +NS_DEFINE_NAMED_CID(NS_BMPENCODER_CID); + +static const mozilla::Module::CIDEntry kImageCIDs[] = { + { &kNS_IMGLOADER_CID, false, nullptr, imgLoaderConstructor, }, + { &kNS_IMGREQUESTPROXY_CID, false, nullptr, imgRequestProxyConstructor, }, + { &kNS_IMGTOOLS_CID, false, nullptr, imgToolsConstructor, }, + { &kNS_ICOENCODER_CID, false, nullptr, nsICOEncoderConstructor, }, + { &kNS_JPEGENCODER_CID, false, nullptr, nsJPEGEncoderConstructor, }, + { &kNS_PNGENCODER_CID, false, nullptr, nsPNGEncoderConstructor, }, + { &kNS_BMPENCODER_CID, false, nullptr, nsBMPEncoderConstructor, }, + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry kImageContracts[] = { + { "@mozilla.org/image/cache;1", &kNS_IMGLOADER_CID }, + { "@mozilla.org/image/loader;1", &kNS_IMGLOADER_CID }, + { "@mozilla.org/image/request;1", &kNS_IMGREQUESTPROXY_CID }, + { "@mozilla.org/image/tools;1", &kNS_IMGTOOLS_CID }, + { "@mozilla.org/image/encoder;2?type=" IMAGE_ICO_MS, &kNS_ICOENCODER_CID }, + { "@mozilla.org/image/encoder;2?type=" IMAGE_JPEG, &kNS_JPEGENCODER_CID }, + { "@mozilla.org/image/encoder;2?type=" IMAGE_PNG, &kNS_PNGENCODER_CID }, + { "@mozilla.org/image/encoder;2?type=" IMAGE_BMP, &kNS_BMPENCODER_CID }, + { nullptr } +}; + +static const mozilla::Module::CategoryEntry kImageCategories[] = { + { "Gecko-Content-Viewers", IMAGE_GIF, "@mozilla.org/content/document-loader-factory;1" }, + { "Gecko-Content-Viewers", IMAGE_JPEG, "@mozilla.org/content/document-loader-factory;1" }, + { "Gecko-Content-Viewers", IMAGE_PJPEG, "@mozilla.org/content/document-loader-factory;1" }, + { "Gecko-Content-Viewers", IMAGE_JPG, "@mozilla.org/content/document-loader-factory;1" }, + { "Gecko-Content-Viewers", IMAGE_ICO, "@mozilla.org/content/document-loader-factory;1" }, + { "Gecko-Content-Viewers", IMAGE_ICO_MS, "@mozilla.org/content/document-loader-factory;1" }, + { "Gecko-Content-Viewers", IMAGE_BMP, "@mozilla.org/content/document-loader-factory;1" }, + { "Gecko-Content-Viewers", IMAGE_BMP_MS, "@mozilla.org/content/document-loader-factory;1" }, + { "Gecko-Content-Viewers", IMAGE_ICON_MS, "@mozilla.org/content/document-loader-factory;1" }, + { "Gecko-Content-Viewers", IMAGE_PNG, "@mozilla.org/content/document-loader-factory;1" }, + { "Gecko-Content-Viewers", IMAGE_APNG, "@mozilla.org/content/document-loader-factory;1" }, + { "Gecko-Content-Viewers", IMAGE_X_PNG, "@mozilla.org/content/document-loader-factory;1" }, + { "content-sniffing-services", "@mozilla.org/image/loader;1", "@mozilla.org/image/loader;1" }, + { nullptr } +}; + +static bool sInitialized = false; +nsresult +mozilla::image::EnsureModuleInitialized() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (sInitialized) { + return NS_OK; + } + + // Make sure the preferences are initialized + gfxPrefs::GetSingleton(); + + mozilla::image::ShutdownTracker::Initialize(); + mozilla::image::ImageFactory::Initialize(); + mozilla::image::DecodePool::Initialize(); + mozilla::image::SurfaceCache::Initialize(); + mozilla::image::SurfacePipe::Initialize(); + imgLoader::GlobalInit(); + sInitialized = true; + return NS_OK; +} + +void +mozilla::image::ShutdownModule() +{ + if (!sInitialized) { + return; + } + imgLoader::Shutdown(); + mozilla::image::SurfaceCache::Shutdown(); + sInitialized = false; +} + +static const mozilla::Module kImageModule = { + mozilla::Module::kVersion, + kImageCIDs, + kImageContracts, + kImageCategories, + nullptr, + mozilla::image::EnsureModuleInitialized, + // We need to be careful about shutdown ordering to avoid intermittent crashes + // when hashtable enumeration decides to destroy modules in an unfortunate + // order. So our shutdown is invoked explicitly during layout module shutdown. + nullptr +}; + +NSMODULE_DEFN(nsImageLib2Module) = &kImageModule; diff --git a/image/build/nsImageModule.h b/image/build/nsImageModule.h new file mode 100644 index 000000000..bb2ace0f3 --- /dev/null +++ b/image/build/nsImageModule.h @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_build_nsImageModule_h +#define mozilla_image_build_nsImageModule_h + +#include "nsError.h" + +namespace mozilla { +namespace image { + +nsresult EnsureModuleInitialized(); +void ShutdownModule(); + +} /* namespace image */ +} /* namespace mozilla */ + +#endif // mozilla_image_build_nsImageModule_h diff --git a/image/decoders/EXIF.cpp b/image/decoders/EXIF.cpp new file mode 100644 index 000000000..8197c886c --- /dev/null +++ b/image/decoders/EXIF.cpp @@ -0,0 +1,331 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "EXIF.h" + +#include "mozilla/EndianUtils.h" + +namespace mozilla { +namespace image { + +// Section references in this file refer to the EXIF v2.3 standard, also known +// as CIPA DC-008-Translation-2010. + +// See Section 4.6.4, Table 4. +// Typesafe enums are intentionally not used here since we're comparing to raw +// integers produced by parsing. +enum EXIFTag +{ + OrientationTag = 0x112, +}; + +// See Section 4.6.2. +enum EXIFType +{ + ByteType = 1, + ASCIIType = 2, + ShortType = 3, + LongType = 4, + RationalType = 5, + UndefinedType = 7, + SignedLongType = 9, + SignedRational = 10, +}; + +static const char* EXIFHeader = "Exif\0\0"; +static const uint32_t EXIFHeaderLength = 6; + +///////////////////////////////////////////////////////////// +// Parse EXIF data, typically found in a JPEG's APP1 segment. +///////////////////////////////////////////////////////////// +EXIFData +EXIFParser::ParseEXIF(const uint8_t* aData, const uint32_t aLength) +{ + if (!Initialize(aData, aLength)) { + return EXIFData(); + } + + if (!ParseEXIFHeader()) { + return EXIFData(); + } + + uint32_t offsetIFD; + if (!ParseTIFFHeader(offsetIFD)) { + return EXIFData(); + } + + JumpTo(offsetIFD); + + Orientation orientation; + if (!ParseIFD0(orientation)) { + return EXIFData(); + } + + // We only care about orientation at this point, so we don't bother with the + // other IFDs. If we got this far we're done. + return EXIFData(orientation); +} + +///////////////////////////////////////////////////////// +// Parse the EXIF header. (Section 4.7.2, Figure 30) +///////////////////////////////////////////////////////// +bool +EXIFParser::ParseEXIFHeader() +{ + return MatchString(EXIFHeader, EXIFHeaderLength); +} + +///////////////////////////////////////////////////////// +// Parse the TIFF header. (Section 4.5.2, Table 1) +///////////////////////////////////////////////////////// +bool +EXIFParser::ParseTIFFHeader(uint32_t& aIFD0OffsetOut) +{ + // Determine byte order. + if (MatchString("MM\0*", 4)) { + mByteOrder = ByteOrder::BigEndian; + } else if (MatchString("II*\0", 4)) { + mByteOrder = ByteOrder::LittleEndian; + } else { + return false; + } + + // Determine offset of the 0th IFD. (It shouldn't be greater than 64k, which + // is the maximum size of the entry APP1 segment.) + uint32_t ifd0Offset; + if (!ReadUInt32(ifd0Offset) || ifd0Offset > 64 * 1024) { + return false; + } + + // The IFD offset is relative to the beginning of the TIFF header, which + // begins after the EXIF header, so we need to increase the offset + // appropriately. + aIFD0OffsetOut = ifd0Offset + EXIFHeaderLength; + return true; +} + +///////////////////////////////////////////////////////// +// Parse the entries in IFD0. (Section 4.6.2) +///////////////////////////////////////////////////////// +bool +EXIFParser::ParseIFD0(Orientation& aOrientationOut) +{ + uint16_t entryCount; + if (!ReadUInt16(entryCount)) { + return false; + } + + for (uint16_t entry = 0 ; entry < entryCount ; ++entry) { + // Read the fields of the entry. + uint16_t tag; + if (!ReadUInt16(tag)) { + return false; + } + + // Right now, we only care about orientation, so we immediately skip to the + // next entry if we find anything else. + if (tag != OrientationTag) { + Advance(10); + continue; + } + + uint16_t type; + if (!ReadUInt16(type)) { + return false; + } + + uint32_t count; + if (!ReadUInt32(count)) { + return false; + } + + // We should have an orientation value here; go ahead and parse it. + if (!ParseOrientation(type, count, aOrientationOut)) { + return false; + } + + // Since the orientation is all we care about, we're done. + return true; + } + + // We didn't find an orientation field in the IFD. That's OK; we assume the + // default orientation in that case. + aOrientationOut = Orientation(); + return true; +} + +bool +EXIFParser::ParseOrientation(uint16_t aType, uint32_t aCount, Orientation& aOut) +{ + // Sanity check the type and count. + if (aType != ShortType || aCount != 1) { + return false; + } + + uint16_t value; + if (!ReadUInt16(value)) { + return false; + } + + switch (value) { + case 1: aOut = Orientation(Angle::D0, Flip::Unflipped); break; + case 2: aOut = Orientation(Angle::D0, Flip::Horizontal); break; + case 3: aOut = Orientation(Angle::D180, Flip::Unflipped); break; + case 4: aOut = Orientation(Angle::D180, Flip::Horizontal); break; + case 5: aOut = Orientation(Angle::D90, Flip::Horizontal); break; + case 6: aOut = Orientation(Angle::D90, Flip::Unflipped); break; + case 7: aOut = Orientation(Angle::D270, Flip::Horizontal); break; + case 8: aOut = Orientation(Angle::D270, Flip::Unflipped); break; + default: return false; + } + + // This is a 32-bit field, but the orientation value only occupies the first + // 16 bits. We need to advance another 16 bits to consume the entire field. + Advance(2); + return true; +} + +bool +EXIFParser::Initialize(const uint8_t* aData, const uint32_t aLength) +{ + if (aData == nullptr) { + return false; + } + + // An APP1 segment larger than 64k violates the JPEG standard. + if (aLength > 64 * 1024) { + return false; + } + + mStart = mCurrent = aData; + mLength = mRemainingLength = aLength; + mByteOrder = ByteOrder::Unknown; + return true; +} + +void +EXIFParser::Advance(const uint32_t aDistance) +{ + if (mRemainingLength >= aDistance) { + mCurrent += aDistance; + mRemainingLength -= aDistance; + } else { + mCurrent = mStart; + mRemainingLength = 0; + } +} + +void +EXIFParser::JumpTo(const uint32_t aOffset) +{ + if (mLength >= aOffset) { + mCurrent = mStart + aOffset; + mRemainingLength = mLength - aOffset; + } else { + mCurrent = mStart; + mRemainingLength = 0; + } +} + +bool +EXIFParser::MatchString(const char* aString, const uint32_t aLength) +{ + if (mRemainingLength < aLength) { + return false; + } + + for (uint32_t i = 0 ; i < aLength ; ++i) { + if (mCurrent[i] != aString[i]) { + return false; + } + } + + Advance(aLength); + return true; +} + +bool +EXIFParser::MatchUInt16(const uint16_t aValue) +{ + if (mRemainingLength < 2) { + return false; + } + + bool matched; + switch (mByteOrder) { + case ByteOrder::LittleEndian: + matched = LittleEndian::readUint16(mCurrent) == aValue; + break; + case ByteOrder::BigEndian: + matched = BigEndian::readUint16(mCurrent) == aValue; + break; + default: + NS_NOTREACHED("Should know the byte order by now"); + matched = false; + } + + if (matched) { + Advance(2); + } + + return matched; +} + +bool +EXIFParser::ReadUInt16(uint16_t& aValue) +{ + if (mRemainingLength < 2) { + return false; + } + + bool matched = true; + switch (mByteOrder) { + case ByteOrder::LittleEndian: + aValue = LittleEndian::readUint16(mCurrent); + break; + case ByteOrder::BigEndian: + aValue = BigEndian::readUint16(mCurrent); + break; + default: + NS_NOTREACHED("Should know the byte order by now"); + matched = false; + } + + if (matched) { + Advance(2); + } + + return matched; +} + +bool +EXIFParser::ReadUInt32(uint32_t& aValue) +{ + if (mRemainingLength < 4) { + return false; + } + + bool matched = true; + switch (mByteOrder) { + case ByteOrder::LittleEndian: + aValue = LittleEndian::readUint32(mCurrent); + break; + case ByteOrder::BigEndian: + aValue = BigEndian::readUint32(mCurrent); + break; + default: + NS_NOTREACHED("Should know the byte order by now"); + matched = false; + } + + if (matched) { + Advance(4); + } + + return matched; +} + +} // namespace image +} // namespace mozilla diff --git a/image/decoders/EXIF.h b/image/decoders/EXIF.h new file mode 100644 index 000000000..9028f2cf8 --- /dev/null +++ b/image/decoders/EXIF.h @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_decoders_EXIF_h +#define mozilla_image_decoders_EXIF_h + +#include +#include "nsDebug.h" + +#include "Orientation.h" + +namespace mozilla { +namespace image { + +enum class ByteOrder : uint8_t { + Unknown, + LittleEndian, + BigEndian +}; + +struct EXIFData +{ + EXIFData() { } + explicit EXIFData(Orientation aOrientation) : orientation(aOrientation) { } + + const Orientation orientation; +}; + +class EXIFParser +{ +public: + static EXIFData + Parse(const uint8_t* aData, const uint32_t aLength) + { + EXIFParser parser; + return parser.ParseEXIF(aData, aLength); + } + +private: + EXIFParser() + : mStart(nullptr) + , mCurrent(nullptr) + , mLength(0) + , mRemainingLength(0) + , mByteOrder(ByteOrder::Unknown) + { } + + EXIFData ParseEXIF(const uint8_t* aData, const uint32_t aLength); + bool ParseEXIFHeader(); + bool ParseTIFFHeader(uint32_t& aIFD0OffsetOut); + bool ParseIFD0(Orientation& aOrientationOut); + bool ParseOrientation(uint16_t aType, uint32_t aCount, Orientation& aOut); + + bool Initialize(const uint8_t* aData, const uint32_t aLength); + void Advance(const uint32_t aDistance); + void JumpTo(const uint32_t aOffset); + + bool MatchString(const char* aString, const uint32_t aLength); + bool MatchUInt16(const uint16_t aValue); + bool ReadUInt16(uint16_t& aOut); + bool ReadUInt32(uint32_t& aOut); + + const uint8_t* mStart; + const uint8_t* mCurrent; + uint32_t mLength; + uint32_t mRemainingLength; + ByteOrder mByteOrder; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_decoders_EXIF_h diff --git a/image/decoders/GIF2.h b/image/decoders/GIF2.h new file mode 100644 index 000000000..2521667d4 --- /dev/null +++ b/image/decoders/GIF2.h @@ -0,0 +1,65 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_decoders_GIF2_H +#define mozilla_image_decoders_GIF2_H + +#define MAX_LZW_BITS 12 +#define MAX_BITS 4097 // 2^MAX_LZW_BITS+1 +#define MAX_COLORS 256 +#define MIN_HOLD_SIZE 256 + +enum { GIF_TRAILER = 0x3B }; // ';' +enum { GIF_IMAGE_SEPARATOR = 0x2C }; // ',' +enum { GIF_EXTENSION_INTRODUCER = 0x21 }; // '!' +enum { GIF_GRAPHIC_CONTROL_LABEL = 0xF9 }; +enum { GIF_APPLICATION_EXTENSION_LABEL = 0xFF }; + +// A GIF decoder's state +typedef struct gif_struct { + // LZW decoder state machine + uint8_t* stackp; // Current stack pointer + int datasize; + int codesize; + int codemask; + int avail; // Index of next available slot in dictionary + int oldcode; + uint8_t firstchar; + int bits; // Number of unread bits in "datum" + int32_t datum; // 32-bit input buffer + + // Output state machine + int64_t pixels_remaining; // Pixels remaining to be output. + + // Parameters for image frame currently being decoded + int tpixel; // Index of transparent pixel + int32_t disposal_method; // Restore to background, leave in place, etc. + uint32_t* local_colormap; // Per-image colormap + int local_colormap_size; // Size of local colormap array. + uint32_t delay_time; // Display time, in milliseconds, + // for this image in a multi-image GIF + + // Global (multi-image) state + int version; // Either 89 for GIF89 or 87 for GIF87 + int32_t screen_width; // Logical screen width & height + int32_t screen_height; + uint8_t global_colormap_depth; // Depth of global colormap array + uint16_t global_colormap_count; // Number of colors in global colormap + int images_decoded; // Counts images for multi-part GIFs + int loop_count; // Netscape specific extension block to control + // the number of animation loops a GIF + // renders. + + bool is_transparent; // TRUE, if tpixel is valid + + uint16_t prefix[MAX_BITS]; // LZW decoding tables + uint32_t global_colormap[MAX_COLORS]; // Default colormap if local not + // supplied + uint8_t suffix[MAX_BITS]; // LZW decoding tables + uint8_t stack[MAX_BITS]; // Base of LZW decoder stack + +} gif_struct; + +#endif // mozilla_image_decoders_GIF2_H diff --git a/image/decoders/iccjpeg.c b/image/decoders/iccjpeg.c new file mode 100644 index 000000000..af014cf6b --- /dev/null +++ b/image/decoders/iccjpeg.c @@ -0,0 +1,195 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * iccjpeg.c + * + * This file provides code to read and write International Color Consortium + * (ICC) device profiles embedded in JFIF JPEG image files. The ICC has + * defined a standard format for including such data in JPEG "APP2" markers. + * The code given here does not know anything about the internal structure + * of the ICC profile data; it just knows how to put the profile data into + * a JPEG file being written, or get it back out when reading. + * + * This code depends on new features added to the IJG JPEG library as of + * IJG release 6b; it will not compile or work with older IJG versions. + * + * NOTE: this code would need surgery to work on 16-bit-int machines + * with ICC profiles exceeding 64K bytes in size. If you need to do that, + * change all the "unsigned int" variables to "INT32". You'll also need + * to find a malloc() replacement that can allocate more than 64K. + */ + +#include "iccjpeg.h" +#include /* define malloc() */ + + +/* + * Since an ICC profile can be larger than the maximum size of a JPEG marker + * (64K), we need provisions to split it into multiple markers. The format + * defined by the ICC specifies one or more APP2 markers containing the + * following data: + * Identifying string ASCII "ICC_PROFILE\0" (12 bytes) + * Marker sequence number 1 for first APP2, 2 for next, etc (1 byte) + * Number of markers Total number of APP2's used (1 byte) + * Profile data (remainder of APP2 data) + * Decoders should use the marker sequence numbers to reassemble the profile, + * rather than assuming that the APP2 markers appear in the correct sequence. + */ + +#define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */ +#define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */ +#define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */ +#define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN) + +/* + * Prepare for reading an ICC profile + */ + +void +setup_read_icc_profile (j_decompress_ptr cinfo) +{ + /* Tell the library to keep any APP2 data it may find */ + jpeg_save_markers(cinfo, ICC_MARKER, 0xFFFF); +} + + +/* + * Handy subroutine to test whether a saved marker is an ICC profile marker. + */ + +static boolean +marker_is_icc (jpeg_saved_marker_ptr marker) +{ + return + marker->marker == ICC_MARKER && + marker->data_length >= ICC_OVERHEAD_LEN && + /* verify the identifying string */ + GETJOCTET(marker->data[0]) == 0x49 && + GETJOCTET(marker->data[1]) == 0x43 && + GETJOCTET(marker->data[2]) == 0x43 && + GETJOCTET(marker->data[3]) == 0x5F && + GETJOCTET(marker->data[4]) == 0x50 && + GETJOCTET(marker->data[5]) == 0x52 && + GETJOCTET(marker->data[6]) == 0x4F && + GETJOCTET(marker->data[7]) == 0x46 && + GETJOCTET(marker->data[8]) == 0x49 && + GETJOCTET(marker->data[9]) == 0x4C && + GETJOCTET(marker->data[10]) == 0x45 && + GETJOCTET(marker->data[11]) == 0x0; +} + + +/* + * See if there was an ICC profile in the JPEG file being read; + * if so, reassemble and return the profile data. + * + * TRUE is returned if an ICC profile was found, FALSE if not. + * If TRUE is returned, *icc_data_ptr is set to point to the + * returned data, and *icc_data_len is set to its length. + * + * IMPORTANT: the data at **icc_data_ptr has been allocated with malloc() + * and must be freed by the caller with free() when the caller no longer + * needs it. (Alternatively, we could write this routine to use the + * IJG library's memory allocator, so that the data would be freed implicitly + * at jpeg_finish_decompress() time. But it seems likely that many apps + * will prefer to have the data stick around after decompression finishes.) + * + * NOTE: if the file contains invalid ICC APP2 markers, we just silently + * return FALSE. You might want to issue an error message instead. + */ + +boolean +read_icc_profile (j_decompress_ptr cinfo, + JOCTET** icc_data_ptr, + unsigned int* icc_data_len) +{ + jpeg_saved_marker_ptr marker; + int num_markers = 0; + int seq_no; + JOCTET* icc_data; + unsigned int total_length; +#define MAX_SEQ_NO 255 /* sufficient since marker numbers are bytes */ + char marker_present[MAX_SEQ_NO+1]; /* 1 if marker found */ + unsigned int data_length[MAX_SEQ_NO+1]; /* size of profile data in marker */ + unsigned int data_offset[MAX_SEQ_NO+1]; /* offset for data in marker */ + + *icc_data_ptr = NULL; /* avoid confusion if FALSE return */ + *icc_data_len = 0; + + /* This first pass over the saved markers discovers whether there are + * any ICC markers and verifies the consistency of the marker numbering. + */ + + for (seq_no = 1; seq_no <= MAX_SEQ_NO; seq_no++) { + marker_present[seq_no] = 0; + } + + for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) { + if (marker_is_icc(marker)) { + if (num_markers == 0) { + num_markers = GETJOCTET(marker->data[13]); + } else if (num_markers != GETJOCTET(marker->data[13])) { + return FALSE; /* inconsistent num_markers fields */ + } + seq_no = GETJOCTET(marker->data[12]); + if (seq_no <= 0 || seq_no > num_markers) { + return FALSE; /* bogus sequence number */ + } + if (marker_present[seq_no]) { + return FALSE; /* duplicate sequence numbers */ + } + marker_present[seq_no] = 1; + data_length[seq_no] = marker->data_length - ICC_OVERHEAD_LEN; + } + } + + if (num_markers == 0) { + return FALSE; + } + + /* Check for missing markers, count total space needed, + * compute offset of each marker's part of the data. + */ + + total_length = 0; + for (seq_no = 1; seq_no <= num_markers; seq_no++) { + if (marker_present[seq_no] == 0) { + return FALSE; /* missing sequence number */ + } + data_offset[seq_no] = total_length; + total_length += data_length[seq_no]; + } + + if (total_length <= 0) { + return FALSE; /* found only empty markers? */ + } + + /* Allocate space for assembled data */ + icc_data = (JOCTET*) malloc(total_length * sizeof(JOCTET)); + if (icc_data == NULL) { + return FALSE; /* oops, out of memory */ + } + + /* and fill it in */ + for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) { + if (marker_is_icc(marker)) { + JOCTET FAR* src_ptr; + JOCTET* dst_ptr; + unsigned int length; + seq_no = GETJOCTET(marker->data[12]); + dst_ptr = icc_data + data_offset[seq_no]; + src_ptr = marker->data + ICC_OVERHEAD_LEN; + length = data_length[seq_no]; + while (length--) { + *dst_ptr++ = *src_ptr++; + } + } + } + + *icc_data_ptr = icc_data; + *icc_data_len = total_length; + + return TRUE; +} diff --git a/image/decoders/iccjpeg.h b/image/decoders/iccjpeg.h new file mode 100644 index 000000000..474df719c --- /dev/null +++ b/image/decoders/iccjpeg.h @@ -0,0 +1,67 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * iccjpeg.h + * + * This file provides code to read and write International Color Consortium + * (ICC) device profiles embedded in JFIF JPEG image files. The ICC has + * defined a standard format for including such data in JPEG "APP2" markers. + * The code given here does not know anything about the internal structure + * of the ICC profile data; it just knows how to put the profile data into + * a JPEG file being written, or get it back out when reading. + * + * This code depends on new features added to the IJG JPEG library as of + * IJG release 6b; it will not compile or work with older IJG versions. + * + * NOTE: this code would need surgery to work on 16-bit-int machines + * with ICC profiles exceeding 64K bytes in size. See iccprofile.c + * for details. + */ + +#ifndef mozilla_image_decoders_iccjpeg_h +#define mozilla_image_decoders_iccjpeg_h + +#include /* needed to define "FILE", "NULL" */ +#include "jpeglib.h" + +/* + * Reading a JPEG file that may contain an ICC profile requires two steps: + * + * 1. After jpeg_create_decompress() but before jpeg_read_header(), + * call setup_read_icc_profile(). This routine tells the IJG library + * to save in memory any APP2 markers it may find in the file. + * + * 2. After jpeg_read_header(), call read_icc_profile() to find out + * whether there was a profile and obtain it if so. + */ + + +/* + * Prepare for reading an ICC profile + */ + +extern void setup_read_icc_profile JPP((j_decompress_ptr cinfo)); + + +/* + * See if there was an ICC profile in the JPEG file being read; + * if so, reassemble and return the profile data. + * + * TRUE is returned if an ICC profile was found, FALSE if not. + * If TRUE is returned, *icc_data_ptr is set to point to the + * returned data, and *icc_data_len is set to its length. + * + * IMPORTANT: the data at **icc_data_ptr has been allocated with malloc() + * and must be freed by the caller with free() when the caller no longer + * needs it. (Alternatively, we could write this routine to use the + * IJG library's memory allocator, so that the data would be freed implicitly + * at jpeg_finish_decompress() time. But it seems likely that many apps + * will prefer to have the data stick around after decompression finishes.) + */ + +extern boolean read_icc_profile JPP((j_decompress_ptr cinfo, + JOCTET** icc_data_ptr, + unsigned int* icc_data_len)); +#endif // mozilla_image_decoders_iccjpeg_h diff --git a/image/decoders/icon/android/moz.build b/image/decoders/icon/android/moz.build new file mode 100644 index 000000000..5e58ff0b6 --- /dev/null +++ b/image/decoders/icon/android/moz.build @@ -0,0 +1,13 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SOURCES += [ + 'nsIconChannel.cpp', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' diff --git a/image/decoders/icon/android/nsIconChannel.cpp b/image/decoders/icon/android/nsIconChannel.cpp new file mode 100644 index 000000000..5670bf2f9 --- /dev/null +++ b/image/decoders/icon/android/nsIconChannel.cpp @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include "mozilla/dom/ContentChild.h" +#include "nsMimeTypes.h" +#include "nsIURL.h" +#include "nsXULAppAPI.h" +#include "AndroidBridge.h" +#include "nsIconChannel.h" +#include "nsIStringStream.h" +#include "nsNetUtil.h" +#include "nsComponentManagerUtils.h" +#include "nsNullPrincipal.h" + +NS_IMPL_ISUPPORTS(nsIconChannel, + nsIRequest, + nsIChannel) + +using namespace mozilla; +using mozilla::dom::ContentChild; + +static nsresult +GetIconForExtension(const nsACString& aFileExt, uint32_t aIconSize, + uint8_t* const aBuf) +{ + if (!AndroidBridge::Bridge()) { + return NS_ERROR_FAILURE; + } + + AndroidBridge::Bridge()->GetIconForExtension(aFileExt, aIconSize, aBuf); + + return NS_OK; +} + +static nsresult +CallRemoteGetIconForExtension(const nsACString& aFileExt, uint32_t aIconSize, + uint8_t* const aBuf) +{ + NS_ENSURE_TRUE(aBuf != nullptr, NS_ERROR_NULL_POINTER); + + // An array has to be used to get data from remote process + InfallibleTArray bits; + uint32_t bufSize = aIconSize * aIconSize * 4; + + if (!ContentChild::GetSingleton()->SendGetIconForExtension( + PromiseFlatCString(aFileExt), aIconSize, &bits)) { + return NS_ERROR_FAILURE; + } + + NS_ASSERTION(bits.Length() == bufSize, "Pixels array is incomplete"); + if (bits.Length() != bufSize) { + return NS_ERROR_FAILURE; + } + + memcpy(aBuf, bits.Elements(), bufSize); + + return NS_OK; +} + +static nsresult +moz_icon_to_channel(nsIURI* aURI, const nsACString& aFileExt, + uint32_t aIconSize, nsIChannel** aChannel) +{ + NS_ENSURE_TRUE(aIconSize < 256 && aIconSize > 0, NS_ERROR_UNEXPECTED); + + int width = aIconSize; + int height = aIconSize; + + // moz-icon data should have two bytes for the size, + // then the ARGB pixel values with pre-multiplied Alpha + const int channels = 4; + long int buf_size = 2 + channels * height * width; + uint8_t* const buf = (uint8_t*)moz_xmalloc(buf_size); + NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY); + uint8_t* out = buf; + + *(out++) = width; + *(out++) = height; + + nsresult rv; + if (XRE_IsParentProcess()) { + rv = GetIconForExtension(aFileExt, aIconSize, out); + } else { + rv = CallRemoteGetIconForExtension(aFileExt, aIconSize, out); + } + NS_ENSURE_SUCCESS(rv, rv); + + // Encode the RGBA data + const uint8_t* in = out; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + uint8_t r = *(in++); + uint8_t g = *(in++); + uint8_t b = *(in++); + uint8_t a = *(in++); +#define DO_PREMULTIPLY(c_) uint8_t(uint16_t(c_) * uint16_t(a) / uint16_t(255)) + *(out++) = DO_PREMULTIPLY(b); + *(out++) = DO_PREMULTIPLY(g); + *(out++) = DO_PREMULTIPLY(r); + *(out++) = a; +#undef DO_PREMULTIPLY + } + } + + nsCOMPtr stream = + do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = stream->AdoptData((char*)buf, buf_size); + NS_ENSURE_SUCCESS(rv, rv); + + // nsIconProtocolHandler::NewChannel2 will provide the correct loadInfo for + // this iconChannel. Use the most restrictive security settings for the + // temporary loadInfo to make sure the channel can not be openend. + nsCOMPtr nullPrincipal = nsNullPrincipal::Create(); + return NS_NewInputStreamChannel(aChannel, + aURI, + stream, + nullPrincipal, + nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED, + nsIContentPolicy::TYPE_INTERNAL_IMAGE, + NS_LITERAL_CSTRING(IMAGE_ICON_MS)); +} + +nsresult +nsIconChannel::Init(nsIURI* aURI) +{ + nsCOMPtr iconURI = do_QueryInterface(aURI); + NS_ASSERTION(iconURI, "URI is not an nsIMozIconURI"); + + nsAutoCString stockIcon; + iconURI->GetStockIcon(stockIcon); + + uint32_t desiredImageSize; + iconURI->GetImageSize(&desiredImageSize); + + nsAutoCString iconFileExt; + iconURI->GetFileExtension(iconFileExt); + + return moz_icon_to_channel(iconURI, iconFileExt, desiredImageSize, + getter_AddRefs(mRealChannel)); +} diff --git a/image/decoders/icon/android/nsIconChannel.h b/image/decoders/icon/android/nsIconChannel.h new file mode 100644 index 000000000..be5542998 --- /dev/null +++ b/image/decoders/icon/android/nsIconChannel.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_decoders_icon_android_nsIconChannel_h +#define mozilla_image_decoders_icon_android_nsIconChannel_h + +#include "mozilla/Attributes.h" + +#include "nsIChannel.h" +#include "nsIURI.h" +#include "nsIIconURI.h" +#include "nsCOMPtr.h" + +/** + * This class is the Android implementation of nsIconChannel. + * It asks Android for an icon, and creates a new channel for + * that file to which all calls will be proxied. + */ +class nsIconChannel final : public nsIChannel { + public: + NS_DECL_ISUPPORTS + NS_FORWARD_NSIREQUEST(mRealChannel->) + NS_FORWARD_NSICHANNEL(mRealChannel->) + + nsIconChannel() { } + + /** + * Called by nsIconProtocolHandler after it creates this channel. + * Must be called before calling any other function on this object. + * If this method fails, no other function must be called on this object. + */ + nsresult Init(nsIURI* aURI); + + private: + ~nsIconChannel() { } + + /** + * The channel to the temp icon file (e.g. to /tmp/2qy9wjqw.html). + * Will always be non-null after a successful Init. + */ + nsCOMPtr mRealChannel; +}; + +#endif // mozilla_image_decoders_icon_android_nsIconChannel_h diff --git a/image/decoders/icon/gtk/moz.build b/image/decoders/icon/gtk/moz.build new file mode 100644 index 000000000..a8116ca87 --- /dev/null +++ b/image/decoders/icon/gtk/moz.build @@ -0,0 +1,16 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SOURCES += [ + 'nsIconChannel.cpp', +] + +FINAL_LIBRARY = 'xul' + +if CONFIG['MOZ_ENABLE_GNOMEUI']: + CXXFLAGS += CONFIG['MOZ_GNOMEUI_CFLAGS'] +else: + CXXFLAGS += CONFIG['TK_CFLAGS'] diff --git a/image/decoders/icon/gtk/nsIconChannel.cpp b/image/decoders/icon/gtk/nsIconChannel.cpp new file mode 100644 index 000000000..b014e4c04 --- /dev/null +++ b/image/decoders/icon/gtk/nsIconChannel.cpp @@ -0,0 +1,427 @@ +/* vim:set ts=2 sw=2 sts=2 cin et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIconChannel.h" + +#include +#include + +#include "mozilla/DebugOnly.h" +#include "mozilla/EndianUtils.h" +#include + +#ifdef MOZ_ENABLE_GIO +#include +#endif + +#include + +#include "nsMimeTypes.h" +#include "nsIMIMEService.h" + +#include "nsServiceManagerUtils.h" + +#include "nsNetUtil.h" +#include "nsComponentManagerUtils.h" +#include "nsIStringStream.h" +#include "nsServiceManagerUtils.h" +#include "nsNullPrincipal.h" +#include "nsIURL.h" +#include "prlink.h" + +NS_IMPL_ISUPPORTS(nsIconChannel, + nsIRequest, + nsIChannel) + +static nsresult +moz_gdk_pixbuf_to_channel(GdkPixbuf* aPixbuf, nsIURI* aURI, + nsIChannel** aChannel) +{ + int width = gdk_pixbuf_get_width(aPixbuf); + int height = gdk_pixbuf_get_height(aPixbuf); + NS_ENSURE_TRUE(height < 256 && width < 256 && height > 0 && width > 0 && + gdk_pixbuf_get_colorspace(aPixbuf) == GDK_COLORSPACE_RGB && + gdk_pixbuf_get_bits_per_sample(aPixbuf) == 8 && + gdk_pixbuf_get_has_alpha(aPixbuf) && + gdk_pixbuf_get_n_channels(aPixbuf) == 4, + NS_ERROR_UNEXPECTED); + + const int n_channels = 4; + gsize buf_size = 2 + n_channels * height * width; + uint8_t* const buf = (uint8_t*)moz_xmalloc(buf_size); + NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY); + uint8_t* out = buf; + + *(out++) = width; + *(out++) = height; + + const guchar* const pixels = gdk_pixbuf_get_pixels(aPixbuf); + int rowextra = gdk_pixbuf_get_rowstride(aPixbuf) - width * n_channels; + + // encode the RGB data and the A data + const guchar* in = pixels; + for (int y = 0; y < height; ++y, in += rowextra) { + for (int x = 0; x < width; ++x) { + uint8_t r = *(in++); + uint8_t g = *(in++); + uint8_t b = *(in++); + uint8_t a = *(in++); +#define DO_PREMULTIPLY(c_) uint8_t(uint16_t(c_) * uint16_t(a) / uint16_t(255)) +#if MOZ_LITTLE_ENDIAN + *(out++) = DO_PREMULTIPLY(b); + *(out++) = DO_PREMULTIPLY(g); + *(out++) = DO_PREMULTIPLY(r); + *(out++) = a; +#else + *(out++) = a; + *(out++) = DO_PREMULTIPLY(r); + *(out++) = DO_PREMULTIPLY(g); + *(out++) = DO_PREMULTIPLY(b); +#endif +#undef DO_PREMULTIPLY + } + } + + NS_ASSERTION(out == buf + buf_size, "size miscalculation"); + + nsresult rv; + nsCOMPtr stream = + do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv); + + // Prevent the leaking of buf + if (NS_WARN_IF(NS_FAILED(rv))) { + free(buf); + return rv; + } + + // stream takes ownership of buf and will free it on destruction. + // This function cannot fail. + rv = stream->AdoptData((char*)buf, buf_size); + + // If this no longer holds then re-examine buf's lifetime. + MOZ_ASSERT(NS_SUCCEEDED(rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // nsIconProtocolHandler::NewChannel2 will provide the correct loadInfo for + // this iconChannel. Use the most restrictive security settings for the + // temporary loadInfo to make sure the channel can not be openend. + nsCOMPtr nullPrincipal = nsNullPrincipal::Create(); + return NS_NewInputStreamChannel(aChannel, + aURI, + stream, + nullPrincipal, + nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED, + nsIContentPolicy::TYPE_INTERNAL_IMAGE, + NS_LITERAL_CSTRING(IMAGE_ICON_MS)); +} + +static GtkWidget* gProtoWindow = nullptr; +static GtkWidget* gStockImageWidget = nullptr; + +static void +ensure_stock_image_widget() +{ + // Only the style of the GtkImage needs to be used, but the widget is kept + // to track dynamic style changes. + if (!gProtoWindow) { + gProtoWindow = gtk_window_new(GTK_WINDOW_POPUP); + GtkWidget* protoLayout = gtk_fixed_new(); + gtk_container_add(GTK_CONTAINER(gProtoWindow), protoLayout); + + gStockImageWidget = gtk_image_new(); + gtk_container_add(GTK_CONTAINER(protoLayout), gStockImageWidget); + + gtk_widget_ensure_style(gStockImageWidget); + } +} + +static GtkIconSize +moz_gtk_icon_size(const char* name) +{ + if (strcmp(name, "button") == 0) { + return GTK_ICON_SIZE_BUTTON; + } + + if (strcmp(name, "menu") == 0) { + return GTK_ICON_SIZE_MENU; + } + + if (strcmp(name, "toolbar") == 0) { + return GTK_ICON_SIZE_LARGE_TOOLBAR; + } + + if (strcmp(name, "toolbarsmall") == 0) { + return GTK_ICON_SIZE_SMALL_TOOLBAR; + } + + if (strcmp(name, "dnd") == 0) { + return GTK_ICON_SIZE_DND; + } + + if (strcmp(name, "dialog") == 0) { + return GTK_ICON_SIZE_DIALOG; + } + + return GTK_ICON_SIZE_MENU; +} + +#ifdef MOZ_ENABLE_GIO +static int32_t +GetIconSize(nsIMozIconURI* aIconURI) +{ + nsAutoCString iconSizeString; + + aIconURI->GetIconSize(iconSizeString); + if (iconSizeString.IsEmpty()) { + uint32_t size; + mozilla::DebugOnly rv = aIconURI->GetImageSize(&size); + NS_ASSERTION(NS_SUCCEEDED(rv), "GetImageSize failed"); + return size; + } else { + int size; + + GtkIconSize icon_size = moz_gtk_icon_size(iconSizeString.get()); + gtk_icon_size_lookup(icon_size, &size, nullptr); + return size; + } +} + +/* Scale icon buffer to preferred size */ +static nsresult +ScaleIconBuf(GdkPixbuf** aBuf, int32_t iconSize) +{ + // Scale buffer only if width or height differ from preferred size + if (gdk_pixbuf_get_width(*aBuf) != iconSize && + gdk_pixbuf_get_height(*aBuf) != iconSize) { + GdkPixbuf* scaled = gdk_pixbuf_scale_simple(*aBuf, iconSize, iconSize, + GDK_INTERP_BILINEAR); + // replace original buffer by scaled + g_object_unref(*aBuf); + *aBuf = scaled; + if (!scaled) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + return NS_OK; +} + +nsresult +nsIconChannel::InitWithGIO(nsIMozIconURI* aIconURI) +{ + GIcon *icon = nullptr; + nsCOMPtr fileURI; + + // Read icon content + aIconURI->GetIconURL(getter_AddRefs(fileURI)); + + // Get icon for file specified by URI + if (fileURI) { + bool isFile; + nsAutoCString spec; + fileURI->GetAsciiSpec(spec); + if (NS_SUCCEEDED(fileURI->SchemeIs("file", &isFile)) && isFile) { + GFile* file = g_file_new_for_uri(spec.get()); + GFileInfo* fileInfo = g_file_query_info(file, + G_FILE_ATTRIBUTE_STANDARD_ICON, + G_FILE_QUERY_INFO_NONE, + nullptr, nullptr); + g_object_unref(file); + if (fileInfo) { + // icon from g_content_type_get_icon doesn't need unref + icon = g_file_info_get_icon(fileInfo); + if (icon) { + g_object_ref(icon); + } + g_object_unref(fileInfo); + } + } + } + + // Try to get icon by using MIME type + if (!icon) { + nsAutoCString type; + aIconURI->GetContentType(type); + // Try to get MIME type from file extension by using nsIMIMEService + if (type.IsEmpty()) { + nsCOMPtr ms(do_GetService("@mozilla.org/mime;1")); + if (ms) { + nsAutoCString fileExt; + aIconURI->GetFileExtension(fileExt); + ms->GetTypeFromExtension(fileExt, type); + } + } + char* ctype = nullptr; // character representation of content type + if (!type.IsEmpty()) { + ctype = g_content_type_from_mime_type(type.get()); + } + if (ctype) { + icon = g_content_type_get_icon(ctype); + g_free(ctype); + } + } + + // Get default icon theme + GtkIconTheme* iconTheme = gtk_icon_theme_get_default(); + GtkIconInfo* iconInfo = nullptr; + // Get icon size + int32_t iconSize = GetIconSize(aIconURI); + + if (icon) { + // Use icon and theme to get GtkIconInfo + iconInfo = gtk_icon_theme_lookup_by_gicon(iconTheme, + icon, iconSize, + (GtkIconLookupFlags)0); + g_object_unref(icon); + } + + if (!iconInfo) { + // Mozilla's mimetype lookup failed. Try the "unknown" icon. + iconInfo = gtk_icon_theme_lookup_icon(iconTheme, + "unknown", iconSize, + (GtkIconLookupFlags)0); + if (!iconInfo) { + return NS_ERROR_NOT_AVAILABLE; + } + } + + // Create a GdkPixbuf buffer containing icon and scale it + GdkPixbuf* buf = gtk_icon_info_load_icon(iconInfo, nullptr); + gtk_icon_info_free(iconInfo); + if (!buf) { + return NS_ERROR_UNEXPECTED; + } + + nsresult rv = ScaleIconBuf(&buf, iconSize); + NS_ENSURE_SUCCESS(rv, rv); + + rv = moz_gdk_pixbuf_to_channel(buf, aIconURI, + getter_AddRefs(mRealChannel)); + g_object_unref(buf); + return rv; +} +#endif // MOZ_ENABLE_GIO + +nsresult +nsIconChannel::Init(nsIURI* aURI) +{ + nsCOMPtr iconURI = do_QueryInterface(aURI); + NS_ASSERTION(iconURI, "URI is not an nsIMozIconURI"); + + nsAutoCString stockIcon; + iconURI->GetStockIcon(stockIcon); + if (stockIcon.IsEmpty()) { +#ifdef MOZ_ENABLE_GIO + return InitWithGIO(iconURI); +#else + return NS_ERROR_NOT_AVAILABLE; +#endif + } + + // Search for stockIcon + nsAutoCString iconSizeString; + iconURI->GetIconSize(iconSizeString); + + nsAutoCString iconStateString; + iconURI->GetIconState(iconStateString); + + GtkIconSize icon_size = moz_gtk_icon_size(iconSizeString.get()); + GtkStateType state = iconStateString.EqualsLiteral("disabled") ? + GTK_STATE_INSENSITIVE : GTK_STATE_NORMAL; + + // First lookup the icon by stock id and text direction. + GtkTextDirection direction = GTK_TEXT_DIR_NONE; + if (StringEndsWith(stockIcon, NS_LITERAL_CSTRING("-ltr"))) { + direction = GTK_TEXT_DIR_LTR; + } else if (StringEndsWith(stockIcon, NS_LITERAL_CSTRING("-rtl"))) { + direction = GTK_TEXT_DIR_RTL; + } + + bool forceDirection = direction != GTK_TEXT_DIR_NONE; + nsAutoCString stockID; + bool useIconName = false; + if (!forceDirection) { + direction = gtk_widget_get_default_direction(); + stockID = stockIcon; + } else { + // GTK versions < 2.22 use icon names from concatenating stock id with + // -(rtl|ltr), which is how the moz-icon stock name is interpreted here. + stockID = Substring(stockIcon, 0, stockIcon.Length() - 4); + // However, if we lookup bidi icons by the stock name, then GTK versions + // >= 2.22 will use a bidi lookup convention that most icon themes do not + // yet follow. Therefore, we first check to see if the theme supports the + // old icon name as this will have bidi support (if found). + GtkIconTheme* icon_theme = gtk_icon_theme_get_default(); + // Micking what gtk_icon_set_render_icon does with sizes, though it's not + // critical as icons will be scaled to suit size. It just means we follow + // the same pathes and so share caches. + gint width, height; + if (gtk_icon_size_lookup(icon_size, &width, &height)) { + gint size = std::min(width, height); + // We use gtk_icon_theme_lookup_icon() without + // GTK_ICON_LOOKUP_USE_BUILTIN instead of gtk_icon_theme_has_icon() so + // we don't pick up fallback icons added by distributions for backward + // compatibility. + GtkIconInfo* icon = + gtk_icon_theme_lookup_icon(icon_theme, stockIcon.get(), + size, (GtkIconLookupFlags)0); + if (icon) { + useIconName = true; + gtk_icon_info_free(icon); + } + } + } + + ensure_stock_image_widget(); + GtkStyle* style = gtk_widget_get_style(gStockImageWidget); + GtkIconSet* icon_set = nullptr; + if (!useIconName) { + icon_set = gtk_style_lookup_icon_set(style, stockID.get()); + } + + if (!icon_set) { + // Either we have choosen icon-name lookup for a bidi icon, or stockIcon is + // not a stock id so we assume it is an icon name. + useIconName = true; + // Creating a GtkIconSet is a convenient way to allow the style to + // render the icon, possibly with variations suitable for insensitive + // states. + icon_set = gtk_icon_set_new(); + GtkIconSource* icon_source = gtk_icon_source_new(); + + gtk_icon_source_set_icon_name(icon_source, stockIcon.get()); + gtk_icon_set_add_source(icon_set, icon_source); + gtk_icon_source_free(icon_source); + } + + GdkPixbuf* icon = + gtk_icon_set_render_icon(icon_set, style, direction, state, + icon_size, gStockImageWidget, nullptr); + if (useIconName) { + gtk_icon_set_unref(icon_set); + } + + // According to documentation, gtk_icon_set_render_icon() never returns + // nullptr, but it does return nullptr when we have the problem reported + // here: https://bugzilla.gnome.org/show_bug.cgi?id=629878#c13 + if (!icon) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv = moz_gdk_pixbuf_to_channel(icon, iconURI, + getter_AddRefs(mRealChannel)); + + g_object_unref(icon); + + return rv; +} + +void +nsIconChannel::Shutdown() { + if (gProtoWindow) { + gtk_widget_destroy(gProtoWindow); + gProtoWindow = nullptr; + gStockImageWidget = nullptr; + } +} diff --git a/image/decoders/icon/gtk/nsIconChannel.h b/image/decoders/icon/gtk/nsIconChannel.h new file mode 100644 index 000000000..5d59272d5 --- /dev/null +++ b/image/decoders/icon/gtk/nsIconChannel.h @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_decoders_icon_gtk_nsIconChannel_h +#define mozilla_image_decoders_icon_gtk_nsIconChannel_h + +#include "mozilla/Attributes.h" + +#include "nsIChannel.h" +#include "nsIStreamListener.h" +#include "nsIURI.h" +#include "nsIIconURI.h" +#include "nsCOMPtr.h" + +/// This class is the gnome implementation of nsIconChannel. It basically asks +/// gtk/gnome for an icon, saves it as a tmp icon, and creates a new channel for +/// that file to which all calls will be proxied. +class nsIconChannel final : public nsIChannel +{ + public: + NS_DECL_ISUPPORTS + NS_FORWARD_NSIREQUEST(mRealChannel->) + NS_FORWARD_NSICHANNEL(mRealChannel->) + + nsIconChannel() { } + + static void Shutdown(); + + /// Called by nsIconProtocolHandler after it creates this channel. + /// Must be called before calling any other function on this object. + /// If this method fails, no other function must be called on this object. + nsresult Init(nsIURI* aURI); + private: + ~nsIconChannel() { } + /// The channel to the temp icon file (e.g. to /tmp/2qy9wjqw.html). + /// Will always be non-null after a successful Init. + nsCOMPtr mRealChannel; + + nsresult InitWithGIO(nsIMozIconURI* aIconURI); +}; + +#endif // mozilla_image_decoders_icon_gtk_nsIconChannel_h diff --git a/image/decoders/icon/mac/moz.build b/image/decoders/icon/mac/moz.build new file mode 100644 index 000000000..158c326ea --- /dev/null +++ b/image/decoders/icon/mac/moz.build @@ -0,0 +1,11 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SOURCES += [ + 'nsIconChannelCocoa.mm', +] + +FINAL_LIBRARY = 'xul' diff --git a/image/decoders/icon/mac/nsIconChannel.h b/image/decoders/icon/mac/nsIconChannel.h new file mode 100644 index 000000000..9fef17119 --- /dev/null +++ b/image/decoders/icon/mac/nsIconChannel.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_encoders_icon_mac_nsIconChannel_h +#define mozilla_image_encoders_icon_mac_nsIconChannel_h + +#include "mozilla/Attributes.h" + +#include "nsCOMPtr.h" +#include "nsXPIDLString.h" +#include "nsIChannel.h" +#include "nsILoadGroup.h" +#include "nsILoadInfo.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIInputStreamPump.h" +#include "nsIStreamListener.h" +#include "nsIURI.h" + +class nsIFile; + +class nsIconChannel final : public nsIChannel, public nsIStreamListener +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUEST + NS_DECL_NSICHANNEL + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + nsIconChannel(); + + nsresult Init(nsIURI* uri); + +protected: + virtual ~nsIconChannel(); + + nsCOMPtr mUrl; + nsCOMPtr mOriginalURI; + nsCOMPtr mLoadGroup; + nsCOMPtr mCallbacks; + nsCOMPtr mOwner; + nsCOMPtr mLoadInfo; + + nsCOMPtr mPump; + nsCOMPtr mListener; + + nsresult MakeInputStream(nsIInputStream** _retval, bool nonBlocking); + + nsresult ExtractIconInfoFromUrl(nsIFile** aLocalFile, + uint32_t* aDesiredImageSize, + nsACString& aContentType, + nsACString& aFileExtension); +}; + +#endif // mozilla_image_encoders_icon_mac_nsIconChannel_h diff --git a/image/decoders/icon/mac/nsIconChannelCocoa.mm b/image/decoders/icon/mac/nsIconChannelCocoa.mm new file mode 100644 index 000000000..9c2686cdd --- /dev/null +++ b/image/decoders/icon/mac/nsIconChannelCocoa.mm @@ -0,0 +1,565 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsContentUtils.h" +#include "nsIconChannel.h" +#include "mozilla/EndianUtils.h" +#include "nsIIconURI.h" +#include "nsIServiceManager.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsXPIDLString.h" +#include "nsMimeTypes.h" +#include "nsMemory.h" +#include "nsIStringStream.h" +#include "nsIURL.h" +#include "nsNetCID.h" +#include "nsIPipe.h" +#include "nsIOutputStream.h" +#include "nsIMIMEService.h" +#include "nsCExternalHandlerService.h" +#include "nsILocalFileMac.h" +#include "nsIFileURL.h" +#include "nsTArray.h" +#include "nsObjCExceptions.h" +#include "nsProxyRelease.h" +#include "nsContentSecurityManager.h" + +#include + +// nsIconChannel methods +nsIconChannel::nsIconChannel() +{ +} + +nsIconChannel::~nsIconChannel() +{ + if (mLoadInfo) { + NS_ReleaseOnMainThread(mLoadInfo.forget()); + } +} + +NS_IMPL_ISUPPORTS(nsIconChannel, + nsIChannel, + nsIRequest, + nsIRequestObserver, + nsIStreamListener) + +nsresult +nsIconChannel::Init(nsIURI* uri) +{ + NS_ASSERTION(uri, "no uri"); + mUrl = uri; + mOriginalURI = uri; + nsresult rv; + mPump = do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv); + return rv; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIRequest methods: + +NS_IMETHODIMP +nsIconChannel::GetName(nsACString& result) +{ + return mUrl->GetSpec(result); +} + +NS_IMETHODIMP +nsIconChannel::IsPending(bool* result) +{ + return mPump->IsPending(result); +} + +NS_IMETHODIMP +nsIconChannel::GetStatus(nsresult* status) +{ + return mPump->GetStatus(status); +} + +NS_IMETHODIMP +nsIconChannel::Cancel(nsresult status) +{ + return mPump->Cancel(status); +} + +NS_IMETHODIMP +nsIconChannel::Suspend(void) +{ + return mPump->Suspend(); +} + +NS_IMETHODIMP +nsIconChannel::Resume(void) +{ + return mPump->Resume(); +} + +// nsIRequestObserver methods +NS_IMETHODIMP +nsIconChannel::OnStartRequest(nsIRequest* aRequest, + nsISupports* aContext) +{ + if (mListener) { + return mListener->OnStartRequest(this, aContext); + } + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::OnStopRequest(nsIRequest* aRequest, + nsISupports* aContext, + nsresult aStatus) +{ + if (mListener) { + mListener->OnStopRequest(this, aContext, aStatus); + mListener = nullptr; + } + + // Remove from load group + if (mLoadGroup) { + mLoadGroup->RemoveRequest(this, nullptr, aStatus); + } + + return NS_OK; +} + +// nsIStreamListener methods +NS_IMETHODIMP +nsIconChannel::OnDataAvailable(nsIRequest* aRequest, + nsISupports* aContext, + nsIInputStream* aStream, + uint64_t aOffset, + uint32_t aCount) +{ + if (mListener) { + return mListener->OnDataAvailable(this, aContext, aStream, aOffset, aCount); + } + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIChannel methods: + +NS_IMETHODIMP +nsIconChannel::GetOriginalURI(nsIURI** aURI) +{ + *aURI = mOriginalURI; + NS_ADDREF(*aURI); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::SetOriginalURI(nsIURI* aURI) +{ + NS_ENSURE_ARG_POINTER(aURI); + mOriginalURI = aURI; + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::GetURI(nsIURI** aURI) +{ + *aURI = mUrl; + NS_IF_ADDREF(*aURI); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::Open(nsIInputStream** _retval) +{ + return MakeInputStream(_retval, false); +} + +NS_IMETHODIMP +nsIconChannel::Open2(nsIInputStream** aStream) +{ + nsCOMPtr listener; + nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); + NS_ENSURE_SUCCESS(rv, rv); + return Open(aStream); +} + +nsresult +nsIconChannel::ExtractIconInfoFromUrl(nsIFile** aLocalFile, + uint32_t* aDesiredImageSize, + nsACString& aContentType, + nsACString& aFileExtension) +{ + nsresult rv = NS_OK; + nsCOMPtr iconURI (do_QueryInterface(mUrl, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + iconURI->GetImageSize(aDesiredImageSize); + iconURI->GetContentType(aContentType); + iconURI->GetFileExtension(aFileExtension); + + nsCOMPtr url; + rv = iconURI->GetIconURL(getter_AddRefs(url)); + if (NS_FAILED(rv) || !url) return NS_OK; + + nsCOMPtr fileURL = do_QueryInterface(url, &rv); + if (NS_FAILED(rv) || !fileURL) return NS_OK; + + nsCOMPtr file; + rv = fileURL->GetFile(getter_AddRefs(file)); + if (NS_FAILED(rv) || !file) return NS_OK; + + nsCOMPtr localFileMac (do_QueryInterface(file, &rv)); + if (NS_FAILED(rv) || !localFileMac) return NS_OK; + + *aLocalFile = file; + NS_IF_ADDREF(*aLocalFile); + + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::AsyncOpen(nsIStreamListener* aListener, + nsISupports* ctxt) +{ + MOZ_ASSERT(!mLoadInfo || + mLoadInfo->GetSecurityMode() == 0 || + mLoadInfo->GetInitialSecurityCheckDone() || + (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL && + nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())), + "security flags in loadInfo but asyncOpen2() not called"); + + nsCOMPtr inStream; + nsresult rv = MakeInputStream(getter_AddRefs(inStream), true); + NS_ENSURE_SUCCESS(rv, rv); + + // Init our stream pump + rv = mPump->Init(inStream, int64_t(-1), int64_t(-1), 0, 0, false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mPump->AsyncRead(this, ctxt); + if (NS_SUCCEEDED(rv)) { + // Store our real listener + mListener = aListener; + // Add ourself to the load group, if available + if (mLoadGroup) { + mLoadGroup->AddRequest(this, nullptr); + } + } + + return rv; +} + +NS_IMETHODIMP +nsIconChannel::AsyncOpen2(nsIStreamListener* aListener) +{ + nsCOMPtr listener = aListener; + nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); + NS_ENSURE_SUCCESS(rv, rv); + return AsyncOpen(listener, nullptr); +} + +nsresult +nsIconChannel::MakeInputStream(nsIInputStream** _retval, + bool aNonBlocking) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + nsXPIDLCString contentType; + nsAutoCString fileExt; + nsCOMPtr fileloc; // file we want an icon for + uint32_t desiredImageSize; + nsresult rv = ExtractIconInfoFromUrl(getter_AddRefs(fileloc), + &desiredImageSize, contentType, fileExt); + NS_ENSURE_SUCCESS(rv, rv); + + bool fileExists = false; + if (fileloc) { + // ensure that we DO NOT resolve aliases, very important for file views + fileloc->SetFollowLinks(false); + fileloc->Exists(&fileExists); + } + + NSImage* iconImage = nil; + + // first try to get the icon from the file if it exists + if (fileExists) { + nsCOMPtr localFileMac(do_QueryInterface(fileloc, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + CFURLRef macURL; + if (NS_SUCCEEDED(localFileMac->GetCFURL(&macURL))) { + iconImage = [[NSWorkspace sharedWorkspace] + iconForFile:[(NSURL*)macURL path]]; + ::CFRelease(macURL); + } + } + + // if we don't have an icon yet try to get one by extension + if (!iconImage && !fileExt.IsEmpty()) { + NSString* fileExtension = [NSString stringWithUTF8String:fileExt.get()]; + iconImage = [[NSWorkspace sharedWorkspace] iconForFileType:fileExtension]; + } + + // If we still don't have an icon, get the generic document icon. + if (!iconImage) { + iconImage = [[NSWorkspace sharedWorkspace] + iconForFileType:NSFileTypeUnknown]; + } + + if (!iconImage) { + return NS_ERROR_FAILURE; + } + + // we have an icon now, size it + NSRect desiredSizeRect = NSMakeRect(0, 0, desiredImageSize, desiredImageSize); + [iconImage setSize:desiredSizeRect.size]; + + [iconImage lockFocus]; + NSBitmapImageRep* bitmapRep = [[[NSBitmapImageRep alloc] + initWithFocusedViewRect:desiredSizeRect] + autorelease]; + [iconImage unlockFocus]; + + // we expect the following things to be true about our bitmapRep + NS_ENSURE_TRUE(![bitmapRep isPlanar] && + // Not necessarily: on a HiDPI-capable system, we'll get + // a 2x bitmap + // (unsigned int)[bitmapRep bytesPerPlane] == + // desiredImageSize * desiredImageSize * 4 && + [bitmapRep bitsPerPixel] == 32 && + [bitmapRep samplesPerPixel] == 4 && + [bitmapRep hasAlpha] == YES, + NS_ERROR_UNEXPECTED); + + // check what size we actually got, and ensure it isn't too big to return + uint32_t actualImageSize = [bitmapRep bytesPerRow] / 4; + NS_ENSURE_TRUE(actualImageSize < 256, NS_ERROR_UNEXPECTED); + + // now we can validate the amount of data + NS_ENSURE_TRUE((unsigned int)[bitmapRep bytesPerPlane] == + actualImageSize * actualImageSize * 4, + NS_ERROR_UNEXPECTED); + + // rgba, pre-multiplied data + uint8_t* bitmapRepData = (uint8_t*)[bitmapRep bitmapData]; + + // create our buffer + int32_t bufferCapacity = 2 + [bitmapRep bytesPerPlane]; + AutoTArray iconBuffer; // initial size is for + // 16x16 + iconBuffer.SetLength(bufferCapacity); + + uint8_t* iconBufferPtr = iconBuffer.Elements(); + + // write header data into buffer + *iconBufferPtr++ = actualImageSize; + *iconBufferPtr++ = actualImageSize; + + uint32_t dataCount = [bitmapRep bytesPerPlane]; + uint32_t index = 0; + while (index < dataCount) { + // get data from the bitmap + uint8_t r = bitmapRepData[index++]; + uint8_t g = bitmapRepData[index++]; + uint8_t b = bitmapRepData[index++]; + uint8_t a = bitmapRepData[index++]; + + // write data out to our buffer + // non-cairo uses native image format, but the A channel is ignored. + // cairo uses ARGB (highest to lowest bits) +#if MOZ_LITTLE_ENDIAN + *iconBufferPtr++ = b; + *iconBufferPtr++ = g; + *iconBufferPtr++ = r; + *iconBufferPtr++ = a; +#else + *iconBufferPtr++ = a; + *iconBufferPtr++ = r; + *iconBufferPtr++ = g; + *iconBufferPtr++ = b; +#endif + } + + NS_ASSERTION(iconBufferPtr == iconBuffer.Elements() + bufferCapacity, + "buffer size miscalculation"); + + // Now, create a pipe and stuff our data into it + nsCOMPtr inStream; + nsCOMPtr outStream; + rv = NS_NewPipe(getter_AddRefs(inStream), getter_AddRefs(outStream), + bufferCapacity, bufferCapacity, aNonBlocking); + + if (NS_SUCCEEDED(rv)) { + uint32_t written; + rv = outStream->Write((char*)iconBuffer.Elements(), bufferCapacity, + &written); + if (NS_SUCCEEDED(rv)) { + NS_IF_ADDREF(*_retval = inStream); + } + } + + // Drop notification callbacks to prevent cycles. + mCallbacks = nullptr; + + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +NS_IMETHODIMP +nsIconChannel::GetLoadFlags(uint32_t* aLoadAttributes) +{ + return mPump->GetLoadFlags(aLoadAttributes); +} + +NS_IMETHODIMP +nsIconChannel::SetLoadFlags(uint32_t aLoadAttributes) +{ + return mPump->SetLoadFlags(aLoadAttributes); +} + +NS_IMETHODIMP +nsIconChannel::GetContentType(nsACString& aContentType) +{ + aContentType.AssignLiteral(IMAGE_ICON_MS); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::SetContentType(const nsACString& aContentType) +{ + //It doesn't make sense to set the content-type on this type + // of channel... + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsIconChannel::GetContentCharset(nsACString& aContentCharset) +{ + aContentCharset.AssignLiteral(IMAGE_ICON_MS); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::SetContentCharset(const nsACString& aContentCharset) +{ + //It doesn't make sense to set the content-type on this type + // of channel... + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsIconChannel::GetContentDisposition(uint32_t* aContentDisposition) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsIconChannel::SetContentDisposition(uint32_t aContentDisposition) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsIconChannel:: + GetContentDispositionFilename(nsAString& aContentDispositionFilename) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsIconChannel:: + SetContentDispositionFilename(const nsAString& aContentDispositionFilename) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsIconChannel:: + GetContentDispositionHeader(nsACString& aContentDispositionHeader) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsIconChannel::GetContentLength(int64_t* aContentLength) +{ + *aContentLength = 0; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsIconChannel::SetContentLength(int64_t aContentLength) +{ + NS_NOTREACHED("nsIconChannel::SetContentLength"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsIconChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) +{ + *aLoadGroup = mLoadGroup; + NS_IF_ADDREF(*aLoadGroup); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) +{ + mLoadGroup = aLoadGroup; + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::GetOwner(nsISupports** aOwner) +{ + *aOwner = mOwner.get(); + NS_IF_ADDREF(*aOwner); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::SetOwner(nsISupports* aOwner) +{ + mOwner = aOwner; + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) +{ + NS_IF_ADDREF(*aLoadInfo = mLoadInfo); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) +{ + mLoadInfo = aLoadInfo; + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel:: + GetNotificationCallbacks(nsIInterfaceRequestor** aNotificationCallbacks) +{ + *aNotificationCallbacks = mCallbacks.get(); + NS_IF_ADDREF(*aNotificationCallbacks); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel:: + SetNotificationCallbacks(nsIInterfaceRequestor* aNotificationCallbacks) +{ + mCallbacks = aNotificationCallbacks; + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::GetSecurityInfo(nsISupports** aSecurityInfo) +{ + *aSecurityInfo = nullptr; + return NS_OK; +} + diff --git a/image/decoders/icon/moz.build b/image/decoders/icon/moz.build new file mode 100644 index 000000000..9c6106fa7 --- /dev/null +++ b/image/decoders/icon/moz.build @@ -0,0 +1,32 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES += [ + 'nsIconModule.cpp', + 'nsIconProtocolHandler.cpp', + 'nsIconURI.cpp', +] + +FINAL_LIBRARY = 'xul' + +include('/ipc/chromium/chromium-config.mozbuild') + +platform = None + +if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']: + platform = 'gtk' + +if CONFIG['OS_ARCH'] == 'WINNT': + platform = 'win' + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + platform = 'mac' + +if CONFIG['OS_TARGET'] == 'Android': + platform = 'android' + +if platform: + LOCAL_INCLUDES += [platform] diff --git a/image/decoders/icon/nsIconModule.cpp b/image/decoders/icon/nsIconModule.cpp new file mode 100644 index 000000000..36225374d --- /dev/null +++ b/image/decoders/icon/nsIconModule.cpp @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ModuleUtils.h" +#include "nsServiceManagerUtils.h" + +#include "nsIconProtocolHandler.h" +#include "nsIconURI.h" +#include "nsIconChannel.h" + +// objects that just require generic constructors +//***************************************************************************** +// Protocol CIDs + +#define NS_ICONPROTOCOL_CID { 0xd0f9db12, 0x249c, 0x11d5, \ + { 0x99, 0x5, 0x0, 0x10, 0x83, 0x1, 0xe, 0x9b } } + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsIconProtocolHandler) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsMozIconURI) + +NS_DEFINE_NAMED_CID(NS_ICONPROTOCOL_CID); +NS_DEFINE_NAMED_CID(NS_MOZICONURI_CID); + +static const mozilla::Module::CIDEntry kIconCIDs[] = { + { &kNS_ICONPROTOCOL_CID, false, nullptr, nsIconProtocolHandlerConstructor }, + { &kNS_MOZICONURI_CID, false, nullptr, nsMozIconURIConstructor }, + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry kIconContracts[] = { + { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "moz-icon", &kNS_ICONPROTOCOL_CID }, + { nullptr } +}; + +static const mozilla::Module::CategoryEntry kIconCategories[] = { + { nullptr } +}; + +static void +IconDecoderModuleDtor() +{ +#if (MOZ_WIDGET_GTK == 2) + nsIconChannel::Shutdown(); +#endif +} + +static const mozilla::Module kIconModule = { + mozilla::Module::kVersion, + kIconCIDs, + kIconContracts, + kIconCategories, + nullptr, + nullptr, + IconDecoderModuleDtor +}; + +NSMODULE_DEFN(nsIconDecoderModule) = &kIconModule; diff --git a/image/decoders/icon/nsIconProtocolHandler.cpp b/image/decoders/icon/nsIconProtocolHandler.cpp new file mode 100644 index 000000000..03aaa2a14 --- /dev/null +++ b/image/decoders/icon/nsIconProtocolHandler.cpp @@ -0,0 +1,126 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIconProtocolHandler.h" + +#include "nsIconChannel.h" +#include "nsIconURI.h" +#include "nsIURL.h" +#include "nsCRT.h" +#include "nsCOMPtr.h" +#include "nsIComponentManager.h" +#include "nsIServiceManager.h" +#include "nsNetCID.h" + +/////////////////////////////////////////////////////////////////////////////// + +nsIconProtocolHandler::nsIconProtocolHandler() +{ } + +nsIconProtocolHandler::~nsIconProtocolHandler() +{ } + +NS_IMPL_ISUPPORTS(nsIconProtocolHandler, nsIProtocolHandler, + nsISupportsWeakReference) + + +/////////////////////////////////////////////////////////////////////////////// +// nsIProtocolHandler methods: + +NS_IMETHODIMP +nsIconProtocolHandler::GetScheme(nsACString& result) +{ + result = "moz-icon"; + return NS_OK; +} + +NS_IMETHODIMP +nsIconProtocolHandler::GetDefaultPort(int32_t* result) +{ + *result = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsIconProtocolHandler::AllowPort(int32_t port, + const char* scheme, + bool* _retval) +{ + // don't override anything. + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP +nsIconProtocolHandler::GetProtocolFlags(uint32_t* result) +{ + *result = URI_NORELATIVE | URI_NOAUTH | URI_IS_UI_RESOURCE | + URI_IS_LOCAL_RESOURCE; + return NS_OK; +} + +NS_IMETHODIMP +nsIconProtocolHandler::NewURI(const nsACString& aSpec, + const char* aOriginCharset, // ignored + nsIURI* aBaseURI, + nsIURI** result) +{ + nsCOMPtr uri = new nsMozIconURI(); + if (!uri) return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv = uri->SetSpec(aSpec); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr iconURL; + uri->GetIconURL(getter_AddRefs(iconURL)); + if (iconURL) { + uri = new nsNestedMozIconURI(); + rv = uri->SetSpec(aSpec); + if (NS_FAILED(rv)) { + return rv; + } + } + + NS_ADDREF(*result = uri); + return NS_OK; +} + +NS_IMETHODIMP +nsIconProtocolHandler::NewChannel2(nsIURI* url, + nsILoadInfo* aLoadInfo, + nsIChannel** result) +{ + NS_ENSURE_ARG_POINTER(url); + nsIconChannel* channel = new nsIconChannel; + if (!channel) { + return NS_ERROR_OUT_OF_MEMORY; + } + NS_ADDREF(channel); + + nsresult rv = channel->Init(url); + if (NS_FAILED(rv)) { + NS_RELEASE(channel); + return rv; + } + + // set the loadInfo on the new channel + rv = channel->SetLoadInfo(aLoadInfo); + if (NS_FAILED(rv)) { + NS_RELEASE(channel); + return rv; + } + + *result = channel; + return NS_OK; +} + +NS_IMETHODIMP +nsIconProtocolHandler::NewChannel(nsIURI* url, nsIChannel** result) +{ + return NewChannel2(url, nullptr, result); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/image/decoders/icon/nsIconProtocolHandler.h b/image/decoders/icon/nsIconProtocolHandler.h new file mode 100644 index 000000000..9653d36f6 --- /dev/null +++ b/image/decoders/icon/nsIconProtocolHandler.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_decoders_icon_nsIconProtocolHandler_h +#define mozilla_image_decoders_icon_nsIconProtocolHandler_h + +#include "nsWeakReference.h" +#include "nsIProtocolHandler.h" + +class nsIconProtocolHandler : public nsIProtocolHandler, + public nsSupportsWeakReference +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPROTOCOLHANDLER + + // nsIconProtocolHandler methods: + nsIconProtocolHandler(); + +protected: + virtual ~nsIconProtocolHandler(); +}; + +#endif // mozilla_image_decoders_icon_nsIconProtocolHandler_h diff --git a/image/decoders/icon/nsIconURI.cpp b/image/decoders/icon/nsIconURI.cpp new file mode 100644 index 000000000..2c2788c8f --- /dev/null +++ b/image/decoders/icon/nsIconURI.cpp @@ -0,0 +1,699 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set sw=2 sts=2 ts=2 et tw=80: + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIconURI.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/Sprintf.h" + +#include "nsIIOService.h" +#include "nsIURL.h" +#include "nsNetUtil.h" +#include "plstr.h" +#include + +using namespace mozilla; +using namespace mozilla::ipc; + +#define DEFAULT_IMAGE_SIZE 16 + +#if defined(MAX_PATH) +#define SANE_FILE_NAME_LEN MAX_PATH +#elif defined(PATH_MAX) +#define SANE_FILE_NAME_LEN PATH_MAX +#else +#define SANE_FILE_NAME_LEN 1024 +#endif + +// helper function for parsing out attributes like size, and contentType +// from the icon url. +static void extractAttributeValue(const char* aSearchString, + const char* aAttributeName, + nsCString& aResult); + +static const char* kSizeStrings[] = +{ + "button", + "toolbar", + "toolbarsmall", + "menu", + "dnd", + "dialog" +}; + +static const char* kStateStrings[] = +{ + "normal", + "disabled" +}; + +//////////////////////////////////////////////////////////////////////////////// + +nsMozIconURI::nsMozIconURI() + : mSize(DEFAULT_IMAGE_SIZE), + mIconSize(-1), + mIconState(-1) +{ } + +nsMozIconURI::~nsMozIconURI() +{ } + +NS_IMPL_ISUPPORTS(nsMozIconURI, nsIMozIconURI, nsIURI, nsIIPCSerializableURI) + +#define MOZICON_SCHEME "moz-icon:" +#define MOZICON_SCHEME_LEN (sizeof(MOZICON_SCHEME) - 1) + +//////////////////////////////////////////////////////////////////////////////// +// nsIURI methods: + +NS_IMETHODIMP +nsMozIconURI::GetSpec(nsACString& aSpec) +{ + aSpec = MOZICON_SCHEME; + + if (mIconURL) { + nsAutoCString fileIconSpec; + nsresult rv = mIconURL->GetSpec(fileIconSpec); + NS_ENSURE_SUCCESS(rv, rv); + aSpec += fileIconSpec; + } else if (!mStockIcon.IsEmpty()) { + aSpec += "//stock/"; + aSpec += mStockIcon; + } else { + aSpec += "//"; + aSpec += mFileName; + } + + aSpec += "?size="; + if (mIconSize >= 0) { + aSpec += kSizeStrings[mIconSize]; + } else { + char buf[20]; + SprintfLiteral(buf, "%d", mSize); + aSpec.Append(buf); + } + + if (mIconState >= 0) { + aSpec += "&state="; + aSpec += kStateStrings[mIconState]; + } + + if (!mContentType.IsEmpty()) { + aSpec += "&contentType="; + aSpec += mContentType.get(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::GetSpecIgnoringRef(nsACString& result) +{ + return GetSpec(result); +} + +NS_IMETHODIMP +nsMozIconURI::GetHasRef(bool* result) +{ + *result = false; + return NS_OK; +} + +// takes a string like ?size=32&contentType=text/html and returns a new string +// containing just the attribute value. i.e you could pass in this string with +// an attribute name of 'size=', this will return 32 +// Assumption: attribute pairs in the string are separated by '&'. +void +extractAttributeValue(const char* aSearchString, + const char* aAttributeName, + nsCString& aResult) +{ + //NS_ENSURE_ARG_POINTER(extractAttributeValue); + + aResult.Truncate(); + + if (aSearchString && aAttributeName) { + // search the string for attributeName + uint32_t attributeNameSize = strlen(aAttributeName); + const char* startOfAttribute = PL_strcasestr(aSearchString, aAttributeName); + if (startOfAttribute && + ( *(startOfAttribute-1) == '?' || *(startOfAttribute-1) == '&') ) { + startOfAttribute += attributeNameSize; // skip over the attributeName + // is there something after the attribute name + if (*startOfAttribute) { + const char* endofAttribute = strchr(startOfAttribute, '&'); + if (endofAttribute) { + aResult.Assign(Substring(startOfAttribute, endofAttribute)); + } else { + aResult.Assign(startOfAttribute); + } + } // if we have a attribute value + } // if we have a attribute name + } // if we got non-null search string and attribute name values +} + +NS_IMETHODIMP +nsMozIconURI::SetSpec(const nsACString& aSpec) +{ + // Reset everything to default values. + mIconURL = nullptr; + mSize = DEFAULT_IMAGE_SIZE; + mContentType.Truncate(); + mFileName.Truncate(); + mStockIcon.Truncate(); + mIconSize = -1; + mIconState = -1; + + nsAutoCString iconSpec(aSpec); + if (!Substring(iconSpec, 0, + MOZICON_SCHEME_LEN).EqualsLiteral(MOZICON_SCHEME)) { + return NS_ERROR_MALFORMED_URI; + } + + int32_t questionMarkPos = iconSpec.Find("?"); + if (questionMarkPos != -1 && + static_cast(iconSpec.Length()) > (questionMarkPos + 1)) { + extractAttributeValue(iconSpec.get(), "contentType=", mContentType); + + nsAutoCString sizeString; + extractAttributeValue(iconSpec.get(), "size=", sizeString); + if (!sizeString.IsEmpty()) { + const char* sizeStr = sizeString.get(); + for (uint32_t i = 0; i < ArrayLength(kSizeStrings); i++) { + if (PL_strcasecmp(sizeStr, kSizeStrings[i]) == 0) { + mIconSize = i; + break; + } + } + + int32_t sizeValue = atoi(sizeString.get()); + if (sizeValue > 0) { + mSize = sizeValue; + } + } + + nsAutoCString stateString; + extractAttributeValue(iconSpec.get(), "state=", stateString); + if (!stateString.IsEmpty()) { + const char* stateStr = stateString.get(); + for (uint32_t i = 0; i < ArrayLength(kStateStrings); i++) { + if (PL_strcasecmp(stateStr, kStateStrings[i]) == 0) { + mIconState = i; + break; + } + } + } + } + + int32_t pathLength = iconSpec.Length() - MOZICON_SCHEME_LEN; + if (questionMarkPos != -1) { + pathLength = questionMarkPos - MOZICON_SCHEME_LEN; + } + if (pathLength < 3) { + return NS_ERROR_MALFORMED_URI; + } + + nsAutoCString iconPath(Substring(iconSpec, MOZICON_SCHEME_LEN, pathLength)); + + // Icon URI path can have three forms: + // (1) //stock/ + // (2) // + // (3) a valid URL + + if (!strncmp("//stock/", iconPath.get(), 8)) { + mStockIcon.Assign(Substring(iconPath, 8)); + // An icon identifier must always be specified. + if (mStockIcon.IsEmpty()) { + return NS_ERROR_MALFORMED_URI; + } + return NS_OK; + } + + if (StringBeginsWith(iconPath, NS_LITERAL_CSTRING("//"))) { + // Sanity check this supposed dummy file name. + if (iconPath.Length() > SANE_FILE_NAME_LEN) { + return NS_ERROR_MALFORMED_URI; + } + iconPath.Cut(0, 2); + mFileName.Assign(iconPath); + } + + nsresult rv; + nsCOMPtr ioService(do_GetService(NS_IOSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr uri; + ioService->NewURI(iconPath, nullptr, nullptr, getter_AddRefs(uri)); + mIconURL = do_QueryInterface(uri); + if (mIconURL) { + mFileName.Truncate(); + } else if (mFileName.IsEmpty()) { + return NS_ERROR_MALFORMED_URI; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::GetPrePath(nsACString& prePath) +{ + prePath = MOZICON_SCHEME; + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::GetScheme(nsACString& aScheme) +{ + aScheme = "moz-icon"; + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::SetScheme(const nsACString& aScheme) +{ + // doesn't make sense to set the scheme of a moz-icon URL + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::GetUsername(nsACString& aUsername) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::SetUsername(const nsACString& aUsername) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::GetPassword(nsACString& aPassword) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::SetPassword(const nsACString& aPassword) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::GetUserPass(nsACString& aUserPass) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::SetUserPass(const nsACString& aUserPass) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::GetHostPort(nsACString& aHostPort) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::SetHostPort(const nsACString& aHostPort) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::SetHostAndPort(const nsACString& aHostPort) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::GetHost(nsACString& aHost) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::SetHost(const nsACString& aHost) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::GetPort(int32_t* aPort) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::SetPort(int32_t aPort) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::GetPath(nsACString& aPath) +{ + aPath.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::SetPath(const nsACString& aPath) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::GetRef(nsACString& aRef) +{ + aRef.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::SetRef(const nsACString& aRef) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::Equals(nsIURI* other, bool* result) +{ + *result = false; + NS_ENSURE_ARG_POINTER(other); + NS_PRECONDITION(result, "null pointer"); + + nsAutoCString spec1; + nsAutoCString spec2; + + nsresult rv = GetSpec(spec1); + NS_ENSURE_SUCCESS(rv, rv); + rv = other->GetSpec(spec2); + NS_ENSURE_SUCCESS(rv, rv); + + if (!PL_strcasecmp(spec1.get(), spec2.get())) { + *result = true; + } else { + *result = false; + } + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::EqualsExceptRef(nsIURI* other, bool* result) +{ + // GetRef/SetRef not supported by nsMozIconURI, so + // EqualsExceptRef() is the same as Equals(). + return Equals(other, result); +} + +NS_IMETHODIMP +nsMozIconURI::SchemeIs(const char* aScheme, bool* aEquals) +{ + NS_ENSURE_ARG_POINTER(aEquals); + if (!aScheme) { + return NS_ERROR_INVALID_ARG; + } + + *aEquals = PL_strcasecmp("moz-icon", aScheme) ? false : true; + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::Clone(nsIURI** result) +{ + nsCOMPtr newIconURL; + if (mIconURL) { + nsCOMPtr newURI; + nsresult rv = mIconURL->Clone(getter_AddRefs(newURI)); + if (NS_FAILED(rv)) { + return rv; + } + newIconURL = do_QueryInterface(newURI, &rv); + if (NS_FAILED(rv)) { + return rv; + } + } + + nsMozIconURI* uri = new nsMozIconURI(); + newIconURL.swap(uri->mIconURL); + uri->mSize = mSize; + uri->mContentType = mContentType; + uri->mFileName = mFileName; + uri->mStockIcon = mStockIcon; + uri->mIconSize = mIconSize; + uri->mIconState = mIconState; + NS_ADDREF(*result = uri); + + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::CloneIgnoringRef(nsIURI** result) +{ + // GetRef/SetRef not supported by nsMozIconURI, so + // CloneIgnoringRef() is the same as Clone(). + return Clone(result); +} + +NS_IMETHODIMP +nsMozIconURI::CloneWithNewRef(const nsACString& newRef, nsIURI** result) +{ + // GetRef/SetRef not supported by nsMozIconURI, so + // CloneWithNewRef() is the same as Clone(). + return Clone(result); +} + + +NS_IMETHODIMP +nsMozIconURI::Resolve(const nsACString& relativePath, nsACString& result) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMozIconURI::GetAsciiSpec(nsACString& aSpecA) +{ + return GetSpec(aSpecA); +} + +NS_IMETHODIMP +nsMozIconURI::GetAsciiHostPort(nsACString& aHostPortA) +{ + return GetHostPort(aHostPortA); +} + +NS_IMETHODIMP +nsMozIconURI::GetAsciiHost(nsACString& aHostA) +{ + return GetHost(aHostA); +} + +NS_IMETHODIMP +nsMozIconURI::GetOriginCharset(nsACString& result) +{ + result.Truncate(); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIIconUri methods: + +NS_IMETHODIMP +nsMozIconURI::GetIconURL(nsIURL** aFileUrl) +{ + *aFileUrl = mIconURL; + NS_IF_ADDREF(*aFileUrl); + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::SetIconURL(nsIURL* aFileUrl) +{ + // this isn't called anywhere, needs to go through SetSpec parsing + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMozIconURI::GetImageSize(uint32_t* aImageSize) + // measured by # of pixels in a row. defaults to 16. +{ + *aImageSize = mSize; + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::SetImageSize(uint32_t aImageSize) + // measured by # of pixels in a row. defaults to 16. +{ + mSize = aImageSize; + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::GetContentType(nsACString& aContentType) +{ + aContentType = mContentType; + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::SetContentType(const nsACString& aContentType) +{ + mContentType = aContentType; + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::GetFileExtension(nsACString& aFileExtension) +{ + // First, try to get the extension from mIconURL if we have one + if (mIconURL) { + nsAutoCString fileExt; + if (NS_SUCCEEDED(mIconURL->GetFileExtension(fileExt))) { + if (!fileExt.IsEmpty()) { + // unfortunately, this code doesn't give us the required '.' in + // front of the extension so we have to do it ourselves. + aFileExtension.Assign('.'); + aFileExtension.Append(fileExt); + } + } + return NS_OK; + } + + if (!mFileName.IsEmpty()) { + // truncate the extension out of the file path... + const char* chFileName = mFileName.get(); // get the underlying buffer + const char* fileExt = strrchr(chFileName, '.'); + if (!fileExt) { + return NS_OK; + } + aFileExtension = fileExt; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::GetStockIcon(nsACString& aStockIcon) +{ + aStockIcon = mStockIcon; + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::GetIconSize(nsACString& aSize) +{ + if (mIconSize >= 0) { + aSize = kSizeStrings[mIconSize]; + } else { + aSize.Truncate(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::GetIconState(nsACString& aState) +{ + if (mIconState >= 0) { + aState = kStateStrings[mIconState]; + } else { + aState.Truncate(); + } + return NS_OK; +} +//////////////////////////////////////////////////////////////////////////////// +// nsIIPCSerializableURI methods: + +void +nsMozIconURI::Serialize(URIParams& aParams) +{ + IconURIParams params; + + if (mIconURL) { + URIParams iconURLParams; + SerializeURI(mIconURL, iconURLParams); + if (iconURLParams.type() == URIParams::T__None) { + // Serialization failed, bail. + return; + } + + params.uri() = iconURLParams; + } else { + params.uri() = void_t(); + } + + params.size() = mSize; + params.fileName() = mFileName; + params.stockIcon() = mStockIcon; + params.iconSize() = mIconSize; + params.iconState() = mIconState; + + aParams = params; +} + +bool +nsMozIconURI::Deserialize(const URIParams& aParams) +{ + if (aParams.type() != URIParams::TIconURIParams) { + MOZ_ASSERT_UNREACHABLE("Received unknown URI from other process!"); + return false; + } + + const IconURIParams& params = aParams.get_IconURIParams(); + if (params.uri().type() != OptionalURIParams::Tvoid_t) { + nsCOMPtr uri = DeserializeURI(params.uri().get_URIParams()); + mIconURL = do_QueryInterface(uri); + if (!mIconURL) { + MOZ_ASSERT_UNREACHABLE("bad nsIURI passed"); + return false; + } + } + + mSize = params.size(); + mContentType = params.contentType(); + mFileName = params.fileName(); + mStockIcon = params.stockIcon(); + mIconSize = params.iconSize(); + mIconState = params.iconState(); + + return true; +} + +//////////////////////////////////////////////////////////// +// Nested version of nsIconURI + +nsNestedMozIconURI::nsNestedMozIconURI() +{ } + +nsNestedMozIconURI::~nsNestedMozIconURI() +{ } + +NS_IMPL_ISUPPORTS_INHERITED(nsNestedMozIconURI, nsMozIconURI, nsINestedURI) + +NS_IMETHODIMP +nsNestedMozIconURI::GetInnerURI(nsIURI** aURI) +{ + nsCOMPtr iconURL = do_QueryInterface(mIconURL); + if (iconURL) { + iconURL.forget(aURI); + } else { + *aURI = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP +nsNestedMozIconURI::GetInnermostURI(nsIURI** aURI) +{ + return NS_ImplGetInnermostURI(this, aURI); +} + diff --git a/image/decoders/icon/nsIconURI.h b/image/decoders/icon/nsIconURI.h new file mode 100644 index 000000000..1c0310bec --- /dev/null +++ b/image/decoders/icon/nsIconURI.h @@ -0,0 +1,63 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_decoders_icon_nsIconURI_h +#define mozilla_image_decoders_icon_nsIconURI_h + +#include "nsIIconURI.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsIIPCSerializableURI.h" +#include "nsINestedURI.h" + +class nsMozIconURI : public nsIMozIconURI + , public nsIIPCSerializableURI +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIURI + NS_DECL_NSIMOZICONURI + NS_DECL_NSIIPCSERIALIZABLEURI + + // nsMozIconURI + nsMozIconURI(); + +protected: + virtual ~nsMozIconURI(); + nsCOMPtr mIconURL; // a URL that we want the icon for + uint32_t mSize; // the # of pixels in a row that we want for this image. + // Typically 16, 32, 128, etc. + nsCString mContentType; // optional field explicitly specifying the content + // type + nsCString mFileName; // for if we don't have an actual file path, we're just + // given a filename with an extension + nsCString mStockIcon; + int32_t mIconSize; // -1 if not specified, otherwise index into + // kSizeStrings + int32_t mIconState; // -1 if not specified, otherwise index into + // kStateStrings +}; + +// For moz-icon URIs that point to an actual file on disk and are +// therefore nested URIs +class nsNestedMozIconURI final : public nsMozIconURI + , public nsINestedURI +{ + NS_DECL_ISUPPORTS_INHERITED + NS_FORWARD_NSIURI(nsMozIconURI::) + NS_FORWARD_NSIMOZICONURI(nsMozIconURI::) + NS_FORWARD_NSIIPCSERIALIZABLEURI(nsMozIconURI::) + + NS_DECL_NSINESTEDURI + + nsNestedMozIconURI(); + +protected: + virtual ~nsNestedMozIconURI(); + +}; + +#endif // mozilla_image_decoders_icon_nsIconURI_h diff --git a/image/decoders/icon/win/moz.build b/image/decoders/icon/win/moz.build new file mode 100644 index 000000000..6f763d953 --- /dev/null +++ b/image/decoders/icon/win/moz.build @@ -0,0 +1,11 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SOURCES += [ + 'nsIconChannel.cpp', +] + +FINAL_LIBRARY = 'xul' diff --git a/image/decoders/icon/win/nsIconChannel.cpp b/image/decoders/icon/win/nsIconChannel.cpp new file mode 100644 index 000000000..9ddcbbc48 --- /dev/null +++ b/image/decoders/icon/win/nsIconChannel.cpp @@ -0,0 +1,841 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" + +#include "nsIconChannel.h" +#include "nsIIconURI.h" +#include "nsIServiceManager.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsXPIDLString.h" +#include "nsReadableUtils.h" +#include "nsMimeTypes.h" +#include "nsMemory.h" +#include "nsIStringStream.h" +#include "nsIURL.h" +#include "nsIOutputStream.h" +#include "nsIPipe.h" +#include "nsNetCID.h" +#include "nsIFile.h" +#include "nsIFileURL.h" +#include "nsIMIMEService.h" +#include "nsCExternalHandlerService.h" +#include "nsDirectoryServiceDefs.h" +#include "nsProxyRelease.h" +#include "nsContentSecurityManager.h" +#include "nsContentUtils.h" + +#ifdef _WIN32_WINNT +#undef _WIN32_WINNT +#endif +#define _WIN32_WINNT 0x0600 + +// we need windows.h to read out registry information... +#include +#include +#include +#include +#include + +using namespace mozilla; + +struct ICONFILEHEADER { + uint16_t ifhReserved; + uint16_t ifhType; + uint16_t ifhCount; +}; + +struct ICONENTRY { + int8_t ieWidth; + int8_t ieHeight; + uint8_t ieColors; + uint8_t ieReserved; + uint16_t iePlanes; + uint16_t ieBitCount; + uint32_t ieSizeImage; + uint32_t ieFileOffset; +}; + +// Match stock icons with names +static SHSTOCKICONID +GetStockIconIDForName(const nsACString& aStockName) +{ + return aStockName.EqualsLiteral("uac-shield") ? SIID_SHIELD : + SIID_INVALID; +} + +// nsIconChannel methods +nsIconChannel::nsIconChannel() +{ +} + +nsIconChannel::~nsIconChannel() +{ + if (mLoadInfo) { + NS_ReleaseOnMainThread(mLoadInfo.forget()); + } +} + +NS_IMPL_ISUPPORTS(nsIconChannel, + nsIChannel, + nsIRequest, + nsIRequestObserver, + nsIStreamListener) + +nsresult +nsIconChannel::Init(nsIURI* uri) +{ + NS_ASSERTION(uri, "no uri"); + mUrl = uri; + mOriginalURI = uri; + nsresult rv; + mPump = do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv); + return rv; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIRequest methods: + +NS_IMETHODIMP +nsIconChannel::GetName(nsACString& result) +{ + return mUrl->GetSpec(result); +} + +NS_IMETHODIMP +nsIconChannel::IsPending(bool* result) +{ + return mPump->IsPending(result); +} + +NS_IMETHODIMP +nsIconChannel::GetStatus(nsresult* status) +{ + return mPump->GetStatus(status); +} + +NS_IMETHODIMP +nsIconChannel::Cancel(nsresult status) +{ + return mPump->Cancel(status); +} + +NS_IMETHODIMP +nsIconChannel::Suspend(void) +{ + return mPump->Suspend(); +} + +NS_IMETHODIMP +nsIconChannel::Resume(void) +{ + return mPump->Resume(); +} +NS_IMETHODIMP +nsIconChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) +{ + *aLoadGroup = mLoadGroup; + NS_IF_ADDREF(*aLoadGroup); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) +{ + mLoadGroup = aLoadGroup; + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::GetLoadFlags(uint32_t* aLoadAttributes) +{ + return mPump->GetLoadFlags(aLoadAttributes); +} + +NS_IMETHODIMP +nsIconChannel::SetLoadFlags(uint32_t aLoadAttributes) +{ + return mPump->SetLoadFlags(aLoadAttributes); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIChannel methods: + +NS_IMETHODIMP +nsIconChannel::GetOriginalURI(nsIURI** aURI) +{ + *aURI = mOriginalURI; + NS_ADDREF(*aURI); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::SetOriginalURI(nsIURI* aURI) +{ + NS_ENSURE_ARG_POINTER(aURI); + mOriginalURI = aURI; + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::GetURI(nsIURI** aURI) +{ + *aURI = mUrl; + NS_IF_ADDREF(*aURI); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::Open(nsIInputStream** _retval) +{ + return MakeInputStream(_retval, false); +} + +NS_IMETHODIMP +nsIconChannel::Open2(nsIInputStream** aStream) +{ + nsCOMPtr listener; + nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); + NS_ENSURE_SUCCESS(rv, rv); + return Open(aStream); +} + +nsresult +nsIconChannel::ExtractIconInfoFromUrl(nsIFile** aLocalFile, + uint32_t* aDesiredImageSize, nsCString& aContentType, + nsCString& aFileExtension) +{ + nsresult rv = NS_OK; + nsCOMPtr iconURI (do_QueryInterface(mUrl, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + iconURI->GetImageSize(aDesiredImageSize); + iconURI->GetContentType(aContentType); + iconURI->GetFileExtension(aFileExtension); + + nsCOMPtr url; + rv = iconURI->GetIconURL(getter_AddRefs(url)); + if (NS_FAILED(rv) || !url) return NS_OK; + + nsCOMPtr fileURL = do_QueryInterface(url, &rv); + if (NS_FAILED(rv) || !fileURL) return NS_OK; + + nsCOMPtr file; + rv = fileURL->GetFile(getter_AddRefs(file)); + if (NS_FAILED(rv) || !file) return NS_OK; + + return file->Clone(aLocalFile); +} + +NS_IMETHODIMP +nsIconChannel::AsyncOpen(nsIStreamListener* aListener, + nsISupports* ctxt) +{ + MOZ_ASSERT(!mLoadInfo || + mLoadInfo->GetSecurityMode() == 0 || + mLoadInfo->GetInitialSecurityCheckDone() || + (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL && + nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())), + "security flags in loadInfo but asyncOpen2() not called"); + + nsCOMPtr inStream; + nsresult rv = MakeInputStream(getter_AddRefs(inStream), true); + if (NS_FAILED(rv)) { + return rv; + } + + // Init our streampump + rv = mPump->Init(inStream, int64_t(-1), int64_t(-1), 0, 0, false); + if (NS_FAILED(rv)) { + return rv; + } + + rv = mPump->AsyncRead(this, ctxt); + if (NS_SUCCEEDED(rv)) { + // Store our real listener + mListener = aListener; + // Add ourself to the load group, if available + if (mLoadGroup) { + mLoadGroup->AddRequest(this, nullptr); + } + } + return rv; +} + +NS_IMETHODIMP +nsIconChannel::AsyncOpen2(nsIStreamListener* aListener) +{ + nsCOMPtr listener = aListener; + nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); + NS_ENSURE_SUCCESS(rv, rv); + return AsyncOpen(listener, nullptr); +} + +static DWORD +GetSpecialFolderIcon(nsIFile* aFile, int aFolder, + SHFILEINFOW* aSFI, UINT aInfoFlags) +{ + DWORD shellResult = 0; + + if (!aFile) { + return shellResult; + } + + wchar_t fileNativePath[MAX_PATH]; + nsAutoString fileNativePathStr; + aFile->GetPath(fileNativePathStr); + ::GetShortPathNameW(fileNativePathStr.get(), fileNativePath, + ArrayLength(fileNativePath)); + + LPITEMIDLIST idList; + HRESULT hr = ::SHGetSpecialFolderLocation(nullptr, aFolder, &idList); + if (SUCCEEDED(hr)) { + wchar_t specialNativePath[MAX_PATH]; + ::SHGetPathFromIDListW(idList, specialNativePath); + ::GetShortPathNameW(specialNativePath, specialNativePath, + ArrayLength(specialNativePath)); + + if (!wcsicmp(fileNativePath, specialNativePath)) { + aInfoFlags |= (SHGFI_PIDL | SHGFI_SYSICONINDEX); + shellResult = ::SHGetFileInfoW((LPCWSTR)(LPCITEMIDLIST)idList, 0, + aSFI, + sizeof(*aSFI), aInfoFlags); + } + } + CoTaskMemFree(idList); + return shellResult; +} + +static UINT +GetSizeInfoFlag(uint32_t aDesiredImageSize) +{ + return + (UINT) (aDesiredImageSize > 16 ? SHGFI_SHELLICONSIZE : SHGFI_SMALLICON); +} + +nsresult +nsIconChannel::GetHIconFromFile(HICON* hIcon) +{ + nsXPIDLCString contentType; + nsCString fileExt; + nsCOMPtr localFile; // file we want an icon for + uint32_t desiredImageSize; + nsresult rv = ExtractIconInfoFromUrl(getter_AddRefs(localFile), + &desiredImageSize, contentType, + fileExt); + NS_ENSURE_SUCCESS(rv, rv); + + // if the file exists, we are going to use it's real attributes... + // otherwise we only want to use it for it's extension... + SHFILEINFOW sfi; + UINT infoFlags = SHGFI_ICON; + + bool fileExists = false; + + nsAutoString filePath; + CopyASCIItoUTF16(fileExt, filePath); + if (localFile) { + rv = localFile->Normalize(); + NS_ENSURE_SUCCESS(rv, rv); + + localFile->GetPath(filePath); + if (filePath.Length() < 2 || filePath[1] != ':') { + return NS_ERROR_MALFORMED_URI; // UNC + } + + if (filePath.Last() == ':') { + filePath.Append('\\'); + } else { + localFile->Exists(&fileExists); + if (!fileExists) { + localFile->GetLeafName(filePath); + } + } + } + + if (!fileExists) { + infoFlags |= SHGFI_USEFILEATTRIBUTES; + } + + infoFlags |= GetSizeInfoFlag(desiredImageSize); + + // if we have a content type... then use it! but for existing files, + // we want to show their real icon. + if (!fileExists && !contentType.IsEmpty()) { + nsCOMPtr mimeService + (do_GetService(NS_MIMESERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString defFileExt; + mimeService->GetPrimaryExtension(contentType, fileExt, defFileExt); + // If the mime service does not know about this mime type, we show + // the generic icon. + // In any case, we need to insert a '.' before the extension. + filePath = NS_LITERAL_STRING(".") + + NS_ConvertUTF8toUTF16(defFileExt); + } + + // Is this the "Desktop" folder? + DWORD shellResult = GetSpecialFolderIcon(localFile, CSIDL_DESKTOP, + &sfi, infoFlags); + if (!shellResult) { + // Is this the "My Documents" folder? + shellResult = GetSpecialFolderIcon(localFile, CSIDL_PERSONAL, + &sfi, infoFlags); + } + + // There are other "Special Folders" and Namespace entities that we + // are not fetching icons for, see: + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ + // shellcc/platform/shell/reference/enums/csidl.asp + // If we ever need to get them, code to do so would be inserted here. + + // Not a special folder, or something else failed above. + if (!shellResult) { + shellResult = ::SHGetFileInfoW(filePath.get(), + FILE_ATTRIBUTE_ARCHIVE, + &sfi, sizeof(sfi), infoFlags); + } + + if (shellResult && sfi.hIcon) { + *hIcon = sfi.hIcon; + } else { + rv = NS_ERROR_NOT_AVAILABLE; + } + + return rv; +} + +nsresult +nsIconChannel::GetStockHIcon(nsIMozIconURI* aIconURI, + HICON* hIcon) +{ + nsresult rv = NS_OK; + + // We can only do this on Vista or above + HMODULE hShellDLL = ::LoadLibraryW(L"shell32.dll"); + decltype(SHGetStockIconInfo)* pSHGetStockIconInfo = + (decltype(SHGetStockIconInfo)*) ::GetProcAddress(hShellDLL, + "SHGetStockIconInfo"); + + if (pSHGetStockIconInfo) { + uint32_t desiredImageSize; + aIconURI->GetImageSize(&desiredImageSize); + nsAutoCString stockIcon; + aIconURI->GetStockIcon(stockIcon); + + SHSTOCKICONID stockIconID = GetStockIconIDForName(stockIcon); + if (stockIconID == SIID_INVALID) { + return NS_ERROR_NOT_AVAILABLE; + } + + UINT infoFlags = SHGSI_ICON; + infoFlags |= GetSizeInfoFlag(desiredImageSize); + + SHSTOCKICONINFO sii = {0}; + sii.cbSize = sizeof(sii); + HRESULT hr = pSHGetStockIconInfo(stockIconID, infoFlags, &sii); + + if (SUCCEEDED(hr)) { + *hIcon = sii.hIcon; + } else { + rv = NS_ERROR_FAILURE; + } + } else { + rv = NS_ERROR_NOT_AVAILABLE; + } + + if (hShellDLL) { + ::FreeLibrary(hShellDLL); + } + + return rv; +} + +// Given a BITMAPINFOHEADER, returns the size of the color table. +static int +GetColorTableSize(BITMAPINFOHEADER* aHeader) +{ + int colorTableSize = -1; + + // http://msdn.microsoft.com/en-us/library/dd183376%28v=VS.85%29.aspx + switch (aHeader->biBitCount) { + case 0: + colorTableSize = 0; + break; + case 1: + colorTableSize = 2 * sizeof(RGBQUAD); + break; + case 4: + case 8: { + // The maximum possible size for the color table is 2**bpp, so check for + // that and fail if we're not in those bounds + unsigned int maxEntries = 1 << (aHeader->biBitCount); + if (aHeader->biClrUsed > 0 && aHeader->biClrUsed <= maxEntries) { + colorTableSize = aHeader->biClrUsed * sizeof(RGBQUAD); + } else if (aHeader->biClrUsed == 0) { + colorTableSize = maxEntries * sizeof(RGBQUAD); + } + break; + } + case 16: + case 32: + // If we have BI_BITFIELDS compression, we would normally need 3 DWORDS for + // the bitfields mask which would be stored in the color table; However, + // we instead force the bitmap to request data of type BI_RGB so the color + // table should be of size 0. + // Setting aHeader->biCompression = BI_RGB forces the later call to + // GetDIBits to return to us BI_RGB data. + if (aHeader->biCompression == BI_BITFIELDS) { + aHeader->biCompression = BI_RGB; + } + colorTableSize = 0; + break; + case 24: + colorTableSize = 0; + break; + } + + if (colorTableSize < 0) { + NS_WARNING("Unable to figure out the color table size for this bitmap"); + } + + return colorTableSize; +} + +// Given a header and a size, creates a freshly allocated BITMAPINFO structure. +// It is the caller's responsibility to null-check and delete the structure. +static BITMAPINFO* +CreateBitmapInfo(BITMAPINFOHEADER* aHeader, size_t aColorTableSize) +{ + BITMAPINFO* bmi = (BITMAPINFO*) ::operator new(sizeof(BITMAPINFOHEADER) + + aColorTableSize, + mozilla::fallible); + if (bmi) { + memcpy(bmi, aHeader, sizeof(BITMAPINFOHEADER)); + memset(bmi->bmiColors, 0, aColorTableSize); + } + return bmi; +} + +nsresult +nsIconChannel::MakeInputStream(nsIInputStream** _retval, bool aNonBlocking) +{ + // Check whether the icon requested's a file icon or a stock icon + nsresult rv = NS_ERROR_NOT_AVAILABLE; + + // GetDIBits does not exist on windows mobile. + HICON hIcon = nullptr; + + nsCOMPtr iconURI(do_QueryInterface(mUrl, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString stockIcon; + iconURI->GetStockIcon(stockIcon); + if (!stockIcon.IsEmpty()) { + rv = GetStockHIcon(iconURI, &hIcon); + } else { + rv = GetHIconFromFile(&hIcon); + } + + NS_ENSURE_SUCCESS(rv, rv); + + if (hIcon) { + // we got a handle to an icon. Now we want to get a bitmap for the icon + // using GetIconInfo.... + ICONINFO iconInfo; + if (GetIconInfo(hIcon, &iconInfo)) { + // we got the bitmaps, first find out their size + HDC hDC = CreateCompatibleDC(nullptr); // get a device context for + // the screen. + BITMAPINFOHEADER maskHeader = {sizeof(BITMAPINFOHEADER)}; + BITMAPINFOHEADER colorHeader = {sizeof(BITMAPINFOHEADER)}; + int colorTableSize, maskTableSize; + if (GetDIBits(hDC, iconInfo.hbmMask, 0, 0, nullptr, + (BITMAPINFO*)&maskHeader, DIB_RGB_COLORS) && + GetDIBits(hDC, iconInfo.hbmColor, 0, 0, nullptr, + (BITMAPINFO*)&colorHeader, DIB_RGB_COLORS) && + maskHeader.biHeight == colorHeader.biHeight && + maskHeader.biWidth == colorHeader.biWidth && + colorHeader.biBitCount > 8 && + colorHeader.biSizeImage > 0 && + colorHeader.biWidth >= 0 && colorHeader.biWidth <= 255 && + colorHeader.biHeight >= 0 && colorHeader.biHeight <= 255 && + maskHeader.biSizeImage > 0 && + (colorTableSize = GetColorTableSize(&colorHeader)) >= 0 && + (maskTableSize = GetColorTableSize(&maskHeader)) >= 0) { + uint32_t iconSize = sizeof(ICONFILEHEADER) + + sizeof(ICONENTRY) + + sizeof(BITMAPINFOHEADER) + + colorHeader.biSizeImage + + maskHeader.biSizeImage; + + UniquePtr buffer = MakeUnique(iconSize); + if (!buffer) { + rv = NS_ERROR_OUT_OF_MEMORY; + } else { + char* whereTo = buffer.get(); + int howMuch; + + // the data starts with an icon file header + ICONFILEHEADER iconHeader; + iconHeader.ifhReserved = 0; + iconHeader.ifhType = 1; + iconHeader.ifhCount = 1; + howMuch = sizeof(ICONFILEHEADER); + memcpy(whereTo, &iconHeader, howMuch); + whereTo += howMuch; + + // followed by the single icon entry + ICONENTRY iconEntry; + iconEntry.ieWidth = static_cast(colorHeader.biWidth); + iconEntry.ieHeight = static_cast(colorHeader.biHeight); + iconEntry.ieColors = 0; + iconEntry.ieReserved = 0; + iconEntry.iePlanes = 1; + iconEntry.ieBitCount = colorHeader.biBitCount; + iconEntry.ieSizeImage = sizeof(BITMAPINFOHEADER) + + colorHeader.biSizeImage + + maskHeader.biSizeImage; + iconEntry.ieFileOffset = sizeof(ICONFILEHEADER) + sizeof(ICONENTRY); + howMuch = sizeof(ICONENTRY); + memcpy(whereTo, &iconEntry, howMuch); + whereTo += howMuch; + + // followed by the bitmap info header + // (doubling the height because icons have two bitmaps) + colorHeader.biHeight *= 2; + colorHeader.biSizeImage += maskHeader.biSizeImage; + howMuch = sizeof(BITMAPINFOHEADER); + memcpy(whereTo, &colorHeader, howMuch); + whereTo += howMuch; + colorHeader.biHeight /= 2; + colorHeader.biSizeImage -= maskHeader.biSizeImage; + + // followed by the XOR bitmap data (colorHeader) + // (you'd expect the color table to come here, but it apparently + // doesn't) + BITMAPINFO* colorInfo = CreateBitmapInfo(&colorHeader, + colorTableSize); + if (colorInfo && GetDIBits(hDC, iconInfo.hbmColor, 0, + colorHeader.biHeight, whereTo, colorInfo, + DIB_RGB_COLORS)) { + whereTo += colorHeader.biSizeImage; + + // and finally the AND bitmap data (maskHeader) + BITMAPINFO* maskInfo = CreateBitmapInfo(&maskHeader, maskTableSize); + if (maskInfo && GetDIBits(hDC, iconInfo.hbmMask, 0, + maskHeader.biHeight, whereTo, maskInfo, + DIB_RGB_COLORS)) { + // Now, create a pipe and stuff our data into it + nsCOMPtr inStream; + nsCOMPtr outStream; + rv = NS_NewPipe(getter_AddRefs(inStream), + getter_AddRefs(outStream), + iconSize, iconSize, aNonBlocking); + if (NS_SUCCEEDED(rv)) { + uint32_t written; + rv = outStream->Write(buffer.get(), iconSize, &written); + if (NS_SUCCEEDED(rv)) { + NS_ADDREF(*_retval = inStream); + } + } + + } // if we got bitmap bits + delete maskInfo; + } // if we got mask bits + delete colorInfo; + } // if we allocated the buffer + } // if we got mask size + + DeleteDC(hDC); + DeleteObject(iconInfo.hbmColor); + DeleteObject(iconInfo.hbmMask); + } // if we got icon info + DestroyIcon(hIcon); + } // if we got an hIcon + + // If we didn't make a stream, then fail. + if (!*_retval && NS_SUCCEEDED(rv)) { + rv = NS_ERROR_NOT_AVAILABLE; + } + return rv; +} + +NS_IMETHODIMP +nsIconChannel::GetContentType(nsACString& aContentType) +{ + aContentType.AssignLiteral(IMAGE_ICO); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::SetContentType(const nsACString& aContentType) +{ + // It doesn't make sense to set the content-type on this type + // of channel... + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsIconChannel::GetContentCharset(nsACString& aContentCharset) +{ + aContentCharset.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::SetContentCharset(const nsACString& aContentCharset) +{ + // It doesn't make sense to set the content-charset on this type + // of channel... + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsIconChannel::GetContentDisposition(uint32_t* aContentDisposition) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsIconChannel::SetContentDisposition(uint32_t aContentDisposition) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsIconChannel:: + GetContentDispositionFilename(nsAString& aContentDispositionFilename) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsIconChannel:: + SetContentDispositionFilename(const nsAString& aContentDispositionFilename) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsIconChannel:: + GetContentDispositionHeader(nsACString& aContentDispositionHeader) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsIconChannel::GetContentLength(int64_t* aContentLength) +{ + *aContentLength = 0; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsIconChannel::SetContentLength(int64_t aContentLength) +{ + NS_NOTREACHED("nsIconChannel::SetContentLength"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsIconChannel::GetOwner(nsISupports** aOwner) +{ + *aOwner = mOwner.get(); + NS_IF_ADDREF(*aOwner); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::SetOwner(nsISupports* aOwner) +{ + mOwner = aOwner; + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) +{ + NS_IF_ADDREF(*aLoadInfo = mLoadInfo); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) +{ + mLoadInfo = aLoadInfo; + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel:: + GetNotificationCallbacks(nsIInterfaceRequestor** aNotificationCallbacks) +{ + *aNotificationCallbacks = mCallbacks.get(); + NS_IF_ADDREF(*aNotificationCallbacks); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel:: + SetNotificationCallbacks(nsIInterfaceRequestor* aNotificationCallbacks) +{ + mCallbacks = aNotificationCallbacks; + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::GetSecurityInfo(nsISupports** aSecurityInfo) +{ + *aSecurityInfo = nullptr; + return NS_OK; +} + +// nsIRequestObserver methods +NS_IMETHODIMP nsIconChannel::OnStartRequest(nsIRequest* aRequest, + nsISupports* aContext) +{ + if (mListener) { + return mListener->OnStartRequest(this, aContext); + } + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::OnStopRequest(nsIRequest* aRequest, + nsISupports* aContext, + nsresult aStatus) +{ + if (mListener) { + mListener->OnStopRequest(this, aContext, aStatus); + mListener = nullptr; + } + + // Remove from load group + if (mLoadGroup) { + mLoadGroup->RemoveRequest(this, nullptr, aStatus); + } + + // Drop notification callbacks to prevent cycles. + mCallbacks = nullptr; + + return NS_OK; +} + +// nsIStreamListener methods +NS_IMETHODIMP +nsIconChannel::OnDataAvailable(nsIRequest* aRequest, + nsISupports* aContext, + nsIInputStream* aStream, + uint64_t aOffset, + uint32_t aCount) +{ + if (mListener) { + return mListener->OnDataAvailable(this, aContext, aStream, aOffset, aCount); + } + return NS_OK; +} diff --git a/image/decoders/icon/win/nsIconChannel.h b/image/decoders/icon/win/nsIconChannel.h new file mode 100644 index 000000000..94df5a52e --- /dev/null +++ b/image/decoders/icon/win/nsIconChannel.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_encoders_icon_win_nsIconChannel_h +#define mozilla_image_encoders_icon_win_nsIconChannel_h + +#include "mozilla/Attributes.h" + +#include "nsCOMPtr.h" +#include "nsXPIDLString.h" +#include "nsIChannel.h" +#include "nsILoadGroup.h" +#include "nsILoadInfo.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIURI.h" +#include "nsIInputStreamPump.h" +#include "nsIStreamListener.h" +#include "nsIIconURI.h" + +#include + +class nsIFile; + +class nsIconChannel final : public nsIChannel, public nsIStreamListener +{ + ~nsIconChannel(); + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUEST + NS_DECL_NSICHANNEL + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + nsIconChannel(); + + nsresult Init(nsIURI* uri); + +protected: + nsCOMPtr mUrl; + nsCOMPtr mOriginalURI; + nsCOMPtr mLoadGroup; + nsCOMPtr mCallbacks; + nsCOMPtr mOwner; + nsCOMPtr mLoadInfo; + + nsCOMPtr mPump; + nsCOMPtr mListener; + + nsresult ExtractIconInfoFromUrl(nsIFile** aLocalFile, + uint32_t* aDesiredImageSize, + nsCString& aContentType, + nsCString& aFileExtension); + nsresult GetHIconFromFile(HICON* hIcon); + nsresult MakeInputStream(nsIInputStream** _retval, bool nonBlocking); + + // Functions specific to Vista and above +protected: + nsresult GetStockHIcon(nsIMozIconURI* aIconURI, HICON* hIcon); +}; + +#endif // mozilla_image_encoders_icon_win_nsIconChannel_h diff --git a/image/decoders/moz.build b/image/decoders/moz.build new file mode 100644 index 000000000..775c3eaa5 --- /dev/null +++ b/image/decoders/moz.build @@ -0,0 +1,47 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +toolkit = CONFIG['MOZ_WIDGET_TOOLKIT'] + +# The Icon Channel stuff really shouldn't live in decoders/icon, but we'll +# fix that another time. +if 'gtk' in toolkit: + DIRS += ['icon/gtk', 'icon'] + +if CONFIG['OS_ARCH'] == 'WINNT': + DIRS += ['icon/win', 'icon'] + +if toolkit == 'cocoa': + DIRS += ['icon/mac', 'icon'] +elif toolkit == 'android': + DIRS += ['icon/android', 'icon'] + +UNIFIED_SOURCES += [ + 'EXIF.cpp', + 'iccjpeg.c', + 'nsBMPDecoder.cpp', + 'nsGIFDecoder2.cpp', + 'nsICODecoder.cpp', + 'nsIconDecoder.cpp', + 'nsJPEGDecoder.cpp', + 'nsPNGDecoder.cpp', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +LOCAL_INCLUDES += [ + # Access to Skia headers for Downscaler. + '/gfx/2d', + # Decoders need ImageLib headers. + '/image', +] + +LOCAL_INCLUDES += CONFIG['SKIA_INCLUDES'] + +FINAL_LIBRARY = 'xul' + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/image/decoders/nsBMPDecoder.cpp b/image/decoders/nsBMPDecoder.cpp new file mode 100644 index 000000000..1f0449e4e --- /dev/null +++ b/image/decoders/nsBMPDecoder.cpp @@ -0,0 +1,1067 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This is a cross-platform BMP Decoder, which should work everywhere, +// including big-endian machines like the PowerPC. +// +// BMP is a format that has been extended multiple times. To understand the +// decoder you need to understand this history. The summary of the history +// below was determined from the following documents. +// +// - http://www.fileformat.info/format/bmp/egff.htm +// - http://www.fileformat.info/format/os2bmp/egff.htm +// - http://fileformats.archiveteam.org/wiki/BMP +// - http://fileformats.archiveteam.org/wiki/OS/2_BMP +// - https://en.wikipedia.org/wiki/BMP_file_format +// - https://upload.wikimedia.org/wikipedia/commons/c/c4/BMPfileFormat.png +// +// WINDOWS VERSIONS OF THE BMP FORMAT +// ---------------------------------- +// WinBMPv1. +// - This version is no longer used and can be ignored. +// +// WinBMPv2. +// - First is a 14 byte file header that includes: the magic number ("BM"), +// file size, and offset to the pixel data (|mDataOffset|). +// - Next is a 12 byte info header which includes: the info header size +// (mBIHSize), width, height, number of color planes, and bits-per-pixel +// (|mBpp|) which must be 1, 4, 8 or 24. +// - Next is the semi-optional color table, which has length 2^|mBpp| and has 3 +// bytes per value (BGR). The color table is required if |mBpp| is 1, 4, or 8. +// - Next is an optional gap. +// - Next is the pixel data, which is pointed to by |mDataOffset|. +// +// WinBMPv3. This is the most widely used version. +// - It changed the info header to 40 bytes by taking the WinBMPv2 info +// header, enlargening its width and height fields, and adding more fields +// including: a compression type (|mCompression|) and number of colors +// (|mNumColors|). +// - The semi-optional color table is now 4 bytes per value (BGR0), and its +// length is |mNumColors|, or 2^|mBpp| if |mNumColors| is zero. +// - |mCompression| can be RGB (i.e. no compression), RLE4 (if |mBpp|==4) or +// RLE8 (if |mBpp|==8) values. +// +// WinBMPv3-NT. A variant of WinBMPv3. +// - It did not change the info header layout from WinBMPv3. +// - |mBpp| can now be 16 or 32, in which case |mCompression| can be RGB or the +// new BITFIELDS value; in the latter case an additional 12 bytes of color +// bitfields follow the info header. +// +// WinBMPv4. +// - It extended the info header to 108 bytes, including the 12 bytes of color +// mask data from WinBMPv3-NT, plus alpha mask data, and also color-space and +// gamma correction fields. +// +// WinBMPv5. +// - It extended the info header to 124 bytes, adding color profile data. +// - It also added an optional color profile table after the pixel data (and +// another optional gap). +// +// WinBMPv3-ICO. This is a variant of WinBMPv3. +// - It's the BMP format used for BMP images within ICO files. +// - The only difference with WinBMPv3 is that if an image is 32bpp and has no +// compression, then instead of treating the pixel data as 0RGB it is treated +// as ARGB, but only if one or more of the A values are non-zero. +// +// OS/2 VERSIONS OF THE BMP FORMAT +// ------------------------------- +// OS2-BMPv1. +// - Almost identical to WinBMPv2; the differences are basically ignorable. +// +// OS2-BMPv2. +// - Similar to WinBMPv3. +// - The info header is 64 bytes but can be reduced to as little as 16; any +// omitted fields are treated as zero. The first 40 bytes of these fields are +// nearly identical to the WinBMPv3 info header; the remaining 24 bytes are +// different. +// - Also adds compression types "Huffman 1D" and "RLE24", which we don't +// support. +// - We treat OS2-BMPv2 files as if they are WinBMPv3 (i.e. ignore the extra 24 +// bytes in the info header), which in practice is good enough. + +#include "ImageLogging.h" +#include "nsBMPDecoder.h" + +#include + +#include "mozilla/Attributes.h" +#include "mozilla/EndianUtils.h" +#include "mozilla/Likely.h" + +#include "nsIInputStream.h" +#include "RasterImage.h" +#include + +using namespace mozilla::gfx; + +namespace mozilla { +namespace image { +namespace bmp { + +struct Compression { + enum { + RGB = 0, + RLE8 = 1, + RLE4 = 2, + BITFIELDS = 3 + }; +}; + +// RLE escape codes and constants. +struct RLE { + enum { + ESCAPE = 0, + ESCAPE_EOL = 0, + ESCAPE_EOF = 1, + ESCAPE_DELTA = 2, + + SEGMENT_LENGTH = 2, + DELTA_LENGTH = 2 + }; +}; + +} // namespace bmp + +using namespace bmp; + +/// Sets the pixel data in aDecoded to the given values. +/// @param aDecoded pointer to pixel to be set, will be incremented to point to +/// the next pixel. +static void +SetPixel(uint32_t*& aDecoded, uint8_t aRed, uint8_t aGreen, + uint8_t aBlue, uint8_t aAlpha = 0xFF) +{ + *aDecoded++ = gfxPackedPixel(aAlpha, aRed, aGreen, aBlue); +} + +static void +SetPixel(uint32_t*& aDecoded, uint8_t idx, + const UniquePtr& aColors) +{ + SetPixel(aDecoded, + aColors[idx].mRed, aColors[idx].mGreen, aColors[idx].mBlue); +} + +/// Sets two (or one if aCount = 1) pixels +/// @param aDecoded where the data is stored. Will be moved 4 resp 8 bytes +/// depending on whether one or two pixels are written. +/// @param aData The values for the two pixels +/// @param aCount Current count. Is decremented by one or two. +static void +Set4BitPixel(uint32_t*& aDecoded, uint8_t aData, uint32_t& aCount, + const UniquePtr& aColors) +{ + uint8_t idx = aData >> 4; + SetPixel(aDecoded, idx, aColors); + if (--aCount > 0) { + idx = aData & 0xF; + SetPixel(aDecoded, idx, aColors); + --aCount; + } +} + +static mozilla::LazyLogModule sBMPLog("BMPDecoder"); + +// The length of the mBIHSize field in the info header. +static const uint32_t BIHSIZE_FIELD_LENGTH = 4; + +nsBMPDecoder::nsBMPDecoder(RasterImage* aImage, State aState, size_t aLength) + : Decoder(aImage) + , mLexer(Transition::To(aState, aLength), Transition::TerminateSuccess()) + , mIsWithinICO(false) + , mMayHaveTransparency(false) + , mDoesHaveTransparency(false) + , mNumColors(0) + , mColors(nullptr) + , mBytesPerColor(0) + , mPreGapLength(0) + , mPixelRowSize(0) + , mCurrentRow(0) + , mCurrentPos(0) + , mAbsoluteModeNumPixels(0) +{ +} + +// Constructor for normal BMP files. +nsBMPDecoder::nsBMPDecoder(RasterImage* aImage) + : nsBMPDecoder(aImage, State::FILE_HEADER, FILE_HEADER_LENGTH) +{ +} + +// Constructor used for WinBMPv3-ICO files, which lack a file header. +nsBMPDecoder::nsBMPDecoder(RasterImage* aImage, uint32_t aDataOffset) + : nsBMPDecoder(aImage, State::INFO_HEADER_SIZE, BIHSIZE_FIELD_LENGTH) +{ + SetIsWithinICO(); + + // Even though the file header isn't present in this case, the dataOffset + // field is set as if it is, and so we must increment mPreGapLength + // accordingly. + mPreGapLength += FILE_HEADER_LENGTH; + + // This is the one piece of data we normally get from a BMP file header, so + // it must be provided via an argument. + mH.mDataOffset = aDataOffset; +} + +nsBMPDecoder::~nsBMPDecoder() +{ +} + +// Obtains the size of the compressed image resource. +int32_t +nsBMPDecoder::GetCompressedImageSize() const +{ + // In the RGB case mImageSize might not be set, so compute it manually. + MOZ_ASSERT(mPixelRowSize != 0); + return mH.mCompression == Compression::RGB + ? mPixelRowSize * AbsoluteHeight() + : mH.mImageSize; +} + +nsresult +nsBMPDecoder::BeforeFinishInternal() +{ + if (!IsMetadataDecode() && !mImageData) { + return NS_ERROR_FAILURE; // No image; something went wrong. + } + + return NS_OK; +} + +nsresult +nsBMPDecoder::FinishInternal() +{ + // We shouldn't be called in error cases. + MOZ_ASSERT(!HasError(), "Can't call FinishInternal on error!"); + + // We should never make multiple frames. + MOZ_ASSERT(GetFrameCount() <= 1, "Multiple BMP frames?"); + + // Send notifications if appropriate. + if (!IsMetadataDecode() && HasSize()) { + + // We should have image data. + MOZ_ASSERT(mImageData); + + // If it was truncated, fill in the missing pixels as black. + while (mCurrentRow > 0) { + uint32_t* dst = RowBuffer(); + while (mCurrentPos < mH.mWidth) { + SetPixel(dst, 0, 0, 0); + mCurrentPos++; + } + mCurrentPos = 0; + FinishRow(); + } + + // Invalidate. + nsIntRect r(0, 0, mH.mWidth, AbsoluteHeight()); + PostInvalidation(r); + + MOZ_ASSERT_IF(mDoesHaveTransparency, mMayHaveTransparency); + + // We have transparency if we either detected some in the image itself + // (i.e., |mDoesHaveTransparency| is true) or we're in an ICO, which could + // mean we have an AND mask that provides transparency (i.e., |mIsWithinICO| + // is true). + // XXX(seth): We can tell when we create the decoder if the AND mask is + // present, so we could be more precise about this. + const Opacity opacity = mDoesHaveTransparency || mIsWithinICO + ? Opacity::SOME_TRANSPARENCY + : Opacity::FULLY_OPAQUE; + + PostFrameStop(opacity); + PostDecodeDone(); + } + + return NS_OK; +} + +// ---------------------------------------- +// Actual Data Processing +// ---------------------------------------- + +void +BitFields::Value::Set(uint32_t aMask) +{ + mMask = aMask; + + // Handle this exceptional case first. The chosen values don't matter + // (because a mask of zero will always give a value of zero) except that + // mBitWidth: + // - shouldn't be zero, because that would cause an infinite loop in Get(); + // - shouldn't be 5 or 8, because that could cause a false positive match in + // IsR5G5B5() or IsR8G8B8(). + if (mMask == 0x0) { + mRightShift = 0; + mBitWidth = 1; + return; + } + + // Find the rightmost 1. + uint8_t i; + for (i = 0; i < 32; i++) { + if (mMask & (1 << i)) { + break; + } + } + mRightShift = i; + + // Now find the leftmost 1 in the same run of 1s. (If there are multiple runs + // of 1s -- which isn't valid -- we'll behave as if only the lowest run was + // present, which seems reasonable.) + for (i = i + 1; i < 32; i++) { + if (!(mMask & (1 << i))) { + break; + } + } + mBitWidth = i - mRightShift; +} + +MOZ_ALWAYS_INLINE uint8_t +BitFields::Value::Get(uint32_t aValue) const +{ + // Extract the unscaled value. + uint32_t v = (aValue & mMask) >> mRightShift; + + // Idea: to upscale v precisely we need to duplicate its bits, possibly + // repeatedly, possibly partially in the last case, from bit 7 down to bit 0 + // in v2. For example: + // + // - mBitWidth=1: v2 = v<<7 | v<<6 | ... | v<<1 | v>>0 k -> kkkkkkkk + // - mBitWidth=2: v2 = v<<6 | v<<4 | v<<2 | v>>0 jk -> jkjkjkjk + // - mBitWidth=3: v2 = v<<5 | v<<2 | v>>1 ijk -> ijkijkij + // - mBitWidth=4: v2 = v<<4 | v>>0 hijk -> hijkhijk + // - mBitWidth=5: v2 = v<<3 | v>>2 ghijk -> ghijkghi + // - mBitWidth=6: v2 = v<<2 | v>>4 fghijk -> fghijkfg + // - mBitWidth=7: v2 = v<<1 | v>>6 efghijk -> efghijke + // - mBitWidth=8: v2 = v>>0 defghijk -> defghijk + // - mBitWidth=9: v2 = v>>1 cdefghijk -> cdefghij + // - mBitWidth=10: v2 = v>>2 bcdefghijk -> bcdefghi + // - mBitWidth=11: v2 = v>>3 abcdefghijk -> abcdefgh + // - etc. + // + uint8_t v2 = 0; + int32_t i; // must be a signed integer + for (i = 8 - mBitWidth; i > 0; i -= mBitWidth) { + v2 |= v << uint32_t(i); + } + v2 |= v >> uint32_t(-i); + return v2; +} + +MOZ_ALWAYS_INLINE uint8_t +BitFields::Value::GetAlpha(uint32_t aValue, bool& aHasAlphaOut) const +{ + if (mMask == 0x0) { + return 0xff; + } + aHasAlphaOut = true; + return Get(aValue); +} + +MOZ_ALWAYS_INLINE uint8_t +BitFields::Value::Get5(uint32_t aValue) const +{ + MOZ_ASSERT(mBitWidth == 5); + uint32_t v = (aValue & mMask) >> mRightShift; + return (v << 3u) | (v >> 2u); +} + +MOZ_ALWAYS_INLINE uint8_t +BitFields::Value::Get8(uint32_t aValue) const +{ + MOZ_ASSERT(mBitWidth == 8); + uint32_t v = (aValue & mMask) >> mRightShift; + return v; +} + +void +BitFields::SetR5G5B5() +{ + mRed.Set(0x7c00); + mGreen.Set(0x03e0); + mBlue.Set(0x001f); +} + +void +BitFields::SetR8G8B8() +{ + mRed.Set(0xff0000); + mGreen.Set(0xff00); + mBlue.Set(0x00ff); +} + +bool +BitFields::IsR5G5B5() const +{ + return mRed.mBitWidth == 5 && + mGreen.mBitWidth == 5 && + mBlue.mBitWidth == 5 && + mAlpha.mMask == 0x0; +} + +bool +BitFields::IsR8G8B8() const +{ + return mRed.mBitWidth == 8 && + mGreen.mBitWidth == 8 && + mBlue.mBitWidth == 8 && + mAlpha.mMask == 0x0; +} + +uint32_t* +nsBMPDecoder::RowBuffer() +{ + if (mDownscaler) { + return reinterpret_cast(mDownscaler->RowBuffer()) + mCurrentPos; + } + + // Convert from row (1..mHeight) to absolute line (0..mHeight-1). + int32_t line = (mH.mHeight < 0) + ? -mH.mHeight - mCurrentRow + : mCurrentRow - 1; + int32_t offset = line * mH.mWidth + mCurrentPos; + return reinterpret_cast(mImageData) + offset; +} + +void +nsBMPDecoder::FinishRow() +{ + if (mDownscaler) { + mDownscaler->CommitRow(); + + if (mDownscaler->HasInvalidation()) { + DownscalerInvalidRect invalidRect = mDownscaler->TakeInvalidRect(); + PostInvalidation(invalidRect.mOriginalSizeRect, + Some(invalidRect.mTargetSizeRect)); + } + } else { + PostInvalidation(IntRect(0, mCurrentRow, mH.mWidth, 1)); + } + mCurrentRow--; +} + +LexerResult +nsBMPDecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume) +{ + MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!"); + + return mLexer.Lex(aIterator, aOnResume, + [=](State aState, const char* aData, size_t aLength) { + switch (aState) { + case State::FILE_HEADER: return ReadFileHeader(aData, aLength); + case State::INFO_HEADER_SIZE: return ReadInfoHeaderSize(aData, aLength); + case State::INFO_HEADER_REST: return ReadInfoHeaderRest(aData, aLength); + case State::BITFIELDS: return ReadBitfields(aData, aLength); + case State::COLOR_TABLE: return ReadColorTable(aData, aLength); + case State::GAP: return SkipGap(); + case State::AFTER_GAP: return AfterGap(); + case State::PIXEL_ROW: return ReadPixelRow(aData); + case State::RLE_SEGMENT: return ReadRLESegment(aData); + case State::RLE_DELTA: return ReadRLEDelta(aData); + case State::RLE_ABSOLUTE: return ReadRLEAbsolute(aData, aLength); + default: + MOZ_CRASH("Unknown State"); + } + }); +} + +LexerTransition +nsBMPDecoder::ReadFileHeader(const char* aData, size_t aLength) +{ + mPreGapLength += aLength; + + bool signatureOk = aData[0] == 'B' && aData[1] == 'M'; + if (!signatureOk) { + return Transition::TerminateFailure(); + } + + // We ignore the filesize (aData + 2) and reserved (aData + 6) fields. + + mH.mDataOffset = LittleEndian::readUint32(aData + 10); + + return Transition::To(State::INFO_HEADER_SIZE, BIHSIZE_FIELD_LENGTH); +} + +// We read the info header in two steps: (a) read the mBIHSize field to +// determine how long the header is; (b) read the rest of the header. +LexerTransition +nsBMPDecoder::ReadInfoHeaderSize(const char* aData, size_t aLength) +{ + mPreGapLength += aLength; + + mH.mBIHSize = LittleEndian::readUint32(aData); + + bool bihSizeOk = mH.mBIHSize == InfoHeaderLength::WIN_V2 || + mH.mBIHSize == InfoHeaderLength::WIN_V3 || + mH.mBIHSize == InfoHeaderLength::WIN_V4 || + mH.mBIHSize == InfoHeaderLength::WIN_V5 || + (mH.mBIHSize >= InfoHeaderLength::OS2_V2_MIN && + mH.mBIHSize <= InfoHeaderLength::OS2_V2_MAX); + if (!bihSizeOk) { + return Transition::TerminateFailure(); + } + // ICO BMPs must have a WinBMPv3 header. nsICODecoder should have already + // terminated decoding if this isn't the case. + MOZ_ASSERT_IF(mIsWithinICO, mH.mBIHSize == InfoHeaderLength::WIN_V3); + + return Transition::To(State::INFO_HEADER_REST, + mH.mBIHSize - BIHSIZE_FIELD_LENGTH); +} + +LexerTransition +nsBMPDecoder::ReadInfoHeaderRest(const char* aData, size_t aLength) +{ + mPreGapLength += aLength; + + // |mWidth| and |mHeight| may be signed (Windows) or unsigned (OS/2). We just + // read as unsigned because in practice that's good enough. + if (mH.mBIHSize == InfoHeaderLength::WIN_V2) { + mH.mWidth = LittleEndian::readUint16(aData + 0); + mH.mHeight = LittleEndian::readUint16(aData + 2); + // We ignore the planes (aData + 4) field; it should always be 1. + mH.mBpp = LittleEndian::readUint16(aData + 6); + } else { + mH.mWidth = LittleEndian::readUint32(aData + 0); + mH.mHeight = LittleEndian::readUint32(aData + 4); + // We ignore the planes (aData + 4) field; it should always be 1. + mH.mBpp = LittleEndian::readUint16(aData + 10); + + // For OS2-BMPv2 the info header may be as little as 16 bytes, so be + // careful for these fields. + mH.mCompression = aLength >= 16 ? LittleEndian::readUint32(aData + 12) : 0; + mH.mImageSize = aLength >= 20 ? LittleEndian::readUint32(aData + 16) : 0; + // We ignore the xppm (aData + 20) and yppm (aData + 24) fields. + mH.mNumColors = aLength >= 32 ? LittleEndian::readUint32(aData + 28) : 0; + // We ignore the important_colors (aData + 36) field. + + // For WinBMPv4, WinBMPv5 and (possibly) OS2-BMPv2 there are additional + // fields in the info header which we ignore, with the possible exception + // of the color bitfields (see below). + } + + // Run with MOZ_LOG=BMPDecoder:5 set to see this output. + MOZ_LOG(sBMPLog, LogLevel::Debug, + ("BMP: bihsize=%u, %d x %d, bpp=%u, compression=%u, colors=%u\n", + mH.mBIHSize, mH.mWidth, mH.mHeight, uint32_t(mH.mBpp), + mH.mCompression, mH.mNumColors)); + + // BMPs with negative width are invalid. Also, reject extremely wide images + // to keep the math sane. And reject INT_MIN as a height because you can't + // get its absolute value (because -INT_MIN is one more than INT_MAX). + const int32_t k64KWidth = 0x0000FFFF; + bool sizeOk = 0 <= mH.mWidth && mH.mWidth <= k64KWidth && + mH.mHeight != INT_MIN; + if (!sizeOk) { + return Transition::TerminateFailure(); + } + + // Check mBpp and mCompression. + bool bppCompressionOk = + (mH.mCompression == Compression::RGB && + (mH.mBpp == 1 || mH.mBpp == 4 || mH.mBpp == 8 || + mH.mBpp == 16 || mH.mBpp == 24 || mH.mBpp == 32)) || + (mH.mCompression == Compression::RLE8 && mH.mBpp == 8) || + (mH.mCompression == Compression::RLE4 && mH.mBpp == 4) || + (mH.mCompression == Compression::BITFIELDS && + // For BITFIELDS compression we require an exact match for one of the + // WinBMP BIH sizes; this clearly isn't an OS2 BMP. + (mH.mBIHSize == InfoHeaderLength::WIN_V3 || + mH.mBIHSize == InfoHeaderLength::WIN_V4 || + mH.mBIHSize == InfoHeaderLength::WIN_V5) && + (mH.mBpp == 16 || mH.mBpp == 32)); + if (!bppCompressionOk) { + return Transition::TerminateFailure(); + } + + // Initialize our current row to the top of the image. + mCurrentRow = AbsoluteHeight(); + + // Round it up to the nearest byte count, then pad to 4-byte boundary. + // Compute this even for a metadate decode because GetCompressedImageSize() + // relies on it. + mPixelRowSize = (mH.mBpp * mH.mWidth + 7) / 8; + uint32_t surplus = mPixelRowSize % 4; + if (surplus != 0) { + mPixelRowSize += 4 - surplus; + } + + size_t bitFieldsLengthStillToRead = 0; + if (mH.mCompression == Compression::BITFIELDS) { + // Need to read bitfields. + if (mH.mBIHSize >= InfoHeaderLength::WIN_V4) { + // Bitfields are present in the info header, so we can read them + // immediately. + mBitFields.ReadFromHeader(aData + 36, /* aReadAlpha = */ true); + } else { + // Bitfields are present after the info header, so we will read them in + // ReadBitfields(). + bitFieldsLengthStillToRead = BitFields::LENGTH; + } + } else if (mH.mBpp == 16) { + // No bitfields specified; use the default 5-5-5 values. + mBitFields.SetR5G5B5(); + } else if (mH.mBpp == 32) { + // No bitfields specified; use the default 8-8-8 values. + mBitFields.SetR8G8B8(); + } + + return Transition::To(State::BITFIELDS, bitFieldsLengthStillToRead); +} + +void +BitFields::ReadFromHeader(const char* aData, bool aReadAlpha) +{ + mRed.Set (LittleEndian::readUint32(aData + 0)); + mGreen.Set(LittleEndian::readUint32(aData + 4)); + mBlue.Set (LittleEndian::readUint32(aData + 8)); + if (aReadAlpha) { + mAlpha.Set(LittleEndian::readUint32(aData + 12)); + } +} + +LexerTransition +nsBMPDecoder::ReadBitfields(const char* aData, size_t aLength) +{ + mPreGapLength += aLength; + + // If aLength is zero there are no bitfields to read, or we already read them + // in ReadInfoHeader(). + if (aLength != 0) { + mBitFields.ReadFromHeader(aData, /* aReadAlpha = */ false); + } + + // Note that RLE-encoded BMPs might be transparent because the 'delta' mode + // can skip pixels and cause implicit transparency. + mMayHaveTransparency = + mIsWithinICO || + mH.mCompression == Compression::RLE8 || + mH.mCompression == Compression::RLE4 || + (mH.mCompression == Compression::BITFIELDS && + mBitFields.mAlpha.IsPresent()); + if (mMayHaveTransparency) { + PostHasTransparency(); + } + + // Post our size to the superclass. + PostSize(mH.mWidth, AbsoluteHeight()); + + // We've now read all the headers. If we're doing a metadata decode, we're + // done. + if (IsMetadataDecode()) { + return Transition::TerminateSuccess(); + } + + // Set up the color table, if present; it'll be filled in by ReadColorTable(). + if (mH.mBpp <= 8) { + mNumColors = 1 << mH.mBpp; + if (0 < mH.mNumColors && mH.mNumColors < mNumColors) { + mNumColors = mH.mNumColors; + } + + // Always allocate and zero 256 entries, even though mNumColors might be + // smaller, because the file might erroneously index past mNumColors. + mColors = MakeUnique(256); + memset(mColors.get(), 0, 256 * sizeof(ColorTableEntry)); + + // OS/2 Bitmaps have no padding byte. + mBytesPerColor = (mH.mBIHSize == InfoHeaderLength::WIN_V2) ? 3 : 4; + } + + MOZ_ASSERT(!mImageData, "Already have a buffer allocated?"); + nsresult rv = AllocateFrame(/* aFrameNum = */ 0, OutputSize(), + FullOutputFrame(), + mMayHaveTransparency ? SurfaceFormat::B8G8R8A8 + : SurfaceFormat::B8G8R8X8); + if (NS_FAILED(rv)) { + return Transition::TerminateFailure(); + } + MOZ_ASSERT(mImageData, "Should have a buffer now"); + + if (mDownscaler) { + // BMPs store their rows in reverse order, so the downscaler needs to + // reverse them again when writing its output. Unless the height is + // negative! + rv = mDownscaler->BeginFrame(Size(), Nothing(), + mImageData, mMayHaveTransparency, + /* aFlipVertically = */ mH.mHeight >= 0); + if (NS_FAILED(rv)) { + return Transition::TerminateFailure(); + } + } + + return Transition::To(State::COLOR_TABLE, mNumColors * mBytesPerColor); +} + +LexerTransition +nsBMPDecoder::ReadColorTable(const char* aData, size_t aLength) +{ + MOZ_ASSERT_IF(aLength != 0, mNumColors > 0 && mColors); + + mPreGapLength += aLength; + + for (uint32_t i = 0; i < mNumColors; i++) { + // The format is BGR or BGR0. + mColors[i].mBlue = uint8_t(aData[0]); + mColors[i].mGreen = uint8_t(aData[1]); + mColors[i].mRed = uint8_t(aData[2]); + aData += mBytesPerColor; + } + + // We know how many bytes we've read so far (mPreGapLength) and we know the + // offset of the pixel data (mH.mDataOffset), so we can determine the length + // of the gap (possibly zero) between the color table and the pixel data. + // + // If the gap is negative the file must be malformed (e.g. mH.mDataOffset + // points into the middle of the color palette instead of past the end) and + // we give up. + if (mPreGapLength > mH.mDataOffset) { + return Transition::TerminateFailure(); + } + + uint32_t gapLength = mH.mDataOffset - mPreGapLength; + return Transition::ToUnbuffered(State::AFTER_GAP, State::GAP, gapLength); +} + +LexerTransition +nsBMPDecoder::SkipGap() +{ + return Transition::ContinueUnbuffered(State::GAP); +} + +LexerTransition +nsBMPDecoder::AfterGap() +{ + // If there are no pixels we can stop. + // + // XXX: normally, if there are no pixels we will have stopped decoding before + // now, outside of this decoder. However, if the BMP is within an ICO file, + // it's possible that the ICO claimed the image had a non-zero size while the + // BMP claims otherwise. This test is to catch that awkward case. If we ever + // come up with a more general solution to this ICO-and-BMP-disagree-on-size + // problem, this test can be removed. + if (mH.mWidth == 0 || mH.mHeight == 0) { + return Transition::TerminateSuccess(); + } + + bool hasRLE = mH.mCompression == Compression::RLE8 || + mH.mCompression == Compression::RLE4; + return hasRLE + ? Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH) + : Transition::To(State::PIXEL_ROW, mPixelRowSize); +} + +LexerTransition +nsBMPDecoder::ReadPixelRow(const char* aData) +{ + MOZ_ASSERT(mCurrentRow > 0); + MOZ_ASSERT(mCurrentPos == 0); + + const uint8_t* src = reinterpret_cast(aData); + uint32_t* dst = RowBuffer(); + uint32_t lpos = mH.mWidth; + switch (mH.mBpp) { + case 1: + while (lpos > 0) { + int8_t bit; + uint8_t idx; + for (bit = 7; bit >= 0 && lpos > 0; bit--) { + idx = (*src >> bit) & 1; + SetPixel(dst, idx, mColors); + --lpos; + } + ++src; + } + break; + + case 4: + while (lpos > 0) { + Set4BitPixel(dst, *src, lpos, mColors); + ++src; + } + break; + + case 8: + while (lpos > 0) { + SetPixel(dst, *src, mColors); + --lpos; + ++src; + } + break; + + case 16: + if (mBitFields.IsR5G5B5()) { + // Specialize this common case. + while (lpos > 0) { + uint16_t val = LittleEndian::readUint16(src); + SetPixel(dst, mBitFields.mRed.Get5(val), + mBitFields.mGreen.Get5(val), + mBitFields.mBlue.Get5(val)); + --lpos; + src += 2; + } + } else { + bool anyHasAlpha = false; + while (lpos > 0) { + uint16_t val = LittleEndian::readUint16(src); + SetPixel(dst, mBitFields.mRed.Get(val), + mBitFields.mGreen.Get(val), + mBitFields.mBlue.Get(val), + mBitFields.mAlpha.GetAlpha(val, anyHasAlpha)); + --lpos; + src += 2; + } + if (anyHasAlpha) { + MOZ_ASSERT(mMayHaveTransparency); + mDoesHaveTransparency = true; + } + } + break; + + case 24: + while (lpos > 0) { + SetPixel(dst, src[2], src[1], src[0]); + --lpos; + src += 3; + } + break; + + case 32: + if (mH.mCompression == Compression::RGB && mIsWithinICO && + mH.mBpp == 32) { + // This is a special case only used for 32bpp WinBMPv3-ICO files, which + // could be in either 0RGB or ARGB format. We start by assuming it's + // an 0RGB image. If we hit a non-zero alpha value, then we know it's + // actually an ARGB image, and change tack accordingly. + // (Note: a fully-transparent ARGB image is indistinguishable from a + // 0RGB image, and we will render such an image as a 0RGB image, i.e. + // opaquely. This is unlikely to be a problem in practice.) + while (lpos > 0) { + if (!mDoesHaveTransparency && src[3] != 0) { + // Up until now this looked like an 0RGB image, but we now know + // it's actually an ARGB image. Which means every pixel we've seen + // so far has been fully transparent. So we go back and redo them. + + // Tell the Downscaler to go back to the start. + if (mDownscaler) { + mDownscaler->ResetForNextProgressivePass(); + } + + // Redo the complete rows we've already done. + MOZ_ASSERT(mCurrentPos == 0); + int32_t currentRow = mCurrentRow; + mCurrentRow = AbsoluteHeight(); + while (mCurrentRow > currentRow) { + dst = RowBuffer(); + for (int32_t i = 0; i < mH.mWidth; i++) { + SetPixel(dst, 0, 0, 0, 0); + } + FinishRow(); + } + + // Redo the part of this row we've already done. + dst = RowBuffer(); + int32_t n = mH.mWidth - lpos; + for (int32_t i = 0; i < n; i++) { + SetPixel(dst, 0, 0, 0, 0); + } + + MOZ_ASSERT(mMayHaveTransparency); + mDoesHaveTransparency = true; + } + + // If mDoesHaveTransparency is false, treat this as an 0RGB image. + // Otherwise, treat this as an ARGB image. + SetPixel(dst, src[2], src[1], src[0], + mDoesHaveTransparency ? src[3] : 0xff); + src += 4; + --lpos; + } + } else if (mBitFields.IsR8G8B8()) { + // Specialize this common case. + while (lpos > 0) { + uint32_t val = LittleEndian::readUint32(src); + SetPixel(dst, mBitFields.mRed.Get8(val), + mBitFields.mGreen.Get8(val), + mBitFields.mBlue.Get8(val)); + --lpos; + src += 4; + } + } else { + bool anyHasAlpha = false; + while (lpos > 0) { + uint32_t val = LittleEndian::readUint32(src); + SetPixel(dst, mBitFields.mRed.Get(val), + mBitFields.mGreen.Get(val), + mBitFields.mBlue.Get(val), + mBitFields.mAlpha.GetAlpha(val, anyHasAlpha)); + --lpos; + src += 4; + } + if (anyHasAlpha) { + MOZ_ASSERT(mMayHaveTransparency); + mDoesHaveTransparency = true; + } + } + break; + + default: + MOZ_CRASH("Unsupported color depth; earlier check didn't catch it?"); + } + + FinishRow(); + return mCurrentRow == 0 + ? Transition::TerminateSuccess() + : Transition::To(State::PIXEL_ROW, mPixelRowSize); +} + +LexerTransition +nsBMPDecoder::ReadRLESegment(const char* aData) +{ + if (mCurrentRow == 0) { + return Transition::TerminateSuccess(); + } + + uint8_t byte1 = uint8_t(aData[0]); + uint8_t byte2 = uint8_t(aData[1]); + + if (byte1 != RLE::ESCAPE) { + // Encoded mode consists of two bytes: byte1 specifies the number of + // consecutive pixels to be drawn using the color index contained in + // byte2. + // + // Work around bitmaps that specify too many pixels. + uint32_t pixelsNeeded = + std::min(mH.mWidth - mCurrentPos, byte1); + if (pixelsNeeded) { + uint32_t* dst = RowBuffer(); + mCurrentPos += pixelsNeeded; + if (mH.mCompression == Compression::RLE8) { + do { + SetPixel(dst, byte2, mColors); + pixelsNeeded --; + } while (pixelsNeeded); + } else { + do { + Set4BitPixel(dst, byte2, pixelsNeeded, mColors); + } while (pixelsNeeded); + } + } + return Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH); + } + + if (byte2 == RLE::ESCAPE_EOL) { + mCurrentPos = 0; + FinishRow(); + return mCurrentRow == 0 + ? Transition::TerminateSuccess() + : Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH); + } + + if (byte2 == RLE::ESCAPE_EOF) { + return Transition::TerminateSuccess(); + } + + if (byte2 == RLE::ESCAPE_DELTA) { + return Transition::To(State::RLE_DELTA, RLE::DELTA_LENGTH); + } + + // Absolute mode. |byte2| gives the number of pixels. The length depends on + // whether it's 4-bit or 8-bit RLE. Also, the length must be even (and zero + // padding is used to achieve this when necessary). + MOZ_ASSERT(mAbsoluteModeNumPixels == 0); + mAbsoluteModeNumPixels = byte2; + uint32_t length = byte2; + if (mH.mCompression == Compression::RLE4) { + length = (length + 1) / 2; // halve, rounding up + } + if (length & 1) { + length++; + } + return Transition::To(State::RLE_ABSOLUTE, length); +} + +LexerTransition +nsBMPDecoder::ReadRLEDelta(const char* aData) +{ + // Delta encoding makes it possible to skip pixels making part of the image + // transparent. + MOZ_ASSERT(mMayHaveTransparency); + mDoesHaveTransparency = true; + + if (mDownscaler) { + // Clear the skipped pixels. (This clears to the end of the row, + // which is perfect if there's a Y delta and harmless if not). + mDownscaler->ClearRestOfRow(/* aStartingAtCol = */ mCurrentPos); + } + + // Handle the XDelta. + mCurrentPos += uint8_t(aData[0]); + if (mCurrentPos > mH.mWidth) { + mCurrentPos = mH.mWidth; + } + + // Handle the Y Delta. + int32_t yDelta = std::min(uint8_t(aData[1]), mCurrentRow); + mCurrentRow -= yDelta; + + if (mDownscaler && yDelta > 0) { + // Commit the current row (the first of the skipped rows). + mDownscaler->CommitRow(); + + // Clear and commit the remaining skipped rows. + for (int32_t line = 1; line < yDelta; line++) { + mDownscaler->ClearRow(); + mDownscaler->CommitRow(); + } + } + + return mCurrentRow == 0 + ? Transition::TerminateSuccess() + : Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH); +} + +LexerTransition +nsBMPDecoder::ReadRLEAbsolute(const char* aData, size_t aLength) +{ + uint32_t n = mAbsoluteModeNumPixels; + mAbsoluteModeNumPixels = 0; + + if (mCurrentPos + n > uint32_t(mH.mWidth)) { + // Bad data. Stop decoding; at least part of the image may have been + // decoded. + return Transition::TerminateSuccess(); + } + + // In absolute mode, n represents the number of pixels that follow, each of + // which contains the color index of a single pixel. + uint32_t* dst = RowBuffer(); + uint32_t iSrc = 0; + uint32_t* oldPos = dst; + if (mH.mCompression == Compression::RLE8) { + while (n > 0) { + SetPixel(dst, aData[iSrc], mColors); + n--; + iSrc++; + } + } else { + while (n > 0) { + Set4BitPixel(dst, aData[iSrc], n, mColors); + iSrc++; + } + } + mCurrentPos += dst - oldPos; + + // We should read all the data (unless the last byte is zero padding). + MOZ_ASSERT(iSrc == aLength - 1 || iSrc == aLength); + + return Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH); +} + +} // namespace image +} // namespace mozilla diff --git a/image/decoders/nsBMPDecoder.h b/image/decoders/nsBMPDecoder.h new file mode 100644 index 000000000..0cf2af689 --- /dev/null +++ b/image/decoders/nsBMPDecoder.h @@ -0,0 +1,235 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_decoders_nsBMPDecoder_h +#define mozilla_image_decoders_nsBMPDecoder_h + +#include "BMPHeaders.h" +#include "Decoder.h" +#include "gfxColor.h" +#include "StreamingLexer.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { +namespace image { + +namespace bmp { + +/// This struct contains the fields from the file header and info header that +/// we use during decoding. (Excluding bitfields fields, which are kept in +/// BitFields.) +struct Header { + uint32_t mDataOffset; // Offset to raster data. + uint32_t mBIHSize; // Header size. + int32_t mWidth; // Image width. + int32_t mHeight; // Image height. + uint16_t mBpp; // Bits per pixel. + uint32_t mCompression; // See struct Compression for valid values. + uint32_t mImageSize; // (compressed) image size. Can be 0 if + // mCompression==0. + uint32_t mNumColors; // Used colors. + + Header() + : mDataOffset(0) + , mBIHSize(0) + , mWidth(0) + , mHeight(0) + , mBpp(0) + , mCompression(0) + , mImageSize(0) + , mNumColors(0) + {} +}; + +/// An entry in the color table. +struct ColorTableEntry { + uint8_t mRed; + uint8_t mGreen; + uint8_t mBlue; +}; + +/// All the color-related bitfields for 16bpp and 32bpp images. We use this +/// even for older format BMPs that don't have explicit bitfields. +class BitFields { + class Value { + friend class BitFields; + + uint32_t mMask; // The mask for the value. + uint8_t mRightShift; // The amount to right-shift after masking. + uint8_t mBitWidth; // The width (in bits) of the value. + + /// Sets the mask (and thus the right-shift and bit-width as well). + void Set(uint32_t aMask); + + public: + Value() + { + mMask = 0; + mRightShift = 0; + mBitWidth = 0; + } + + /// Returns true if this channel is used. Only used for alpha. + bool IsPresent() const { return mMask != 0x0; } + + /// Extracts the single color value from the multi-color value. + uint8_t Get(uint32_t aVal) const; + + /// Like Get(), but specially for alpha. + uint8_t GetAlpha(uint32_t aVal, bool& aHasAlphaOut) const; + + /// Specialized versions of Get() for when the bit-width is 5 or 8. + /// (They will assert if called and the bit-width is not 5 or 8.) + uint8_t Get5(uint32_t aVal) const; + uint8_t Get8(uint32_t aVal) const; + }; + +public: + /// The individual color channels. + Value mRed; + Value mGreen; + Value mBlue; + Value mAlpha; + + /// Set bitfields to the standard 5-5-5 16bpp values. + void SetR5G5B5(); + + /// Set bitfields to the standard 8-8-8 32bpp values. + void SetR8G8B8(); + + /// Test if bitfields have the standard 5-5-5 16bpp values. + bool IsR5G5B5() const; + + /// Test if bitfields have the standard 8-8-8 32bpp values. + bool IsR8G8B8() const; + + /// Read the bitfields from a header. The reading of the alpha mask is + /// optional. + void ReadFromHeader(const char* aData, bool aReadAlpha); + + /// Length of the bitfields structure in the BMP file. + static const size_t LENGTH = 12; +}; + +} // namespace bmp + +class RasterImage; + +/// Decoder for BMP-Files, as used by Windows and OS/2. + +class nsBMPDecoder : public Decoder +{ +public: + ~nsBMPDecoder(); + + /// Obtains the internal output image buffer. + uint32_t* GetImageData() { return reinterpret_cast(mImageData); } + + /// Obtains the length of the internal output image buffer. + size_t GetImageDataLength() const { return mImageDataLength; } + + /// Obtains the size of the compressed image resource. + int32_t GetCompressedImageSize() const; + + /// Mark this BMP as being within an ICO file. Only used for testing purposes + /// because the ICO-specific constructor does this marking automatically. + void SetIsWithinICO() { mIsWithinICO = true; } + + /// Did the BMP file have alpha data of any kind? (Only use this after the + /// bitmap has been fully decoded.) + bool HasTransparency() const { return mDoesHaveTransparency; } + + LexerResult DoDecode(SourceBufferIterator& aIterator, + IResumable* aOnResume) override; + nsresult BeforeFinishInternal() override; + nsresult FinishInternal() override; + +private: + friend class DecoderFactory; + + enum class State { + FILE_HEADER, + INFO_HEADER_SIZE, + INFO_HEADER_REST, + BITFIELDS, + COLOR_TABLE, + GAP, + AFTER_GAP, + PIXEL_ROW, + RLE_SEGMENT, + RLE_DELTA, + RLE_ABSOLUTE + }; + + // This is the constructor used for normal BMP images. + explicit nsBMPDecoder(RasterImage* aImage); + + // This is the constructor used for BMP resources in ICO images. + nsBMPDecoder(RasterImage* aImage, uint32_t aDataOffset); + + // Helper constructor called by the other two. + nsBMPDecoder(RasterImage* aImage, State aState, size_t aLength); + + int32_t AbsoluteHeight() const { return abs(mH.mHeight); } + + uint32_t* RowBuffer(); + + void FinishRow(); + + LexerTransition ReadFileHeader(const char* aData, size_t aLength); + LexerTransition ReadInfoHeaderSize(const char* aData, size_t aLength); + LexerTransition ReadInfoHeaderRest(const char* aData, size_t aLength); + LexerTransition ReadBitfields(const char* aData, size_t aLength); + LexerTransition ReadColorTable(const char* aData, size_t aLength); + LexerTransition SkipGap(); + LexerTransition AfterGap(); + LexerTransition ReadPixelRow(const char* aData); + LexerTransition ReadRLESegment(const char* aData); + LexerTransition ReadRLEDelta(const char* aData); + LexerTransition ReadRLEAbsolute(const char* aData, size_t aLength); + + StreamingLexer mLexer; + + bmp::Header mH; + + // If the BMP is within an ICO file our treatment of it differs slightly. + bool mIsWithinICO; + + bmp::BitFields mBitFields; + + // Might the image have transparency? Determined from the headers during + // metadata decode. (Does not guarantee the image actually has transparency.) + bool mMayHaveTransparency; + + // Does the image have transparency? Determined during full decoding, so only + // use this after that has been completed. + bool mDoesHaveTransparency; + + uint32_t mNumColors; // The number of used colors, i.e. the number of + // entries in mColors, if it's present. + UniquePtr mColors; // The color table, if it's present. + uint32_t mBytesPerColor; // 3 or 4, depending on the format + + // The number of bytes prior to the optional gap that have been read. This + // is used to find the start of the pixel data. + uint32_t mPreGapLength; + + uint32_t mPixelRowSize; // The number of bytes per pixel row. + + int32_t mCurrentRow; // Index of the row of the image that's currently + // being decoded: [height,1]. + int32_t mCurrentPos; // Index into the current line. Used when + // doing RLE decoding and when filling in pixels + // for truncated files. + + // Only used in RLE_ABSOLUTE state: the number of pixels to read. + uint32_t mAbsoluteModeNumPixels; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_decoders_nsBMPDecoder_h diff --git a/image/decoders/nsGIFDecoder2.cpp b/image/decoders/nsGIFDecoder2.cpp new file mode 100644 index 000000000..7955438e4 --- /dev/null +++ b/image/decoders/nsGIFDecoder2.cpp @@ -0,0 +1,1085 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +/* +The Graphics Interchange Format(c) is the copyright property of CompuServe +Incorporated. Only CompuServe Incorporated is authorized to define, redefine, +enhance, alter, modify or change in any way the definition of the format. + +CompuServe Incorporated hereby grants a limited, non-exclusive, royalty-free +license for the use of the Graphics Interchange Format(sm) in computer +software; computer software utilizing GIF(sm) must acknowledge ownership of the +Graphics Interchange Format and its Service Mark by CompuServe Incorporated, in +User and Technical Documentation. Computer software utilizing GIF, which is +distributed or may be distributed without User or Technical Documentation must +display to the screen or printer a message acknowledging ownership of the +Graphics Interchange Format and the Service Mark by CompuServe Incorporated; in +this case, the acknowledgement may be displayed in an opening screen or leading +banner, or a closing screen or trailing banner. A message such as the following +may be used: + + "The Graphics Interchange Format(c) is the Copyright property of + CompuServe Incorporated. GIF(sm) is a Service Mark property of + CompuServe Incorporated." + +For further information, please contact : + + CompuServe Incorporated + Graphics Technology Department + 5000 Arlington Center Boulevard + Columbus, Ohio 43220 + U. S. A. + +CompuServe Incorporated maintains a mailing list with all those individuals and +organizations who wish to receive copies of this document when it is corrected +or revised. This service is offered free of charge; please provide us with your +mailing address. +*/ + +#include "nsGIFDecoder2.h" + +#include + +#include "imgFrame.h" +#include "mozilla/EndianUtils.h" +#include "nsIInputStream.h" +#include "RasterImage.h" +#include "SurfacePipeFactory.h" + +#include "gfxColor.h" +#include "gfxPlatform.h" +#include "qcms.h" +#include +#include "mozilla/Telemetry.h" + +using namespace mozilla::gfx; + +using std::max; + +namespace mozilla { +namespace image { + +////////////////////////////////////////////////////////////////////// +// GIF Decoder Implementation + +static const size_t GIF_HEADER_LEN = 6; +static const size_t GIF_SCREEN_DESCRIPTOR_LEN = 7; +static const size_t BLOCK_HEADER_LEN = 1; +static const size_t SUB_BLOCK_HEADER_LEN = 1; +static const size_t EXTENSION_HEADER_LEN = 2; +static const size_t GRAPHIC_CONTROL_EXTENSION_LEN = 4; +static const size_t APPLICATION_EXTENSION_LEN = 11; +static const size_t IMAGE_DESCRIPTOR_LEN = 9; + +// Masks for reading color table information from packed fields in the screen +// descriptor and image descriptor blocks. +static const uint8_t PACKED_FIELDS_COLOR_TABLE_BIT = 0x80; +static const uint8_t PACKED_FIELDS_INTERLACED_BIT = 0x40; +static const uint8_t PACKED_FIELDS_TABLE_DEPTH_MASK = 0x07; + +nsGIFDecoder2::nsGIFDecoder2(RasterImage* aImage) + : Decoder(aImage) + , mLexer(Transition::To(State::GIF_HEADER, GIF_HEADER_LEN), + Transition::TerminateSuccess()) + , mOldColor(0) + , mCurrentFrameIndex(-1) + , mColorTablePos(0) + , mGIFOpen(false) + , mSawTransparency(false) +{ + // Clear out the structure, excluding the arrays. + memset(&mGIFStruct, 0, sizeof(mGIFStruct)); + + // Initialize as "animate once" in case no NETSCAPE2.0 extension is found. + mGIFStruct.loop_count = 1; +} + +nsGIFDecoder2::~nsGIFDecoder2() +{ + free(mGIFStruct.local_colormap); +} + +nsresult +nsGIFDecoder2::FinishInternal() +{ + MOZ_ASSERT(!HasError(), "Shouldn't call FinishInternal after error!"); + + // If the GIF got cut off, handle it anyway + if (!IsMetadataDecode() && mGIFOpen) { + if (mCurrentFrameIndex == mGIFStruct.images_decoded) { + EndImageFrame(); + } + PostDecodeDone(mGIFStruct.loop_count - 1); + mGIFOpen = false; + } + + return NS_OK; +} + +void +nsGIFDecoder2::FlushImageData() +{ + Maybe invalidRect = mPipe.TakeInvalidRect(); + if (!invalidRect) { + return; + } + + PostInvalidation(invalidRect->mInputSpaceRect, + Some(invalidRect->mOutputSpaceRect)); +} + +//****************************************************************************** +// GIF decoder callback methods. Part of public API for GIF2 +//****************************************************************************** + +//****************************************************************************** +void +nsGIFDecoder2::BeginGIF() +{ + if (mGIFOpen) { + return; + } + + mGIFOpen = true; + + PostSize(mGIFStruct.screen_width, mGIFStruct.screen_height); +} + +bool +nsGIFDecoder2::CheckForTransparency(const IntRect& aFrameRect) +{ + // Check if the image has a transparent color in its palette. + if (mGIFStruct.is_transparent) { + PostHasTransparency(); + return true; + } + + if (mGIFStruct.images_decoded > 0) { + return false; // We only care about first frame padding below. + } + + // If we need padding on the first frame, that means we don't draw into part + // of the image at all. Report that as transparency. + IntRect imageRect(0, 0, mGIFStruct.screen_width, mGIFStruct.screen_height); + if (!imageRect.IsEqualEdges(aFrameRect)) { + PostHasTransparency(); + mSawTransparency = true; // Make sure we don't optimize it away. + return true; + } + + return false; +} + +//****************************************************************************** +nsresult +nsGIFDecoder2::BeginImageFrame(const IntRect& aFrameRect, + uint16_t aDepth, + bool aIsInterlaced) +{ + MOZ_ASSERT(HasSize()); + + bool hasTransparency = CheckForTransparency(aFrameRect); + gfx::SurfaceFormat format = hasTransparency ? SurfaceFormat::B8G8R8A8 + : SurfaceFormat::B8G8R8X8; + + // Make sure there's no animation if we're downscaling. + MOZ_ASSERT_IF(Size() != OutputSize(), !GetImageMetadata().HasAnimation()); + + SurfacePipeFlags pipeFlags = aIsInterlaced + ? SurfacePipeFlags::DEINTERLACE + : SurfacePipeFlags(); + + Maybe pipe; + if (mGIFStruct.images_decoded == 0) { + // The first frame may be displayed progressively. + pipeFlags |= SurfacePipeFlags::PROGRESSIVE_DISPLAY; + + // The first frame is always decoded into an RGB surface. + pipe = + SurfacePipeFactory::CreateSurfacePipe(this, mGIFStruct.images_decoded, + Size(), OutputSize(), + aFrameRect, format, pipeFlags); + } else { + // This is an animation frame (and not the first). To minimize the memory + // usage of animations, the image data is stored in paletted form. + MOZ_ASSERT(Size() == OutputSize()); + pipe = + SurfacePipeFactory::CreatePalettedSurfacePipe(this, mGIFStruct.images_decoded, + Size(), aFrameRect, format, + aDepth, pipeFlags); + } + + mCurrentFrameIndex = mGIFStruct.images_decoded; + + if (!pipe) { + mPipe = SurfacePipe(); + return NS_ERROR_FAILURE; + } + + mPipe = Move(*pipe); + return NS_OK; +} + + +//****************************************************************************** +void +nsGIFDecoder2::EndImageFrame() +{ + Opacity opacity = Opacity::SOME_TRANSPARENCY; + + if (mGIFStruct.images_decoded == 0) { + // We need to send invalidations for the first frame. + FlushImageData(); + + // The first frame was preallocated with alpha; if it wasn't transparent, we + // should fix that. We can also mark it opaque unconditionally if we didn't + // actually see any transparent pixels - this test is only valid for the + // first frame. + if (!mGIFStruct.is_transparent && !mSawTransparency) { + opacity = Opacity::FULLY_OPAQUE; + } + } + + // Unconditionally increment images_decoded, because we unconditionally + // append frames in BeginImageFrame(). This ensures that images_decoded + // always refers to the frame in mImage we're currently decoding, + // even if some of them weren't decoded properly and thus are blank. + mGIFStruct.images_decoded++; + + // Tell the superclass we finished a frame + PostFrameStop(opacity, + DisposalMethod(mGIFStruct.disposal_method), + FrameTimeout::FromRawMilliseconds(mGIFStruct.delay_time)); + + // Reset the transparent pixel + if (mOldColor) { + mColormap[mGIFStruct.tpixel] = mOldColor; + mOldColor = 0; + } + + mCurrentFrameIndex = -1; +} + +template +PixelSize +nsGIFDecoder2::ColormapIndexToPixel(uint8_t aIndex) +{ + MOZ_ASSERT(sizeof(PixelSize) == sizeof(uint32_t)); + + // Retrieve the next color, clamping to the size of the colormap. + uint32_t color = mColormap[aIndex & mColorMask]; + + // Check for transparency. + if (mGIFStruct.is_transparent) { + mSawTransparency = mSawTransparency || color == 0; + } + + return color; +} + +template <> +uint8_t +nsGIFDecoder2::ColormapIndexToPixel(uint8_t aIndex) +{ + return aIndex & mColorMask; +} + +template +NextPixel +nsGIFDecoder2::YieldPixel(const uint8_t* aData, + size_t aLength, + size_t* aBytesReadOut) +{ + MOZ_ASSERT(aData); + MOZ_ASSERT(aBytesReadOut); + MOZ_ASSERT(mGIFStruct.stackp >= mGIFStruct.stack); + + // Advance to the next byte we should read. + const uint8_t* data = aData + *aBytesReadOut; + + // If we don't have any decoded data to yield, try to read some input and + // produce some. + if (mGIFStruct.stackp == mGIFStruct.stack) { + while (mGIFStruct.bits < mGIFStruct.codesize && *aBytesReadOut < aLength) { + // Feed the next byte into the decoder's 32-bit input buffer. + mGIFStruct.datum += int32_t(*data) << mGIFStruct.bits; + mGIFStruct.bits += 8; + data += 1; + *aBytesReadOut += 1; + } + + if (mGIFStruct.bits < mGIFStruct.codesize) { + return AsVariant(WriteState::NEED_MORE_DATA); + } + + // Get the leading variable-length symbol from the data stream. + int code = mGIFStruct.datum & mGIFStruct.codemask; + mGIFStruct.datum >>= mGIFStruct.codesize; + mGIFStruct.bits -= mGIFStruct.codesize; + + const int clearCode = ClearCode(); + + // Reset the dictionary to its original state, if requested + if (code == clearCode) { + mGIFStruct.codesize = mGIFStruct.datasize + 1; + mGIFStruct.codemask = (1 << mGIFStruct.codesize) - 1; + mGIFStruct.avail = clearCode + 2; + mGIFStruct.oldcode = -1; + return AsVariant(WriteState::NEED_MORE_DATA); + } + + // Check for explicit end-of-stream code. It should only appear after all + // image data, but if that was the case we wouldn't be in this function, so + // this is always an error condition. + if (code == (clearCode + 1)) { + return AsVariant(WriteState::FAILURE); + } + + if (mGIFStruct.oldcode == -1) { + if (code >= MAX_BITS) { + return AsVariant(WriteState::FAILURE); // The code's too big; something's wrong. + } + + mGIFStruct.firstchar = mGIFStruct.oldcode = code; + + // Yield a pixel at the appropriate index in the colormap. + mGIFStruct.pixels_remaining--; + return AsVariant(ColormapIndexToPixel(mGIFStruct.suffix[code])); + } + + int incode = code; + if (code >= mGIFStruct.avail) { + *mGIFStruct.stackp++ = mGIFStruct.firstchar; + code = mGIFStruct.oldcode; + + if (mGIFStruct.stackp >= mGIFStruct.stack + MAX_BITS) { + return AsVariant(WriteState::FAILURE); // Stack overflow; something's wrong. + } + } + + while (code >= clearCode) { + if ((code >= MAX_BITS) || (code == mGIFStruct.prefix[code])) { + return AsVariant(WriteState::FAILURE); + } + + *mGIFStruct.stackp++ = mGIFStruct.suffix[code]; + code = mGIFStruct.prefix[code]; + + if (mGIFStruct.stackp >= mGIFStruct.stack + MAX_BITS) { + return AsVariant(WriteState::FAILURE); // Stack overflow; something's wrong. + } + } + + *mGIFStruct.stackp++ = mGIFStruct.firstchar = mGIFStruct.suffix[code]; + + // Define a new codeword in the dictionary. + if (mGIFStruct.avail < 4096) { + mGIFStruct.prefix[mGIFStruct.avail] = mGIFStruct.oldcode; + mGIFStruct.suffix[mGIFStruct.avail] = mGIFStruct.firstchar; + mGIFStruct.avail++; + + // If we've used up all the codewords of a given length increase the + // length of codewords by one bit, but don't exceed the specified maximum + // codeword size of 12 bits. + if (((mGIFStruct.avail & mGIFStruct.codemask) == 0) && + (mGIFStruct.avail < 4096)) { + mGIFStruct.codesize++; + mGIFStruct.codemask += mGIFStruct.avail; + } + } + + mGIFStruct.oldcode = incode; + } + + if (MOZ_UNLIKELY(mGIFStruct.stackp <= mGIFStruct.stack)) { + MOZ_ASSERT_UNREACHABLE("No decoded data but we didn't return early?"); + return AsVariant(WriteState::FAILURE); + } + + // Yield a pixel at the appropriate index in the colormap. + mGIFStruct.pixels_remaining--; + return AsVariant(ColormapIndexToPixel(*--mGIFStruct.stackp)); +} + +/// Expand the colormap from RGB to Packed ARGB as needed by Cairo. +/// And apply any LCMS transformation. +static void +ConvertColormap(uint32_t* aColormap, uint32_t aColors) +{ + // Apply CMS transformation if enabled and available + if (gfxPlatform::GetCMSMode() == eCMSMode_All) { + qcms_transform* transform = gfxPlatform::GetCMSRGBTransform(); + if (transform) { + qcms_transform_data(transform, aColormap, aColormap, aColors); + } + } + + // Convert from the GIF's RGB format to the Cairo format. + // Work from end to begin, because of the in-place expansion + uint8_t* from = ((uint8_t*)aColormap) + 3 * aColors; + uint32_t* to = aColormap + aColors; + + // Convert color entries to Cairo format + + // set up for loops below + if (!aColors) { + return; + } + uint32_t c = aColors; + + // copy as bytes until source pointer is 32-bit-aligned + // NB: can't use 32-bit reads, they might read off the end of the buffer + for (; (NS_PTR_TO_UINT32(from) & 0x3) && c; --c) { + from -= 3; + *--to = gfxPackedPixel(0xFF, from[0], from[1], from[2]); + } + + // bulk copy of pixels. + while (c >= 4) { + from -= 12; + to -= 4; + c -= 4; + GFX_BLOCK_RGB_TO_FRGB(from,to); + } + + // copy remaining pixel(s) + // NB: can't use 32-bit reads, they might read off the end of the buffer + while (c--) { + from -= 3; + *--to = gfxPackedPixel(0xFF, from[0], from[1], from[2]); + } +} + +LexerResult +nsGIFDecoder2::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume) +{ + MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!"); + + return mLexer.Lex(aIterator, aOnResume, + [=](State aState, const char* aData, size_t aLength) { + switch(aState) { + case State::GIF_HEADER: + return ReadGIFHeader(aData); + case State::SCREEN_DESCRIPTOR: + return ReadScreenDescriptor(aData); + case State::GLOBAL_COLOR_TABLE: + return ReadGlobalColorTable(aData, aLength); + case State::FINISHED_GLOBAL_COLOR_TABLE: + return FinishedGlobalColorTable(); + case State::BLOCK_HEADER: + return ReadBlockHeader(aData); + case State::EXTENSION_HEADER: + return ReadExtensionHeader(aData); + case State::GRAPHIC_CONTROL_EXTENSION: + return ReadGraphicControlExtension(aData); + case State::APPLICATION_IDENTIFIER: + return ReadApplicationIdentifier(aData); + case State::NETSCAPE_EXTENSION_SUB_BLOCK: + return ReadNetscapeExtensionSubBlock(aData); + case State::NETSCAPE_EXTENSION_DATA: + return ReadNetscapeExtensionData(aData); + case State::IMAGE_DESCRIPTOR: + return ReadImageDescriptor(aData); + case State::FINISH_IMAGE_DESCRIPTOR: + return FinishImageDescriptor(aData); + case State::LOCAL_COLOR_TABLE: + return ReadLocalColorTable(aData, aLength); + case State::FINISHED_LOCAL_COLOR_TABLE: + return FinishedLocalColorTable(); + case State::IMAGE_DATA_BLOCK: + return ReadImageDataBlock(aData); + case State::IMAGE_DATA_SUB_BLOCK: + return ReadImageDataSubBlock(aData); + case State::LZW_DATA: + return ReadLZWData(aData, aLength); + case State::SKIP_LZW_DATA: + return Transition::ContinueUnbuffered(State::SKIP_LZW_DATA); + case State::FINISHED_LZW_DATA: + return Transition::To(State::IMAGE_DATA_SUB_BLOCK, SUB_BLOCK_HEADER_LEN); + case State::SKIP_SUB_BLOCKS: + return SkipSubBlocks(aData); + case State::SKIP_DATA_THEN_SKIP_SUB_BLOCKS: + return Transition::ContinueUnbuffered(State::SKIP_DATA_THEN_SKIP_SUB_BLOCKS); + case State::FINISHED_SKIPPING_DATA: + return Transition::To(State::SKIP_SUB_BLOCKS, SUB_BLOCK_HEADER_LEN); + default: + MOZ_CRASH("Unknown State"); + } + }); +} + +LexerTransition +nsGIFDecoder2::ReadGIFHeader(const char* aData) +{ + // We retrieve the version here but because many GIF encoders set header + // fields incorrectly, we barely use it; features which should only appear in + // GIF89a are always accepted. + if (strncmp(aData, "GIF87a", GIF_HEADER_LEN) == 0) { + mGIFStruct.version = 87; + } else if (strncmp(aData, "GIF89a", GIF_HEADER_LEN) == 0) { + mGIFStruct.version = 89; + } else { + return Transition::TerminateFailure(); + } + + return Transition::To(State::SCREEN_DESCRIPTOR, GIF_SCREEN_DESCRIPTOR_LEN); +} + +LexerTransition +nsGIFDecoder2::ReadScreenDescriptor(const char* aData) +{ + mGIFStruct.screen_width = LittleEndian::readUint16(aData + 0); + mGIFStruct.screen_height = LittleEndian::readUint16(aData + 2); + + const uint8_t packedFields = aData[4]; + + // XXX: Should we be capturing these values even if there is no global color + // table? + mGIFStruct.global_colormap_depth = + (packedFields & PACKED_FIELDS_TABLE_DEPTH_MASK) + 1; + mGIFStruct.global_colormap_count = 1 << mGIFStruct.global_colormap_depth; + + // We ignore several fields in the header. We don't care about the 'sort + // flag', which indicates if the global color table's entries are sorted in + // order of importance - if we need to render this image for a device with a + // narrower color gamut than GIF supports we'll handle that at a different + // layer. We have no use for the pixel aspect ratio as well. Finally, we + // intentionally ignore the background color index, as implementing that + // feature would not be web compatible - when a GIF image frame doesn't cover + // the entire area of the image, the area that's not covered should always be + // transparent. + + if (packedFields & PACKED_FIELDS_COLOR_TABLE_BIT) { + MOZ_ASSERT(mColorTablePos == 0); + + // We read the global color table in unbuffered mode since it can be quite + // large and it'd be preferable to avoid unnecessary copies. + const size_t globalColorTableSize = 3 * mGIFStruct.global_colormap_count; + return Transition::ToUnbuffered(State::FINISHED_GLOBAL_COLOR_TABLE, + State::GLOBAL_COLOR_TABLE, + globalColorTableSize); + } + + return Transition::To(State::BLOCK_HEADER, BLOCK_HEADER_LEN); +} + +LexerTransition +nsGIFDecoder2::ReadGlobalColorTable(const char* aData, size_t aLength) +{ + uint8_t* dest = reinterpret_cast(mGIFStruct.global_colormap) + + mColorTablePos; + memcpy(dest, aData, aLength); + mColorTablePos += aLength; + return Transition::ContinueUnbuffered(State::GLOBAL_COLOR_TABLE); +} + +LexerTransition +nsGIFDecoder2::FinishedGlobalColorTable() +{ + ConvertColormap(mGIFStruct.global_colormap, mGIFStruct.global_colormap_count); + mColorTablePos = 0; + return Transition::To(State::BLOCK_HEADER, BLOCK_HEADER_LEN); +} + +LexerTransition +nsGIFDecoder2::ReadBlockHeader(const char* aData) +{ + // Determine what type of block we're dealing with. + switch (aData[0]) { + case GIF_EXTENSION_INTRODUCER: + return Transition::To(State::EXTENSION_HEADER, EXTENSION_HEADER_LEN); + + case GIF_IMAGE_SEPARATOR: + return Transition::To(State::IMAGE_DESCRIPTOR, IMAGE_DESCRIPTOR_LEN); + + case GIF_TRAILER: + FinishInternal(); + return Transition::TerminateSuccess(); + + default: + // If we get anything other than GIF_IMAGE_SEPARATOR, + // GIF_EXTENSION_INTRODUCER, or GIF_TRAILER, there is extraneous data + // between blocks. The GIF87a spec tells us to keep reading until we find + // an image separator, but GIF89a says such a file is corrupt. We follow + // GIF89a and bail out. + + if (mGIFStruct.images_decoded > 0) { + // The file is corrupt, but we successfully decoded some frames, so we + // may as well consider the decode successful and display them. + FinishInternal(); + return Transition::TerminateSuccess(); + } + + // No images decoded; there is nothing to display. + return Transition::TerminateFailure(); + } +} + +LexerTransition +nsGIFDecoder2::ReadExtensionHeader(const char* aData) +{ + const uint8_t label = aData[0]; + const uint8_t extensionHeaderLength = aData[1]; + + // If the extension header is zero length, just treat it as a block terminator + // and move on to the next block immediately. + if (extensionHeaderLength == 0) { + return Transition::To(State::BLOCK_HEADER, BLOCK_HEADER_LEN); + } + + switch (label) { + case GIF_GRAPHIC_CONTROL_LABEL: + // The GIF spec mandates that the Control Extension header block length is + // 4 bytes, and the parser for this block reads 4 bytes, so we must + // enforce that the buffer contains at least this many bytes. If the GIF + // specifies a different length, we allow that, so long as it's larger; + // the additional data will simply be ignored. + return Transition::To(State::GRAPHIC_CONTROL_EXTENSION, + max(extensionHeaderLength, + GRAPHIC_CONTROL_EXTENSION_LEN)); + + case GIF_APPLICATION_EXTENSION_LABEL: + // Again, the spec specifies that an application extension header is 11 + // bytes, but for compatibility with GIFs in the wild, we allow deviation + // from the spec. This is important for real-world compatibility, as GIFs + // in the wild exist with application extension headers that are both + // shorter and longer than 11 bytes. However, we only try to actually + // interpret the application extension if the length is correct; + // otherwise, we just skip the block unconditionally. + return extensionHeaderLength == APPLICATION_EXTENSION_LEN + ? Transition::To(State::APPLICATION_IDENTIFIER, extensionHeaderLength) + : Transition::ToUnbuffered(State::FINISHED_SKIPPING_DATA, + State::SKIP_DATA_THEN_SKIP_SUB_BLOCKS, + extensionHeaderLength); + + default: + // Skip over any other type of extension block, including comment and + // plain text blocks. + return Transition::ToUnbuffered(State::FINISHED_SKIPPING_DATA, + State::SKIP_DATA_THEN_SKIP_SUB_BLOCKS, + extensionHeaderLength); + } +} + +LexerTransition +nsGIFDecoder2::ReadGraphicControlExtension(const char* aData) +{ + mGIFStruct.is_transparent = aData[0] & 0x1; + mGIFStruct.tpixel = uint8_t(aData[3]); + mGIFStruct.disposal_method = (aData[0] >> 2) & 0x7; + + if (mGIFStruct.disposal_method == 4) { + // Some encoders (and apparently some specs) represent + // DisposalMethod::RESTORE_PREVIOUS as 4, but 3 is used in the canonical + // spec and is more popular, so we normalize to 3. + mGIFStruct.disposal_method = 3; + } else if (mGIFStruct.disposal_method > 4) { + // This GIF is using a disposal method which is undefined in the spec. + // Treat it as DisposalMethod::NOT_SPECIFIED. + mGIFStruct.disposal_method = 0; + } + + DisposalMethod method = DisposalMethod(mGIFStruct.disposal_method); + if (method == DisposalMethod::CLEAR_ALL || method == DisposalMethod::CLEAR) { + // We may have to display the background under this image during animation + // playback, so we regard it as transparent. + PostHasTransparency(); + } + + mGIFStruct.delay_time = LittleEndian::readUint16(aData + 1) * 10; + if (mGIFStruct.delay_time > 0) { + PostIsAnimated(FrameTimeout::FromRawMilliseconds(mGIFStruct.delay_time)); + } + + return Transition::To(State::SKIP_SUB_BLOCKS, SUB_BLOCK_HEADER_LEN); +} + +LexerTransition +nsGIFDecoder2::ReadApplicationIdentifier(const char* aData) +{ + if ((strncmp(aData, "NETSCAPE2.0", 11) == 0) || + (strncmp(aData, "ANIMEXTS1.0", 11) == 0)) { + // This is a Netscape application extension block. + return Transition::To(State::NETSCAPE_EXTENSION_SUB_BLOCK, + SUB_BLOCK_HEADER_LEN); + } + + // This is an application extension we don't care about. Just skip it. + return Transition::To(State::SKIP_SUB_BLOCKS, SUB_BLOCK_HEADER_LEN); +} + +LexerTransition +nsGIFDecoder2::ReadNetscapeExtensionSubBlock(const char* aData) +{ + const uint8_t blockLength = aData[0]; + if (blockLength == 0) { + // We hit the block terminator. + return Transition::To(State::BLOCK_HEADER, BLOCK_HEADER_LEN); + } + + // We consume a minimum of 3 bytes in accordance with the specs for the + // Netscape application extension block, such as they are. + const size_t extensionLength = max(blockLength, 3); + return Transition::To(State::NETSCAPE_EXTENSION_DATA, extensionLength); +} + +LexerTransition +nsGIFDecoder2::ReadNetscapeExtensionData(const char* aData) +{ + // Documentation for NETSCAPE2.0 / ANIMEXTS1.0 extensions can be found at: + // https://wiki.whatwg.org/wiki/GIF + static const uint8_t NETSCAPE_LOOPING_EXTENSION_SUB_BLOCK_ID = 1; + static const uint8_t NETSCAPE_BUFFERING_EXTENSION_SUB_BLOCK_ID = 2; + + const uint8_t subBlockID = aData[0] & 7; + switch (subBlockID) { + case NETSCAPE_LOOPING_EXTENSION_SUB_BLOCK_ID: + // This is looping extension. + mGIFStruct.loop_count = LittleEndian::readUint16(aData + 1); + return Transition::To(State::NETSCAPE_EXTENSION_SUB_BLOCK, + SUB_BLOCK_HEADER_LEN); + + case NETSCAPE_BUFFERING_EXTENSION_SUB_BLOCK_ID: + // We allow, but ignore, this extension. + return Transition::To(State::NETSCAPE_EXTENSION_SUB_BLOCK, + SUB_BLOCK_HEADER_LEN); + + default: + return Transition::TerminateFailure(); + } +} + +LexerTransition +nsGIFDecoder2::ReadImageDescriptor(const char* aData) +{ + // On the first frame, we don't need to yield, and none of the other checks + // below apply, so we can just jump right into FinishImageDescriptor(). + if (mGIFStruct.images_decoded == 0) { + return FinishImageDescriptor(aData); + } + + if (!HasAnimation()) { + // We should've already called PostIsAnimated(); this must be a corrupt + // animated image with a first frame timeout of zero. Signal that we're + // animated now, before the first-frame decode early exit below, so that + // RasterImage can detect that this happened. + PostIsAnimated(FrameTimeout::FromRawMilliseconds(0)); + } + + if (IsFirstFrameDecode()) { + // We're about to get a second frame, but we only want the first. Stop + // decoding now. + FinishInternal(); + return Transition::TerminateSuccess(); + } + + MOZ_ASSERT(Size() == OutputSize(), "Downscaling an animated image?"); + + // Yield to allow access to the previous frame before we start a new one. + return Transition::ToAfterYield(State::FINISH_IMAGE_DESCRIPTOR); +} + +LexerTransition +nsGIFDecoder2::FinishImageDescriptor(const char* aData) +{ + IntRect frameRect; + + // Get image offsets with respect to the screen origin. + frameRect.x = LittleEndian::readUint16(aData + 0); + frameRect.y = LittleEndian::readUint16(aData + 2); + frameRect.width = LittleEndian::readUint16(aData + 4); + frameRect.height = LittleEndian::readUint16(aData + 6); + + if (!mGIFStruct.images_decoded) { + // Work around GIF files where + // * at least one of the logical screen dimensions is smaller than the + // same dimension in the first image, or + // * GIF87a files where the first image's dimensions do not match the + // logical screen dimensions. + if (mGIFStruct.screen_height < frameRect.height || + mGIFStruct.screen_width < frameRect.width || + mGIFStruct.version == 87) { + mGIFStruct.screen_height = frameRect.height; + mGIFStruct.screen_width = frameRect.width; + frameRect.MoveTo(0, 0); + } + + // Create the image container with the right size. + BeginGIF(); + if (HasError()) { + // Setting the size led to an error. + return Transition::TerminateFailure(); + } + + // If we're doing a metadata decode, we're done. + if (IsMetadataDecode()) { + CheckForTransparency(frameRect); + FinishInternal(); + return Transition::TerminateSuccess(); + } + } + + // Work around broken GIF files that have zero frame width or height; in this + // case, we'll treat the frame as having the same size as the overall image. + if (frameRect.height == 0 || frameRect.width == 0) { + frameRect.height = mGIFStruct.screen_height; + frameRect.width = mGIFStruct.screen_width; + + // If that still resulted in zero frame width or height, give up. + if (frameRect.height == 0 || frameRect.width == 0) { + return Transition::TerminateFailure(); + } + } + + // Determine |depth| (log base 2 of the number of colors in the palette). + bool haveLocalColorTable = false; + uint16_t depth = 0; + uint8_t packedFields = aData[8]; + + if (packedFields & PACKED_FIELDS_COLOR_TABLE_BIT) { + // Get the palette depth from the local color table. + depth = (packedFields & PACKED_FIELDS_TABLE_DEPTH_MASK) + 1; + haveLocalColorTable = true; + } else { + // Get the palette depth from the global color table. + depth = mGIFStruct.global_colormap_depth; + } + + // If the transparent color index is greater than the number of colors in the + // color table, we may need a higher color depth than |depth| would specify. + // Our internal representation of the image will instead use |realDepth|, + // which is the smallest color depth that can accomodate the existing palette + // *and* the transparent color index. + uint16_t realDepth = depth; + while (mGIFStruct.tpixel >= (1 << realDepth) && + realDepth < 8) { + realDepth++; + } + + // Create a mask used to ensure that color values fit within the colormap. + mColorMask = 0xFF >> (8 - realDepth); + + // Determine if this frame is interlaced or not. + const bool isInterlaced = packedFields & PACKED_FIELDS_INTERLACED_BIT; + + // Create the SurfacePipe we'll use to write output for this frame. + if (NS_FAILED(BeginImageFrame(frameRect, realDepth, isInterlaced))) { + return Transition::TerminateFailure(); + } + + // Clear state from last image. + mGIFStruct.pixels_remaining = frameRect.width * frameRect.height; + + if (haveLocalColorTable) { + // We have a local color table, so prepare to read it into the palette of + // the current frame. + mGIFStruct.local_colormap_size = 1 << depth; + + if (mGIFStruct.images_decoded == 0) { + // The first frame has a local color table. Allocate space for it as we + // use a BGRA or BGRX surface for the first frame; such surfaces don't + // have their own palettes internally. + mColormapSize = sizeof(uint32_t) << realDepth; + if (!mGIFStruct.local_colormap) { + mGIFStruct.local_colormap = + static_cast(moz_xmalloc(mColormapSize)); + } + mColormap = mGIFStruct.local_colormap; + } + + const size_t size = 3 << depth; + if (mColormapSize > size) { + // Clear the part of the colormap which will be unused with this palette. + // If a GIF references an invalid palette entry, ensure the entry is opaque white. + // This is needed for Skia as if it isn't, RGBX surfaces will cause blending issues + // with Skia. + memset(reinterpret_cast(mColormap) + size, 0xFF, + mColormapSize - size); + } + + MOZ_ASSERT(mColorTablePos == 0); + + // We read the local color table in unbuffered mode since it can be quite + // large and it'd be preferable to avoid unnecessary copies. + return Transition::ToUnbuffered(State::FINISHED_LOCAL_COLOR_TABLE, + State::LOCAL_COLOR_TABLE, + size); + } + + // There's no local color table; copy the global color table into the palette + // of the current frame. + if (mGIFStruct.images_decoded > 0) { + memcpy(mColormap, mGIFStruct.global_colormap, mColormapSize); + } else { + mColormap = mGIFStruct.global_colormap; + } + + return Transition::To(State::IMAGE_DATA_BLOCK, BLOCK_HEADER_LEN); +} + +LexerTransition +nsGIFDecoder2::ReadLocalColorTable(const char* aData, size_t aLength) +{ + uint8_t* dest = reinterpret_cast(mColormap) + mColorTablePos; + memcpy(dest, aData, aLength); + mColorTablePos += aLength; + return Transition::ContinueUnbuffered(State::LOCAL_COLOR_TABLE); +} + +LexerTransition +nsGIFDecoder2::FinishedLocalColorTable() +{ + ConvertColormap(mColormap, mGIFStruct.local_colormap_size); + mColorTablePos = 0; + return Transition::To(State::IMAGE_DATA_BLOCK, BLOCK_HEADER_LEN); +} + +LexerTransition +nsGIFDecoder2::ReadImageDataBlock(const char* aData) +{ + // Make sure the transparent pixel is transparent in the colormap. + if (mGIFStruct.is_transparent) { + // Save the old value so we can restore it later. + if (mColormap == mGIFStruct.global_colormap) { + mOldColor = mColormap[mGIFStruct.tpixel]; + } + mColormap[mGIFStruct.tpixel] = 0; + } + + // Initialize the LZW decoder. + mGIFStruct.datasize = uint8_t(aData[0]); + const int clearCode = ClearCode(); + if (mGIFStruct.datasize > MAX_LZW_BITS || clearCode >= MAX_BITS) { + return Transition::TerminateFailure(); + } + + mGIFStruct.avail = clearCode + 2; + mGIFStruct.oldcode = -1; + mGIFStruct.codesize = mGIFStruct.datasize + 1; + mGIFStruct.codemask = (1 << mGIFStruct.codesize) - 1; + mGIFStruct.datum = mGIFStruct.bits = 0; + + // Initialize the tables. + for (int i = 0; i < clearCode; i++) { + mGIFStruct.suffix[i] = i; + } + + mGIFStruct.stackp = mGIFStruct.stack; + + // Begin reading image data sub-blocks. + return Transition::To(State::IMAGE_DATA_SUB_BLOCK, SUB_BLOCK_HEADER_LEN); +} + +LexerTransition +nsGIFDecoder2::ReadImageDataSubBlock(const char* aData) +{ + const uint8_t subBlockLength = aData[0]; + if (subBlockLength == 0) { + // We hit the block terminator. + EndImageFrame(); + return Transition::To(State::BLOCK_HEADER, BLOCK_HEADER_LEN); + } + + if (mGIFStruct.pixels_remaining == 0) { + // We've already written to the entire image; we should've hit the block + // terminator at this point. This image is corrupt, but we'll tolerate it. + + if (subBlockLength == GIF_TRAILER) { + // This GIF is missing the block terminator for the final block; we'll put + // up with it. + FinishInternal(); + return Transition::TerminateSuccess(); + } + + // We're not at the end of the image, so just skip the extra data. + return Transition::ToUnbuffered(State::FINISHED_LZW_DATA, + State::SKIP_LZW_DATA, + subBlockLength); + } + + // Handle the standard case: there's data in the sub-block and pixels left to + // fill in the image. We read the sub-block unbuffered so we can get pixels on + // the screen as soon as possible. + return Transition::ToUnbuffered(State::FINISHED_LZW_DATA, + State::LZW_DATA, + subBlockLength); +} + +LexerTransition +nsGIFDecoder2::ReadLZWData(const char* aData, size_t aLength) +{ + const uint8_t* data = reinterpret_cast(aData); + size_t length = aLength; + + while (mGIFStruct.pixels_remaining > 0 && + (length > 0 || mGIFStruct.bits >= mGIFStruct.codesize)) { + size_t bytesRead = 0; + + auto result = mGIFStruct.images_decoded == 0 + ? mPipe.WritePixels([&]{ return YieldPixel(data, length, &bytesRead); }) + : mPipe.WritePixels([&]{ return YieldPixel(data, length, &bytesRead); }); + + if (MOZ_UNLIKELY(bytesRead > length)) { + MOZ_ASSERT_UNREACHABLE("Overread?"); + bytesRead = length; + } + + // Advance our position in the input based upon what YieldPixel() consumed. + data += bytesRead; + length -= bytesRead; + + switch (result) { + case WriteState::NEED_MORE_DATA: + continue; + + case WriteState::FINISHED: + NS_WARNING_ASSERTION(mGIFStruct.pixels_remaining <= 0, + "too many pixels"); + mGIFStruct.pixels_remaining = 0; + break; + + case WriteState::FAILURE: + return Transition::TerminateFailure(); + } + } + + // We're done, but keep going until we consume all the data in the sub-block. + return Transition::ContinueUnbuffered(State::LZW_DATA); +} + +LexerTransition +nsGIFDecoder2::SkipSubBlocks(const char* aData) +{ + // In the SKIP_SUB_BLOCKS state we skip over data sub-blocks that we're not + // interested in. Blocks consist of a block header (which can be up to 255 + // bytes in length) and a series of data sub-blocks. Each data sub-block + // consists of a single byte length value, followed by the data itself. A data + // sub-block with a length of zero terminates the overall block. + // SKIP_SUB_BLOCKS reads a sub-block length value. If it's zero, we've arrived + // at the next block. Otherwise, we enter the SKIP_DATA_THEN_SKIP_SUB_BLOCKS + // state to skip over the sub-block data and return to SKIP_SUB_BLOCKS at the + // start of the next sub-block. + + const uint8_t nextSubBlockLength = aData[0]; + if (nextSubBlockLength == 0) { + // We hit the block terminator, so the sequence of data sub-blocks is over; + // begin processing another block. + return Transition::To(State::BLOCK_HEADER, BLOCK_HEADER_LEN); + } + + // Skip to the next sub-block length value. + return Transition::ToUnbuffered(State::FINISHED_SKIPPING_DATA, + State::SKIP_DATA_THEN_SKIP_SUB_BLOCKS, + nextSubBlockLength); +} + +Maybe +nsGIFDecoder2::SpeedHistogram() const +{ + return Some(Telemetry::IMAGE_DECODE_SPEED_GIF); +} + +} // namespace image +} // namespace mozilla diff --git a/image/decoders/nsGIFDecoder2.h b/image/decoders/nsGIFDecoder2.h new file mode 100644 index 000000000..c903ce890 --- /dev/null +++ b/image/decoders/nsGIFDecoder2.h @@ -0,0 +1,152 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_decoders_nsGIFDecoder2_h +#define mozilla_image_decoders_nsGIFDecoder2_h + +#include "Decoder.h" +#include "GIF2.h" +#include "StreamingLexer.h" +#include "SurfacePipe.h" + +namespace mozilla { +namespace image { +class RasterImage; + +////////////////////////////////////////////////////////////////////// +// nsGIFDecoder2 Definition + +class nsGIFDecoder2 : public Decoder +{ +public: + ~nsGIFDecoder2(); + +protected: + LexerResult DoDecode(SourceBufferIterator& aIterator, + IResumable* aOnResume) override; + nsresult FinishInternal() override; + + Maybe SpeedHistogram() const override; + +private: + friend class DecoderFactory; + + // Decoders should only be instantiated via DecoderFactory. + explicit nsGIFDecoder2(RasterImage* aImage); + + /// Called when we begin decoding the image. + void BeginGIF(); + + /** + * Called when we begin decoding a frame. + * + * @param aFrameRect The region of the image that contains data. The region + * outside this rect is transparent. + * @param aDepth The palette depth of this frame. + * @param aIsInterlaced If true, this frame is an interlaced frame. + */ + nsresult BeginImageFrame(const gfx::IntRect& aFrameRect, + uint16_t aDepth, + bool aIsInterlaced); + + /// Called when we finish decoding a frame. + void EndImageFrame(); + + /// Called when we finish decoding the entire image. + void FlushImageData(); + + /// Transforms a palette index into a pixel. + template PixelSize + ColormapIndexToPixel(uint8_t aIndex); + + /// A generator function that performs LZW decompression and yields pixels. + template NextPixel + YieldPixel(const uint8_t* aData, size_t aLength, size_t* aBytesReadOut); + + /// Checks if we have transparency, either because the header indicates that + /// there's alpha, or because the frame rect doesn't cover the entire image. + bool CheckForTransparency(const gfx::IntRect& aFrameRect); + + // @return the clear code used for LZW decompression. + int ClearCode() const { return 1 << mGIFStruct.datasize; } + + enum class State + { + FAILURE, + SUCCESS, + GIF_HEADER, + SCREEN_DESCRIPTOR, + GLOBAL_COLOR_TABLE, + FINISHED_GLOBAL_COLOR_TABLE, + BLOCK_HEADER, + EXTENSION_HEADER, + GRAPHIC_CONTROL_EXTENSION, + APPLICATION_IDENTIFIER, + NETSCAPE_EXTENSION_SUB_BLOCK, + NETSCAPE_EXTENSION_DATA, + IMAGE_DESCRIPTOR, + FINISH_IMAGE_DESCRIPTOR, + LOCAL_COLOR_TABLE, + FINISHED_LOCAL_COLOR_TABLE, + IMAGE_DATA_BLOCK, + IMAGE_DATA_SUB_BLOCK, + LZW_DATA, + SKIP_LZW_DATA, + FINISHED_LZW_DATA, + SKIP_SUB_BLOCKS, + SKIP_DATA_THEN_SKIP_SUB_BLOCKS, + FINISHED_SKIPPING_DATA + }; + + LexerTransition ReadGIFHeader(const char* aData); + LexerTransition ReadScreenDescriptor(const char* aData); + LexerTransition ReadGlobalColorTable(const char* aData, size_t aLength); + LexerTransition FinishedGlobalColorTable(); + LexerTransition ReadBlockHeader(const char* aData); + LexerTransition ReadExtensionHeader(const char* aData); + LexerTransition ReadGraphicControlExtension(const char* aData); + LexerTransition ReadApplicationIdentifier(const char* aData); + LexerTransition ReadNetscapeExtensionSubBlock(const char* aData); + LexerTransition ReadNetscapeExtensionData(const char* aData); + LexerTransition ReadImageDescriptor(const char* aData); + LexerTransition FinishImageDescriptor(const char* aData); + LexerTransition ReadLocalColorTable(const char* aData, size_t aLength); + LexerTransition FinishedLocalColorTable(); + LexerTransition ReadImageDataBlock(const char* aData); + LexerTransition ReadImageDataSubBlock(const char* aData); + LexerTransition ReadLZWData(const char* aData, size_t aLength); + LexerTransition SkipSubBlocks(const char* aData); + + // The StreamingLexer used to manage input. The initial size of the buffer is + // chosen as a little larger than the maximum size of any fixed-length data we + // have to read for a state. We read variable-length data in unbuffered mode + // so the buffer shouldn't have to be resized during decoding. + StreamingLexer mLexer; + + uint32_t mOldColor; // The old value of the transparent pixel + + // The frame number of the currently-decoding frame when we're in the middle + // of decoding it, and -1 otherwise. + int32_t mCurrentFrameIndex; + + // When we're reading in the global or local color table, this records our + // current position - i.e., the offset into which the next byte should be + // written. + size_t mColorTablePos; + + uint8_t mColorMask; // Apply this to the pixel to keep within colormap + bool mGIFOpen; + bool mSawTransparency; + + gif_struct mGIFStruct; + + SurfacePipe mPipe; /// The SurfacePipe used to write to the output surface. +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_decoders_nsGIFDecoder2_h diff --git a/image/decoders/nsICODecoder.cpp b/image/decoders/nsICODecoder.cpp new file mode 100644 index 000000000..633bb1255 --- /dev/null +++ b/image/decoders/nsICODecoder.cpp @@ -0,0 +1,673 @@ +/* vim:set tw=80 expandtab softtabstop=2 ts=2 sw=2: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* This is a Cross-Platform ICO Decoder, which should work everywhere, including + * Big-Endian machines like the PowerPC. */ + +#include "nsICODecoder.h" + +#include + +#include "mozilla/EndianUtils.h" +#include "mozilla/Move.h" + +#include "RasterImage.h" + +using namespace mozilla::gfx; + +namespace mozilla { +namespace image { + +// Constants. +static const uint32_t ICOHEADERSIZE = 6; +static const uint32_t BITMAPINFOSIZE = bmp::InfoHeaderLength::WIN_ICO; + +// ---------------------------------------- +// Actual Data Processing +// ---------------------------------------- + +// Obtains the number of colors from the bits per pixel +uint16_t +nsICODecoder::GetNumColors() +{ + uint16_t numColors = 0; + if (mBPP <= 8) { + switch (mBPP) { + case 1: + numColors = 2; + break; + case 4: + numColors = 16; + break; + case 8: + numColors = 256; + break; + default: + numColors = (uint16_t)-1; + } + } + return numColors; +} + +nsICODecoder::nsICODecoder(RasterImage* aImage) + : Decoder(aImage) + , mLexer(Transition::To(ICOState::HEADER, ICOHEADERSIZE), + Transition::TerminateSuccess()) + , mBiggestResourceColorDepth(0) + , mBestResourceDelta(INT_MIN) + , mBestResourceColorDepth(0) + , mNumIcons(0) + , mCurrIcon(0) + , mBPP(0) + , mMaskRowSize(0) + , mCurrMaskLine(0) + , mIsCursor(false) + , mHasMaskAlpha(false) +{ } + +nsresult +nsICODecoder::FinishInternal() +{ + // We shouldn't be called in error cases + MOZ_ASSERT(!HasError(), "Shouldn't call FinishInternal after error!"); + + return GetFinalStateFromContainedDecoder(); +} + +nsresult +nsICODecoder::FinishWithErrorInternal() +{ + return GetFinalStateFromContainedDecoder(); +} + +nsresult +nsICODecoder::GetFinalStateFromContainedDecoder() +{ + if (!mContainedDecoder) { + return NS_OK; + } + + MOZ_ASSERT(mContainedSourceBuffer, + "Should have a SourceBuffer if we have a decoder"); + + // Let the contained decoder finish up if necessary. + if (!mContainedSourceBuffer->IsComplete()) { + mContainedSourceBuffer->Complete(NS_OK); + mContainedDecoder->Decode(); + } + + // Make our state the same as the state of the contained decoder. + mDecodeDone = mContainedDecoder->GetDecodeDone(); + mProgress |= mContainedDecoder->TakeProgress(); + mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect()); + mCurrentFrame = mContainedDecoder->GetCurrentFrameRef(); + + // Propagate errors. + nsresult rv = HasError() || mContainedDecoder->HasError() + ? NS_ERROR_FAILURE + : NS_OK; + + MOZ_ASSERT(NS_FAILED(rv) || !mCurrentFrame || mCurrentFrame->IsFinished()); + return rv; +} + +bool +nsICODecoder::CheckAndFixBitmapSize(int8_t* aBIH) +{ + // Get the width from the BMP file information header. This is + // (unintuitively) a signed integer; see the documentation at: + // + // https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx + // + // However, we reject negative widths since they aren't meaningful. + const int32_t width = LittleEndian::readInt32(aBIH + 4); + if (width <= 0 || width > 256) { + return false; + } + + // Verify that the BMP width matches the width we got from the ICO directory + // entry. If not, decoding fails, because if we were to allow it to continue + // the intrinsic size of the image wouldn't match the size of the decoded + // surface. + if (width != int32_t(GetRealWidth())) { + return false; + } + + // Get the height from the BMP file information header. This is also signed, + // but in this case negative values are meaningful; see below. + int32_t height = LittleEndian::readInt32(aBIH + 8); + if (height == 0) { + return false; + } + + // BMPs can be stored inverted by having a negative height. + // XXX(seth): Should we really be writing the absolute value into the BIH + // below? Seems like this could be problematic for inverted BMPs. + height = abs(height); + + // The height field is double the actual height of the image to account for + // the AND mask. This is true even if the AND mask is not present. + height /= 2; + if (height > 256) { + return false; + } + + // Verify that the BMP height matches the height we got from the ICO directory + // entry. If not, again, decoding fails. + if (height != int32_t(GetRealHeight())) { + return false; + } + + // Fix the BMP height in the BIH so that the BMP decoder, which does not know + // about the AND mask that may follow the actual bitmap, can work properly. + LittleEndian::writeInt32(aBIH + 8, GetRealHeight()); + + return true; +} + +LexerTransition +nsICODecoder::ReadHeader(const char* aData) +{ + // If the third byte is 1, this is an icon. If 2, a cursor. + if ((aData[2] != 1) && (aData[2] != 2)) { + return Transition::TerminateFailure(); + } + mIsCursor = (aData[2] == 2); + + // The fifth and sixth bytes specify the number of resources in the file. + mNumIcons = LittleEndian::readUint16(aData + 4); + if (mNumIcons == 0) { + return Transition::TerminateSuccess(); // Nothing to do. + } + + // Downscale-during-decode can end up decoding different resources in the ICO + // file depending on the target size. Since the resources are not necessarily + // scaled versions of the same image, some may be transparent and some may not + // be. We could be precise about transparency if we decoded the metadata of + // every resource, but for now we don't and it's safest to assume that + // transparency could be present. + PostHasTransparency(); + + return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE); +} + +size_t +nsICODecoder::FirstResourceOffset() const +{ + MOZ_ASSERT(mNumIcons > 0, + "Calling FirstResourceOffset before processing header"); + + // The first resource starts right after the directory, which starts right + // after the ICO header. + return ICOHEADERSIZE + mNumIcons * ICODIRENTRYSIZE; +} + +LexerTransition +nsICODecoder::ReadDirEntry(const char* aData) +{ + mCurrIcon++; + + // Read the directory entry. + IconDirEntry e; + e.mWidth = aData[0]; + e.mHeight = aData[1]; + e.mColorCount = aData[2]; + e.mReserved = aData[3]; + e.mPlanes = LittleEndian::readUint16(aData + 4); + e.mBitCount = LittleEndian::readUint16(aData + 6); + e.mBytesInRes = LittleEndian::readUint32(aData + 8); + e.mImageOffset = LittleEndian::readUint32(aData + 12); + + // If an explicit output size was specified, we'll try to select the resource + // that matches it best below. + const Maybe desiredSize = ExplicitOutputSize(); + + // Determine if this is the biggest resource we've seen so far. We always use + // the biggest resource for the intrinsic size, and if we don't have a + // specific desired size, we select it as the best resource as well. + IntSize entrySize(GetRealWidth(e), GetRealHeight(e)); + if (e.mBitCount >= mBiggestResourceColorDepth && + entrySize.width * entrySize.height >= + mBiggestResourceSize.width * mBiggestResourceSize.height) { + mBiggestResourceSize = entrySize; + mBiggestResourceColorDepth = e.mBitCount; + mBiggestResourceHotSpot = IntSize(e.mXHotspot, e.mYHotspot); + + if (!desiredSize) { + mDirEntry = e; + } + } + + if (desiredSize) { + // Calculate the delta between this resource's size and the desired size, so + // we can see if it is better than our current-best option. In the case of + // several equally-good resources, we use the last one. "Better" in this + // case is determined by |delta|, a measure of the difference in size + // between the entry we've found and the desired size. We will choose the + // smallest resource that is greater than or equal to the desired size (i.e. + // we assume it's better to downscale a larger icon than to upscale a + // smaller one). + int32_t delta = std::min(entrySize.width - desiredSize->width, + entrySize.height - desiredSize->height); + if (e.mBitCount >= mBestResourceColorDepth && + ((mBestResourceDelta < 0 && delta >= mBestResourceDelta) || + (delta >= 0 && delta <= mBestResourceDelta))) { + mBestResourceDelta = delta; + mBestResourceColorDepth = e.mBitCount; + mDirEntry = e; + } + } + + if (mCurrIcon == mNumIcons) { + // Ensure the resource we selected has an offset past the ICO headers. + if (mDirEntry.mImageOffset < FirstResourceOffset()) { + return Transition::TerminateFailure(); + } + + // If this is a cursor, set the hotspot. We use the hotspot from the biggest + // resource since we also use that resource for the intrinsic size. + if (mIsCursor) { + mImageMetadata.SetHotspot(mBiggestResourceHotSpot.width, + mBiggestResourceHotSpot.height); + } + + // We always report the biggest resource's size as the intrinsic size; this + // is necessary for downscale-during-decode to work since we won't even + // attempt to *upscale* while decoding. + PostSize(mBiggestResourceSize.width, mBiggestResourceSize.height); + if (IsMetadataDecode()) { + return Transition::TerminateSuccess(); + } + + // If the resource we selected matches the output size perfectly, we don't + // need to do any downscaling. + if (GetRealSize() == OutputSize()) { + MOZ_ASSERT_IF(desiredSize, GetRealSize() == *desiredSize); + MOZ_ASSERT_IF(!desiredSize, GetRealSize() == Size()); + mDownscaler.reset(); + } + + size_t offsetToResource = mDirEntry.mImageOffset - FirstResourceOffset(); + return Transition::ToUnbuffered(ICOState::FOUND_RESOURCE, + ICOState::SKIP_TO_RESOURCE, + offsetToResource); + } + + return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE); +} + +LexerTransition +nsICODecoder::SniffResource(const char* aData) +{ + // We use the first PNGSIGNATURESIZE bytes to determine whether this resource + // is a PNG or a BMP. + bool isPNG = !memcmp(aData, nsPNGDecoder::pngSignatureBytes, + PNGSIGNATURESIZE); + if (isPNG) { + // Create a PNG decoder which will do the rest of the work for us. + mContainedSourceBuffer = new SourceBuffer(); + mContainedSourceBuffer->ExpectLength(mDirEntry.mBytesInRes); + mContainedDecoder = + DecoderFactory::CreateDecoderForICOResource(DecoderType::PNG, + WrapNotNull(mContainedSourceBuffer), + WrapNotNull(this)); + + if (!WriteToContainedDecoder(aData, PNGSIGNATURESIZE)) { + return Transition::TerminateFailure(); + } + + if (mDirEntry.mBytesInRes <= PNGSIGNATURESIZE) { + return Transition::TerminateFailure(); + } + + // Read in the rest of the PNG unbuffered. + size_t toRead = mDirEntry.mBytesInRes - PNGSIGNATURESIZE; + return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE, + ICOState::READ_PNG, + toRead); + } else { + // Make sure we have a sane size for the bitmap information header. + int32_t bihSize = LittleEndian::readUint32(aData); + if (bihSize != static_cast(BITMAPINFOSIZE)) { + return Transition::TerminateFailure(); + } + + // Buffer the first part of the bitmap information header. + memcpy(mBIHraw, aData, PNGSIGNATURESIZE); + + // Read in the rest of the bitmap information header. + return Transition::To(ICOState::READ_BIH, + BITMAPINFOSIZE - PNGSIGNATURESIZE); + } +} + +LexerTransition +nsICODecoder::ReadPNG(const char* aData, uint32_t aLen) +{ + if (!WriteToContainedDecoder(aData, aLen)) { + return Transition::TerminateFailure(); + } + + // Raymond Chen says that 32bpp only are valid PNG ICOs + // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx + if (!static_cast(mContainedDecoder.get())->IsValidICO()) { + return Transition::TerminateFailure(); + } + + return Transition::ContinueUnbuffered(ICOState::READ_PNG); +} + +LexerTransition +nsICODecoder::ReadBIH(const char* aData) +{ + // Buffer the rest of the bitmap information header. + memcpy(mBIHraw + PNGSIGNATURESIZE, aData, BITMAPINFOSIZE - PNGSIGNATURESIZE); + + // Extract the BPP from the BIH header; it should be trusted over the one + // we have from the ICO header which is usually set to 0. + mBPP = LittleEndian::readUint16(mBIHraw + 14); + + // The ICO format when containing a BMP does not include the 14 byte + // bitmap file header. So we create the BMP decoder via the constructor that + // tells it to skip this, and pass in the required data (dataOffset) that + // would have been present in the header. + uint32_t dataOffset = bmp::FILE_HEADER_LENGTH + BITMAPINFOSIZE; + if (mDirEntry.mBitCount <= 8) { + // The color table is present only if BPP is <= 8. + uint16_t numColors = GetNumColors(); + if (numColors == (uint16_t)-1) { + return Transition::TerminateFailure(); + } + dataOffset += 4 * numColors; + } + + // Create a BMP decoder which will do most of the work for us; the exception + // is the AND mask, which isn't present in standalone BMPs. + mContainedSourceBuffer = new SourceBuffer(); + mContainedSourceBuffer->ExpectLength(mDirEntry.mBytesInRes); + mContainedDecoder = + DecoderFactory::CreateDecoderForICOResource(DecoderType::BMP, + WrapNotNull(mContainedSourceBuffer), + WrapNotNull(this), + Some(dataOffset)); + RefPtr bmpDecoder = + static_cast(mContainedDecoder.get()); + + // Verify that the BIH width and height values match the ICO directory entry, + // and fix the BIH height value to compensate for the fact that the underlying + // BMP decoder doesn't know about AND masks. + if (!CheckAndFixBitmapSize(reinterpret_cast(mBIHraw))) { + return Transition::TerminateFailure(); + } + + // Write out the BMP's bitmap info header. + if (!WriteToContainedDecoder(mBIHraw, sizeof(mBIHraw))) { + return Transition::TerminateFailure(); + } + + // Check to make sure we have valid color settings. + uint16_t numColors = GetNumColors(); + if (numColors == uint16_t(-1)) { + return Transition::TerminateFailure(); + } + + // Do we have an AND mask on this BMP? If so, we need to read it after we read + // the BMP data itself. + uint32_t bmpDataLength = bmpDecoder->GetCompressedImageSize() + 4 * numColors; + bool hasANDMask = (BITMAPINFOSIZE + bmpDataLength) < mDirEntry.mBytesInRes; + ICOState afterBMPState = hasANDMask ? ICOState::PREPARE_FOR_MASK + : ICOState::FINISHED_RESOURCE; + + // Read in the rest of the BMP unbuffered. + return Transition::ToUnbuffered(afterBMPState, + ICOState::READ_BMP, + bmpDataLength); +} + +LexerTransition +nsICODecoder::ReadBMP(const char* aData, uint32_t aLen) +{ + if (!WriteToContainedDecoder(aData, aLen)) { + return Transition::TerminateFailure(); + } + + return Transition::ContinueUnbuffered(ICOState::READ_BMP); +} + +LexerTransition +nsICODecoder::PrepareForMask() +{ + RefPtr bmpDecoder = + static_cast(mContainedDecoder.get()); + + uint16_t numColors = GetNumColors(); + MOZ_ASSERT(numColors != uint16_t(-1)); + + // Determine the length of the AND mask. + uint32_t bmpLengthWithHeader = + BITMAPINFOSIZE + bmpDecoder->GetCompressedImageSize() + 4 * numColors; + MOZ_ASSERT(bmpLengthWithHeader < mDirEntry.mBytesInRes); + uint32_t maskLength = mDirEntry.mBytesInRes - bmpLengthWithHeader; + + // If the BMP provides its own transparency, we ignore the AND mask. We can + // also obviously ignore it if the image has zero width or zero height. + if (bmpDecoder->HasTransparency() || + GetRealWidth() == 0 || GetRealHeight() == 0) { + return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE, + ICOState::SKIP_MASK, + maskLength); + } + + // Compute the row size for the mask. + mMaskRowSize = ((GetRealWidth() + 31) / 32) * 4; // + 31 to round up + + // If the expected size of the AND mask is larger than its actual size, then + // we must have a truncated (and therefore corrupt) AND mask. + uint32_t expectedLength = mMaskRowSize * GetRealHeight(); + if (maskLength < expectedLength) { + return Transition::TerminateFailure(); + } + + // If we're downscaling, the mask is the wrong size for the surface we've + // produced, so we need to downscale the mask into a temporary buffer and then + // combine the mask's alpha values with the color values from the image. + if (mDownscaler) { + MOZ_ASSERT(bmpDecoder->GetImageDataLength() == + mDownscaler->TargetSize().width * + mDownscaler->TargetSize().height * + sizeof(uint32_t)); + mMaskBuffer = MakeUnique(bmpDecoder->GetImageDataLength()); + nsresult rv = mDownscaler->BeginFrame(GetRealSize(), Nothing(), + mMaskBuffer.get(), + /* aHasAlpha = */ true, + /* aFlipVertically = */ true); + if (NS_FAILED(rv)) { + return Transition::TerminateFailure(); + } + } + + mCurrMaskLine = GetRealHeight(); + return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize); +} + + +LexerTransition +nsICODecoder::ReadMaskRow(const char* aData) +{ + mCurrMaskLine--; + + uint8_t sawTransparency = 0; + + // Get the mask row we're reading. + const uint8_t* mask = reinterpret_cast(aData); + const uint8_t* maskRowEnd = mask + mMaskRowSize; + + // Get the corresponding row of the mask buffer (if we're downscaling) or the + // decoded image data (if we're not). + uint32_t* decoded = nullptr; + if (mDownscaler) { + // Initialize the row to all white and fully opaque. + memset(mDownscaler->RowBuffer(), 0xFF, GetRealWidth() * sizeof(uint32_t)); + + decoded = reinterpret_cast(mDownscaler->RowBuffer()); + } else { + RefPtr bmpDecoder = + static_cast(mContainedDecoder.get()); + uint32_t* imageData = bmpDecoder->GetImageData(); + if (!imageData) { + return Transition::TerminateFailure(); + } + + decoded = imageData + mCurrMaskLine * GetRealWidth(); + } + + MOZ_ASSERT(decoded); + uint32_t* decodedRowEnd = decoded + GetRealWidth(); + + // Iterate simultaneously through the AND mask and the image data. + while (mask < maskRowEnd) { + uint8_t idx = *mask++; + sawTransparency |= idx; + for (uint8_t bit = 0x80; bit && decoded < decodedRowEnd; bit >>= 1) { + // Clear pixel completely for transparency. + if (idx & bit) { + *decoded = 0; + } + decoded++; + } + } + + if (mDownscaler) { + mDownscaler->CommitRow(); + } + + // If any bits are set in sawTransparency, then we know at least one pixel was + // transparent. + if (sawTransparency) { + mHasMaskAlpha = true; + } + + if (mCurrMaskLine == 0) { + return Transition::To(ICOState::FINISH_MASK, 0); + } + + return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize); +} + +LexerTransition +nsICODecoder::FinishMask() +{ + // If we're downscaling, we now have the appropriate alpha values in + // mMaskBuffer. We just need to transfer them to the image. + if (mDownscaler) { + // Retrieve the image data. + RefPtr bmpDecoder = + static_cast(mContainedDecoder.get()); + uint8_t* imageData = reinterpret_cast(bmpDecoder->GetImageData()); + if (!imageData) { + return Transition::TerminateFailure(); + } + + // Iterate through the alpha values, copying from mask to image. + MOZ_ASSERT(mMaskBuffer); + MOZ_ASSERT(bmpDecoder->GetImageDataLength() > 0); + for (size_t i = 3 ; i < bmpDecoder->GetImageDataLength() ; i += 4) { + imageData[i] = mMaskBuffer[i]; + } + } + + return Transition::To(ICOState::FINISHED_RESOURCE, 0); +} + +LexerTransition +nsICODecoder::FinishResource() +{ + // Make sure the actual size of the resource matches the size in the directory + // entry. If not, we consider the image corrupt. + if (mContainedDecoder->HasSize() && + mContainedDecoder->Size() != GetRealSize()) { + return Transition::TerminateFailure(); + } + + return Transition::TerminateSuccess(); +} + +LexerResult +nsICODecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume) +{ + MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!"); + + return mLexer.Lex(aIterator, aOnResume, + [=](ICOState aState, const char* aData, size_t aLength) { + switch (aState) { + case ICOState::HEADER: + return ReadHeader(aData); + case ICOState::DIR_ENTRY: + return ReadDirEntry(aData); + case ICOState::SKIP_TO_RESOURCE: + return Transition::ContinueUnbuffered(ICOState::SKIP_TO_RESOURCE); + case ICOState::FOUND_RESOURCE: + return Transition::To(ICOState::SNIFF_RESOURCE, PNGSIGNATURESIZE); + case ICOState::SNIFF_RESOURCE: + return SniffResource(aData); + case ICOState::READ_PNG: + return ReadPNG(aData, aLength); + case ICOState::READ_BIH: + return ReadBIH(aData); + case ICOState::READ_BMP: + return ReadBMP(aData, aLength); + case ICOState::PREPARE_FOR_MASK: + return PrepareForMask(); + case ICOState::READ_MASK_ROW: + return ReadMaskRow(aData); + case ICOState::FINISH_MASK: + return FinishMask(); + case ICOState::SKIP_MASK: + return Transition::ContinueUnbuffered(ICOState::SKIP_MASK); + case ICOState::FINISHED_RESOURCE: + return FinishResource(); + default: + MOZ_CRASH("Unknown ICOState"); + } + }); +} + +bool +nsICODecoder::WriteToContainedDecoder(const char* aBuffer, uint32_t aCount) +{ + MOZ_ASSERT(mContainedDecoder); + MOZ_ASSERT(mContainedSourceBuffer); + + // Append the provided data to the SourceBuffer that the contained decoder is + // reading from. + mContainedSourceBuffer->Append(aBuffer, aCount); + + bool succeeded = true; + + // Write to the contained decoder. If we run out of data, the ICO decoder will + // get resumed when there's more data available, as usual, so we don't need + // the contained decoder to get resumed too. To avoid that, we provide an + // IResumable which just does nothing. + LexerResult result = mContainedDecoder->Decode(); + if (result == LexerResult(TerminalState::FAILURE)) { + succeeded = false; + } + + MOZ_ASSERT(result != LexerResult(Yield::OUTPUT_AVAILABLE), + "Unexpected yield"); + + // Make our state the same as the state of the contained decoder, and + // propagate errors. + mProgress |= mContainedDecoder->TakeProgress(); + mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect()); + if (mContainedDecoder->HasError()) { + succeeded = false; + } + + return succeeded; +} + +} // namespace image +} // namespace mozilla diff --git a/image/decoders/nsICODecoder.h b/image/decoders/nsICODecoder.h new file mode 100644 index 000000000..46e1377aa --- /dev/null +++ b/image/decoders/nsICODecoder.h @@ -0,0 +1,137 @@ +/* vim:set tw=80 expandtab softtabstop=4 ts=4 sw=4: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#ifndef mozilla_image_decoders_nsICODecoder_h +#define mozilla_image_decoders_nsICODecoder_h + +#include "StreamingLexer.h" +#include "Decoder.h" +#include "imgFrame.h" +#include "mozilla/gfx/2D.h" +#include "nsBMPDecoder.h" +#include "nsPNGDecoder.h" +#include "ICOFileHeaders.h" + +namespace mozilla { +namespace image { + +class RasterImage; + +enum class ICOState +{ + HEADER, + DIR_ENTRY, + SKIP_TO_RESOURCE, + FOUND_RESOURCE, + SNIFF_RESOURCE, + READ_PNG, + READ_BIH, + READ_BMP, + PREPARE_FOR_MASK, + READ_MASK_ROW, + FINISH_MASK, + SKIP_MASK, + FINISHED_RESOURCE +}; + +class nsICODecoder : public Decoder +{ +public: + virtual ~nsICODecoder() { } + + /// @return the width of the icon directory entry @aEntry. + static uint32_t GetRealWidth(const IconDirEntry& aEntry) + { + return aEntry.mWidth == 0 ? 256 : aEntry.mWidth; + } + + /// @return the width of the selected directory entry (mDirEntry). + uint32_t GetRealWidth() const { return GetRealWidth(mDirEntry); } + + /// @return the height of the icon directory entry @aEntry. + static uint32_t GetRealHeight(const IconDirEntry& aEntry) + { + return aEntry.mHeight == 0 ? 256 : aEntry.mHeight; + } + + /// @return the height of the selected directory entry (mDirEntry). + uint32_t GetRealHeight() const { return GetRealHeight(mDirEntry); } + + /// @return the size of the selected directory entry (mDirEntry). + gfx::IntSize GetRealSize() const + { + return gfx::IntSize(GetRealWidth(), GetRealHeight()); + } + + /// @return The offset from the beginning of the ICO to the first resource. + size_t FirstResourceOffset() const; + + LexerResult DoDecode(SourceBufferIterator& aIterator, + IResumable* aOnResume) override; + nsresult FinishInternal() override; + nsresult FinishWithErrorInternal() override; + +private: + friend class DecoderFactory; + + // Decoders should only be instantiated via DecoderFactory. + explicit nsICODecoder(RasterImage* aImage); + + // Writes to the contained decoder and sets the appropriate errors + // Returns true if there are no errors. + bool WriteToContainedDecoder(const char* aBuffer, uint32_t aCount); + + // Gets decoder state from the contained decoder so it's visible externally. + nsresult GetFinalStateFromContainedDecoder(); + + /** + * Verifies that the width and height values in @aBIH are valid and match the + * values we read from the ICO directory entry. If everything looks OK, the + * height value in @aBIH is updated to compensate for the AND mask, which the + * underlying BMP decoder doesn't know about. + * + * @return true if the width and height values in @aBIH are valid and correct. + */ + bool CheckAndFixBitmapSize(int8_t* aBIH); + + // Obtains the number of colors from the BPP, mBPP must be filled in + uint16_t GetNumColors(); + + LexerTransition ReadHeader(const char* aData); + LexerTransition ReadDirEntry(const char* aData); + LexerTransition SniffResource(const char* aData); + LexerTransition ReadPNG(const char* aData, uint32_t aLen); + LexerTransition ReadBIH(const char* aData); + LexerTransition ReadBMP(const char* aData, uint32_t aLen); + LexerTransition PrepareForMask(); + LexerTransition ReadMaskRow(const char* aData); + LexerTransition FinishMask(); + LexerTransition FinishResource(); + + StreamingLexer mLexer; // The lexer. + RefPtr mContainedDecoder; // Either a BMP or PNG decoder. + RefPtr mContainedSourceBuffer; // SourceBuffer for mContainedDecoder. + UniquePtr mMaskBuffer; // A temporary buffer for the alpha mask. + char mBIHraw[bmp::InfoHeaderLength::WIN_ICO]; // The bitmap information header. + IconDirEntry mDirEntry; // The dir entry for the selected resource. + gfx::IntSize mBiggestResourceSize; // Used to select the intrinsic size. + gfx::IntSize mBiggestResourceHotSpot; // Used to select the intrinsic size. + uint16_t mBiggestResourceColorDepth; // Used to select the intrinsic size. + int32_t mBestResourceDelta; // Used to select the best resource. + uint16_t mBestResourceColorDepth; // Used to select the best resource. + uint16_t mNumIcons; // Stores the number of icons in the ICO file. + uint16_t mCurrIcon; // Stores the current dir entry index we are processing. + uint16_t mBPP; // The BPP of the resource we're decoding. + uint32_t mMaskRowSize; // The size in bytes of each row in the BMP alpha mask. + uint32_t mCurrMaskLine; // The line of the BMP alpha mask we're processing. + bool mIsCursor; // Is this ICO a cursor? + bool mHasMaskAlpha; // Did the BMP alpha mask have any transparency? +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_decoders_nsICODecoder_h diff --git a/image/decoders/nsIconDecoder.cpp b/image/decoders/nsIconDecoder.cpp new file mode 100644 index 000000000..9ca63f5ad --- /dev/null +++ b/image/decoders/nsIconDecoder.cpp @@ -0,0 +1,128 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIconDecoder.h" +#include "RasterImage.h" +#include "SurfacePipeFactory.h" + +using namespace mozilla::gfx; + +namespace mozilla { +namespace image { + +static const uint32_t ICON_HEADER_SIZE = 2; + +nsIconDecoder::nsIconDecoder(RasterImage* aImage) + : Decoder(aImage) + , mLexer(Transition::To(State::HEADER, ICON_HEADER_SIZE), + Transition::TerminateSuccess()) + , mBytesPerRow() // set by ReadHeader() +{ + // Nothing to do +} + +nsIconDecoder::~nsIconDecoder() +{ } + +LexerResult +nsIconDecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume) +{ + MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!"); + + return mLexer.Lex(aIterator, aOnResume, + [=](State aState, const char* aData, size_t aLength) { + switch (aState) { + case State::HEADER: + return ReadHeader(aData); + case State::ROW_OF_PIXELS: + return ReadRowOfPixels(aData, aLength); + case State::FINISH: + return Finish(); + default: + MOZ_CRASH("Unknown State"); + } + }); +} + +LexerTransition +nsIconDecoder::ReadHeader(const char* aData) +{ + // Grab the width and height. + uint8_t width = uint8_t(aData[0]); + uint8_t height = uint8_t(aData[1]); + + // The input is 32bpp, so we expect 4 bytes of data per pixel. + mBytesPerRow = width * 4; + + // Post our size to the superclass. + PostSize(width, height); + + // Icons have alpha. + PostHasTransparency(); + + // If we're doing a metadata decode, we're done. + if (IsMetadataDecode()) { + return Transition::TerminateSuccess(); + } + + MOZ_ASSERT(!mImageData, "Already have a buffer allocated?"); + Maybe pipe = + SurfacePipeFactory::CreateSurfacePipe(this, 0, Size(), OutputSize(), + FullFrame(), SurfaceFormat::B8G8R8A8, + SurfacePipeFlags()); + if (!pipe) { + return Transition::TerminateFailure(); + } + + mPipe = Move(*pipe); + + MOZ_ASSERT(mImageData, "Should have a buffer now"); + + return Transition::To(State::ROW_OF_PIXELS, mBytesPerRow); +} + +LexerTransition +nsIconDecoder::ReadRowOfPixels(const char* aData, size_t aLength) +{ + MOZ_ASSERT(aLength % 4 == 0, "Rows should contain a multiple of four bytes"); + + auto result = mPipe.WritePixels([&]() -> NextPixel { + if (aLength == 0) { + return AsVariant(WriteState::NEED_MORE_DATA); // Done with this row. + } + + uint32_t pixel; + memcpy(&pixel, aData, 4); + aData += 4; + aLength -= 4; + + return AsVariant(pixel); + }); + + MOZ_ASSERT(result != WriteState::FAILURE); + + Maybe invalidRect = mPipe.TakeInvalidRect(); + if (invalidRect) { + PostInvalidation(invalidRect->mInputSpaceRect, + Some(invalidRect->mOutputSpaceRect)); + } + + return result == WriteState::FINISHED + ? Transition::To(State::FINISH, 0) + : Transition::To(State::ROW_OF_PIXELS, mBytesPerRow); +} + +LexerTransition +nsIconDecoder::Finish() +{ + PostFrameStop(); + PostDecodeDone(); + + return Transition::TerminateSuccess(); +} + +} // namespace image +} // namespace mozilla diff --git a/image/decoders/nsIconDecoder.h b/image/decoders/nsIconDecoder.h new file mode 100644 index 000000000..69198315b --- /dev/null +++ b/image/decoders/nsIconDecoder.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_decoders_nsIconDecoder_h +#define mozilla_image_decoders_nsIconDecoder_h + +#include "Decoder.h" +#include "StreamingLexer.h" +#include "SurfacePipe.h" + +namespace mozilla { +namespace image { + +class RasterImage; + +//////////////////////////////////////////////////////////////////////////////// +// The icon decoder is a decoder specifically tailored for loading icons +// from the OS. We've defined our own little format to represent these icons +// and this decoder takes that format and converts it into 24-bit RGB with +// alpha channel support. It was modeled a bit off the PPM decoder. +// +// The format of the incoming data is as follows: +// +// The first two bytes contain the width and the height of the icon. +// The remaining bytes contain the icon data, 4 bytes per pixel, in +// ARGB order (platform endianness, A in highest bits, B in lowest +// bits), row-primary, top-to-bottom, left-to-right, with +// premultiplied alpha. +// +//////////////////////////////////////////////////////////////////////////////// + +class nsIconDecoder : public Decoder +{ +public: + virtual ~nsIconDecoder(); + + LexerResult DoDecode(SourceBufferIterator& aIterator, + IResumable* aOnResume) override; + +private: + friend class DecoderFactory; + + // Decoders should only be instantiated via DecoderFactory. + explicit nsIconDecoder(RasterImage* aImage); + + enum class State { + HEADER, + ROW_OF_PIXELS, + FINISH + }; + + LexerTransition ReadHeader(const char* aData); + LexerTransition ReadRowOfPixels(const char* aData, size_t aLength); + LexerTransition Finish(); + + StreamingLexer mLexer; + SurfacePipe mPipe; + uint32_t mBytesPerRow; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_decoders_nsIconDecoder_h diff --git a/image/decoders/nsJPEGDecoder.cpp b/image/decoders/nsJPEGDecoder.cpp new file mode 100644 index 000000000..e76ffcbaf --- /dev/null +++ b/image/decoders/nsJPEGDecoder.cpp @@ -0,0 +1,1006 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageLogging.h" // Must appear first. + +#include "nsJPEGDecoder.h" + +#include + +#include "imgFrame.h" +#include "Orientation.h" +#include "EXIF.h" + +#include "nsIInputStream.h" + +#include "nspr.h" +#include "nsCRT.h" +#include "gfxColor.h" + +#include "jerror.h" + +#include "gfxPlatform.h" +#include "mozilla/EndianUtils.h" +#include "mozilla/Telemetry.h" + +extern "C" { +#include "iccjpeg.h" +} + +#if MOZ_BIG_ENDIAN +#define MOZ_JCS_EXT_NATIVE_ENDIAN_XRGB JCS_EXT_XRGB +#else +#define MOZ_JCS_EXT_NATIVE_ENDIAN_XRGB JCS_EXT_BGRX +#endif + +static void cmyk_convert_rgb(JSAMPROW row, JDIMENSION width); + +namespace mozilla { +namespace image { + +static mozilla::LazyLogModule sJPEGLog("JPEGDecoder"); + +static mozilla::LazyLogModule sJPEGDecoderAccountingLog("JPEGDecoderAccounting"); + +static qcms_profile* +GetICCProfile(struct jpeg_decompress_struct& info) +{ + JOCTET* profilebuf; + uint32_t profileLength; + qcms_profile* profile = nullptr; + + if (read_icc_profile(&info, &profilebuf, &profileLength)) { + profile = qcms_profile_from_memory(profilebuf, profileLength); + free(profilebuf); + } + + return profile; +} + +METHODDEF(void) init_source (j_decompress_ptr jd); +METHODDEF(boolean) fill_input_buffer (j_decompress_ptr jd); +METHODDEF(void) skip_input_data (j_decompress_ptr jd, long num_bytes); +METHODDEF(void) term_source (j_decompress_ptr jd); +METHODDEF(void) my_error_exit (j_common_ptr cinfo); + +// Normal JFIF markers can't have more bytes than this. +#define MAX_JPEG_MARKER_LENGTH (((uint32_t)1 << 16) - 1) + +nsJPEGDecoder::nsJPEGDecoder(RasterImage* aImage, + Decoder::DecodeStyle aDecodeStyle) + : Decoder(aImage) + , mLexer(Transition::ToUnbuffered(State::FINISHED_JPEG_DATA, + State::JPEG_DATA, + SIZE_MAX), + Transition::TerminateSuccess()) + , mDecodeStyle(aDecodeStyle) + , mSampleSize(0) +{ + mState = JPEG_HEADER; + mReading = true; + mImageData = nullptr; + + mBytesToSkip = 0; + memset(&mInfo, 0, sizeof(jpeg_decompress_struct)); + memset(&mSourceMgr, 0, sizeof(mSourceMgr)); + mInfo.client_data = (void*)this; + + mSegment = nullptr; + mSegmentLen = 0; + + mBackBuffer = nullptr; + mBackBufferLen = mBackBufferSize = mBackBufferUnreadLen = 0; + + mInProfile = nullptr; + mTransform = nullptr; + + mCMSMode = 0; + + MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug, + ("nsJPEGDecoder::nsJPEGDecoder: Creating JPEG decoder %p", + this)); +} + +nsJPEGDecoder::~nsJPEGDecoder() +{ + // Step 8: Release JPEG decompression object + mInfo.src = nullptr; + jpeg_destroy_decompress(&mInfo); + + PR_FREEIF(mBackBuffer); + if (mTransform) { + qcms_transform_release(mTransform); + } + if (mInProfile) { + qcms_profile_release(mInProfile); + } + + MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug, + ("nsJPEGDecoder::~nsJPEGDecoder: Destroying JPEG decoder %p", + this)); +} + +Maybe +nsJPEGDecoder::SpeedHistogram() const +{ + return Some(Telemetry::IMAGE_DECODE_SPEED_JPEG); +} + +nsresult +nsJPEGDecoder::InitInternal() +{ + mCMSMode = gfxPlatform::GetCMSMode(); + if (GetSurfaceFlags() & SurfaceFlags::NO_COLORSPACE_CONVERSION) { + mCMSMode = eCMSMode_Off; + } + + // We set up the normal JPEG error routines, then override error_exit. + mInfo.err = jpeg_std_error(&mErr.pub); + // mInfo.err = jpeg_std_error(&mErr.pub); + mErr.pub.error_exit = my_error_exit; + // Establish the setjmp return context for my_error_exit to use. + if (setjmp(mErr.setjmp_buffer)) { + // If we get here, the JPEG code has signaled an error, and initialization + // has failed. + return NS_ERROR_FAILURE; + } + + // Step 1: allocate and initialize JPEG decompression object + jpeg_create_decompress(&mInfo); + // Set the source manager + mInfo.src = &mSourceMgr; + + // Step 2: specify data source (eg, a file) + + // Setup callback functions. + mSourceMgr.init_source = init_source; + mSourceMgr.fill_input_buffer = fill_input_buffer; + mSourceMgr.skip_input_data = skip_input_data; + mSourceMgr.resync_to_restart = jpeg_resync_to_restart; + mSourceMgr.term_source = term_source; + + // Record app markers for ICC data + for (uint32_t m = 0; m < 16; m++) { + jpeg_save_markers(&mInfo, JPEG_APP0 + m, 0xFFFF); + } + + return NS_OK; +} + +nsresult +nsJPEGDecoder::FinishInternal() +{ + // If we're not in any sort of error case, force our state to JPEG_DONE. + if ((mState != JPEG_DONE && mState != JPEG_SINK_NON_JPEG_TRAILER) && + (mState != JPEG_ERROR) && + !IsMetadataDecode()) { + mState = JPEG_DONE; + } + + return NS_OK; +} + +LexerResult +nsJPEGDecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume) +{ + MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!"); + + return mLexer.Lex(aIterator, aOnResume, + [=](State aState, const char* aData, size_t aLength) { + switch (aState) { + case State::JPEG_DATA: + return ReadJPEGData(aData, aLength); + case State::FINISHED_JPEG_DATA: + return FinishedJPEGData(); + } + MOZ_CRASH("Unknown State"); + }); +} + +LexerTransition +nsJPEGDecoder::ReadJPEGData(const char* aData, size_t aLength) +{ + mSegment = reinterpret_cast(aData); + mSegmentLen = aLength; + + // Return here if there is a fatal error within libjpeg. + nsresult error_code; + // This cast to nsresult makes sense because setjmp() returns whatever we + // passed to longjmp(), which was actually an nsresult. + if ((error_code = static_cast(setjmp(mErr.setjmp_buffer))) != NS_OK) { + if (error_code == NS_ERROR_FAILURE) { + // Error due to corrupt data. Make sure that we don't feed any more data + // to libjpeg-turbo. + mState = JPEG_SINK_NON_JPEG_TRAILER; + MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug, + ("} (setjmp returned NS_ERROR_FAILURE)")); + } else { + // Error for another reason. (Possibly OOM.) + mState = JPEG_ERROR; + MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug, + ("} (setjmp returned an error)")); + } + + return Transition::TerminateFailure(); + } + + MOZ_LOG(sJPEGLog, LogLevel::Debug, + ("[this=%p] nsJPEGDecoder::Write -- processing JPEG data\n", this)); + + switch (mState) { + case JPEG_HEADER: { + LOG_SCOPE((mozilla::LogModule*)sJPEGLog, "nsJPEGDecoder::Write -- entering JPEG_HEADER" + " case"); + + // Step 3: read file parameters with jpeg_read_header() + if (jpeg_read_header(&mInfo, TRUE) == JPEG_SUSPENDED) { + MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug, + ("} (JPEG_SUSPENDED)")); + return Transition::ContinueUnbuffered(State::JPEG_DATA); // I/O suspension + } + + // If we have a sample size specified for -moz-sample-size, use it. + if (mSampleSize > 0) { + mInfo.scale_num = 1; + mInfo.scale_denom = mSampleSize; + } + + // Used to set up image size so arrays can be allocated + jpeg_calc_output_dimensions(&mInfo); + + // Post our size to the superclass + PostSize(mInfo.output_width, mInfo.output_height, + ReadOrientationFromEXIF()); + if (HasError()) { + // Setting the size led to an error. + mState = JPEG_ERROR; + return Transition::TerminateFailure(); + } + + // If we're doing a metadata decode, we're done. + if (IsMetadataDecode()) { + return Transition::TerminateSuccess(); + } + + // We're doing a full decode. + if (mCMSMode != eCMSMode_Off && + (mInProfile = GetICCProfile(mInfo)) != nullptr) { + uint32_t profileSpace = qcms_profile_get_color_space(mInProfile); + bool mismatch = false; + +#ifdef DEBUG_tor + fprintf(stderr, "JPEG profileSpace: 0x%08X\n", profileSpace); +#endif + switch (mInfo.jpeg_color_space) { + case JCS_GRAYSCALE: + if (profileSpace == icSigRgbData) { + mInfo.out_color_space = JCS_RGB; + } else if (profileSpace != icSigGrayData) { + mismatch = true; + } + break; + case JCS_RGB: + if (profileSpace != icSigRgbData) { + mismatch = true; + } + break; + case JCS_YCbCr: + if (profileSpace == icSigRgbData) { + mInfo.out_color_space = JCS_RGB; + } else { + // qcms doesn't support ycbcr + mismatch = true; + } + break; + case JCS_CMYK: + case JCS_YCCK: + // qcms doesn't support cmyk + mismatch = true; + break; + default: + mState = JPEG_ERROR; + MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug, + ("} (unknown colorpsace (1))")); + return Transition::TerminateFailure(); + } + + if (!mismatch) { + qcms_data_type type; + switch (mInfo.out_color_space) { + case JCS_GRAYSCALE: + type = QCMS_DATA_GRAY_8; + break; + case JCS_RGB: + type = QCMS_DATA_RGB_8; + break; + default: + mState = JPEG_ERROR; + MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug, + ("} (unknown colorpsace (2))")); + return Transition::TerminateFailure(); + } +#if 0 + // We don't currently support CMYK profiles. The following + // code dealt with lcms types. Add something like this + // back when we gain support for CMYK. + + // Adobe Photoshop writes YCCK/CMYK files with inverted data + if (mInfo.out_color_space == JCS_CMYK) { + type |= FLAVOR_SH(mInfo.saw_Adobe_marker ? 1 : 0); + } +#endif + + if (gfxPlatform::GetCMSOutputProfile()) { + + // Calculate rendering intent. + int intent = gfxPlatform::GetRenderingIntent(); + if (intent == -1) { + intent = qcms_profile_get_rendering_intent(mInProfile); + } + + // Create the color management transform. + mTransform = qcms_transform_create(mInProfile, + type, + gfxPlatform::GetCMSOutputProfile(), + QCMS_DATA_RGB_8, + (qcms_intent)intent); + } + } else { +#ifdef DEBUG_tor + fprintf(stderr, "ICM profile colorspace mismatch\n"); +#endif + } + } + + if (!mTransform) { + switch (mInfo.jpeg_color_space) { + case JCS_GRAYSCALE: + case JCS_RGB: + case JCS_YCbCr: + // if we're not color managing we can decode directly to + // MOZ_JCS_EXT_NATIVE_ENDIAN_XRGB + if (mCMSMode != eCMSMode_All) { + mInfo.out_color_space = MOZ_JCS_EXT_NATIVE_ENDIAN_XRGB; + mInfo.out_color_components = 4; + } else { + mInfo.out_color_space = JCS_RGB; + } + break; + case JCS_CMYK: + case JCS_YCCK: + // libjpeg can convert from YCCK to CMYK, but not to RGB + mInfo.out_color_space = JCS_CMYK; + break; + default: + mState = JPEG_ERROR; + MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug, + ("} (unknown colorpsace (3))")); + return Transition::TerminateFailure(); + } + } + + // Don't allocate a giant and superfluous memory buffer + // when not doing a progressive decode. + mInfo.buffered_image = mDecodeStyle == PROGRESSIVE && + jpeg_has_multiple_scans(&mInfo); + + MOZ_ASSERT(!mImageData, "Already have a buffer allocated?"); + nsresult rv = AllocateFrame(/* aFrameNum = */ 0, OutputSize(), + FullOutputFrame(), SurfaceFormat::B8G8R8X8); + if (NS_FAILED(rv)) { + mState = JPEG_ERROR; + MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug, + ("} (could not initialize image frame)")); + return Transition::TerminateFailure(); + } + + MOZ_ASSERT(mImageData, "Should have a buffer now"); + + if (mDownscaler) { + nsresult rv = mDownscaler->BeginFrame(Size(), Nothing(), + mImageData, + /* aHasAlpha = */ false); + if (NS_FAILED(rv)) { + mState = JPEG_ERROR; + return Transition::TerminateFailure(); + } + } + + MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug, + (" JPEGDecoderAccounting: nsJPEGDecoder::" + "Write -- created image frame with %ux%u pixels", + mInfo.output_width, mInfo.output_height)); + + mState = JPEG_START_DECOMPRESS; + MOZ_FALLTHROUGH; // to start decompressing. + } + + case JPEG_START_DECOMPRESS: { + LOG_SCOPE((mozilla::LogModule*)sJPEGLog, "nsJPEGDecoder::Write -- entering" + " JPEG_START_DECOMPRESS case"); + // Step 4: set parameters for decompression + + // FIXME -- Should reset dct_method and dither mode + // for final pass of progressive JPEG + + mInfo.dct_method = JDCT_ISLOW; + mInfo.dither_mode = JDITHER_FS; + mInfo.do_fancy_upsampling = TRUE; + mInfo.enable_2pass_quant = FALSE; + mInfo.do_block_smoothing = TRUE; + + // Step 5: Start decompressor + if (jpeg_start_decompress(&mInfo) == FALSE) { + MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug, + ("} (I/O suspension after jpeg_start_decompress())")); + return Transition::ContinueUnbuffered(State::JPEG_DATA); // I/O suspension + } + + // If this is a progressive JPEG ... + mState = mInfo.buffered_image ? + JPEG_DECOMPRESS_PROGRESSIVE : JPEG_DECOMPRESS_SEQUENTIAL; + MOZ_FALLTHROUGH; // to decompress sequential JPEG. + } + + case JPEG_DECOMPRESS_SEQUENTIAL: { + if (mState == JPEG_DECOMPRESS_SEQUENTIAL) { + LOG_SCOPE((mozilla::LogModule*)sJPEGLog, "nsJPEGDecoder::Write -- " + "JPEG_DECOMPRESS_SEQUENTIAL case"); + + bool suspend; + OutputScanlines(&suspend); + + if (suspend) { + MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug, + ("} (I/O suspension after OutputScanlines() - SEQUENTIAL)")); + return Transition::ContinueUnbuffered(State::JPEG_DATA); // I/O suspension + } + + // If we've completed image output ... + NS_ASSERTION(mInfo.output_scanline == mInfo.output_height, + "We didn't process all of the data!"); + mState = JPEG_DONE; + } + MOZ_FALLTHROUGH; // to decompress progressive JPEG. + } + + case JPEG_DECOMPRESS_PROGRESSIVE: { + if (mState == JPEG_DECOMPRESS_PROGRESSIVE) { + LOG_SCOPE((mozilla::LogModule*)sJPEGLog, + "nsJPEGDecoder::Write -- JPEG_DECOMPRESS_PROGRESSIVE case"); + + int status; + do { + status = jpeg_consume_input(&mInfo); + } while ((status != JPEG_SUSPENDED) && + (status != JPEG_REACHED_EOI)); + + for (;;) { + if (mInfo.output_scanline == 0) { + int scan = mInfo.input_scan_number; + + // if we haven't displayed anything yet (output_scan_number==0) + // and we have enough data for a complete scan, force output + // of the last full scan + if ((mInfo.output_scan_number == 0) && + (scan > 1) && + (status != JPEG_REACHED_EOI)) + scan--; + + if (!jpeg_start_output(&mInfo, scan)) { + MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug, + ("} (I/O suspension after jpeg_start_output() -" + " PROGRESSIVE)")); + return Transition::ContinueUnbuffered(State::JPEG_DATA); // I/O suspension + } + } + + if (mInfo.output_scanline == 0xffffff) { + mInfo.output_scanline = 0; + } + + bool suspend; + OutputScanlines(&suspend); + + if (suspend) { + if (mInfo.output_scanline == 0) { + // didn't manage to read any lines - flag so we don't call + // jpeg_start_output() multiple times for the same scan + mInfo.output_scanline = 0xffffff; + } + MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug, + ("} (I/O suspension after OutputScanlines() - PROGRESSIVE)")); + return Transition::ContinueUnbuffered(State::JPEG_DATA); // I/O suspension + } + + if (mInfo.output_scanline == mInfo.output_height) { + if (!jpeg_finish_output(&mInfo)) { + MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug, + ("} (I/O suspension after jpeg_finish_output() -" + " PROGRESSIVE)")); + return Transition::ContinueUnbuffered(State::JPEG_DATA); // I/O suspension + } + + if (jpeg_input_complete(&mInfo) && + (mInfo.input_scan_number == mInfo.output_scan_number)) + break; + + mInfo.output_scanline = 0; + if (mDownscaler) { + mDownscaler->ResetForNextProgressivePass(); + } + } + } + + mState = JPEG_DONE; + } + MOZ_FALLTHROUGH; // to finish decompressing. + } + + case JPEG_DONE: { + LOG_SCOPE((mozilla::LogModule*)sJPEGLog, "nsJPEGDecoder::ProcessData -- entering" + " JPEG_DONE case"); + + // Step 7: Finish decompression + + if (jpeg_finish_decompress(&mInfo) == FALSE) { + MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug, + ("} (I/O suspension after jpeg_finish_decompress() - DONE)")); + return Transition::ContinueUnbuffered(State::JPEG_DATA); // I/O suspension + } + + // Make sure we don't feed any more data to libjpeg-turbo. + mState = JPEG_SINK_NON_JPEG_TRAILER; + + // We're done. + return Transition::TerminateSuccess(); + } + case JPEG_SINK_NON_JPEG_TRAILER: + MOZ_LOG(sJPEGLog, LogLevel::Debug, + ("[this=%p] nsJPEGDecoder::ProcessData -- entering" + " JPEG_SINK_NON_JPEG_TRAILER case\n", this)); + + MOZ_ASSERT_UNREACHABLE("Should stop getting data after entering state " + "JPEG_SINK_NON_JPEG_TRAILER"); + + return Transition::TerminateSuccess(); + + case JPEG_ERROR: + MOZ_ASSERT_UNREACHABLE("Should stop getting data after entering state " + "JPEG_ERROR"); + + return Transition::TerminateFailure(); + } + + MOZ_ASSERT_UNREACHABLE("Escaped the JPEG decoder state machine"); + return Transition::TerminateFailure(); +} + +LexerTransition +nsJPEGDecoder::FinishedJPEGData() +{ + // Since we set up an unbuffered read for SIZE_MAX bytes, if we actually read + // all that data something is really wrong. + MOZ_ASSERT_UNREACHABLE("Read the entire address space?"); + return Transition::TerminateFailure(); +} + +Orientation +nsJPEGDecoder::ReadOrientationFromEXIF() +{ + jpeg_saved_marker_ptr marker; + + // Locate the APP1 marker, where EXIF data is stored, in the marker list. + for (marker = mInfo.marker_list ; marker != nullptr ; marker = marker->next) { + if (marker->marker == JPEG_APP0 + 1) { + break; + } + } + + // If we're at the end of the list, there's no EXIF data. + if (!marker) { + return Orientation(); + } + + // Extract the orientation information. + EXIFData exif = EXIFParser::Parse(marker->data, + static_cast(marker->data_length)); + return exif.orientation; +} + +void +nsJPEGDecoder::NotifyDone() +{ + PostFrameStop(Opacity::FULLY_OPAQUE); + PostDecodeDone(); +} + +void +nsJPEGDecoder::OutputScanlines(bool* suspend) +{ + *suspend = false; + + const uint32_t top = mInfo.output_scanline; + + while ((mInfo.output_scanline < mInfo.output_height)) { + uint32_t* imageRow = nullptr; + if (mDownscaler) { + imageRow = reinterpret_cast(mDownscaler->RowBuffer()); + } else { + imageRow = reinterpret_cast(mImageData) + + (mInfo.output_scanline * mInfo.output_width); + } + + MOZ_ASSERT(imageRow, "Should have a row buffer here"); + + if (mInfo.out_color_space == MOZ_JCS_EXT_NATIVE_ENDIAN_XRGB) { + // Special case: scanline will be directly converted into packed ARGB + if (jpeg_read_scanlines(&mInfo, (JSAMPARRAY)&imageRow, 1) != 1) { + *suspend = true; // suspend + break; + } + if (mDownscaler) { + mDownscaler->CommitRow(); + } + continue; // all done for this row! + } + + JSAMPROW sampleRow = (JSAMPROW)imageRow; + if (mInfo.output_components == 3) { + // Put the pixels at end of row to enable in-place expansion + sampleRow += mInfo.output_width; + } + + // Request one scanline. Returns 0 or 1 scanlines. + if (jpeg_read_scanlines(&mInfo, &sampleRow, 1) != 1) { + *suspend = true; // suspend + break; + } + + if (mTransform) { + JSAMPROW source = sampleRow; + if (mInfo.out_color_space == JCS_GRAYSCALE) { + // Convert from the 1byte grey pixels at begin of row + // to the 3byte RGB byte pixels at 'end' of row + sampleRow += mInfo.output_width; + } + qcms_transform_data(mTransform, source, sampleRow, mInfo.output_width); + // Move 3byte RGB data to end of row + if (mInfo.out_color_space == JCS_CMYK) { + memmove(sampleRow + mInfo.output_width, + sampleRow, + 3 * mInfo.output_width); + sampleRow += mInfo.output_width; + } + } else { + if (mInfo.out_color_space == JCS_CMYK) { + // Convert from CMYK to RGB + // We cannot convert directly to Cairo, as the CMSRGBTransform + // may wants to do a RGB transform... + // Would be better to have platform CMSenabled transformation + // from CMYK to (A)RGB... + cmyk_convert_rgb((JSAMPROW)imageRow, mInfo.output_width); + sampleRow += mInfo.output_width; + } + if (mCMSMode == eCMSMode_All) { + // No embedded ICC profile - treat as sRGB + qcms_transform* transform = gfxPlatform::GetCMSRGBTransform(); + if (transform) { + qcms_transform_data(transform, sampleRow, sampleRow, + mInfo.output_width); + } + } + } + + // counter for while() loops below + uint32_t idx = mInfo.output_width; + + // copy as bytes until source pointer is 32-bit-aligned + for (; (NS_PTR_TO_UINT32(sampleRow) & 0x3) && idx; --idx) { + *imageRow++ = gfxPackedPixel(0xFF, sampleRow[0], sampleRow[1], + sampleRow[2]); + sampleRow += 3; + } + + // copy pixels in blocks of 4 + while (idx >= 4) { + GFX_BLOCK_RGB_TO_FRGB(sampleRow, imageRow); + idx -= 4; + sampleRow += 12; + imageRow += 4; + } + + // copy remaining pixel(s) + while (idx--) { + // 32-bit read of final pixel will exceed buffer, so read bytes + *imageRow++ = gfxPackedPixel(0xFF, sampleRow[0], sampleRow[1], + sampleRow[2]); + sampleRow += 3; + } + + if (mDownscaler) { + mDownscaler->CommitRow(); + } + } + + if (mDownscaler && mDownscaler->HasInvalidation()) { + DownscalerInvalidRect invalidRect = mDownscaler->TakeInvalidRect(); + PostInvalidation(invalidRect.mOriginalSizeRect, + Some(invalidRect.mTargetSizeRect)); + MOZ_ASSERT(!mDownscaler->HasInvalidation()); + } else if (!mDownscaler && top != mInfo.output_scanline) { + PostInvalidation(nsIntRect(0, top, + mInfo.output_width, + mInfo.output_scanline - top)); + } +} + +// Override the standard error method in the IJG JPEG decoder code. +METHODDEF(void) +my_error_exit (j_common_ptr cinfo) +{ + decoder_error_mgr* err = (decoder_error_mgr*) cinfo->err; + + // Convert error to a browser error code + nsresult error_code = err->pub.msg_code == JERR_OUT_OF_MEMORY + ? NS_ERROR_OUT_OF_MEMORY + : NS_ERROR_FAILURE; + +#ifdef DEBUG + char buffer[JMSG_LENGTH_MAX]; + + // Create the message + (*err->pub.format_message) (cinfo, buffer); + + fprintf(stderr, "JPEG decoding error:\n%s\n", buffer); +#endif + + // Return control to the setjmp point. We pass an nsresult masquerading as + // an int, which works because the setjmp() caller casts it back. + longjmp(err->setjmp_buffer, static_cast(error_code)); +} + +/******************************************************************************* + * This is the callback routine from the IJG JPEG library used to supply new + * data to the decompressor when its input buffer is exhausted. It juggles + * multiple buffers in an attempt to avoid unnecessary copying of input data. + * + * (A simpler scheme is possible: It's much easier to use only a single + * buffer; when fill_input_buffer() is called, move any unconsumed data + * (beyond the current pointer/count) down to the beginning of this buffer and + * then load new data into the remaining buffer space. This approach requires + * a little more data copying but is far easier to get right.) + * + * At any one time, the JPEG decompressor is either reading from the necko + * input buffer, which is volatile across top-level calls to the IJG library, + * or the "backtrack" buffer. The backtrack buffer contains the remaining + * unconsumed data from the necko buffer after parsing was suspended due + * to insufficient data in some previous call to the IJG library. + * + * When suspending, the decompressor will back up to a convenient restart + * point (typically the start of the current MCU). The variables + * next_input_byte & bytes_in_buffer indicate where the restart point will be + * if the current call returns FALSE. Data beyond this point must be + * rescanned after resumption, so it must be preserved in case the decompressor + * decides to backtrack. + * + * Returns: + * TRUE if additional data is available, FALSE if no data present and + * the JPEG library should therefore suspend processing of input stream + ******************************************************************************/ + +/******************************************************************************/ +/* data source manager method */ +/******************************************************************************/ + +/******************************************************************************/ +/* data source manager method + Initialize source. This is called by jpeg_read_header() before any + data is actually read. May leave + bytes_in_buffer set to 0 (in which case a fill_input_buffer() call + will occur immediately). +*/ +METHODDEF(void) +init_source (j_decompress_ptr jd) +{ +} + +/******************************************************************************/ +/* data source manager method + Skip num_bytes worth of data. The buffer pointer and count should + be advanced over num_bytes input bytes, refilling the buffer as + needed. This is used to skip over a potentially large amount of + uninteresting data (such as an APPn marker). In some applications + it may be possible to optimize away the reading of the skipped data, + but it's not clear that being smart is worth much trouble; large + skips are uncommon. bytes_in_buffer may be zero on return. + A zero or negative skip count should be treated as a no-op. +*/ +METHODDEF(void) +skip_input_data (j_decompress_ptr jd, long num_bytes) +{ + struct jpeg_source_mgr* src = jd->src; + nsJPEGDecoder* decoder = (nsJPEGDecoder*)(jd->client_data); + + if (num_bytes > (long)src->bytes_in_buffer) { + // Can't skip it all right now until we get more data from + // network stream. Set things up so that fill_input_buffer + // will skip remaining amount. + decoder->mBytesToSkip = (size_t)num_bytes - src->bytes_in_buffer; + src->next_input_byte += src->bytes_in_buffer; + src->bytes_in_buffer = 0; + + } else { + // Simple case. Just advance buffer pointer + + src->bytes_in_buffer -= (size_t)num_bytes; + src->next_input_byte += num_bytes; + } +} + +/******************************************************************************/ +/* data source manager method + This is called whenever bytes_in_buffer has reached zero and more + data is wanted. In typical applications, it should read fresh data + into the buffer (ignoring the current state of next_input_byte and + bytes_in_buffer), reset the pointer & count to the start of the + buffer, and return TRUE indicating that the buffer has been reloaded. + It is not necessary to fill the buffer entirely, only to obtain at + least one more byte. bytes_in_buffer MUST be set to a positive value + if TRUE is returned. A FALSE return should only be used when I/O + suspension is desired. +*/ +METHODDEF(boolean) +fill_input_buffer (j_decompress_ptr jd) +{ + struct jpeg_source_mgr* src = jd->src; + nsJPEGDecoder* decoder = (nsJPEGDecoder*)(jd->client_data); + + if (decoder->mReading) { + const JOCTET* new_buffer = decoder->mSegment; + uint32_t new_buflen = decoder->mSegmentLen; + + if (!new_buffer || new_buflen == 0) { + return false; // suspend + } + + decoder->mSegmentLen = 0; + + if (decoder->mBytesToSkip) { + if (decoder->mBytesToSkip < new_buflen) { + // All done skipping bytes; Return what's left. + new_buffer += decoder->mBytesToSkip; + new_buflen -= decoder->mBytesToSkip; + decoder->mBytesToSkip = 0; + } else { + // Still need to skip some more data in the future + decoder->mBytesToSkip -= (size_t)new_buflen; + return false; // suspend + } + } + + decoder->mBackBufferUnreadLen = src->bytes_in_buffer; + + src->next_input_byte = new_buffer; + src->bytes_in_buffer = (size_t)new_buflen; + decoder->mReading = false; + + return true; + } + + if (src->next_input_byte != decoder->mSegment) { + // Backtrack data has been permanently consumed. + decoder->mBackBufferUnreadLen = 0; + decoder->mBackBufferLen = 0; + } + + // Save remainder of netlib buffer in backtrack buffer + const uint32_t new_backtrack_buflen = src->bytes_in_buffer + + decoder->mBackBufferLen; + + // Make sure backtrack buffer is big enough to hold new data. + if (decoder->mBackBufferSize < new_backtrack_buflen) { + // Check for malformed MARKER segment lengths, before allocating space + // for it + if (new_backtrack_buflen > MAX_JPEG_MARKER_LENGTH) { + my_error_exit((j_common_ptr)(&decoder->mInfo)); + } + + // Round up to multiple of 256 bytes. + const size_t roundup_buflen = ((new_backtrack_buflen + 255) >> 8) << 8; + JOCTET* buf = (JOCTET*)PR_REALLOC(decoder->mBackBuffer, roundup_buflen); + // Check for OOM + if (!buf) { + decoder->mInfo.err->msg_code = JERR_OUT_OF_MEMORY; + my_error_exit((j_common_ptr)(&decoder->mInfo)); + } + decoder->mBackBuffer = buf; + decoder->mBackBufferSize = roundup_buflen; + } + + // Copy remainder of netlib segment into backtrack buffer. + memmove(decoder->mBackBuffer + decoder->mBackBufferLen, + src->next_input_byte, + src->bytes_in_buffer); + + // Point to start of data to be rescanned. + src->next_input_byte = decoder->mBackBuffer + decoder->mBackBufferLen - + decoder->mBackBufferUnreadLen; + src->bytes_in_buffer += decoder->mBackBufferUnreadLen; + decoder->mBackBufferLen = (size_t)new_backtrack_buflen; + decoder->mReading = true; + + return false; +} + +/******************************************************************************/ +/* data source manager method */ +/* + * Terminate source --- called by jpeg_finish_decompress() after all + * data has been read to clean up JPEG source manager. NOT called by + * jpeg_abort() or jpeg_destroy(). + */ +METHODDEF(void) +term_source (j_decompress_ptr jd) +{ + nsJPEGDecoder* decoder = (nsJPEGDecoder*)(jd->client_data); + + // This function shouldn't be called if we ran into an error we didn't + // recover from. + MOZ_ASSERT(decoder->mState != JPEG_ERROR, + "Calling term_source on a JPEG with mState == JPEG_ERROR!"); + + // Notify using a helper method to get around protectedness issues. + decoder->NotifyDone(); +} + +} // namespace image +} // namespace mozilla + +///*************** Inverted CMYK -> RGB conversion ************************* +/// Input is (Inverted) CMYK stored as 4 bytes per pixel. +/// Output is RGB stored as 3 bytes per pixel. +/// @param row Points to row buffer containing the CMYK bytes for each pixel +/// in the row. +/// @param width Number of pixels in the row. +static void cmyk_convert_rgb(JSAMPROW row, JDIMENSION width) +{ + // Work from end to front to shrink from 4 bytes per pixel to 3 + JSAMPROW in = row + width*4; + JSAMPROW out = in; + + for (uint32_t i = width; i > 0; i--) { + in -= 4; + out -= 3; + + // Source is 'Inverted CMYK', output is RGB. + // See: http://www.easyrgb.com/math.php?MATH=M12#text12 + // Or: http://www.ilkeratalay.com/colorspacesfaq.php#rgb + + // From CMYK to CMY + // C = ( C * ( 1 - K ) + K ) + // M = ( M * ( 1 - K ) + K ) + // Y = ( Y * ( 1 - K ) + K ) + + // From Inverted CMYK to CMY is thus: + // C = ( (1-iC) * (1 - (1-iK)) + (1-iK) ) => 1 - iC*iK + // Same for M and Y + + // Convert from CMY (0..1) to RGB (0..1) + // R = 1 - C => 1 - (1 - iC*iK) => iC*iK + // G = 1 - M => 1 - (1 - iM*iK) => iM*iK + // B = 1 - Y => 1 - (1 - iY*iK) => iY*iK + + // Convert from Inverted CMYK (0..255) to RGB (0..255) + const uint32_t iC = in[0]; + const uint32_t iM = in[1]; + const uint32_t iY = in[2]; + const uint32_t iK = in[3]; + out[0] = iC*iK/255; // Red + out[1] = iM*iK/255; // Green + out[2] = iY*iK/255; // Blue + } +} diff --git a/image/decoders/nsJPEGDecoder.h b/image/decoders/nsJPEGDecoder.h new file mode 100644 index 000000000..260e91303 --- /dev/null +++ b/image/decoders/nsJPEGDecoder.h @@ -0,0 +1,125 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_decoders_nsJPEGDecoder_h +#define mozilla_image_decoders_nsJPEGDecoder_h + +#include "RasterImage.h" +// On Windows systems, RasterImage.h brings in 'windows.h', which defines INT32. +// But the jpeg decoder has its own definition of INT32. To avoid build issues, +// we need to undefine the version from 'windows.h'. +#undef INT32 + +#include "Decoder.h" + +#include "nsIInputStream.h" +#include "nsIPipe.h" +#include "qcms.h" + +extern "C" { +#include "jpeglib.h" +} + +#include + +namespace mozilla { +namespace image { + +typedef struct { + struct jpeg_error_mgr pub; // "public" fields for IJG library + jmp_buf setjmp_buffer; // For handling catastropic errors +} decoder_error_mgr; + +typedef enum { + JPEG_HEADER, // Reading JFIF headers + JPEG_START_DECOMPRESS, + JPEG_DECOMPRESS_PROGRESSIVE, // Output progressive pixels + JPEG_DECOMPRESS_SEQUENTIAL, // Output sequential pixels + JPEG_DONE, + JPEG_SINK_NON_JPEG_TRAILER, // Some image files have a + // non-JPEG trailer + JPEG_ERROR +} jstate; + +class RasterImage; +struct Orientation; + +class nsJPEGDecoder : public Decoder +{ +public: + virtual ~nsJPEGDecoder(); + + virtual void SetSampleSize(int aSampleSize) override + { + mSampleSize = aSampleSize; + } + + void NotifyDone(); + +protected: + nsresult InitInternal() override; + LexerResult DoDecode(SourceBufferIterator& aIterator, + IResumable* aOnResume) override; + nsresult FinishInternal() override; + + Maybe SpeedHistogram() const override; + +protected: + Orientation ReadOrientationFromEXIF(); + void OutputScanlines(bool* suspend); + +private: + friend class DecoderFactory; + + // Decoders should only be instantiated via DecoderFactory. + nsJPEGDecoder(RasterImage* aImage, Decoder::DecodeStyle aDecodeStyle); + + enum class State + { + JPEG_DATA, + FINISHED_JPEG_DATA + }; + + LexerTransition ReadJPEGData(const char* aData, size_t aLength); + LexerTransition FinishedJPEGData(); + + StreamingLexer mLexer; + +public: + struct jpeg_decompress_struct mInfo; + struct jpeg_source_mgr mSourceMgr; + decoder_error_mgr mErr; + jstate mState; + + uint32_t mBytesToSkip; + + const JOCTET* mSegment; // The current segment we are decoding from + uint32_t mSegmentLen; // amount of data in mSegment + + JOCTET* mBackBuffer; + uint32_t mBackBufferLen; // Offset of end of active backtrack data + uint32_t mBackBufferSize; // size in bytes what mBackBuffer was created with + uint32_t mBackBufferUnreadLen; // amount of data currently in mBackBuffer + + JOCTET * mProfile; + uint32_t mProfileLength; + + qcms_profile* mInProfile; + qcms_transform* mTransform; + + bool mReading; + + const Decoder::DecodeStyle mDecodeStyle; + + uint32_t mCMSMode; + + int mSampleSize; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_decoders_nsJPEGDecoder_h diff --git a/image/decoders/nsPNGDecoder.cpp b/image/decoders/nsPNGDecoder.cpp new file mode 100644 index 000000000..0f385b339 --- /dev/null +++ b/image/decoders/nsPNGDecoder.cpp @@ -0,0 +1,1113 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageLogging.h" // Must appear first +#include "nsPNGDecoder.h" + +#include +#include + +#include "gfxColor.h" +#include "gfxPlatform.h" +#include "imgFrame.h" +#include "nsColor.h" +#include "nsIInputStream.h" +#include "nsMemory.h" +#include "nsRect.h" +#include "nspr.h" +#include "png.h" +#include "RasterImage.h" +#include "SurfacePipeFactory.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Telemetry.h" + +using namespace mozilla::gfx; + +using std::min; + +namespace mozilla { +namespace image { + +static LazyLogModule sPNGLog("PNGDecoder"); +static LazyLogModule sPNGDecoderAccountingLog("PNGDecoderAccounting"); + +// limit image dimensions (bug #251381, #591822, #967656, and #1283961) +#ifndef MOZ_PNG_MAX_WIDTH +# define MOZ_PNG_MAX_WIDTH 0x7fffffff // Unlimited +#endif +#ifndef MOZ_PNG_MAX_HEIGHT +# define MOZ_PNG_MAX_HEIGHT 0x7fffffff // Unlimited +#endif + +nsPNGDecoder::AnimFrameInfo::AnimFrameInfo() + : mDispose(DisposalMethod::KEEP) + , mBlend(BlendMethod::OVER) + , mTimeout(0) +{ } + +#ifdef PNG_APNG_SUPPORTED + +int32_t GetNextFrameDelay(png_structp aPNG, png_infop aInfo) +{ + // Delay, in seconds, is delayNum / delayDen. + png_uint_16 delayNum = png_get_next_frame_delay_num(aPNG, aInfo); + png_uint_16 delayDen = png_get_next_frame_delay_den(aPNG, aInfo); + + if (delayNum == 0) { + return 0; // SetFrameTimeout() will set to a minimum. + } + + if (delayDen == 0) { + delayDen = 100; // So says the APNG spec. + } + + // Need to cast delay_num to float to have a proper division and + // the result to int to avoid a compiler warning. + return static_cast(static_cast(delayNum) * 1000 / delayDen); +} + +nsPNGDecoder::AnimFrameInfo::AnimFrameInfo(png_structp aPNG, png_infop aInfo) + : mDispose(DisposalMethod::KEEP) + , mBlend(BlendMethod::OVER) + , mTimeout(0) +{ + png_byte dispose_op = png_get_next_frame_dispose_op(aPNG, aInfo); + png_byte blend_op = png_get_next_frame_blend_op(aPNG, aInfo); + + if (dispose_op == PNG_DISPOSE_OP_PREVIOUS) { + mDispose = DisposalMethod::RESTORE_PREVIOUS; + } else if (dispose_op == PNG_DISPOSE_OP_BACKGROUND) { + mDispose = DisposalMethod::CLEAR; + } else { + mDispose = DisposalMethod::KEEP; + } + + if (blend_op == PNG_BLEND_OP_SOURCE) { + mBlend = BlendMethod::SOURCE; + } else { + mBlend = BlendMethod::OVER; + } + + mTimeout = GetNextFrameDelay(aPNG, aInfo); +} +#endif + +// First 8 bytes of a PNG file +const uint8_t +nsPNGDecoder::pngSignatureBytes[] = { 137, 80, 78, 71, 13, 10, 26, 10 }; + +nsPNGDecoder::nsPNGDecoder(RasterImage* aImage) + : Decoder(aImage) + , mLexer(Transition::ToUnbuffered(State::FINISHED_PNG_DATA, + State::PNG_DATA, + SIZE_MAX), + Transition::TerminateSuccess()) + , mNextTransition(Transition::ContinueUnbuffered(State::PNG_DATA)) + , mLastChunkLength(0) + , mPNG(nullptr) + , mInfo(nullptr) + , mCMSLine(nullptr) + , interlacebuf(nullptr) + , mInProfile(nullptr) + , mTransform(nullptr) + , format(gfx::SurfaceFormat::UNKNOWN) + , mCMSMode(0) + , mChannels(0) + , mPass(0) + , mFrameIsHidden(false) + , mDisablePremultipliedAlpha(false) + , mNumFrames(0) +{ } + +nsPNGDecoder::~nsPNGDecoder() +{ + if (mPNG) { + png_destroy_read_struct(&mPNG, mInfo ? &mInfo : nullptr, nullptr); + } + if (mCMSLine) { + free(mCMSLine); + } + if (interlacebuf) { + free(interlacebuf); + } + if (mInProfile) { + qcms_profile_release(mInProfile); + + // mTransform belongs to us only if mInProfile is non-null + if (mTransform) { + qcms_transform_release(mTransform); + } + } +} + +nsPNGDecoder::TransparencyType +nsPNGDecoder::GetTransparencyType(SurfaceFormat aFormat, + const IntRect& aFrameRect) +{ + // Check if the image has a transparent color in its palette. + if (aFormat == SurfaceFormat::B8G8R8A8) { + return TransparencyType::eAlpha; + } + if (!aFrameRect.IsEqualEdges(FullFrame())) { + MOZ_ASSERT(HasAnimation()); + return TransparencyType::eFrameRect; + } + + return TransparencyType::eNone; +} + +void +nsPNGDecoder::PostHasTransparencyIfNeeded(TransparencyType aTransparencyType) +{ + switch (aTransparencyType) { + case TransparencyType::eNone: + return; + + case TransparencyType::eAlpha: + PostHasTransparency(); + return; + + case TransparencyType::eFrameRect: + // If the first frame of animated image doesn't draw into the whole image, + // then record that it is transparent. For subsequent frames, this doesn't + // affect transparency, because they're composited on top of all previous + // frames. + if (mNumFrames == 0) { + PostHasTransparency(); + } + return; + } +} + +// CreateFrame() is used for both simple and animated images. +nsresult +nsPNGDecoder::CreateFrame(const FrameInfo& aFrameInfo) +{ + MOZ_ASSERT(HasSize()); + MOZ_ASSERT(!IsMetadataDecode()); + + // Check if we have transparency, and send notifications if needed. + auto transparency = GetTransparencyType(aFrameInfo.mFormat, + aFrameInfo.mFrameRect); + PostHasTransparencyIfNeeded(transparency); + SurfaceFormat format = transparency == TransparencyType::eNone + ? SurfaceFormat::B8G8R8X8 + : SurfaceFormat::B8G8R8A8; + + // Make sure there's no animation or padding if we're downscaling. + MOZ_ASSERT_IF(Size() != OutputSize(), mNumFrames == 0); + MOZ_ASSERT_IF(Size() != OutputSize(), !GetImageMetadata().HasAnimation()); + MOZ_ASSERT_IF(Size() != OutputSize(), + transparency != TransparencyType::eFrameRect); + + // If this image is interlaced, we can display better quality intermediate + // results to the user by post processing them with ADAM7InterpolatingFilter. + SurfacePipeFlags pipeFlags = aFrameInfo.mIsInterlaced + ? SurfacePipeFlags::ADAM7_INTERPOLATE + : SurfacePipeFlags(); + + if (mNumFrames == 0) { + // The first frame may be displayed progressively. + pipeFlags |= SurfacePipeFlags::PROGRESSIVE_DISPLAY; + } + + Maybe pipe = + SurfacePipeFactory::CreateSurfacePipe(this, mNumFrames, Size(), + OutputSize(), aFrameInfo.mFrameRect, + format, pipeFlags); + + if (!pipe) { + mPipe = SurfacePipe(); + return NS_ERROR_FAILURE; + } + + mPipe = Move(*pipe); + + mFrameRect = aFrameInfo.mFrameRect; + mPass = 0; + + MOZ_LOG(sPNGDecoderAccountingLog, LogLevel::Debug, + ("PNGDecoderAccounting: nsPNGDecoder::CreateFrame -- created " + "image frame with %dx%d pixels for decoder %p", + mFrameRect.width, mFrameRect.height, this)); + +#ifdef PNG_APNG_SUPPORTED + if (png_get_valid(mPNG, mInfo, PNG_INFO_acTL)) { + mAnimInfo = AnimFrameInfo(mPNG, mInfo); + + if (mAnimInfo.mDispose == DisposalMethod::CLEAR) { + // We may have to display the background under this image during + // animation playback, so we regard it as transparent. + PostHasTransparency(); + } + } +#endif + + return NS_OK; +} + +// set timeout and frame disposal method for the current frame +void +nsPNGDecoder::EndImageFrame() +{ + if (mFrameIsHidden) { + return; + } + + mNumFrames++; + + Opacity opacity = Opacity::SOME_TRANSPARENCY; + if (format == gfx::SurfaceFormat::B8G8R8X8) { + opacity = Opacity::FULLY_OPAQUE; + } + + PostFrameStop(opacity, mAnimInfo.mDispose, + FrameTimeout::FromRawMilliseconds(mAnimInfo.mTimeout), + mAnimInfo.mBlend, Some(mFrameRect)); +} + +nsresult +nsPNGDecoder::InitInternal() +{ + mCMSMode = gfxPlatform::GetCMSMode(); + if (GetSurfaceFlags() & SurfaceFlags::NO_COLORSPACE_CONVERSION) { + mCMSMode = eCMSMode_Off; + } + mDisablePremultipliedAlpha = + bool(GetSurfaceFlags() & SurfaceFlags::NO_PREMULTIPLY_ALPHA); + +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED + static png_byte color_chunks[]= + { 99, 72, 82, 77, '\0', // cHRM + 105, 67, 67, 80, '\0'}; // iCCP + static png_byte unused_chunks[]= + { 98, 75, 71, 68, '\0', // bKGD + 104, 73, 83, 84, '\0', // hIST + 105, 84, 88, 116, '\0', // iTXt + 111, 70, 70, 115, '\0', // oFFs + 112, 67, 65, 76, '\0', // pCAL + 115, 67, 65, 76, '\0', // sCAL + 112, 72, 89, 115, '\0', // pHYs + 115, 66, 73, 84, '\0', // sBIT + 115, 80, 76, 84, '\0', // sPLT + 116, 69, 88, 116, '\0', // tEXt + 116, 73, 77, 69, '\0', // tIME + 122, 84, 88, 116, '\0'}; // zTXt +#endif + + // Initialize the container's source image header + // Always decode to 24 bit pixdepth + + mPNG = png_create_read_struct(PNG_LIBPNG_VER_STRING, + nullptr, nsPNGDecoder::error_callback, + nsPNGDecoder::warning_callback); + if (!mPNG) { + return NS_ERROR_OUT_OF_MEMORY; + } + + mInfo = png_create_info_struct(mPNG); + if (!mInfo) { + png_destroy_read_struct(&mPNG, nullptr, nullptr); + return NS_ERROR_OUT_OF_MEMORY; + } + +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED + // Ignore unused chunks + if (mCMSMode == eCMSMode_Off || IsMetadataDecode()) { + png_set_keep_unknown_chunks(mPNG, 1, color_chunks, 2); + } + + png_set_keep_unknown_chunks(mPNG, 1, unused_chunks, + (int)sizeof(unused_chunks)/5); +#endif + +#ifdef PNG_SET_USER_LIMITS_SUPPORTED + png_set_user_limits(mPNG, MOZ_PNG_MAX_WIDTH, MOZ_PNG_MAX_HEIGHT); + if (mCMSMode != eCMSMode_Off) { + png_set_chunk_malloc_max(mPNG, 4000000L); + } +#endif + +#ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED + // Disallow palette-index checking, for speed; we would ignore the warning + // anyhow. This feature was added at libpng version 1.5.10 and is disabled + // in the embedded libpng but enabled by default in the system libpng. This + // call also disables it in the system libpng, for decoding speed. + // Bug #745202. + png_set_check_for_invalid_index(mPNG, 0); +#endif + +#ifdef PNG_SET_OPTION_SUPPORTED +#if defined(PNG_sRGB_PROFILE_CHECKS) && PNG_sRGB_PROFILE_CHECKS >= 0 + // Skip checking of sRGB ICC profiles + png_set_option(mPNG, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON); +#endif + +#ifdef PNG_MAXIMUM_INFLATE_WINDOW + // Force a larger zlib inflate window as some images in the wild have + // incorrectly set metadata (specifically CMF bits) which prevent us from + // decoding them otherwise. + png_set_option(mPNG, PNG_MAXIMUM_INFLATE_WINDOW, PNG_OPTION_ON); +#endif +#endif + + // use this as libpng "progressive pointer" (retrieve in callbacks) + png_set_progressive_read_fn(mPNG, static_cast(this), + nsPNGDecoder::info_callback, + nsPNGDecoder::row_callback, + nsPNGDecoder::end_callback); + + return NS_OK; +} + +LexerResult +nsPNGDecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume) +{ + MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!"); + + return mLexer.Lex(aIterator, aOnResume, + [=](State aState, const char* aData, size_t aLength) { + switch (aState) { + case State::PNG_DATA: + return ReadPNGData(aData, aLength); + case State::FINISHED_PNG_DATA: + return FinishedPNGData(); + } + MOZ_CRASH("Unknown State"); + }); +} + +LexerTransition +nsPNGDecoder::ReadPNGData(const char* aData, size_t aLength) +{ + // If we were waiting until after returning from a yield to call + // CreateFrame(), call it now. + if (mNextFrameInfo) { + if (NS_FAILED(CreateFrame(*mNextFrameInfo))) { + return Transition::TerminateFailure(); + } + + MOZ_ASSERT(mImageData, "Should have a buffer now"); + mNextFrameInfo = Nothing(); + } + + // libpng uses setjmp/longjmp for error handling. + if (setjmp(png_jmpbuf(mPNG))) { + return Transition::TerminateFailure(); + } + + // Pass the data off to libpng. + mLastChunkLength = aLength; + mNextTransition = Transition::ContinueUnbuffered(State::PNG_DATA); + png_process_data(mPNG, mInfo, + reinterpret_cast(const_cast((aData))), + aLength); + + // Make sure that we've reached a terminal state if decoding is done. + MOZ_ASSERT_IF(GetDecodeDone(), mNextTransition.NextStateIsTerminal()); + MOZ_ASSERT_IF(HasError(), mNextTransition.NextStateIsTerminal()); + + // Continue with whatever transition the callback code requested. We + // initialized this to Transition::ContinueUnbuffered(State::PNG_DATA) above, + // so by default we just continue the unbuffered read. + return mNextTransition; +} + +LexerTransition +nsPNGDecoder::FinishedPNGData() +{ + // Since we set up an unbuffered read for SIZE_MAX bytes, if we actually read + // all that data something is really wrong. + MOZ_ASSERT_UNREACHABLE("Read the entire address space?"); + return Transition::TerminateFailure(); +} + +// Sets up gamma pre-correction in libpng before our callback gets called. +// We need to do this if we don't end up with a CMS profile. +static void +PNGDoGammaCorrection(png_structp png_ptr, png_infop info_ptr) +{ + double aGamma; + + if (png_get_gAMA(png_ptr, info_ptr, &aGamma)) { + if ((aGamma <= 0.0) || (aGamma > 21474.83)) { + aGamma = 0.45455; + png_set_gAMA(png_ptr, info_ptr, aGamma); + } + png_set_gamma(png_ptr, 2.2, aGamma); + } else { + png_set_gamma(png_ptr, 2.2, 0.45455); + } +} + +// Adapted from http://www.littlecms.com/pngchrm.c example code +static qcms_profile* +PNGGetColorProfile(png_structp png_ptr, png_infop info_ptr, + int color_type, qcms_data_type* inType, uint32_t* intent) +{ + qcms_profile* profile = nullptr; + *intent = QCMS_INTENT_PERCEPTUAL; // Our default + + // First try to see if iCCP chunk is present + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP)) { + png_uint_32 profileLen; + png_bytep profileData; + png_charp profileName; + int compression; + + png_get_iCCP(png_ptr, info_ptr, &profileName, &compression, + &profileData, &profileLen); + + profile = qcms_profile_from_memory((char*)profileData, profileLen); + if (profile) { + uint32_t profileSpace = qcms_profile_get_color_space(profile); + + bool mismatch = false; + if (color_type & PNG_COLOR_MASK_COLOR) { + if (profileSpace != icSigRgbData) { + mismatch = true; + } + } else { + if (profileSpace == icSigRgbData) { + png_set_gray_to_rgb(png_ptr); + } else if (profileSpace != icSigGrayData) { + mismatch = true; + } + } + + if (mismatch) { + qcms_profile_release(profile); + profile = nullptr; + } else { + *intent = qcms_profile_get_rendering_intent(profile); + } + } + } + + // Check sRGB chunk + if (!profile && png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) { + profile = qcms_profile_sRGB(); + + if (profile) { + int fileIntent; + png_set_gray_to_rgb(png_ptr); + png_get_sRGB(png_ptr, info_ptr, &fileIntent); + uint32_t map[] = { QCMS_INTENT_PERCEPTUAL, + QCMS_INTENT_RELATIVE_COLORIMETRIC, + QCMS_INTENT_SATURATION, + QCMS_INTENT_ABSOLUTE_COLORIMETRIC }; + *intent = map[fileIntent]; + } + } + + // Check gAMA/cHRM chunks + if (!profile && + png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA) && + png_get_valid(png_ptr, info_ptr, PNG_INFO_cHRM)) { + qcms_CIE_xyYTRIPLE primaries; + qcms_CIE_xyY whitePoint; + + png_get_cHRM(png_ptr, info_ptr, + &whitePoint.x, &whitePoint.y, + &primaries.red.x, &primaries.red.y, + &primaries.green.x, &primaries.green.y, + &primaries.blue.x, &primaries.blue.y); + whitePoint.Y = + primaries.red.Y = primaries.green.Y = primaries.blue.Y = 1.0; + + double gammaOfFile; + + png_get_gAMA(png_ptr, info_ptr, &gammaOfFile); + + profile = qcms_profile_create_rgb_with_gamma(whitePoint, primaries, + 1.0/gammaOfFile); + + if (profile) { + png_set_gray_to_rgb(png_ptr); + } + } + + if (profile) { + uint32_t profileSpace = qcms_profile_get_color_space(profile); + if (profileSpace == icSigGrayData) { + if (color_type & PNG_COLOR_MASK_ALPHA) { + *inType = QCMS_DATA_GRAYA_8; + } else { + *inType = QCMS_DATA_GRAY_8; + } + } else { + if (color_type & PNG_COLOR_MASK_ALPHA || + png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { + *inType = QCMS_DATA_RGBA_8; + } else { + *inType = QCMS_DATA_RGB_8; + } + } + } + + return profile; +} + +void +nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr) +{ + png_uint_32 width, height; + int bit_depth, color_type, interlace_type, compression_type, filter_type; + unsigned int channels; + + png_bytep trans = nullptr; + int num_trans = 0; + + nsPNGDecoder* decoder = + static_cast(png_get_progressive_ptr(png_ptr)); + + // Always decode to 24-bit RGB or 32-bit RGBA + png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, + &interlace_type, &compression_type, &filter_type); + + const IntRect frameRect(0, 0, width, height); + + // Post our size to the superclass + decoder->PostSize(frameRect.width, frameRect.height); + if (decoder->HasError()) { + // Setting the size led to an error. + png_error(decoder->mPNG, "Sizing error"); + } + + if (color_type == PNG_COLOR_TYPE_PALETTE) { + png_set_expand(png_ptr); + } + + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) { + png_set_expand(png_ptr); + } + + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { + png_color_16p trans_values; + png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values); + // libpng doesn't reject a tRNS chunk with out-of-range samples + // so we check it here to avoid setting up a useless opacity + // channel or producing unexpected transparent pixels (bug #428045) + if (bit_depth < 16) { + png_uint_16 sample_max = (1 << bit_depth) - 1; + if ((color_type == PNG_COLOR_TYPE_GRAY && + trans_values->gray > sample_max) || + (color_type == PNG_COLOR_TYPE_RGB && + (trans_values->red > sample_max || + trans_values->green > sample_max || + trans_values->blue > sample_max))) { + // clear the tRNS valid flag and release tRNS memory + png_free_data(png_ptr, info_ptr, PNG_FREE_TRNS, 0); + num_trans = 0; + } + } + if (num_trans != 0) { + png_set_expand(png_ptr); + } + } + + if (bit_depth == 16) { + png_set_scale_16(png_ptr); + } + + qcms_data_type inType = QCMS_DATA_RGBA_8; + uint32_t intent = -1; + uint32_t pIntent; + if (decoder->mCMSMode != eCMSMode_Off) { + intent = gfxPlatform::GetRenderingIntent(); + decoder->mInProfile = PNGGetColorProfile(png_ptr, info_ptr, + color_type, &inType, &pIntent); + // If we're not mandating an intent, use the one from the image. + if (intent == uint32_t(-1)) { + intent = pIntent; + } + } + if (decoder->mInProfile && gfxPlatform::GetCMSOutputProfile()) { + qcms_data_type outType; + + if (color_type & PNG_COLOR_MASK_ALPHA || num_trans) { + outType = QCMS_DATA_RGBA_8; + } else { + outType = QCMS_DATA_RGB_8; + } + + decoder->mTransform = qcms_transform_create(decoder->mInProfile, + inType, + gfxPlatform::GetCMSOutputProfile(), + outType, + (qcms_intent)intent); + } else { + png_set_gray_to_rgb(png_ptr); + + // only do gamma correction if CMS isn't entirely disabled + if (decoder->mCMSMode != eCMSMode_Off) { + PNGDoGammaCorrection(png_ptr, info_ptr); + } + + if (decoder->mCMSMode == eCMSMode_All) { + if (color_type & PNG_COLOR_MASK_ALPHA || num_trans) { + decoder->mTransform = gfxPlatform::GetCMSRGBATransform(); + } else { + decoder->mTransform = gfxPlatform::GetCMSRGBTransform(); + } + } + } + + // Let libpng expand interlaced images. + const bool isInterlaced = interlace_type == PNG_INTERLACE_ADAM7; + if (isInterlaced) { + png_set_interlace_handling(png_ptr); + } + + // now all of those things we set above are used to update various struct + // members and whatnot, after which we can get channels, rowbytes, etc. + png_read_update_info(png_ptr, info_ptr); + decoder->mChannels = channels = png_get_channels(png_ptr, info_ptr); + + //---------------------------------------------------------------// + // copy PNG info into imagelib structs (formerly png_set_dims()) // + //---------------------------------------------------------------// + + if (channels == 1 || channels == 3) { + decoder->format = gfx::SurfaceFormat::B8G8R8X8; + } else if (channels == 2 || channels == 4) { + decoder->format = gfx::SurfaceFormat::B8G8R8A8; + } else { + png_error(decoder->mPNG, "Invalid number of channels"); + } + +#ifdef PNG_APNG_SUPPORTED + bool isAnimated = png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL); + if (isAnimated) { + int32_t rawTimeout = GetNextFrameDelay(png_ptr, info_ptr); + decoder->PostIsAnimated(FrameTimeout::FromRawMilliseconds(rawTimeout)); + + if (decoder->Size() != decoder->OutputSize() && + !decoder->IsFirstFrameDecode()) { + MOZ_ASSERT_UNREACHABLE("Doing downscale-during-decode " + "for an animated image?"); + png_error(decoder->mPNG, "Invalid downscale attempt"); // Abort decode. + } + } +#endif + + if (decoder->IsMetadataDecode()) { + // If we are animated then the first frame rect is either: + // 1) the whole image if the IDAT chunk is part of the animation + // 2) the frame rect of the first fDAT chunk otherwise. + // If we are not animated then we want to make sure to call + // PostHasTransparency in the metadata decode if we need to. So it's + // okay to pass IntRect(0, 0, width, height) here for animated images; + // they will call with the proper first frame rect in the full decode. + auto transparency = decoder->GetTransparencyType(decoder->format, + frameRect); + decoder->PostHasTransparencyIfNeeded(transparency); + + // We have the metadata we're looking for, so stop here, before we allocate + // buffers below. + return decoder->DoTerminate(png_ptr, TerminalState::SUCCESS); + } + +#ifdef PNG_APNG_SUPPORTED + if (isAnimated) { + png_set_progressive_frame_fn(png_ptr, nsPNGDecoder::frame_info_callback, + nullptr); + } + + if (png_get_first_frame_is_hidden(png_ptr, info_ptr)) { + decoder->mFrameIsHidden = true; + } else { +#endif + nsresult rv = decoder->CreateFrame(FrameInfo{ decoder->format, + frameRect, + isInterlaced }); + if (NS_FAILED(rv)) { + png_error(decoder->mPNG, "CreateFrame failed"); + } + MOZ_ASSERT(decoder->mImageData, "Should have a buffer now"); +#ifdef PNG_APNG_SUPPORTED + } +#endif + + if (decoder->mTransform && (channels <= 2 || isInterlaced)) { + uint32_t bpp[] = { 0, 3, 4, 3, 4 }; + decoder->mCMSLine = + static_cast(malloc(bpp[channels] * frameRect.width)); + if (!decoder->mCMSLine) { + png_error(decoder->mPNG, "malloc of mCMSLine failed"); + } + } + + if (interlace_type == PNG_INTERLACE_ADAM7) { + if (frameRect.height < INT32_MAX / (frameRect.width * int32_t(channels))) { + const size_t bufferSize = channels * frameRect.width * frameRect.height; + decoder->interlacebuf = static_cast(malloc(bufferSize)); + } + if (!decoder->interlacebuf) { + png_error(decoder->mPNG, "malloc of interlacebuf failed"); + } + } +} + +void +nsPNGDecoder::PostInvalidationIfNeeded() +{ + Maybe invalidRect = mPipe.TakeInvalidRect(); + if (!invalidRect) { + return; + } + + PostInvalidation(invalidRect->mInputSpaceRect, + Some(invalidRect->mOutputSpaceRect)); +} + +static NextPixel +PackRGBPixelAndAdvance(uint8_t*& aRawPixelInOut) +{ + const uint32_t pixel = + gfxPackedPixel(0xFF, aRawPixelInOut[0], aRawPixelInOut[1], + aRawPixelInOut[2]); + aRawPixelInOut += 3; + return AsVariant(pixel); +} + +static NextPixel +PackRGBAPixelAndAdvance(uint8_t*& aRawPixelInOut) +{ + const uint32_t pixel = + gfxPackedPixel(aRawPixelInOut[3], aRawPixelInOut[0], + aRawPixelInOut[1], aRawPixelInOut[2]); + aRawPixelInOut += 4; + return AsVariant(pixel); +} + +static NextPixel +PackUnpremultipliedRGBAPixelAndAdvance(uint8_t*& aRawPixelInOut) +{ + const uint32_t pixel = + gfxPackedPixelNoPreMultiply(aRawPixelInOut[3], aRawPixelInOut[0], + aRawPixelInOut[1], aRawPixelInOut[2]); + aRawPixelInOut += 4; + return AsVariant(pixel); +} + +void +nsPNGDecoder::row_callback(png_structp png_ptr, png_bytep new_row, + png_uint_32 row_num, int pass) +{ + /* libpng comments: + * + * This function is called for every row in the image. If the + * image is interlacing, and you turned on the interlace handler, + * this function will be called for every row in every pass. + * Some of these rows will not be changed from the previous pass. + * When the row is not changed, the new_row variable will be + * nullptr. The rows and passes are called in order, so you don't + * really need the row_num and pass, but I'm supplying them + * because it may make your life easier. + * + * For the non-nullptr rows of interlaced images, you must call + * png_progressive_combine_row() passing in the row and the + * old row. You can call this function for nullptr rows (it will + * just return) and for non-interlaced images (it just does the + * memcpy for you) if it will make the code easier. Thus, you + * can just do this for all cases: + * + * png_progressive_combine_row(png_ptr, old_row, new_row); + * + * where old_row is what was displayed for previous rows. Note + * that the first pass (pass == 0 really) will completely cover + * the old row, so the rows do not have to be initialized. After + * the first pass (and only for interlaced images), you will have + * to pass the current row, and the function will combine the + * old row and the new row. + */ + nsPNGDecoder* decoder = + static_cast(png_get_progressive_ptr(png_ptr)); + + if (decoder->mFrameIsHidden) { + return; // Skip this frame. + } + + MOZ_ASSERT_IF(decoder->IsFirstFrameDecode(), decoder->mNumFrames == 0); + + while (pass > decoder->mPass) { + // Advance to the next pass. We may have to do this multiple times because + // libpng will skip passes if the image is so small that no pixels have + // changed on a given pass, but ADAM7InterpolatingFilter needs to be reset + // once for every pass to perform interpolation properly. + decoder->mPipe.ResetToFirstRow(); + decoder->mPass++; + } + + const png_uint_32 height = + static_cast(decoder->mFrameRect.height); + + if (row_num >= height) { + // Bail if we receive extra rows. This is especially important because if we + // didn't, we might overflow the deinterlacing buffer. + MOZ_ASSERT_UNREACHABLE("libpng producing extra rows?"); + return; + } + + // Note that |new_row| may be null here, indicating that this is an interlaced + // image and |row_callback| is being called for a row that hasn't changed. + MOZ_ASSERT_IF(!new_row, decoder->interlacebuf); + uint8_t* rowToWrite = new_row; + + if (decoder->interlacebuf) { + uint32_t width = uint32_t(decoder->mFrameRect.width); + + // We'll output the deinterlaced version of the row. + rowToWrite = decoder->interlacebuf + (row_num * decoder->mChannels * width); + + // Update the deinterlaced version of this row with the new data. + png_progressive_combine_row(png_ptr, rowToWrite, new_row); + } + + decoder->WriteRow(rowToWrite); +} + +void +nsPNGDecoder::WriteRow(uint8_t* aRow) +{ + MOZ_ASSERT(aRow); + + uint8_t* rowToWrite = aRow; + uint32_t width = uint32_t(mFrameRect.width); + + // Apply color management to the row, if necessary, before writing it out. + if (mTransform) { + if (mCMSLine) { + qcms_transform_data(mTransform, rowToWrite, mCMSLine, width); + + // Copy alpha over. + if (mChannels == 2 || mChannels == 4) { + for (uint32_t i = 0; i < width; ++i) { + mCMSLine[4 * i + 3] = rowToWrite[mChannels * i + mChannels - 1]; + } + } + + rowToWrite = mCMSLine; + } else { + qcms_transform_data(mTransform, rowToWrite, rowToWrite, width); + } + } + + // Write this row to the SurfacePipe. + DebugOnly result = WriteState::FAILURE; + switch (format) { + case SurfaceFormat::B8G8R8X8: + result = mPipe.WritePixelsToRow([&]{ + return PackRGBPixelAndAdvance(rowToWrite); + }); + break; + + case SurfaceFormat::B8G8R8A8: + if (mDisablePremultipliedAlpha) { + result = mPipe.WritePixelsToRow([&]{ + return PackUnpremultipliedRGBAPixelAndAdvance(rowToWrite); + }); + } else { + result = mPipe.WritePixelsToRow([&]{ + return PackRGBAPixelAndAdvance(rowToWrite); + }); + } + break; + + default: + png_error(mPNG, "Invalid SurfaceFormat"); + } + + MOZ_ASSERT(WriteState(result) != WriteState::FAILURE); + + PostInvalidationIfNeeded(); +} + +void +nsPNGDecoder::DoTerminate(png_structp aPNGStruct, TerminalState aState) +{ + // Stop processing data. Note that we intentionally ignore the return value of + // png_process_data_pause(), which tells us how many bytes of the data that + // was passed to png_process_data() have not been consumed yet, because now + // that we've reached a terminal state, we won't do any more decoding or call + // back into libpng anymore. + png_process_data_pause(aPNGStruct, /* save = */ false); + + mNextTransition = aState == TerminalState::SUCCESS + ? Transition::TerminateSuccess() + : Transition::TerminateFailure(); +} + +void +nsPNGDecoder::DoYield(png_structp aPNGStruct) +{ + // Pause data processing. png_process_data_pause() returns how many bytes of + // the data that was passed to png_process_data() have not been consumed yet. + // We use this information to tell StreamingLexer where to place us in the + // input stream when we come back from the yield. + png_size_t pendingBytes = png_process_data_pause(aPNGStruct, + /* save = */ false); + + MOZ_ASSERT(pendingBytes < mLastChunkLength); + size_t consumedBytes = mLastChunkLength - min(pendingBytes, mLastChunkLength); + + mNextTransition = + Transition::ContinueUnbufferedAfterYield(State::PNG_DATA, consumedBytes); +} + +#ifdef PNG_APNG_SUPPORTED +// got the header of a new frame that's coming +void +nsPNGDecoder::frame_info_callback(png_structp png_ptr, png_uint_32 frame_num) +{ + nsPNGDecoder* decoder = + static_cast(png_get_progressive_ptr(png_ptr)); + + // old frame is done + decoder->EndImageFrame(); + + const bool previousFrameWasHidden = decoder->mFrameIsHidden; + + if (!previousFrameWasHidden && decoder->IsFirstFrameDecode()) { + // We're about to get a second non-hidden frame, but we only want the first. + // Stop decoding now. (And avoid allocating the unnecessary buffers below.) + decoder->PostDecodeDone(); + return decoder->DoTerminate(png_ptr, TerminalState::SUCCESS); + } + + // Only the first frame can be hidden, so unhide unconditionally here. + decoder->mFrameIsHidden = false; + + // Save the information necessary to create the frame; we'll actually create + // it when we return from the yield. + const IntRect frameRect(png_get_next_frame_x_offset(png_ptr, decoder->mInfo), + png_get_next_frame_y_offset(png_ptr, decoder->mInfo), + png_get_next_frame_width(png_ptr, decoder->mInfo), + png_get_next_frame_height(png_ptr, decoder->mInfo)); + const bool isInterlaced = bool(decoder->interlacebuf); + +#ifndef MOZ_EMBEDDED_LIBPNG + // if using system library, check frame_width and height against 0 + if (frameRect.width == 0) { + png_error(png_ptr, "Frame width must not be 0"); + } + if (frameRect.height == 0) { + png_error(png_ptr, "Frame height must not be 0"); + } +#endif + + const FrameInfo info { decoder->format, frameRect, isInterlaced }; + + // If the previous frame was hidden, skip the yield (which will mislead the + // caller, who will think the previous frame was real) and just allocate the + // new frame here. + if (previousFrameWasHidden) { + if (NS_FAILED(decoder->CreateFrame(info))) { + return decoder->DoTerminate(png_ptr, TerminalState::FAILURE); + } + + MOZ_ASSERT(decoder->mImageData, "Should have a buffer now"); + return; // No yield, so we'll just keep decoding. + } + + // Yield to the caller to notify them that the previous frame is now complete. + decoder->mNextFrameInfo = Some(info); + return decoder->DoYield(png_ptr); +} +#endif + +void +nsPNGDecoder::end_callback(png_structp png_ptr, png_infop info_ptr) +{ + /* libpng comments: + * + * this function is called when the whole image has been read, + * including any chunks after the image (up to and including + * the IEND). You will usually have the same info chunk as you + * had in the header, although some data may have been added + * to the comments and time fields. + * + * Most people won't do much here, perhaps setting a flag that + * marks the image as finished. + */ + + nsPNGDecoder* decoder = + static_cast(png_get_progressive_ptr(png_ptr)); + + // We shouldn't get here if we've hit an error + MOZ_ASSERT(!decoder->HasError(), "Finishing up PNG but hit error!"); + + int32_t loop_count = 0; +#ifdef PNG_APNG_SUPPORTED + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL)) { + int32_t num_plays = png_get_num_plays(png_ptr, info_ptr); + loop_count = num_plays - 1; + } +#endif + + // Send final notifications. + decoder->EndImageFrame(); + decoder->PostDecodeDone(loop_count); + return decoder->DoTerminate(png_ptr, TerminalState::SUCCESS); +} + + +void +nsPNGDecoder::error_callback(png_structp png_ptr, png_const_charp error_msg) +{ + MOZ_LOG(sPNGLog, LogLevel::Error, ("libpng error: %s\n", error_msg)); + png_longjmp(png_ptr, 1); +} + + +void +nsPNGDecoder::warning_callback(png_structp png_ptr, png_const_charp warning_msg) +{ + MOZ_LOG(sPNGLog, LogLevel::Warning, ("libpng warning: %s\n", warning_msg)); +} + +Maybe +nsPNGDecoder::SpeedHistogram() const +{ + return Some(Telemetry::IMAGE_DECODE_SPEED_PNG); +} + +bool +nsPNGDecoder::IsValidICO() const +{ + // Only 32-bit RGBA PNGs are valid ICO resources; see here: + // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx + + // If there are errors in the call to png_get_IHDR, the error_callback in + // nsPNGDecoder.cpp is called. In this error callback we do a longjmp, so + // we need to save the jump buffer here. Otherwise we'll end up without a + // proper callstack. + if (setjmp(png_jmpbuf(mPNG))) { + // We got here from a longjmp call indirectly from png_get_IHDR + return false; + } + + png_uint_32 + png_width, // Unused + png_height; // Unused + + int png_bit_depth, + png_color_type; + + if (png_get_IHDR(mPNG, mInfo, &png_width, &png_height, &png_bit_depth, + &png_color_type, nullptr, nullptr, nullptr)) { + + return ((png_color_type == PNG_COLOR_TYPE_RGB_ALPHA || + png_color_type == PNG_COLOR_TYPE_RGB) && + png_bit_depth == 8); + } else { + return false; + } +} + +} // namespace image +} // namespace mozilla diff --git a/image/decoders/nsPNGDecoder.h b/image/decoders/nsPNGDecoder.h new file mode 100644 index 000000000..4b5c50ac5 --- /dev/null +++ b/image/decoders/nsPNGDecoder.h @@ -0,0 +1,158 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_decoders_nsPNGDecoder_h +#define mozilla_image_decoders_nsPNGDecoder_h + +#include "Decoder.h" +#include "png.h" +#include "qcms.h" +#include "StreamingLexer.h" +#include "SurfacePipe.h" + +namespace mozilla { +namespace image { +class RasterImage; + +class nsPNGDecoder : public Decoder +{ +public: + virtual ~nsPNGDecoder(); + + /// @return true if this PNG is a valid ICO resource. + bool IsValidICO() const; + +protected: + nsresult InitInternal() override; + LexerResult DoDecode(SourceBufferIterator& aIterator, + IResumable* aOnResume) override; + + Maybe SpeedHistogram() const override; + +private: + friend class DecoderFactory; + + // Decoders should only be instantiated via DecoderFactory. + explicit nsPNGDecoder(RasterImage* aImage); + + /// The information necessary to create a frame. + struct FrameInfo + { + gfx::SurfaceFormat mFormat; + gfx::IntRect mFrameRect; + bool mIsInterlaced; + }; + + nsresult CreateFrame(const FrameInfo& aFrameInfo); + void EndImageFrame(); + + enum class TransparencyType + { + eNone, + eAlpha, + eFrameRect + }; + + TransparencyType GetTransparencyType(gfx::SurfaceFormat aFormat, + const gfx::IntRect& aFrameRect); + void PostHasTransparencyIfNeeded(TransparencyType aTransparencyType); + + void PostInvalidationIfNeeded(); + + void WriteRow(uint8_t* aRow); + + // Convenience methods to make interacting with StreamingLexer from inside + // a libpng callback easier. + void DoTerminate(png_structp aPNGStruct, TerminalState aState); + void DoYield(png_structp aPNGStruct); + + enum class State + { + PNG_DATA, + FINISHED_PNG_DATA + }; + + LexerTransition ReadPNGData(const char* aData, size_t aLength); + LexerTransition FinishedPNGData(); + + StreamingLexer mLexer; + + // The next lexer state transition. We need to store it here because we can't + // directly return arbitrary values from libpng callbacks. + LexerTransition mNextTransition; + + // We yield to the caller every time we finish decoding a frame. When this + // happens, we need to allocate the next frame after returning from the yield. + // |mNextFrameInfo| is used to store the information needed to allocate the + // next frame. + Maybe mNextFrameInfo; + + // The length of the last chunk of data passed to ReadPNGData(). We use this + // to arrange to arrive back at the correct spot in the data after yielding. + size_t mLastChunkLength; + +public: + png_structp mPNG; + png_infop mInfo; + nsIntRect mFrameRect; + uint8_t* mCMSLine; + uint8_t* interlacebuf; + qcms_profile* mInProfile; + qcms_transform* mTransform; + + gfx::SurfaceFormat format; + + // whether CMS or premultiplied alpha are forced off + uint32_t mCMSMode; + + uint8_t mChannels; + uint8_t mPass; + bool mFrameIsHidden; + bool mDisablePremultipliedAlpha; + + struct AnimFrameInfo + { + AnimFrameInfo(); +#ifdef PNG_APNG_SUPPORTED + AnimFrameInfo(png_structp aPNG, png_infop aInfo); +#endif + + DisposalMethod mDispose; + BlendMethod mBlend; + int32_t mTimeout; + }; + + AnimFrameInfo mAnimInfo; + + SurfacePipe mPipe; /// The SurfacePipe used to write to the output surface. + + // The number of frames we've finished. + uint32_t mNumFrames; + + // libpng callbacks + // We put these in the class so that they can access protected members. + static void PNGAPI info_callback(png_structp png_ptr, png_infop info_ptr); + static void PNGAPI row_callback(png_structp png_ptr, png_bytep new_row, + png_uint_32 row_num, int pass); +#ifdef PNG_APNG_SUPPORTED + static void PNGAPI frame_info_callback(png_structp png_ptr, + png_uint_32 frame_num); +#endif + static void PNGAPI end_callback(png_structp png_ptr, png_infop info_ptr); + static void PNGAPI error_callback(png_structp png_ptr, + png_const_charp error_msg); + static void PNGAPI warning_callback(png_structp png_ptr, + png_const_charp warning_msg); + + // This is defined in the PNG spec as an invariant. We use it to + // do manual validation without libpng. + static const uint8_t pngSignatureBytes[]; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_decoders_nsPNGDecoder_h diff --git a/image/encoders/bmp/moz.build b/image/encoders/bmp/moz.build new file mode 100644 index 000000000..f061d067a --- /dev/null +++ b/image/encoders/bmp/moz.build @@ -0,0 +1,15 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SOURCES += [ + 'nsBMPEncoder.cpp', +] + +LOCAL_INCLUDES += [ + '/image', +] + +FINAL_LIBRARY = 'xul' diff --git a/image/encoders/bmp/nsBMPEncoder.cpp b/image/encoders/bmp/nsBMPEncoder.cpp new file mode 100644 index 000000000..af959a46e --- /dev/null +++ b/image/encoders/bmp/nsBMPEncoder.cpp @@ -0,0 +1,764 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCRT.h" +#include "mozilla/EndianUtils.h" +#include "mozilla/UniquePtrExtensions.h" +#include "nsBMPEncoder.h" +#include "prprf.h" +#include "nsString.h" +#include "nsStreamUtils.h" +#include "nsTArray.h" +#include "mozilla/CheckedInt.h" + +using namespace mozilla; +using namespace mozilla::image; +using namespace mozilla::image::bmp; + +NS_IMPL_ISUPPORTS(nsBMPEncoder, imgIEncoder, nsIInputStream, + nsIAsyncInputStream) + +nsBMPEncoder::nsBMPEncoder() : mImageBufferStart(nullptr), + mImageBufferCurr(0), + mImageBufferSize(0), + mImageBufferReadPoint(0), + mFinished(false), + mCallback(nullptr), + mCallbackTarget(nullptr), + mNotifyThreshold(0) +{ +} + +nsBMPEncoder::~nsBMPEncoder() +{ + if (mImageBufferStart) { + free(mImageBufferStart); + mImageBufferStart = nullptr; + mImageBufferCurr = nullptr; + } +} + +// nsBMPEncoder::InitFromData +// +// One output option is supported: bpp= +// bpp specifies the bits per pixel to use where bpp_value can be 24 or 32 +NS_IMETHODIMP +nsBMPEncoder::InitFromData(const uint8_t* aData, + uint32_t aLength, // (unused, req'd by JS) + uint32_t aWidth, + uint32_t aHeight, + uint32_t aStride, + uint32_t aInputFormat, + const nsAString& aOutputOptions) +{ + // validate input format + if (aInputFormat != INPUT_FORMAT_RGB && + aInputFormat != INPUT_FORMAT_RGBA && + aInputFormat != INPUT_FORMAT_HOSTARGB) { + return NS_ERROR_INVALID_ARG; + } + + CheckedInt32 check = CheckedInt32(aWidth) * 4; + if (MOZ_UNLIKELY(!check.isValid())) { + return NS_ERROR_INVALID_ARG; + } + + // Stride is the padded width of each row, so it better be longer + if ((aInputFormat == INPUT_FORMAT_RGB && + aStride < aWidth * 3) || + ((aInputFormat == INPUT_FORMAT_RGBA || + aInputFormat == INPUT_FORMAT_HOSTARGB) && + aStride < aWidth * 4)) { + NS_WARNING("Invalid stride for InitFromData"); + return NS_ERROR_INVALID_ARG; + } + + nsresult rv; + rv = StartImageEncode(aWidth, aHeight, aInputFormat, aOutputOptions); + if (NS_FAILED(rv)) { + return rv; + } + + rv = AddImageFrame(aData, aLength, aWidth, aHeight, aStride, + aInputFormat, aOutputOptions); + if (NS_FAILED(rv)) { + return rv; + } + + rv = EndImageEncode(); + return rv; +} + +// Just a helper method to make it explicit in calculations that we are dealing +// with bytes and not bits +static inline uint16_t +BytesPerPixel(uint16_t aBPP) +{ + return aBPP / 8; +} + +// Calculates the number of padding bytes that are needed per row of image data +static inline uint32_t +PaddingBytes(uint16_t aBPP, uint32_t aWidth) +{ + uint32_t rowSize = aWidth * BytesPerPixel(aBPP); + uint8_t paddingSize = 0; + if (rowSize % 4) { + paddingSize = (4 - (rowSize % 4)); + } + return paddingSize; +} + +// See ::InitFromData for other info. +NS_IMETHODIMP +nsBMPEncoder::StartImageEncode(uint32_t aWidth, + uint32_t aHeight, + uint32_t aInputFormat, + const nsAString& aOutputOptions) +{ + // can't initialize more than once + if (mImageBufferStart || mImageBufferCurr) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + // validate input format + if (aInputFormat != INPUT_FORMAT_RGB && + aInputFormat != INPUT_FORMAT_RGBA && + aInputFormat != INPUT_FORMAT_HOSTARGB) { + return NS_ERROR_INVALID_ARG; + } + + // parse and check any provided output options + Version version; + uint16_t bpp; + nsresult rv = ParseOptions(aOutputOptions, version, bpp); + if (NS_FAILED(rv)) { + return rv; + } + MOZ_ASSERT(bpp <= 32); + + rv = InitFileHeader(version, bpp, aWidth, aHeight); + if (NS_FAILED(rv)) { + return rv; + } + rv = InitInfoHeader(version, bpp, aWidth, aHeight); + if (NS_FAILED(rv)) { + return rv; + } + + mImageBufferSize = mBMPFileHeader.filesize; + mImageBufferStart = static_cast(malloc(mImageBufferSize)); + if (!mImageBufferStart) { + return NS_ERROR_OUT_OF_MEMORY; + } + mImageBufferCurr = mImageBufferStart; + + EncodeFileHeader(); + EncodeInfoHeader(); + + return NS_OK; +} + +// Returns the number of bytes in the image buffer used. +// For a BMP file, this is all bytes in the buffer. +NS_IMETHODIMP +nsBMPEncoder::GetImageBufferUsed(uint32_t* aOutputSize) +{ + NS_ENSURE_ARG_POINTER(aOutputSize); + *aOutputSize = mImageBufferSize; + return NS_OK; +} + +// Returns a pointer to the start of the image buffer +NS_IMETHODIMP +nsBMPEncoder::GetImageBuffer(char** aOutputBuffer) +{ + NS_ENSURE_ARG_POINTER(aOutputBuffer); + *aOutputBuffer = reinterpret_cast(mImageBufferStart); + return NS_OK; +} + +NS_IMETHODIMP +nsBMPEncoder::AddImageFrame(const uint8_t* aData, + uint32_t aLength, // (unused, req'd by JS) + uint32_t aWidth, + uint32_t aHeight, + uint32_t aStride, + uint32_t aInputFormat, + const nsAString& aFrameOptions) +{ + // must be initialized + if (!mImageBufferStart || !mImageBufferCurr) { + return NS_ERROR_NOT_INITIALIZED; + } + + // validate input format + if (aInputFormat != INPUT_FORMAT_RGB && + aInputFormat != INPUT_FORMAT_RGBA && + aInputFormat != INPUT_FORMAT_HOSTARGB) { + return NS_ERROR_INVALID_ARG; + } + + if (mBMPInfoHeader.width < 0) { + return NS_ERROR_ILLEGAL_VALUE; + } + + CheckedUint32 size = + CheckedUint32(mBMPInfoHeader.width) * CheckedUint32(BytesPerPixel(mBMPInfoHeader.bpp)); + if (MOZ_UNLIKELY(!size.isValid())) { + return NS_ERROR_FAILURE; + } + + auto row = MakeUniqueFallible(size.value()); + if (!row) { + return NS_ERROR_OUT_OF_MEMORY; + } + + CheckedUint32 check = CheckedUint32(mBMPInfoHeader.height) * aStride; + if (MOZ_UNLIKELY(!check.isValid())) { + return NS_ERROR_FAILURE; + } + + // write each row: if we add more input formats, we may want to + // generalize the conversions + if (aInputFormat == INPUT_FORMAT_HOSTARGB) { + // BMP requires RGBA with post-multiplied alpha, so we need to convert + for (int32_t y = mBMPInfoHeader.height - 1; y >= 0 ; y --) { + ConvertHostARGBRow(&aData[y * aStride], row, mBMPInfoHeader.width); + if(mBMPInfoHeader.bpp == 24) { + EncodeImageDataRow24(row.get()); + } else { + EncodeImageDataRow32(row.get()); + } + } + } else if (aInputFormat == INPUT_FORMAT_RGBA) { + // simple RGBA, no conversion needed + for (int32_t y = 0; y < mBMPInfoHeader.height; y++) { + if (mBMPInfoHeader.bpp == 24) { + EncodeImageDataRow24(row.get()); + } else { + EncodeImageDataRow32(row.get()); + } + } + } else if (aInputFormat == INPUT_FORMAT_RGB) { + // simple RGB, no conversion needed + for (int32_t y = 0; y < mBMPInfoHeader.height; y++) { + if (mBMPInfoHeader.bpp == 24) { + EncodeImageDataRow24(&aData[y * aStride]); + } else { + EncodeImageDataRow32(&aData[y * aStride]); + } + } + } else { + NS_NOTREACHED("Bad format type"); + return NS_ERROR_INVALID_ARG; + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsBMPEncoder::EndImageEncode() +{ + // must be initialized + if (!mImageBufferStart || !mImageBufferCurr) { + return NS_ERROR_NOT_INITIALIZED; + } + + mFinished = true; + NotifyListener(); + + // if output callback can't get enough memory, it will free our buffer + if (!mImageBufferStart || !mImageBufferCurr) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + + +// Parses the encoder options and sets the bits per pixel to use +// See InitFromData for a description of the parse options +nsresult +nsBMPEncoder::ParseOptions(const nsAString& aOptions, Version& aVersionOut, + uint16_t& aBppOut) +{ + aVersionOut = VERSION_3; + aBppOut = 24; + + // Parse the input string into a set of name/value pairs. + // From a format like: name=value;bpp=;name=value + // to format: [0] = name=value, [1] = bpp=, [2] = name=value + nsTArray nameValuePairs; + if (!ParseString(NS_ConvertUTF16toUTF8(aOptions), ';', nameValuePairs)) { + return NS_ERROR_INVALID_ARG; + } + + // For each name/value pair in the set + for (uint32_t i = 0; i < nameValuePairs.Length(); ++i) { + + // Split the name value pair [0] = name, [1] = value + nsTArray nameValuePair; + if (!ParseString(nameValuePairs[i], '=', nameValuePair)) { + return NS_ERROR_INVALID_ARG; + } + if (nameValuePair.Length() != 2) { + return NS_ERROR_INVALID_ARG; + } + + // Parse the bpp portion of the string name=value;version=; + // name=value + if (nameValuePair[0].Equals("version", + nsCaseInsensitiveCStringComparator())) { + if (nameValuePair[1].EqualsLiteral("3")) { + aVersionOut = VERSION_3; + } else if (nameValuePair[1].EqualsLiteral("5")) { + aVersionOut = VERSION_5; + } else { + return NS_ERROR_INVALID_ARG; + } + } + + // Parse the bpp portion of the string name=value;bpp=;name=value + if (nameValuePair[0].Equals("bpp", nsCaseInsensitiveCStringComparator())) { + if (nameValuePair[1].EqualsLiteral("24")) { + aBppOut = 24; + } else if (nameValuePair[1].EqualsLiteral("32")) { + aBppOut = 32; + } else { + return NS_ERROR_INVALID_ARG; + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsBMPEncoder::Close() +{ + if (mImageBufferStart) { + free(mImageBufferStart); + mImageBufferStart = nullptr; + mImageBufferSize = 0; + mImageBufferReadPoint = 0; + mImageBufferCurr = nullptr; + } + + return NS_OK; +} + +// Obtains the available bytes to read +NS_IMETHODIMP +nsBMPEncoder::Available(uint64_t* _retval) +{ + if (!mImageBufferStart || !mImageBufferCurr) { + return NS_BASE_STREAM_CLOSED; + } + + *_retval = GetCurrentImageBufferOffset() - mImageBufferReadPoint; + return NS_OK; +} + +// [noscript] Reads bytes which are available +NS_IMETHODIMP +nsBMPEncoder::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) +{ + return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval); +} + +// [noscript] Reads segments +NS_IMETHODIMP +nsBMPEncoder::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* _retval) +{ + uint32_t maxCount = GetCurrentImageBufferOffset() - mImageBufferReadPoint; + if (maxCount == 0) { + *_retval = 0; + return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK; + } + + if (aCount > maxCount) { + aCount = maxCount; + } + nsresult rv = aWriter(this, aClosure, + reinterpret_cast(mImageBufferStart + + mImageBufferReadPoint), + 0, aCount, _retval); + if (NS_SUCCEEDED(rv)) { + NS_ASSERTION(*_retval <= aCount, "bad write count"); + mImageBufferReadPoint += *_retval; + } + // errors returned from the writer end here! + return NS_OK; +} + +NS_IMETHODIMP +nsBMPEncoder::IsNonBlocking(bool* _retval) +{ + *_retval = true; + return NS_OK; +} + +NS_IMETHODIMP +nsBMPEncoder::AsyncWait(nsIInputStreamCallback* aCallback, + uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aTarget) +{ + if (aFlags != 0) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + if (mCallback || mCallbackTarget) { + return NS_ERROR_UNEXPECTED; + } + + mCallbackTarget = aTarget; + // 0 means "any number of bytes except 0" + mNotifyThreshold = aRequestedCount; + if (!aRequestedCount) { + mNotifyThreshold = 1024; // We don't want to notify incessantly + } + + // We set the callback absolutely last, because NotifyListener uses it to + // determine if someone needs to be notified. If we don't set it last, + // NotifyListener might try to fire off a notification to a null target + // which will generally cause non-threadsafe objects to be used off the + // main thread + mCallback = aCallback; + + // What we are being asked for may be present already + NotifyListener(); + return NS_OK; +} + +NS_IMETHODIMP +nsBMPEncoder::CloseWithStatus(nsresult aStatus) +{ + return Close(); +} + +// nsBMPEncoder::ConvertHostARGBRow +// +// Our colors are stored with premultiplied alphas, but we need +// an output with no alpha in machine-independent byte order. +// +void +nsBMPEncoder::ConvertHostARGBRow(const uint8_t* aSrc, + const UniquePtr& aDest, + uint32_t aPixelWidth) +{ + uint16_t bytes = BytesPerPixel(mBMPInfoHeader.bpp); + + if (mBMPInfoHeader.bpp == 32) { + for (uint32_t x = 0; x < aPixelWidth; x++) { + const uint32_t& pixelIn = ((const uint32_t*)(aSrc))[x]; + uint8_t* pixelOut = &aDest[x * bytes]; + + pixelOut[0] = (pixelIn & 0x00ff0000) >> 16; + pixelOut[1] = (pixelIn & 0x0000ff00) >> 8; + pixelOut[2] = (pixelIn & 0x000000ff) >> 0; + pixelOut[3] = (pixelIn & 0xff000000) >> 24; + } + } else { + for (uint32_t x = 0; x < aPixelWidth; x++) { + const uint32_t& pixelIn = ((const uint32_t*)(aSrc))[x]; + uint8_t* pixelOut = &aDest[x * bytes]; + + pixelOut[0] = (pixelIn & 0xff0000) >> 16; + pixelOut[1] = (pixelIn & 0x00ff00) >> 8; + pixelOut[2] = (pixelIn & 0x0000ff) >> 0; + } + } +} + +void +nsBMPEncoder::NotifyListener() +{ + if (mCallback && + (GetCurrentImageBufferOffset() - mImageBufferReadPoint >= + mNotifyThreshold || mFinished)) { + nsCOMPtr callback; + if (mCallbackTarget) { + callback = NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget); + } else { + callback = mCallback; + } + + NS_ASSERTION(callback, "Shouldn't fail to make the callback"); + // Null the callback first because OnInputStreamReady could + // reenter AsyncWait + mCallback = nullptr; + mCallbackTarget = nullptr; + mNotifyThreshold = 0; + + callback->OnInputStreamReady(this); + } +} + +// Initializes the BMP file header mBMPFileHeader to the passed in values +nsresult +nsBMPEncoder::InitFileHeader(Version aVersion, uint16_t aBPP, uint32_t aWidth, + uint32_t aHeight) +{ + memset(&mBMPFileHeader, 0, sizeof(mBMPFileHeader)); + mBMPFileHeader.signature[0] = 'B'; + mBMPFileHeader.signature[1] = 'M'; + + if (aVersion == VERSION_3) { + mBMPFileHeader.dataoffset = FILE_HEADER_LENGTH + InfoHeaderLength::WIN_V3; + } else { // aVersion == 5 + mBMPFileHeader.dataoffset = FILE_HEADER_LENGTH + InfoHeaderLength::WIN_V5; + } + + // The color table is present only if BPP is <= 8 + if (aBPP <= 8) { + uint32_t numColors = 1 << aBPP; + mBMPFileHeader.dataoffset += 4 * numColors; + CheckedUint32 filesize = + CheckedUint32(mBMPFileHeader.dataoffset) + CheckedUint32(aWidth) * aHeight; + if (MOZ_UNLIKELY(!filesize.isValid())) { + return NS_ERROR_INVALID_ARG; + } + mBMPFileHeader.filesize = filesize.value(); + } else { + CheckedUint32 filesize = + CheckedUint32(mBMPFileHeader.dataoffset) + + (CheckedUint32(aWidth) * BytesPerPixel(aBPP) + PaddingBytes(aBPP, aWidth)) * aHeight; + if (MOZ_UNLIKELY(!filesize.isValid())) { + return NS_ERROR_INVALID_ARG; + } + mBMPFileHeader.filesize = filesize.value(); + } + + mBMPFileHeader.reserved = 0; + + return NS_OK; +} + +#define ENCODE(pImageBufferCurr, value) \ + memcpy(*pImageBufferCurr, &value, sizeof value); \ + *pImageBufferCurr += sizeof value; + +// Initializes the bitmap info header mBMPInfoHeader to the passed in values +nsresult +nsBMPEncoder::InitInfoHeader(Version aVersion, uint16_t aBPP, uint32_t aWidth, + uint32_t aHeight) +{ + memset(&mBMPInfoHeader, 0, sizeof(mBMPInfoHeader)); + if (aVersion == VERSION_3) { + mBMPInfoHeader.bihsize = InfoHeaderLength::WIN_V3; + } else { + MOZ_ASSERT(aVersion == VERSION_5); + mBMPInfoHeader.bihsize = InfoHeaderLength::WIN_V5; + } + + CheckedInt32 width(aWidth); + CheckedInt32 height(aHeight); + if (MOZ_UNLIKELY(!width.isValid() || !height.isValid())) { + return NS_ERROR_INVALID_ARG; + } + mBMPInfoHeader.width = width.value(); + mBMPInfoHeader.height = height.value(); + + mBMPInfoHeader.planes = 1; + mBMPInfoHeader.bpp = aBPP; + mBMPInfoHeader.compression = 0; + mBMPInfoHeader.colors = 0; + mBMPInfoHeader.important_colors = 0; + + CheckedUint32 check = CheckedUint32(aWidth) * BytesPerPixel(aBPP); + if (MOZ_UNLIKELY(!check.isValid())) { + return NS_ERROR_INVALID_ARG; + } + + if (aBPP <= 8) { + CheckedUint32 imagesize = CheckedUint32(aWidth) * aHeight; + if (MOZ_UNLIKELY(!imagesize.isValid())) { + return NS_ERROR_INVALID_ARG; + } + mBMPInfoHeader.image_size = imagesize.value(); + } else { + CheckedUint32 imagesize = + (CheckedUint32(aWidth) * BytesPerPixel(aBPP) + PaddingBytes(aBPP, aWidth)) * CheckedUint32(aHeight); + if (MOZ_UNLIKELY(!imagesize.isValid())) { + return NS_ERROR_INVALID_ARG; + } + mBMPInfoHeader.image_size = imagesize.value(); + } + mBMPInfoHeader.xppm = 0; + mBMPInfoHeader.yppm = 0; + if (aVersion >= VERSION_5) { + mBMPInfoHeader.red_mask = 0x000000FF; + mBMPInfoHeader.green_mask = 0x0000FF00; + mBMPInfoHeader.blue_mask = 0x00FF0000; + mBMPInfoHeader.alpha_mask = 0xFF000000; + mBMPInfoHeader.color_space = V5InfoHeader::COLOR_SPACE_LCS_SRGB; + mBMPInfoHeader.white_point.r.x = 0; + mBMPInfoHeader.white_point.r.y = 0; + mBMPInfoHeader.white_point.r.z = 0; + mBMPInfoHeader.white_point.g.x = 0; + mBMPInfoHeader.white_point.g.y = 0; + mBMPInfoHeader.white_point.g.z = 0; + mBMPInfoHeader.white_point.b.x = 0; + mBMPInfoHeader.white_point.b.y = 0; + mBMPInfoHeader.white_point.b.z = 0; + mBMPInfoHeader.gamma_red = 0; + mBMPInfoHeader.gamma_green = 0; + mBMPInfoHeader.gamma_blue = 0; + mBMPInfoHeader.intent = 0; + mBMPInfoHeader.profile_offset = 0; + mBMPInfoHeader.profile_size = 0; + mBMPInfoHeader.reserved = 0; + } + + return NS_OK; +} + +// Encodes the BMP file header mBMPFileHeader +void +nsBMPEncoder::EncodeFileHeader() +{ + FileHeader littleEndianBFH = mBMPFileHeader; + NativeEndian::swapToLittleEndianInPlace(&littleEndianBFH.filesize, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianBFH.reserved, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianBFH.dataoffset, 1); + + ENCODE(&mImageBufferCurr, littleEndianBFH.signature); + ENCODE(&mImageBufferCurr, littleEndianBFH.filesize); + ENCODE(&mImageBufferCurr, littleEndianBFH.reserved); + ENCODE(&mImageBufferCurr, littleEndianBFH.dataoffset); +} + +// Encodes the BMP infor header mBMPInfoHeader +void +nsBMPEncoder::EncodeInfoHeader() +{ + V5InfoHeader littleEndianmBIH = mBMPInfoHeader; + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.bihsize, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.width, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.height, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.planes, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.bpp, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.compression, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.image_size, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.xppm, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.yppm, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.colors, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.important_colors, + 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.red_mask, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.green_mask, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.blue_mask, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.alpha_mask, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.color_space, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.r.x, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.r.y, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.r.z, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.g.x, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.g.y, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.g.z, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.b.x, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.b.y, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.b.z, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.gamma_red, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.gamma_green, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.gamma_blue, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.intent, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.profile_offset, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.profile_size, 1); + + ENCODE(&mImageBufferCurr, littleEndianmBIH.bihsize); + ENCODE(&mImageBufferCurr, littleEndianmBIH.width); + ENCODE(&mImageBufferCurr, littleEndianmBIH.height); + ENCODE(&mImageBufferCurr, littleEndianmBIH.planes); + ENCODE(&mImageBufferCurr, littleEndianmBIH.bpp); + ENCODE(&mImageBufferCurr, littleEndianmBIH.compression); + ENCODE(&mImageBufferCurr, littleEndianmBIH.image_size); + ENCODE(&mImageBufferCurr, littleEndianmBIH.xppm); + ENCODE(&mImageBufferCurr, littleEndianmBIH.yppm); + ENCODE(&mImageBufferCurr, littleEndianmBIH.colors); + ENCODE(&mImageBufferCurr, littleEndianmBIH.important_colors); + + if (mBMPInfoHeader.bihsize > InfoHeaderLength::WIN_V3) { + ENCODE(&mImageBufferCurr, littleEndianmBIH.red_mask); + ENCODE(&mImageBufferCurr, littleEndianmBIH.green_mask); + ENCODE(&mImageBufferCurr, littleEndianmBIH.blue_mask); + ENCODE(&mImageBufferCurr, littleEndianmBIH.alpha_mask); + ENCODE(&mImageBufferCurr, littleEndianmBIH.color_space); + ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.r.x); + ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.r.y); + ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.r.z); + ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.g.x); + ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.g.y); + ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.g.z); + ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.b.x); + ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.b.y); + ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.b.z); + ENCODE(&mImageBufferCurr, littleEndianmBIH.gamma_red); + ENCODE(&mImageBufferCurr, littleEndianmBIH.gamma_green); + ENCODE(&mImageBufferCurr, littleEndianmBIH.gamma_blue); + ENCODE(&mImageBufferCurr, littleEndianmBIH.intent); + ENCODE(&mImageBufferCurr, littleEndianmBIH.profile_offset); + ENCODE(&mImageBufferCurr, littleEndianmBIH.profile_size); + ENCODE(&mImageBufferCurr, littleEndianmBIH.reserved); + } +} + +// Sets a pixel in the image buffer that doesn't have alpha data +static inline void +SetPixel24(uint8_t*& imageBufferCurr, uint8_t aRed, uint8_t aGreen, + uint8_t aBlue) +{ + *imageBufferCurr = aBlue; + *(imageBufferCurr + 1) = aGreen; + *(imageBufferCurr + 2) = aRed; +} + +// Sets a pixel in the image buffer with alpha data +static inline void +SetPixel32(uint8_t*& imageBufferCurr, uint8_t aRed, uint8_t aGreen, + uint8_t aBlue, uint8_t aAlpha = 0xFF) +{ + *imageBufferCurr = aBlue; + *(imageBufferCurr + 1) = aGreen; + *(imageBufferCurr + 2) = aRed; + *(imageBufferCurr + 3) = aAlpha; +} + +// Encodes a row of image data which does not have alpha data +void +nsBMPEncoder::EncodeImageDataRow24(const uint8_t* aData) +{ + for (int32_t x = 0; x < mBMPInfoHeader.width; x++) { + uint32_t pos = x * BytesPerPixel(mBMPInfoHeader.bpp); + SetPixel24(mImageBufferCurr, aData[pos], aData[pos + 1], aData[pos + 2]); + mImageBufferCurr += BytesPerPixel(mBMPInfoHeader.bpp); + } + + for (uint32_t x = 0; x < PaddingBytes(mBMPInfoHeader.bpp, + mBMPInfoHeader.width); x++) { + *mImageBufferCurr++ = 0; + } +} + +// Encodes a row of image data which does have alpha data +void +nsBMPEncoder::EncodeImageDataRow32(const uint8_t* aData) +{ + for (int32_t x = 0; x < mBMPInfoHeader.width; x++) { + uint32_t pos = x * BytesPerPixel(mBMPInfoHeader.bpp); + SetPixel32(mImageBufferCurr, aData[pos], aData[pos + 1], + aData[pos + 2], aData[pos + 3]); + mImageBufferCurr += 4; + } + + for (uint32_t x = 0; x < PaddingBytes(mBMPInfoHeader.bpp, + mBMPInfoHeader.width); x++) { + *mImageBufferCurr++ = 0; + } +} diff --git a/image/encoders/bmp/nsBMPEncoder.h b/image/encoders/bmp/nsBMPEncoder.h new file mode 100644 index 000000000..b90af4d19 --- /dev/null +++ b/image/encoders/bmp/nsBMPEncoder.h @@ -0,0 +1,157 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_encoders_bmp_nsBMPEncoder_h +#define mozilla_image_encoders_bmp_nsBMPEncoder_h + +#include "mozilla/Attributes.h" +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/UniquePtr.h" + +#include "imgIEncoder.h" +#include "BMPHeaders.h" + +#include "nsCOMPtr.h" + +#define NS_BMPENCODER_CID \ +{ /* 13a5320c-4c91-4FA4-bd16-b081a3ba8c0b */ \ + 0x13a5320c, \ + 0x4c91, \ + 0x4fa4, \ + {0xbd, 0x16, 0xb0, 0x81, 0xa3, 0Xba, 0x8c, 0x0b} \ +} + +namespace mozilla { +namespace image { +namespace bmp { + +struct FileHeader { + char signature[2]; // String "BM". + uint32_t filesize; // File size. + int32_t reserved; // Zero. + uint32_t dataoffset; // Offset to raster data. +}; + +struct XYZ { + int32_t x, y, z; +}; + +struct XYZTriple { + XYZ r, g, b; +}; + +struct V5InfoHeader { + uint32_t bihsize; // Header size + int32_t width; // Uint16 in OS/2 BMPs + int32_t height; // Uint16 in OS/2 BMPs + uint16_t planes; // =1 + uint16_t bpp; // Bits per pixel. + uint32_t compression; // See Compression for valid values + uint32_t image_size; // (compressed) image size. Can be 0 if + // compression==0 + uint32_t xppm; // Pixels per meter, horizontal + uint32_t yppm; // Pixels per meter, vertical + uint32_t colors; // Used Colors + uint32_t important_colors; // Number of important colors. 0=all + // The rest of the header is not available in WIN_V3 BMP Files + uint32_t red_mask; // Bits used for red component + uint32_t green_mask; // Bits used for green component + uint32_t blue_mask; // Bits used for blue component + uint32_t alpha_mask; // Bits used for alpha component + uint32_t color_space; // 0x73524742=LCS_sRGB ... + // These members are unused unless color_space == LCS_CALIBRATED_RGB + XYZTriple white_point; // Logical white point + uint32_t gamma_red; // Red gamma component + uint32_t gamma_green; // Green gamma component + uint32_t gamma_blue; // Blue gamma component + uint32_t intent; // Rendering intent + // These members are unused unless color_space == LCS_PROFILE_* + uint32_t profile_offset; // Offset to profile data in bytes + uint32_t profile_size; // Size of profile data in bytes + uint32_t reserved; // =0 + + static const uint32_t COLOR_SPACE_LCS_SRGB = 0x73524742; +}; + +} // namespace bmp +} // namespace image +} // namespace mozilla + +// Provides BMP encoding functionality. Use InitFromData() to do the +// encoding. See that function definition for encoding options. + +class nsBMPEncoder final : public imgIEncoder +{ + typedef mozilla::ReentrantMonitor ReentrantMonitor; +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_IMGIENCODER + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + + nsBMPEncoder(); + +protected: + ~nsBMPEncoder(); + + enum Version + { + VERSION_3 = 3, + VERSION_5 = 5 + }; + + // See InitData in the cpp for valid parse options + nsresult ParseOptions(const nsAString& aOptions, Version& aVersionOut, + uint16_t& aBppOut); + // Obtains data with no alpha in machine-independent byte order + void ConvertHostARGBRow(const uint8_t* aSrc, + const mozilla::UniquePtr& aDest, + uint32_t aPixelWidth); + // Thread safe notify listener + void NotifyListener(); + + // Initializes the bitmap file header member mBMPFileHeader + nsresult InitFileHeader(Version aVersion, uint16_t aBPP, uint32_t aWidth, + uint32_t aHeight); + // Initializes the bitmap info header member mBMPInfoHeader + nsresult InitInfoHeader(Version aVersion, uint16_t aBPP, uint32_t aWidth, + uint32_t aHeight); + + // Encodes the bitmap file header member mBMPFileHeader + void EncodeFileHeader(); + // Encodes the bitmap info header member mBMPInfoHeader + void EncodeInfoHeader(); + // Encodes a row of image data which does not have alpha data + void EncodeImageDataRow24(const uint8_t* aData); + // Encodes a row of image data which does have alpha data + void EncodeImageDataRow32(const uint8_t* aData); + // Obtains the current offset filled up to for the image buffer + inline int32_t GetCurrentImageBufferOffset() + { + return static_cast(mImageBufferCurr - mImageBufferStart); + } + + // These headers will always contain endian independent stuff + // They store the BMP headers which will be encoded + mozilla::image::bmp::FileHeader mBMPFileHeader; + mozilla::image::bmp::V5InfoHeader mBMPInfoHeader; + + // Keeps track of the start of the image buffer + uint8_t* mImageBufferStart; + // Keeps track of the current position in the image buffer + uint8_t* mImageBufferCurr; + // Keeps track of the image buffer size + uint32_t mImageBufferSize; + // Keeps track of the number of bytes in the image buffer which are read + uint32_t mImageBufferReadPoint; + // Stores true if the image is done being encoded + bool mFinished; + + nsCOMPtr mCallback; + nsCOMPtr mCallbackTarget; + uint32_t mNotifyThreshold; +}; + +#endif // mozilla_image_encoders_bmp_nsBMPEncoder_h diff --git a/image/encoders/ico/moz.build b/image/encoders/ico/moz.build new file mode 100644 index 000000000..c45d49aaf --- /dev/null +++ b/image/encoders/ico/moz.build @@ -0,0 +1,18 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SOURCES += [ + 'nsICOEncoder.cpp', +] + +# Decoders need RasterImage.h +LOCAL_INCLUDES += [ + '/image', + '/image/encoders/bmp', + '/image/encoders/png', +] + +FINAL_LIBRARY = 'xul' diff --git a/image/encoders/ico/nsICOEncoder.cpp b/image/encoders/ico/nsICOEncoder.cpp new file mode 100644 index 000000000..5b26f5ca2 --- /dev/null +++ b/image/encoders/ico/nsICOEncoder.cpp @@ -0,0 +1,542 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCRT.h" +#include "mozilla/EndianUtils.h" +#include "nsBMPEncoder.h" +#include "nsPNGEncoder.h" +#include "nsICOEncoder.h" +#include "prprf.h" +#include "nsString.h" +#include "nsStreamUtils.h" +#include "nsTArray.h" + +using namespace mozilla; +using namespace mozilla::image; + +NS_IMPL_ISUPPORTS(nsICOEncoder, imgIEncoder, nsIInputStream, + nsIAsyncInputStream) + +nsICOEncoder::nsICOEncoder() : mImageBufferStart(nullptr), + mImageBufferCurr(0), + mImageBufferSize(0), + mImageBufferReadPoint(0), + mFinished(false), + mUsePNG(true), + mNotifyThreshold(0) +{ +} + +nsICOEncoder::~nsICOEncoder() +{ + if (mImageBufferStart) { + free(mImageBufferStart); + mImageBufferStart = nullptr; + mImageBufferCurr = nullptr; + } +} + +// nsICOEncoder::InitFromData +// Two output options are supported: format=;bpp= +// format specifies whether to use png or bitmap format +// bpp specifies the bits per pixel to use where bpp_value can be 24 or 32 +NS_IMETHODIMP +nsICOEncoder::InitFromData(const uint8_t* aData, + uint32_t aLength, + uint32_t aWidth, + uint32_t aHeight, + uint32_t aStride, + uint32_t aInputFormat, + const nsAString& aOutputOptions) +{ + // validate input format + if (aInputFormat != INPUT_FORMAT_RGB && + aInputFormat != INPUT_FORMAT_RGBA && + aInputFormat != INPUT_FORMAT_HOSTARGB) { + return NS_ERROR_INVALID_ARG; + } + + // Stride is the padded width of each row, so it better be longer + if ((aInputFormat == INPUT_FORMAT_RGB && + aStride < aWidth * 3) || + ((aInputFormat == INPUT_FORMAT_RGBA || + aInputFormat == INPUT_FORMAT_HOSTARGB) && + aStride < aWidth * 4)) { + NS_WARNING("Invalid stride for InitFromData"); + return NS_ERROR_INVALID_ARG; + } + + nsresult rv; + rv = StartImageEncode(aWidth, aHeight, aInputFormat, aOutputOptions); + NS_ENSURE_SUCCESS(rv, rv); + + rv = AddImageFrame(aData, aLength, aWidth, aHeight, aStride, + aInputFormat, aOutputOptions); + NS_ENSURE_SUCCESS(rv, rv); + + rv = EndImageEncode(); + return rv; +} + +// Returns the number of bytes in the image buffer used +// For an ICO file, this is all bytes in the buffer. +NS_IMETHODIMP +nsICOEncoder::GetImageBufferUsed(uint32_t* aOutputSize) +{ + NS_ENSURE_ARG_POINTER(aOutputSize); + *aOutputSize = mImageBufferSize; + return NS_OK; +} + +// Returns a pointer to the start of the image buffer +NS_IMETHODIMP +nsICOEncoder::GetImageBuffer(char** aOutputBuffer) +{ + NS_ENSURE_ARG_POINTER(aOutputBuffer); + *aOutputBuffer = reinterpret_cast(mImageBufferStart); + return NS_OK; +} + +NS_IMETHODIMP +nsICOEncoder::AddImageFrame(const uint8_t* aData, + uint32_t aLength, + uint32_t aWidth, + uint32_t aHeight, + uint32_t aStride, + uint32_t aInputFormat, + const nsAString& aFrameOptions) +{ + if (mUsePNG) { + + mContainedEncoder = new nsPNGEncoder(); + nsresult rv; + nsAutoString noParams; + rv = mContainedEncoder->InitFromData(aData, aLength, aWidth, aHeight, + aStride, aInputFormat, noParams); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t PNGImageBufferSize; + mContainedEncoder->GetImageBufferUsed(&PNGImageBufferSize); + mImageBufferSize = ICONFILEHEADERSIZE + ICODIRENTRYSIZE + + PNGImageBufferSize; + mImageBufferStart = static_cast(malloc(mImageBufferSize)); + if (!mImageBufferStart) { + return NS_ERROR_OUT_OF_MEMORY; + } + mImageBufferCurr = mImageBufferStart; + mICODirEntry.mBytesInRes = PNGImageBufferSize; + + EncodeFileHeader(); + EncodeInfoHeader(); + + char* imageBuffer; + rv = mContainedEncoder->GetImageBuffer(&imageBuffer); + NS_ENSURE_SUCCESS(rv, rv); + memcpy(mImageBufferCurr, imageBuffer, PNGImageBufferSize); + mImageBufferCurr += PNGImageBufferSize; + } else { + mContainedEncoder = new nsBMPEncoder(); + nsresult rv; + + nsAutoString params; + params.AppendLiteral("bpp="); + params.AppendInt(mICODirEntry.mBitCount); + + rv = mContainedEncoder->InitFromData(aData, aLength, aWidth, aHeight, + aStride, aInputFormat, params); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t andMaskSize = ((GetRealWidth() + 31) / 32) * 4 * // row AND mask + GetRealHeight(); // num rows + + uint32_t BMPImageBufferSize; + mContainedEncoder->GetImageBufferUsed(&BMPImageBufferSize); + mImageBufferSize = ICONFILEHEADERSIZE + ICODIRENTRYSIZE + + BMPImageBufferSize + andMaskSize; + mImageBufferStart = static_cast(malloc(mImageBufferSize)); + if (!mImageBufferStart) { + return NS_ERROR_OUT_OF_MEMORY; + } + mImageBufferCurr = mImageBufferStart; + + // Icon files that wrap a BMP file must not include the BITMAPFILEHEADER + // section at the beginning of the encoded BMP data, so we must skip over + // bmp::FILE_HEADER_LENGTH bytes when adding the BMP content to the icon + // file. + mICODirEntry.mBytesInRes = + BMPImageBufferSize - bmp::FILE_HEADER_LENGTH + andMaskSize; + + // Encode the icon headers + EncodeFileHeader(); + EncodeInfoHeader(); + + char* imageBuffer; + rv = mContainedEncoder->GetImageBuffer(&imageBuffer); + NS_ENSURE_SUCCESS(rv, rv); + memcpy(mImageBufferCurr, imageBuffer + bmp::FILE_HEADER_LENGTH, + BMPImageBufferSize - bmp::FILE_HEADER_LENGTH); + // We need to fix the BMP height to be *2 for the AND mask + uint32_t fixedHeight = GetRealHeight() * 2; + NativeEndian::swapToLittleEndianInPlace(&fixedHeight, 1); + // The height is stored at an offset of 8 from the DIB header + memcpy(mImageBufferCurr + 8, &fixedHeight, sizeof(fixedHeight)); + mImageBufferCurr += BMPImageBufferSize - bmp::FILE_HEADER_LENGTH; + + // Calculate rowsize in DWORD's + uint32_t rowSize = ((GetRealWidth() + 31) / 32) * 4; // + 31 to round up + int32_t currentLine = GetRealHeight(); + + // Write out the AND mask + while (currentLine > 0) { + currentLine--; + uint8_t* encoded = mImageBufferCurr + currentLine * rowSize; + uint8_t* encodedEnd = encoded + rowSize; + while (encoded != encodedEnd) { + *encoded = 0; // make everything visible + encoded++; + } + } + + mImageBufferCurr += andMaskSize; + } + + return NS_OK; +} + +// See ::InitFromData for other info. +NS_IMETHODIMP +nsICOEncoder::StartImageEncode(uint32_t aWidth, + uint32_t aHeight, + uint32_t aInputFormat, + const nsAString& aOutputOptions) +{ + // can't initialize more than once + if (mImageBufferStart || mImageBufferCurr) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + // validate input format + if (aInputFormat != INPUT_FORMAT_RGB && + aInputFormat != INPUT_FORMAT_RGBA && + aInputFormat != INPUT_FORMAT_HOSTARGB) { + return NS_ERROR_INVALID_ARG; + } + + // Icons are only 1 byte, so make sure our bitmap is in range + if (aWidth > 256 || aHeight > 256) { + return NS_ERROR_INVALID_ARG; + } + + // parse and check any provided output options + uint16_t bpp = 24; + bool usePNG = true; + nsresult rv = ParseOptions(aOutputOptions, bpp, usePNG); + NS_ENSURE_SUCCESS(rv, rv); + MOZ_ASSERT(bpp <= 32); + + mUsePNG = usePNG; + + InitFileHeader(); + // The width and height are stored as 0 when we have a value of 256 + InitInfoHeader(bpp, aWidth == 256 ? 0 : (uint8_t)aWidth, + aHeight == 256 ? 0 : (uint8_t)aHeight); + + return NS_OK; +} + +NS_IMETHODIMP +nsICOEncoder::EndImageEncode() +{ + // must be initialized + if (!mImageBufferStart || !mImageBufferCurr) { + return NS_ERROR_NOT_INITIALIZED; + } + + mFinished = true; + NotifyListener(); + + // if output callback can't get enough memory, it will free our buffer + if (!mImageBufferStart || !mImageBufferCurr) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +// Parses the encoder options and sets the bits per pixel to use and PNG or BMP +// See InitFromData for a description of the parse options +nsresult +nsICOEncoder::ParseOptions(const nsAString& aOptions, uint16_t& aBppOut, + bool& aUsePNGOut) +{ + // If no parsing options just use the default of 24BPP and PNG yes + if (aOptions.Length() == 0) { + aUsePNGOut = true; + aBppOut = 24; + } + + // Parse the input string into a set of name/value pairs. + // From format: format=;bpp= + // to format: [0] = format=, [1] = bpp= + nsTArray nameValuePairs; + if (!ParseString(NS_ConvertUTF16toUTF8(aOptions), ';', nameValuePairs)) { + return NS_ERROR_INVALID_ARG; + } + + // For each name/value pair in the set + for (unsigned i = 0; i < nameValuePairs.Length(); ++i) { + + // Split the name value pair [0] = name, [1] = value + nsTArray nameValuePair; + if (!ParseString(nameValuePairs[i], '=', nameValuePair)) { + return NS_ERROR_INVALID_ARG; + } + if (nameValuePair.Length() != 2) { + return NS_ERROR_INVALID_ARG; + } + + // Parse the format portion of the string format=;bpp= + if (nameValuePair[0].Equals("format", + nsCaseInsensitiveCStringComparator())) { + if (nameValuePair[1].Equals("png", + nsCaseInsensitiveCStringComparator())) { + aUsePNGOut = true; + } + else if (nameValuePair[1].Equals("bmp", + nsCaseInsensitiveCStringComparator())) { + aUsePNGOut = false; + } + else { + return NS_ERROR_INVALID_ARG; + } + } + + // Parse the bpp portion of the string format=;bpp= + if (nameValuePair[0].Equals("bpp", nsCaseInsensitiveCStringComparator())) { + if (nameValuePair[1].EqualsLiteral("24")) { + aBppOut = 24; + } + else if (nameValuePair[1].EqualsLiteral("32")) { + aBppOut = 32; + } + else { + return NS_ERROR_INVALID_ARG; + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsICOEncoder::Close() +{ + if (mImageBufferStart) { + free(mImageBufferStart); + mImageBufferStart = nullptr; + mImageBufferSize = 0; + mImageBufferReadPoint = 0; + mImageBufferCurr = nullptr; + } + + return NS_OK; +} + +// Obtains the available bytes to read +NS_IMETHODIMP +nsICOEncoder::Available(uint64_t *_retval) +{ + if (!mImageBufferStart || !mImageBufferCurr) { + return NS_BASE_STREAM_CLOSED; + } + + *_retval = GetCurrentImageBufferOffset() - mImageBufferReadPoint; + return NS_OK; +} + +// [noscript] Reads bytes which are available +NS_IMETHODIMP +nsICOEncoder::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) +{ + return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval); +} + +// [noscript] Reads segments +NS_IMETHODIMP +nsICOEncoder::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* _retval) +{ + uint32_t maxCount = GetCurrentImageBufferOffset() - mImageBufferReadPoint; + if (maxCount == 0) { + *_retval = 0; + return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK; + } + + if (aCount > maxCount) { + aCount = maxCount; + } + + nsresult rv = aWriter(this, aClosure, + reinterpret_cast(mImageBufferStart + + mImageBufferReadPoint), + 0, aCount, _retval); + if (NS_SUCCEEDED(rv)) { + NS_ASSERTION(*_retval <= aCount, "bad write count"); + mImageBufferReadPoint += *_retval; + } + // errors returned from the writer end here! + return NS_OK; +} + +NS_IMETHODIMP +nsICOEncoder::IsNonBlocking(bool* _retval) +{ + *_retval = true; + return NS_OK; +} + +NS_IMETHODIMP +nsICOEncoder::AsyncWait(nsIInputStreamCallback* aCallback, + uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aTarget) +{ + if (aFlags != 0) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + if (mCallback || mCallbackTarget) { + return NS_ERROR_UNEXPECTED; + } + + mCallbackTarget = aTarget; + // 0 means "any number of bytes except 0" + mNotifyThreshold = aRequestedCount; + if (!aRequestedCount) { + mNotifyThreshold = 1024; // We don't want to notify incessantly + } + + // We set the callback absolutely last, because NotifyListener uses it to + // determine if someone needs to be notified. If we don't set it last, + // NotifyListener might try to fire off a notification to a null target + // which will generally cause non-threadsafe objects to be used off the + // main thread + mCallback = aCallback; + + // What we are being asked for may be present already + NotifyListener(); + return NS_OK; +} + +NS_IMETHODIMP +nsICOEncoder::CloseWithStatus(nsresult aStatus) +{ + return Close(); +} + +void +nsICOEncoder::NotifyListener() +{ + if (mCallback && + (GetCurrentImageBufferOffset() - + mImageBufferReadPoint >= mNotifyThreshold || mFinished)) { + nsCOMPtr callback; + if (mCallbackTarget) { + callback = NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget); + } else { + callback = mCallback; + } + + NS_ASSERTION(callback, "Shouldn't fail to make the callback"); + // Null the callback first because OnInputStreamReady could reenter + // AsyncWait + mCallback = nullptr; + mCallbackTarget = nullptr; + mNotifyThreshold = 0; + + callback->OnInputStreamReady(this); + } +} + +// Initializes the icon file header mICOFileHeader +void +nsICOEncoder::InitFileHeader() +{ + memset(&mICOFileHeader, 0, sizeof(mICOFileHeader)); + mICOFileHeader.mReserved = 0; + mICOFileHeader.mType = 1; + mICOFileHeader.mCount = 1; +} + +// Initializes the icon directory info header mICODirEntry +void +nsICOEncoder::InitInfoHeader(uint16_t aBPP, uint8_t aWidth, uint8_t aHeight) +{ + memset(&mICODirEntry, 0, sizeof(mICODirEntry)); + mICODirEntry.mBitCount = aBPP; + mICODirEntry.mBytesInRes = 0; + mICODirEntry.mColorCount = 0; + mICODirEntry.mWidth = aWidth; + mICODirEntry.mHeight = aHeight; + mICODirEntry.mImageOffset = ICONFILEHEADERSIZE + ICODIRENTRYSIZE; + mICODirEntry.mPlanes = 1; + mICODirEntry.mReserved = 0; +} + +// Encodes the icon file header mICOFileHeader +void +nsICOEncoder::EncodeFileHeader() +{ + IconFileHeader littleEndianIFH = mICOFileHeader; + NativeEndian::swapToLittleEndianInPlace(&littleEndianIFH.mReserved, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianIFH.mType, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianIFH.mCount, 1); + + memcpy(mImageBufferCurr, &littleEndianIFH.mReserved, + sizeof(littleEndianIFH.mReserved)); + mImageBufferCurr += sizeof(littleEndianIFH.mReserved); + memcpy(mImageBufferCurr, &littleEndianIFH.mType, + sizeof(littleEndianIFH.mType)); + mImageBufferCurr += sizeof(littleEndianIFH.mType); + memcpy(mImageBufferCurr, &littleEndianIFH.mCount, + sizeof(littleEndianIFH.mCount)); + mImageBufferCurr += sizeof(littleEndianIFH.mCount); +} + +// Encodes the icon directory info header mICODirEntry +void +nsICOEncoder::EncodeInfoHeader() +{ + IconDirEntry littleEndianmIDE = mICODirEntry; + + NativeEndian::swapToLittleEndianInPlace(&littleEndianmIDE.mPlanes, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmIDE.mBitCount, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmIDE.mBytesInRes, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmIDE.mImageOffset, 1); + + memcpy(mImageBufferCurr, &littleEndianmIDE.mWidth, + sizeof(littleEndianmIDE.mWidth)); + mImageBufferCurr += sizeof(littleEndianmIDE.mWidth); + memcpy(mImageBufferCurr, &littleEndianmIDE.mHeight, + sizeof(littleEndianmIDE.mHeight)); + mImageBufferCurr += sizeof(littleEndianmIDE.mHeight); + memcpy(mImageBufferCurr, &littleEndianmIDE.mColorCount, + sizeof(littleEndianmIDE.mColorCount)); + mImageBufferCurr += sizeof(littleEndianmIDE.mColorCount); + memcpy(mImageBufferCurr, &littleEndianmIDE.mReserved, + sizeof(littleEndianmIDE.mReserved)); + mImageBufferCurr += sizeof(littleEndianmIDE.mReserved); + memcpy(mImageBufferCurr, &littleEndianmIDE.mPlanes, + sizeof(littleEndianmIDE.mPlanes)); + mImageBufferCurr += sizeof(littleEndianmIDE.mPlanes); + memcpy(mImageBufferCurr, &littleEndianmIDE.mBitCount, + sizeof(littleEndianmIDE.mBitCount)); + mImageBufferCurr += sizeof(littleEndianmIDE.mBitCount); + memcpy(mImageBufferCurr, &littleEndianmIDE.mBytesInRes, + sizeof(littleEndianmIDE.mBytesInRes)); + mImageBufferCurr += sizeof(littleEndianmIDE.mBytesInRes); + memcpy(mImageBufferCurr, &littleEndianmIDE.mImageOffset, + sizeof(littleEndianmIDE.mImageOffset)); + mImageBufferCurr += sizeof(littleEndianmIDE.mImageOffset); +} diff --git a/image/encoders/ico/nsICOEncoder.h b/image/encoders/ico/nsICOEncoder.h new file mode 100644 index 000000000..a8a9a5049 --- /dev/null +++ b/image/encoders/ico/nsICOEncoder.h @@ -0,0 +1,99 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_encoders_ico_nsICOEncoder_h +#define mozilla_image_encoders_ico_nsICOEncoder_h + +#include "mozilla/Attributes.h" +#include "mozilla/ReentrantMonitor.h" + +#include "imgIEncoder.h" + +#include "nsCOMPtr.h" +#include "ICOFileHeaders.h" + +#define NS_ICOENCODER_CID \ +{ /*92AE3AB2-8968-41B1-8709-B6123BCEAF21 */ \ + 0x92ae3ab2, \ + 0x8968, \ + 0x41b1, \ + {0x87, 0x09, 0xb6, 0x12, 0x3b, 0Xce, 0xaf, 0x21} \ +} + +// Provides ICO encoding functionality. Use InitFromData() to do the +// encoding. See that function definition for encoding options. + +class nsICOEncoder final : public imgIEncoder +{ + typedef mozilla::ReentrantMonitor ReentrantMonitor; +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_IMGIENCODER + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + + nsICOEncoder(); + + // Obtains the width of the icon directory entry + uint32_t GetRealWidth() const + { + return mICODirEntry.mWidth == 0 ? 256 : mICODirEntry.mWidth; + } + + // Obtains the height of the icon directory entry + uint32_t GetRealHeight() const + { + return mICODirEntry.mHeight == 0 ? 256 : mICODirEntry.mHeight; + } + +protected: + ~nsICOEncoder(); + + nsresult ParseOptions(const nsAString& aOptions, uint16_t& aBppOut, + bool& aUsePNGOut); + void NotifyListener(); + + // Initializes the icon file header mICOFileHeader + void InitFileHeader(); + // Initializes the icon directory info header mICODirEntry + void InitInfoHeader(uint16_t aBPP, uint8_t aWidth, uint8_t aHeight); + // Encodes the icon file header mICOFileHeader + void EncodeFileHeader(); + // Encodes the icon directory info header mICODirEntry + void EncodeInfoHeader(); + // Obtains the current offset filled up to for the image buffer + inline int32_t GetCurrentImageBufferOffset() + { + return static_cast(mImageBufferCurr - mImageBufferStart); + } + + // Holds either a PNG or a BMP depending on the encoding options specified + // or if no encoding options specified will use the default (PNG) + nsCOMPtr mContainedEncoder; + + // These headers will always contain endian independent stuff. + // Don't trust the width and height of mICODirEntry directly, + // instead use the accessors GetRealWidth() and GetRealHeight(). + mozilla::image::IconFileHeader mICOFileHeader; + mozilla::image::IconDirEntry mICODirEntry; + + // Keeps track of the start of the image buffer + uint8_t* mImageBufferStart; + // Keeps track of the current position in the image buffer + uint8_t* mImageBufferCurr; + // Keeps track of the image buffer size + uint32_t mImageBufferSize; + // Keeps track of the number of bytes in the image buffer which are read + uint32_t mImageBufferReadPoint; + // Stores true if the image is done being encoded + bool mFinished; + // Stores true if the contained image is a PNG + bool mUsePNG; + + nsCOMPtr mCallback; + nsCOMPtr mCallbackTarget; + uint32_t mNotifyThreshold; +}; + +#endif // mozilla_image_encoders_ico_nsICOEncoder_h diff --git a/image/encoders/jpeg/moz.build b/image/encoders/jpeg/moz.build new file mode 100644 index 000000000..9e5551ce6 --- /dev/null +++ b/image/encoders/jpeg/moz.build @@ -0,0 +1,11 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SOURCES += [ + 'nsJPEGEncoder.cpp', +] + +FINAL_LIBRARY = 'xul' diff --git a/image/encoders/jpeg/nsJPEGEncoder.cpp b/image/encoders/jpeg/nsJPEGEncoder.cpp new file mode 100644 index 000000000..04cfef07b --- /dev/null +++ b/image/encoders/jpeg/nsJPEGEncoder.cpp @@ -0,0 +1,532 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsJPEGEncoder.h" +#include "prprf.h" +#include "nsString.h" +#include "nsStreamUtils.h" +#include "gfxColor.h" + +#include +#include "jerror.h" + +using namespace mozilla; + +NS_IMPL_ISUPPORTS(nsJPEGEncoder, imgIEncoder, nsIInputStream, + nsIAsyncInputStream) + +// used to pass error info through the JPEG library +struct encoder_error_mgr { + jpeg_error_mgr pub; + jmp_buf setjmp_buffer; +}; + +nsJPEGEncoder::nsJPEGEncoder() + : mFinished(false), + mImageBuffer(nullptr), + mImageBufferSize(0), + mImageBufferUsed(0), + mImageBufferReadPoint(0), + mCallback(nullptr), + mCallbackTarget(nullptr), + mNotifyThreshold(0), + mReentrantMonitor("nsJPEGEncoder.mReentrantMonitor") +{ +} + +nsJPEGEncoder::~nsJPEGEncoder() +{ + if (mImageBuffer) { + free(mImageBuffer); + mImageBuffer = nullptr; + } +} + + +// nsJPEGEncoder::InitFromData +// +// One output option is supported: "quality=X" where X is an integer in the +// range 0-100. Higher values for X give better quality. +// +// Transparency is always discarded. + +NS_IMETHODIMP +nsJPEGEncoder::InitFromData(const uint8_t* aData, + uint32_t aLength, // (unused, req'd by JS) + uint32_t aWidth, + uint32_t aHeight, + uint32_t aStride, + uint32_t aInputFormat, + const nsAString& aOutputOptions) +{ + NS_ENSURE_ARG(aData); + + // validate input format + if (aInputFormat != INPUT_FORMAT_RGB && + aInputFormat != INPUT_FORMAT_RGBA && + aInputFormat != INPUT_FORMAT_HOSTARGB) + return NS_ERROR_INVALID_ARG; + + // Stride is the padded width of each row, so it better be longer (I'm afraid + // people will not understand what stride means, so check it well) + if ((aInputFormat == INPUT_FORMAT_RGB && + aStride < aWidth * 3) || + ((aInputFormat == INPUT_FORMAT_RGBA || + aInputFormat == INPUT_FORMAT_HOSTARGB) && + aStride < aWidth * 4)) { + NS_WARNING("Invalid stride for InitFromData"); + return NS_ERROR_INVALID_ARG; + } + + // can't initialize more than once + if (mImageBuffer != nullptr) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + // options: we only have one option so this is easy + int quality = 92; + if (aOutputOptions.Length() > 0) { + // have options string + const nsString qualityPrefix(NS_LITERAL_STRING("quality=")); + if (aOutputOptions.Length() > qualityPrefix.Length() && + StringBeginsWith(aOutputOptions, qualityPrefix)) { + // have quality string + nsCString value = + NS_ConvertUTF16toUTF8(Substring(aOutputOptions, + qualityPrefix.Length())); + int newquality = -1; + if (PR_sscanf(value.get(), "%d", &newquality) == 1) { + if (newquality >= 0 && newquality <= 100) { + quality = newquality; + } else { + NS_WARNING("Quality value out of range, should be 0-100," + " using default"); + } + } else { + NS_WARNING("Quality value invalid, should be integer 0-100," + " using default"); + } + } + else { + return NS_ERROR_INVALID_ARG; + } + } + + jpeg_compress_struct cinfo; + + // We set up the normal JPEG error routines, then override error_exit. + // This must be done before the call to create_compress + encoder_error_mgr errmgr; + cinfo.err = jpeg_std_error(&errmgr.pub); + errmgr.pub.error_exit = errorExit; + // Establish the setjmp return context for my_error_exit to use. + if (setjmp(errmgr.setjmp_buffer)) { + // If we get here, the JPEG code has signaled an error. + // We need to clean up the JPEG object, close the input file, and return. + return NS_ERROR_FAILURE; + } + + jpeg_create_compress(&cinfo); + cinfo.image_width = aWidth; + cinfo.image_height = aHeight; + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + cinfo.data_precision = 8; + + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, quality, 1); // quality here is 0-100 + if (quality >= 90) { + int i; + for (i=0; i < MAX_COMPONENTS; i++) { + cinfo.comp_info[i].h_samp_factor=1; + cinfo.comp_info[i].v_samp_factor=1; + } + } + + // set up the destination manager + jpeg_destination_mgr destmgr; + destmgr.init_destination = initDestination; + destmgr.empty_output_buffer = emptyOutputBuffer; + destmgr.term_destination = termDestination; + cinfo.dest = &destmgr; + cinfo.client_data = this; + + jpeg_start_compress(&cinfo, 1); + + // feed it the rows + if (aInputFormat == INPUT_FORMAT_RGB) { + while (cinfo.next_scanline < cinfo.image_height) { + const uint8_t* row = &aData[cinfo.next_scanline * aStride]; + jpeg_write_scanlines(&cinfo, const_cast(&row), 1); + } + } else if (aInputFormat == INPUT_FORMAT_RGBA) { + UniquePtr rowptr = MakeUnique(aWidth * 3); + uint8_t* row = rowptr.get(); + while (cinfo.next_scanline < cinfo.image_height) { + ConvertRGBARow(&aData[cinfo.next_scanline * aStride], row, aWidth); + jpeg_write_scanlines(&cinfo, &row, 1); + } + } else if (aInputFormat == INPUT_FORMAT_HOSTARGB) { + UniquePtr rowptr = MakeUnique(aWidth * 3); + uint8_t* row = rowptr.get(); + while (cinfo.next_scanline < cinfo.image_height) { + ConvertHostARGBRow(&aData[cinfo.next_scanline * aStride], row, aWidth); + jpeg_write_scanlines(&cinfo, &row, 1); + } + } + + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + + mFinished = true; + NotifyListener(); + + // if output callback can't get enough memory, it will free our buffer + if (!mImageBuffer) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsJPEGEncoder::StartImageEncode(uint32_t aWidth, + uint32_t aHeight, + uint32_t aInputFormat, + const nsAString& aOutputOptions) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +// Returns the number of bytes in the image buffer used. +NS_IMETHODIMP +nsJPEGEncoder::GetImageBufferUsed(uint32_t* aOutputSize) +{ + NS_ENSURE_ARG_POINTER(aOutputSize); + *aOutputSize = mImageBufferUsed; + return NS_OK; +} + +// Returns a pointer to the start of the image buffer +NS_IMETHODIMP +nsJPEGEncoder::GetImageBuffer(char** aOutputBuffer) +{ + NS_ENSURE_ARG_POINTER(aOutputBuffer); + *aOutputBuffer = reinterpret_cast(mImageBuffer); + return NS_OK; +} + +NS_IMETHODIMP +nsJPEGEncoder::AddImageFrame(const uint8_t* aData, + uint32_t aLength, + uint32_t aWidth, + uint32_t aHeight, + uint32_t aStride, + uint32_t aFrameFormat, + const nsAString& aFrameOptions) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsJPEGEncoder::EndImageEncode() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + + +NS_IMETHODIMP +nsJPEGEncoder::Close() +{ + if (mImageBuffer != nullptr) { + free(mImageBuffer); + mImageBuffer = nullptr; + mImageBufferSize = 0; + mImageBufferUsed = 0; + mImageBufferReadPoint = 0; + } + return NS_OK; +} + +NS_IMETHODIMP +nsJPEGEncoder::Available(uint64_t* _retval) +{ + if (!mImageBuffer) { + return NS_BASE_STREAM_CLOSED; + } + + *_retval = mImageBufferUsed - mImageBufferReadPoint; + return NS_OK; +} + +NS_IMETHODIMP +nsJPEGEncoder::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) +{ + return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval); +} + +NS_IMETHODIMP +nsJPEGEncoder::ReadSegments(nsWriteSegmentFun aWriter, + void* aClosure, uint32_t aCount, uint32_t* _retval) +{ + // Avoid another thread reallocing the buffer underneath us + ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor); + + uint32_t maxCount = mImageBufferUsed - mImageBufferReadPoint; + if (maxCount == 0) { + *_retval = 0; + return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK; + } + + if (aCount > maxCount) { + aCount = maxCount; + } + nsresult rv = aWriter(this, aClosure, + reinterpret_cast + (mImageBuffer+mImageBufferReadPoint), + 0, aCount, _retval); + if (NS_SUCCEEDED(rv)) { + NS_ASSERTION(*_retval <= aCount, "bad write count"); + mImageBufferReadPoint += *_retval; + } + + // errors returned from the writer end here! + return NS_OK; +} + +NS_IMETHODIMP +nsJPEGEncoder::IsNonBlocking(bool* _retval) +{ + *_retval = true; + return NS_OK; +} + +NS_IMETHODIMP +nsJPEGEncoder::AsyncWait(nsIInputStreamCallback* aCallback, + uint32_t aFlags, uint32_t aRequestedCount, + nsIEventTarget* aTarget) +{ + if (aFlags != 0) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + if (mCallback || mCallbackTarget) { + return NS_ERROR_UNEXPECTED; + } + + mCallbackTarget = aTarget; + // 0 means "any number of bytes except 0" + mNotifyThreshold = aRequestedCount; + if (!aRequestedCount) { + mNotifyThreshold = 1024; // 1 KB seems good. We don't want to + // notify incessantly + } + + // We set the callback absolutely last, because NotifyListener uses it to + // determine if someone needs to be notified. If we don't set it last, + // NotifyListener might try to fire off a notification to a null target + // which will generally cause non-threadsafe objects to be used off the + // main thread + mCallback = aCallback; + + // What we are being asked for may be present already + NotifyListener(); + return NS_OK; +} + +NS_IMETHODIMP +nsJPEGEncoder::CloseWithStatus(nsresult aStatus) +{ + return Close(); +} + + + +// nsJPEGEncoder::ConvertHostARGBRow +// +// Our colors are stored with premultiplied alphas, but we need +// an output with no alpha in machine-independent byte order. +// +// See gfx/cairo/cairo/src/cairo-png.c +void +nsJPEGEncoder::ConvertHostARGBRow(const uint8_t* aSrc, uint8_t* aDest, + uint32_t aPixelWidth) +{ + for (uint32_t x = 0; x < aPixelWidth; x++) { + const uint32_t& pixelIn = ((const uint32_t*)(aSrc))[x]; + uint8_t* pixelOut = &aDest[x * 3]; + + pixelOut[0] = (pixelIn & 0xff0000) >> 16; + pixelOut[1] = (pixelIn & 0x00ff00) >> 8; + pixelOut[2] = (pixelIn & 0x0000ff) >> 0; + } +} + +/** + * nsJPEGEncoder::ConvertRGBARow + * + * Input is RGBA, output is RGB, so we should alpha-premultiply. + */ +void +nsJPEGEncoder::ConvertRGBARow(const uint8_t* aSrc, uint8_t* aDest, + uint32_t aPixelWidth) +{ + for (uint32_t x = 0; x < aPixelWidth; x++) { + const uint8_t* pixelIn = &aSrc[x * 4]; + uint8_t* pixelOut = &aDest[x * 3]; + + uint8_t alpha = pixelIn[3]; + pixelOut[0] = gfxPreMultiply(pixelIn[0], alpha); + pixelOut[1] = gfxPreMultiply(pixelIn[1], alpha); + pixelOut[2] = gfxPreMultiply(pixelIn[2], alpha); + } +} + +// nsJPEGEncoder::initDestination +// +// Initialize destination. This is called by jpeg_start_compress() before +// any data is actually written. It must initialize next_output_byte and +// free_in_buffer. free_in_buffer must be initialized to a positive value. + +void // static +nsJPEGEncoder::initDestination(jpeg_compress_struct* cinfo) +{ + nsJPEGEncoder* that = static_cast(cinfo->client_data); + NS_ASSERTION(!that->mImageBuffer, "Image buffer already initialized"); + + that->mImageBufferSize = 8192; + that->mImageBuffer = (uint8_t*)malloc(that->mImageBufferSize); + that->mImageBufferUsed = 0; + + cinfo->dest->next_output_byte = that->mImageBuffer; + cinfo->dest->free_in_buffer = that->mImageBufferSize; +} + + +// nsJPEGEncoder::emptyOutputBuffer +// +// This is called whenever the buffer has filled (free_in_buffer reaches +// zero). In typical applications, it should write out the *entire* buffer +// (use the saved start address and buffer length; ignore the current state +// of next_output_byte and free_in_buffer). Then reset the pointer & count +// to the start of the buffer, and return TRUE indicating that the buffer +// has been dumped. free_in_buffer must be set to a positive value when +// TRUE is returned. A FALSE return should only be used when I/O suspension +// is desired (this operating mode is discussed in the next section). + +boolean // static +nsJPEGEncoder::emptyOutputBuffer(jpeg_compress_struct* cinfo) +{ + nsJPEGEncoder* that = static_cast(cinfo->client_data); + NS_ASSERTION(that->mImageBuffer, "No buffer to empty!"); + + // When we're reallocing the buffer we need to take the lock to ensure + // that nobody is trying to read from the buffer we are destroying + ReentrantMonitorAutoEnter autoEnter(that->mReentrantMonitor); + + that->mImageBufferUsed = that->mImageBufferSize; + + // expand buffer, just double size each time + that->mImageBufferSize *= 2; + + uint8_t* newBuf = (uint8_t*)realloc(that->mImageBuffer, + that->mImageBufferSize); + if (!newBuf) { + // can't resize, just zero (this will keep us from writing more) + free(that->mImageBuffer); + that->mImageBuffer = nullptr; + that->mImageBufferSize = 0; + that->mImageBufferUsed = 0; + + // This seems to be the only way to do errors through the JPEG library. We + // pass an nsresult masquerading as an int, which works because the + // setjmp() caller casts it back. + longjmp(((encoder_error_mgr*)(cinfo->err))->setjmp_buffer, + static_cast(NS_ERROR_OUT_OF_MEMORY)); + } + that->mImageBuffer = newBuf; + + cinfo->dest->next_output_byte = &that->mImageBuffer[that->mImageBufferUsed]; + cinfo->dest->free_in_buffer = that->mImageBufferSize - that->mImageBufferUsed; + return 1; +} + + +// nsJPEGEncoder::termDestination +// +// Terminate destination --- called by jpeg_finish_compress() after all data +// has been written. In most applications, this must flush any data +// remaining in the buffer. Use either next_output_byte or free_in_buffer +// to determine how much data is in the buffer. + +void // static +nsJPEGEncoder::termDestination(jpeg_compress_struct* cinfo) +{ + nsJPEGEncoder* that = static_cast(cinfo->client_data); + if (!that->mImageBuffer) { + return; + } + that->mImageBufferUsed = cinfo->dest->next_output_byte - that->mImageBuffer; + NS_ASSERTION(that->mImageBufferUsed < that->mImageBufferSize, + "JPEG library busted, got a bad image buffer size"); + that->NotifyListener(); +} + + +// nsJPEGEncoder::errorExit +// +// Override the standard error method in the IJG JPEG decoder code. This +// was mostly copied from nsJPEGDecoder.cpp + +void // static +nsJPEGEncoder::errorExit(jpeg_common_struct* cinfo) +{ + nsresult error_code; + encoder_error_mgr* err = (encoder_error_mgr*) cinfo->err; + + // Convert error to a browser error code + switch (cinfo->err->msg_code) { + case JERR_OUT_OF_MEMORY: + error_code = NS_ERROR_OUT_OF_MEMORY; + break; + default: + error_code = NS_ERROR_FAILURE; + } + + // Return control to the setjmp point. We pass an nsresult masquerading as + // an int, which works because the setjmp() caller casts it back. + longjmp(err->setjmp_buffer, static_cast(error_code)); +} + +void +nsJPEGEncoder::NotifyListener() +{ + // We might call this function on multiple threads (any threads that call + // AsyncWait and any that do encoding) so we lock to avoid notifying the + // listener twice about the same data (which generally leads to a truncated + // image). + ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor); + + if (mCallback && + (mImageBufferUsed - mImageBufferReadPoint >= mNotifyThreshold || + mFinished)) { + nsCOMPtr callback; + if (mCallbackTarget) { + callback = NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget); + } else { + callback = mCallback; + } + + NS_ASSERTION(callback, "Shouldn't fail to make the callback"); + // Null the callback first because OnInputStreamReady could reenter + // AsyncWait + mCallback = nullptr; + mCallbackTarget = nullptr; + mNotifyThreshold = 0; + + callback->OnInputStreamReady(this); + } +} diff --git a/image/encoders/jpeg/nsJPEGEncoder.h b/image/encoders/jpeg/nsJPEGEncoder.h new file mode 100644 index 000000000..2bcf5c2f3 --- /dev/null +++ b/image/encoders/jpeg/nsJPEGEncoder.h @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_encoders_jpeg_nsJPEGEncoder_h +#define mozilla_image_encoders_jpeg_nsJPEGEncoder_h + +#include "imgIEncoder.h" + +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/Attributes.h" + +#include "nsCOMPtr.h" + +// needed for JPEG library +#include + +extern "C" { +#include "jpeglib.h" +} + +#define NS_JPEGENCODER_CID \ +{ \ + /* ac2bb8fe-eeeb-4572-b40f-be03932b56e0 */ \ + 0xac2bb8fe, \ + 0xeeeb, \ + 0x4572, \ + {0xb4, 0x0f, 0xbe, 0x03, 0x93, 0x2b, 0x56, 0xe0} \ +} + +// Provides JPEG encoding functionality. Use InitFromData() to do the +// encoding. See that function definition for encoding options. + +class nsJPEGEncoder final : public imgIEncoder +{ + typedef mozilla::ReentrantMonitor ReentrantMonitor; +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_IMGIENCODER + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + + nsJPEGEncoder(); + +private: + ~nsJPEGEncoder(); + +protected: + + void ConvertHostARGBRow(const uint8_t* aSrc, uint8_t* aDest, + uint32_t aPixelWidth); + void ConvertRGBARow(const uint8_t* aSrc, uint8_t* aDest, + uint32_t aPixelWidth); + + static void initDestination(jpeg_compress_struct* cinfo); + static boolean emptyOutputBuffer(jpeg_compress_struct* cinfo); + static void termDestination(jpeg_compress_struct* cinfo); + + static void errorExit(jpeg_common_struct* cinfo); + + void NotifyListener(); + + bool mFinished; + + // image buffer + uint8_t* mImageBuffer; + uint32_t mImageBufferSize; + uint32_t mImageBufferUsed; + + uint32_t mImageBufferReadPoint; + + nsCOMPtr mCallback; + nsCOMPtr mCallbackTarget; + uint32_t mNotifyThreshold; + + // nsJPEGEncoder is designed to allow one thread to pump data into it while + // another reads from it. We lock to ensure that the buffer remains + // append-only while we read from it (that it is not realloced) and to ensure + // that only one thread dispatches a callback for each call to AsyncWait. + ReentrantMonitor mReentrantMonitor; +}; + +#endif // mozilla_image_encoders_jpeg_nsJPEGEncoder_h diff --git a/image/encoders/moz.build b/image/encoders/moz.build new file mode 100644 index 000000000..1a76d773a --- /dev/null +++ b/image/encoders/moz.build @@ -0,0 +1,12 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS += [ + 'ico', + 'png', + 'jpeg', + 'bmp', +] diff --git a/image/encoders/png/moz.build b/image/encoders/png/moz.build new file mode 100644 index 000000000..e6665e424 --- /dev/null +++ b/image/encoders/png/moz.build @@ -0,0 +1,15 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SOURCES += [ + 'nsPNGEncoder.cpp', +] + +LOCAL_INCLUDES += [ + '/image', +] + +FINAL_LIBRARY = 'xul' diff --git a/image/encoders/png/nsPNGEncoder.cpp b/image/encoders/png/nsPNGEncoder.cpp new file mode 100644 index 000000000..66294146d --- /dev/null +++ b/image/encoders/png/nsPNGEncoder.cpp @@ -0,0 +1,758 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageLogging.h" +#include "nsCRT.h" +#include "nsPNGEncoder.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "prprf.h" + +using namespace mozilla; + +static LazyLogModule sPNGEncoderLog("PNGEncoder"); + +NS_IMPL_ISUPPORTS(nsPNGEncoder, imgIEncoder, nsIInputStream, + nsIAsyncInputStream) + +nsPNGEncoder::nsPNGEncoder() : mPNG(nullptr), mPNGinfo(nullptr), + mIsAnimation(false), + mFinished(false), + mImageBuffer(nullptr), mImageBufferSize(0), + mImageBufferUsed(0), mImageBufferReadPoint(0), + mCallback(nullptr), + mCallbackTarget(nullptr), mNotifyThreshold(0), + mReentrantMonitor( + "nsPNGEncoder.mReentrantMonitor") +{ } + +nsPNGEncoder::~nsPNGEncoder() +{ + if (mImageBuffer) { + free(mImageBuffer); + mImageBuffer = nullptr; + } + // don't leak if EndImageEncode wasn't called + if (mPNG) { + png_destroy_write_struct(&mPNG, &mPNGinfo); + } +} + +// nsPNGEncoder::InitFromData +// +// One output option is supported: "transparency=none" means that the +// output PNG will not have an alpha channel, even if the input does. +// +// Based partially on gfx/cairo/cairo/src/cairo-png.c +// See also media/libpng/libpng-manual.txt + +NS_IMETHODIMP +nsPNGEncoder::InitFromData(const uint8_t* aData, + uint32_t aLength, // (unused, req'd by JS) + uint32_t aWidth, + uint32_t aHeight, + uint32_t aStride, + uint32_t aInputFormat, + const nsAString& aOutputOptions) +{ + NS_ENSURE_ARG(aData); + nsresult rv; + + rv = StartImageEncode(aWidth, aHeight, aInputFormat, aOutputOptions); + if (!NS_SUCCEEDED(rv)) { + return rv; + } + + rv = AddImageFrame(aData, aLength, aWidth, aHeight, aStride, + aInputFormat, aOutputOptions); + if (!NS_SUCCEEDED(rv)) { + return rv; + } + + rv = EndImageEncode(); + + return rv; +} + + +// nsPNGEncoder::StartImageEncode +// +// +// See ::InitFromData for other info. +NS_IMETHODIMP +nsPNGEncoder::StartImageEncode(uint32_t aWidth, + uint32_t aHeight, + uint32_t aInputFormat, + const nsAString& aOutputOptions) +{ + bool useTransparency = true, skipFirstFrame = false; + uint32_t numFrames = 1; + uint32_t numPlays = 0; // For animations, 0 == forever + + // can't initialize more than once + if (mImageBuffer != nullptr) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + // validate input format + if (aInputFormat != INPUT_FORMAT_RGB && + aInputFormat != INPUT_FORMAT_RGBA && + aInputFormat != INPUT_FORMAT_HOSTARGB) + return NS_ERROR_INVALID_ARG; + + // parse and check any provided output options + nsresult rv = ParseOptions(aOutputOptions, &useTransparency, &skipFirstFrame, + &numFrames, &numPlays, nullptr, nullptr, + nullptr, nullptr, nullptr); + if (rv != NS_OK) { + return rv; + } + +#ifdef PNG_APNG_SUPPORTED + if (numFrames > 1) { + mIsAnimation = true; + } + +#endif + + // initialize + mPNG = png_create_write_struct(PNG_LIBPNG_VER_STRING, + nullptr, + ErrorCallback, + WarningCallback); + if (!mPNG) { + return NS_ERROR_OUT_OF_MEMORY; + } + + mPNGinfo = png_create_info_struct(mPNG); + if (!mPNGinfo) { + png_destroy_write_struct(&mPNG, nullptr); + return NS_ERROR_FAILURE; + } + + // libpng's error handler jumps back here upon an error. + // Note: It's important that all png_* callers do this, or errors + // will result in a corrupt time-warped stack. + if (setjmp(png_jmpbuf(mPNG))) { + png_destroy_write_struct(&mPNG, &mPNGinfo); + return NS_ERROR_FAILURE; + } + + // Set up to read the data into our image buffer, start out with an 8K + // estimated size. Note: we don't have to worry about freeing this data + // in this function. It will be freed on object destruction. + mImageBufferSize = 8192; + mImageBuffer = (uint8_t*)malloc(mImageBufferSize); + if (!mImageBuffer) { + png_destroy_write_struct(&mPNG, &mPNGinfo); + return NS_ERROR_OUT_OF_MEMORY; + } + mImageBufferUsed = 0; + + // set our callback for libpng to give us the data + png_set_write_fn(mPNG, this, WriteCallback, nullptr); + + // include alpha? + int colorType; + if ((aInputFormat == INPUT_FORMAT_HOSTARGB || + aInputFormat == INPUT_FORMAT_RGBA) && + useTransparency) + colorType = PNG_COLOR_TYPE_RGB_ALPHA; + else + colorType = PNG_COLOR_TYPE_RGB; + + png_set_IHDR(mPNG, mPNGinfo, aWidth, aHeight, 8, colorType, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + +#ifdef PNG_APNG_SUPPORTED + if (mIsAnimation) { + png_set_first_frame_is_hidden(mPNG, mPNGinfo, skipFirstFrame); + png_set_acTL(mPNG, mPNGinfo, numFrames, numPlays); + } +#endif + + // XXX: support PLTE, gAMA, tRNS, bKGD? + + png_write_info(mPNG, mPNGinfo); + + return NS_OK; +} + +// Returns the number of bytes in the image buffer used. +NS_IMETHODIMP +nsPNGEncoder::GetImageBufferUsed(uint32_t* aOutputSize) +{ + NS_ENSURE_ARG_POINTER(aOutputSize); + *aOutputSize = mImageBufferUsed; + return NS_OK; +} + +// Returns a pointer to the start of the image buffer +NS_IMETHODIMP +nsPNGEncoder::GetImageBuffer(char** aOutputBuffer) +{ + NS_ENSURE_ARG_POINTER(aOutputBuffer); + *aOutputBuffer = reinterpret_cast(mImageBuffer); + return NS_OK; +} + +NS_IMETHODIMP +nsPNGEncoder::AddImageFrame(const uint8_t* aData, + uint32_t aLength, // (unused, req'd by JS) + uint32_t aWidth, + uint32_t aHeight, + uint32_t aStride, + uint32_t aInputFormat, + const nsAString& aFrameOptions) +{ + bool useTransparency= true; + uint32_t delay_ms = 500; +#ifdef PNG_APNG_SUPPORTED + uint32_t dispose_op = PNG_DISPOSE_OP_NONE; + uint32_t blend_op = PNG_BLEND_OP_SOURCE; +#else + uint32_t dispose_op; + uint32_t blend_op; +#endif + uint32_t x_offset = 0, y_offset = 0; + + // must be initialized + if (mImageBuffer == nullptr) { + return NS_ERROR_NOT_INITIALIZED; + } + + // EndImageEncode was done, or some error occurred earlier + if (!mPNG) { + return NS_BASE_STREAM_CLOSED; + } + + // validate input format + if (aInputFormat != INPUT_FORMAT_RGB && + aInputFormat != INPUT_FORMAT_RGBA && + aInputFormat != INPUT_FORMAT_HOSTARGB) + return NS_ERROR_INVALID_ARG; + + // libpng's error handler jumps back here upon an error. + if (setjmp(png_jmpbuf(mPNG))) { + png_destroy_write_struct(&mPNG, &mPNGinfo); + return NS_ERROR_FAILURE; + } + + // parse and check any provided output options + nsresult rv = ParseOptions(aFrameOptions, &useTransparency, nullptr, + nullptr, nullptr, &dispose_op, &blend_op, + &delay_ms, &x_offset, &y_offset); + if (rv != NS_OK) { + return rv; + } + +#ifdef PNG_APNG_SUPPORTED + if (mIsAnimation) { + // XXX the row pointers arg (#3) is unused, can it be removed? + png_write_frame_head(mPNG, mPNGinfo, nullptr, + aWidth, aHeight, x_offset, y_offset, + delay_ms, 1000, dispose_op, blend_op); + } +#endif + + // Stride is the padded width of each row, so it better be longer + // (I'm afraid people will not understand what stride means, so + // check it well) + if ((aInputFormat == INPUT_FORMAT_RGB && + aStride < aWidth * 3) || + ((aInputFormat == INPUT_FORMAT_RGBA || + aInputFormat == INPUT_FORMAT_HOSTARGB) && + aStride < aWidth * 4)) { + NS_WARNING("Invalid stride for InitFromData/AddImageFrame"); + return NS_ERROR_INVALID_ARG; + } + +#ifdef PNG_WRITE_FILTER_SUPPORTED + png_set_filter(mPNG, PNG_FILTER_TYPE_BASE, PNG_FILTER_VALUE_NONE); +#endif + + // write each row: if we add more input formats, we may want to + // generalize the conversions + if (aInputFormat == INPUT_FORMAT_HOSTARGB) { + // PNG requires RGBA with post-multiplied alpha, so we need to + // convert + UniquePtr row = MakeUnique(aWidth * 4); + for (uint32_t y = 0; y < aHeight; y++) { + ConvertHostARGBRow(&aData[y * aStride], row.get(), aWidth, useTransparency); + png_write_row(mPNG, row.get()); + } + } else if (aInputFormat == INPUT_FORMAT_RGBA && !useTransparency) { + // RBGA, but we need to strip the alpha + UniquePtr row = MakeUnique(aWidth * 4); + for (uint32_t y = 0; y < aHeight; y++) { + StripAlpha(&aData[y * aStride], row.get(), aWidth); + png_write_row(mPNG, row.get()); + } + } else if (aInputFormat == INPUT_FORMAT_RGB || + aInputFormat == INPUT_FORMAT_RGBA) { + // simple RBG(A), no conversion needed + for (uint32_t y = 0; y < aHeight; y++) { + png_write_row(mPNG, (uint8_t*)&aData[y * aStride]); + } + + } else { + NS_NOTREACHED("Bad format type"); + return NS_ERROR_INVALID_ARG; + } + +#ifdef PNG_APNG_SUPPORTED + if (mIsAnimation) { + png_write_frame_tail(mPNG, mPNGinfo); + } +#endif + + return NS_OK; +} + + +NS_IMETHODIMP +nsPNGEncoder::EndImageEncode() +{ + // must be initialized + if (mImageBuffer == nullptr) { + return NS_ERROR_NOT_INITIALIZED; + } + + // EndImageEncode has already been called, or some error + // occurred earlier + if (!mPNG) { + return NS_BASE_STREAM_CLOSED; + } + + // libpng's error handler jumps back here upon an error. + if (setjmp(png_jmpbuf(mPNG))) { + png_destroy_write_struct(&mPNG, &mPNGinfo); + return NS_ERROR_FAILURE; + } + + png_write_end(mPNG, mPNGinfo); + png_destroy_write_struct(&mPNG, &mPNGinfo); + + mFinished = true; + NotifyListener(); + + // if output callback can't get enough memory, it will free our buffer + if (!mImageBuffer) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + + +nsresult +nsPNGEncoder::ParseOptions(const nsAString& aOptions, + bool* useTransparency, + bool* skipFirstFrame, + uint32_t* numFrames, + uint32_t* numPlays, + uint32_t* frameDispose, + uint32_t* frameBlend, + uint32_t* frameDelay, + uint32_t* offsetX, + uint32_t* offsetY) +{ +#ifdef PNG_APNG_SUPPORTED + // Make a copy of aOptions, because strtok() will modify it. + nsAutoCString optionsCopy; + optionsCopy.Assign(NS_ConvertUTF16toUTF8(aOptions)); + char* options = optionsCopy.BeginWriting(); + + while (char* token = nsCRT::strtok(options, ";", &options)) { + // If there's an '=' character, split the token around it. + char* equals = token; + char* value = nullptr; + while(*equals != '=' && *equals) { + ++equals; + } + if (*equals == '=') { + value = equals + 1; + } + + if (value) { + *equals = '\0'; // temporary null + } + + // transparency=[yes|no|none] + if (nsCRT::strcmp(token, "transparency") == 0 && useTransparency) { + if (!value) { + return NS_ERROR_INVALID_ARG; + } + + if (nsCRT::strcmp(value, "none") == 0 || + nsCRT::strcmp(value, "no") == 0) { + *useTransparency = false; + } else if (nsCRT::strcmp(value, "yes") == 0) { + *useTransparency = true; + } else { + return NS_ERROR_INVALID_ARG; + } + + // skipfirstframe=[yes|no] + } else if (nsCRT::strcmp(token, "skipfirstframe") == 0 && + skipFirstFrame) { + if (!value) { + return NS_ERROR_INVALID_ARG; + } + + if (nsCRT::strcmp(value, "no") == 0) { + *skipFirstFrame = false; + } else if (nsCRT::strcmp(value, "yes") == 0) { + *skipFirstFrame = true; + } else { + return NS_ERROR_INVALID_ARG; + } + + // frames=# + } else if (nsCRT::strcmp(token, "frames") == 0 && numFrames) { + if (!value) { + return NS_ERROR_INVALID_ARG; + } + + if (PR_sscanf(value, "%u", numFrames) != 1) { + return NS_ERROR_INVALID_ARG; + } + + // frames=0 is nonsense. + if (*numFrames == 0) { + return NS_ERROR_INVALID_ARG; + } + + // plays=# + } else if (nsCRT::strcmp(token, "plays") == 0 && numPlays) { + if (!value) { + return NS_ERROR_INVALID_ARG; + } + + // plays=0 to loop forever, otherwise play sequence specified + // number of times + if (PR_sscanf(value, "%u", numPlays) != 1) { + return NS_ERROR_INVALID_ARG; + } + + // dispose=[none|background|previous] + } else if (nsCRT::strcmp(token, "dispose") == 0 && frameDispose) { + if (!value) { + return NS_ERROR_INVALID_ARG; + } + + if (nsCRT::strcmp(value, "none") == 0) { + *frameDispose = PNG_DISPOSE_OP_NONE; + } else if (nsCRT::strcmp(value, "background") == 0) { + *frameDispose = PNG_DISPOSE_OP_BACKGROUND; + } else if (nsCRT::strcmp(value, "previous") == 0) { + *frameDispose = PNG_DISPOSE_OP_PREVIOUS; + } else { + return NS_ERROR_INVALID_ARG; + } + + // blend=[source|over] + } else if (nsCRT::strcmp(token, "blend") == 0 && frameBlend) { + if (!value) { + return NS_ERROR_INVALID_ARG; + } + + if (nsCRT::strcmp(value, "source") == 0) { + *frameBlend = PNG_BLEND_OP_SOURCE; + } else if (nsCRT::strcmp(value, "over") == 0) { + *frameBlend = PNG_BLEND_OP_OVER; + } else { + return NS_ERROR_INVALID_ARG; + } + + // delay=# (in ms) + } else if (nsCRT::strcmp(token, "delay") == 0 && frameDelay) { + if (!value) { + return NS_ERROR_INVALID_ARG; + } + + if (PR_sscanf(value, "%u", frameDelay) != 1) { + return NS_ERROR_INVALID_ARG; + } + + // xoffset=# + } else if (nsCRT::strcmp(token, "xoffset") == 0 && offsetX) { + if (!value) { + return NS_ERROR_INVALID_ARG; + } + + if (PR_sscanf(value, "%u", offsetX) != 1) { + return NS_ERROR_INVALID_ARG; + } + + // yoffset=# + } else if (nsCRT::strcmp(token, "yoffset") == 0 && offsetY) { + if (!value) { + return NS_ERROR_INVALID_ARG; + } + + if (PR_sscanf(value, "%u", offsetY) != 1) { + return NS_ERROR_INVALID_ARG; + } + + // unknown token name + } else + return NS_ERROR_INVALID_ARG; + + if (value) { + *equals = '='; // restore '=' so strtok doesn't get lost + } + } + +#endif + return NS_OK; +} + + +NS_IMETHODIMP +nsPNGEncoder::Close() +{ + if (mImageBuffer != nullptr) { + free(mImageBuffer); + mImageBuffer = nullptr; + mImageBufferSize = 0; + mImageBufferUsed = 0; + mImageBufferReadPoint = 0; + } + return NS_OK; +} + +NS_IMETHODIMP +nsPNGEncoder::Available(uint64_t* _retval) +{ + if (!mImageBuffer) { + return NS_BASE_STREAM_CLOSED; + } + + *_retval = mImageBufferUsed - mImageBufferReadPoint; + return NS_OK; +} + +NS_IMETHODIMP +nsPNGEncoder::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) +{ + return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval); +} + +NS_IMETHODIMP +nsPNGEncoder::ReadSegments(nsWriteSegmentFun aWriter, + void* aClosure, uint32_t aCount, + uint32_t* _retval) +{ + // Avoid another thread reallocing the buffer underneath us + ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor); + + uint32_t maxCount = mImageBufferUsed - mImageBufferReadPoint; + if (maxCount == 0) { + *_retval = 0; + return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK; + } + + if (aCount > maxCount) { + aCount = maxCount; + } + + nsresult rv = + aWriter(this, aClosure, + reinterpret_cast(mImageBuffer+mImageBufferReadPoint), + 0, aCount, _retval); + if (NS_SUCCEEDED(rv)) { + NS_ASSERTION(*_retval <= aCount, "bad write count"); + mImageBufferReadPoint += *_retval; + } + + // errors returned from the writer end here! + return NS_OK; +} + +NS_IMETHODIMP +nsPNGEncoder::IsNonBlocking(bool* _retval) +{ + *_retval = true; + return NS_OK; +} + +NS_IMETHODIMP +nsPNGEncoder::AsyncWait(nsIInputStreamCallback* aCallback, + uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aTarget) +{ + if (aFlags != 0) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + if (mCallback || mCallbackTarget) { + return NS_ERROR_UNEXPECTED; + } + + mCallbackTarget = aTarget; + // 0 means "any number of bytes except 0" + mNotifyThreshold = aRequestedCount; + if (!aRequestedCount) { + mNotifyThreshold = 1024; // We don't want to notify incessantly + } + + // We set the callback absolutely last, because NotifyListener uses it to + // determine if someone needs to be notified. If we don't set it last, + // NotifyListener might try to fire off a notification to a null target + // which will generally cause non-threadsafe objects to be used off the main + // thread + mCallback = aCallback; + + // What we are being asked for may be present already + NotifyListener(); + return NS_OK; +} + +NS_IMETHODIMP +nsPNGEncoder::CloseWithStatus(nsresult aStatus) +{ + return Close(); +} + +// nsPNGEncoder::ConvertHostARGBRow +// +// Our colors are stored with premultiplied alphas, but PNGs use +// post-multiplied alpha. This swaps to PNG-style alpha. +// +// Copied from gfx/cairo/cairo/src/cairo-png.c + +void +nsPNGEncoder::ConvertHostARGBRow(const uint8_t* aSrc, uint8_t* aDest, + uint32_t aPixelWidth, + bool aUseTransparency) +{ + uint32_t pixelStride = aUseTransparency ? 4 : 3; + for (uint32_t x = 0; x < aPixelWidth; x++) { + const uint32_t& pixelIn = ((const uint32_t*)(aSrc))[x]; + uint8_t* pixelOut = &aDest[x * pixelStride]; + + uint8_t alpha = (pixelIn & 0xff000000) >> 24; + pixelOut[pixelStride - 1] = alpha; // overwritten below if pixelStride == 3 + if (alpha == 255) { + pixelOut[0] = (pixelIn & 0xff0000) >> 16; + pixelOut[1] = (pixelIn & 0x00ff00) >> 8; + pixelOut[2] = (pixelIn & 0x0000ff) ; + } else if (alpha == 0) { + pixelOut[0] = pixelOut[1] = pixelOut[2] = 0; + } else { + pixelOut[0] = (((pixelIn & 0xff0000) >> 16) * 255 + alpha / 2) / alpha; + pixelOut[1] = (((pixelIn & 0x00ff00) >> 8) * 255 + alpha / 2) / alpha; + pixelOut[2] = (((pixelIn & 0x0000ff) ) * 255 + alpha / 2) / alpha; + } + } +} + + +// nsPNGEncoder::StripAlpha +// +// Input is RGBA, output is RGB + +void +nsPNGEncoder::StripAlpha(const uint8_t* aSrc, uint8_t* aDest, + uint32_t aPixelWidth) +{ + for (uint32_t x = 0; x < aPixelWidth; x++) { + const uint8_t* pixelIn = &aSrc[x * 4]; + uint8_t* pixelOut = &aDest[x * 3]; + pixelOut[0] = pixelIn[0]; + pixelOut[1] = pixelIn[1]; + pixelOut[2] = pixelIn[2]; + } +} + + +// nsPNGEncoder::WarningCallback + +void +nsPNGEncoder::WarningCallback(png_structp png_ptr, + png_const_charp warning_msg) +{ + MOZ_LOG(sPNGEncoderLog, LogLevel::Warning, + ("libpng warning: %s\n", warning_msg)); +} + + +// nsPNGEncoder::ErrorCallback + +void +nsPNGEncoder::ErrorCallback(png_structp png_ptr, + png_const_charp error_msg) +{ + MOZ_LOG(sPNGEncoderLog, LogLevel::Error, ("libpng error: %s\n", error_msg)); + png_longjmp(png_ptr, 1); +} + +// nsPNGEncoder::WriteCallback + +void // static +nsPNGEncoder::WriteCallback(png_structp png, png_bytep data, + png_size_t size) +{ + nsPNGEncoder* that = static_cast(png_get_io_ptr(png)); + if (!that->mImageBuffer) { + return; + } + + if (that->mImageBufferUsed + size > that->mImageBufferSize) { + // When we're reallocing the buffer we need to take the lock to ensure + // that nobody is trying to read from the buffer we are destroying + ReentrantMonitorAutoEnter autoEnter(that->mReentrantMonitor); + + // expand buffer, just double each time + that->mImageBufferSize *= 2; + uint8_t* newBuf = (uint8_t*)realloc(that->mImageBuffer, + that->mImageBufferSize); + if (!newBuf) { + // can't resize, just zero (this will keep us from writing more) + free(that->mImageBuffer); + that->mImageBuffer = nullptr; + that->mImageBufferSize = 0; + that->mImageBufferUsed = 0; + return; + } + that->mImageBuffer = newBuf; + } + memcpy(&that->mImageBuffer[that->mImageBufferUsed], data, size); + that->mImageBufferUsed += size; + that->NotifyListener(); +} + +void +nsPNGEncoder::NotifyListener() +{ + // We might call this function on multiple threads (any threads that call + // AsyncWait and any that do encoding) so we lock to avoid notifying the + // listener twice about the same data (which generally leads to a truncated + // image). + ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor); + + if (mCallback && + (mImageBufferUsed - mImageBufferReadPoint >= mNotifyThreshold || + mFinished)) { + nsCOMPtr callback; + if (mCallbackTarget) { + callback = NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget); + } else { + callback = mCallback; + } + + NS_ASSERTION(callback, "Shouldn't fail to make the callback"); + // Null the callback first because OnInputStreamReady could reenter + // AsyncWait + mCallback = nullptr; + mCallbackTarget = nullptr; + mNotifyThreshold = 0; + + callback->OnInputStreamReady(this); + } +} diff --git a/image/encoders/png/nsPNGEncoder.h b/image/encoders/png/nsPNGEncoder.h new file mode 100644 index 000000000..95e7d5c19 --- /dev/null +++ b/image/encoders/png/nsPNGEncoder.h @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsPNGEncoder_h + +#include + +#include "imgIEncoder.h" +#include "nsCOMPtr.h" + +#include "mozilla/Attributes.h" +#include "mozilla/ReentrantMonitor.h" + +#define NS_PNGENCODER_CID \ +{ /* 38d1592e-b81e-432b-86f8-471878bbfe07 */ \ + 0x38d1592e, \ + 0xb81e, \ + 0x432b, \ + {0x86, 0xf8, 0x47, 0x18, 0x78, 0xbb, 0xfe, 0x07} \ +} + +// Provides PNG encoding functionality. Use InitFromData() to do the +// encoding. See that function definition for encoding options. + +class nsPNGEncoder final : public imgIEncoder +{ + typedef mozilla::ReentrantMonitor ReentrantMonitor; +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_IMGIENCODER + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + + nsPNGEncoder(); + +protected: + ~nsPNGEncoder(); + nsresult ParseOptions(const nsAString& aOptions, + bool* useTransparency, + bool* skipFirstFrame, + uint32_t* numAnimatedFrames, + uint32_t* numIterations, + uint32_t* frameDispose, + uint32_t* frameBlend, + uint32_t* frameDelay, + uint32_t* offsetX, + uint32_t* offsetY); + void ConvertHostARGBRow(const uint8_t* aSrc, uint8_t* aDest, + uint32_t aPixelWidth, bool aUseTransparency); + void StripAlpha(const uint8_t* aSrc, uint8_t* aDest, + uint32_t aPixelWidth); + static void WarningCallback(png_structp png_ptr, png_const_charp warning_msg); + static void ErrorCallback(png_structp png_ptr, png_const_charp error_msg); + static void WriteCallback(png_structp png, png_bytep data, png_size_t size); + void NotifyListener(); + + png_struct* mPNG; + png_info* mPNGinfo; + + bool mIsAnimation; + bool mFinished; + + // image buffer + uint8_t* mImageBuffer; + uint32_t mImageBufferSize; + uint32_t mImageBufferUsed; + + uint32_t mImageBufferReadPoint; + + nsCOMPtr mCallback; + nsCOMPtr mCallbackTarget; + uint32_t mNotifyThreshold; + + // nsPNGEncoder is designed to allow one thread to pump data into it while + // another reads from it. We lock to ensure that the buffer remains + // append-only while we read from it (that it is not realloced) and to + // ensure that only one thread dispatches a callback for each call to + // AsyncWait. + ReentrantMonitor mReentrantMonitor; +}; +#endif // nsPNGEncoder_h diff --git a/image/imgFrame.cpp b/image/imgFrame.cpp new file mode 100644 index 000000000..9c5bf9b99 --- /dev/null +++ b/image/imgFrame.cpp @@ -0,0 +1,939 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "imgFrame.h" +#include "ImageRegion.h" +#include "ShutdownTracker.h" + +#include "prenv.h" + +#include "gfx2DGlue.h" +#include "gfxPlatform.h" +#include "gfxPrefs.h" +#include "gfxUtils.h" +#include "gfxAlphaRecovery.h" + +#include "GeckoProfiler.h" +#include "MainThreadUtils.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/gfx/Tools.h" +#include "mozilla/Likely.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Telemetry.h" +#include "nsMargin.h" +#include "nsThreadUtils.h" + + +namespace mozilla { + +using namespace gfx; + +namespace image { + +static void +VolatileBufferRelease(void* vbuf) +{ + delete static_cast*>(vbuf); +} + +static int32_t +VolatileSurfaceStride(const IntSize& size, SurfaceFormat format) +{ + // Stride must be a multiple of four or cairo will complain. + return (size.width * BytesPerPixel(format) + 0x3) & ~0x3; +} + +static already_AddRefed +CreateLockedSurface(VolatileBuffer* vbuf, + const IntSize& size, + SurfaceFormat format) +{ + VolatileBufferPtr* vbufptr = + new VolatileBufferPtr(vbuf); + MOZ_ASSERT(!vbufptr->WasBufferPurged(), "Expected image data!"); + + const int32_t stride = VolatileSurfaceStride(size, format); + + // The VolatileBufferPtr is held by this DataSourceSurface. + RefPtr surf = + Factory::CreateWrappingDataSourceSurface(*vbufptr, stride, size, format, + &VolatileBufferRelease, + static_cast(vbufptr)); + if (!surf) { + delete vbufptr; + return nullptr; + } + + return surf.forget(); +} + +static already_AddRefed +AllocateBufferForImage(const IntSize& size, SurfaceFormat format) +{ + int32_t stride = VolatileSurfaceStride(size, format); + RefPtr buf = new VolatileBuffer(); + if (buf->Init(stride * size.height, + size_t(1) << gfxAlphaRecovery::GoodAlignmentLog2())) { + return buf.forget(); + } + + return nullptr; +} + +static bool +ClearSurface(VolatileBuffer* aVBuf, const IntSize& aSize, SurfaceFormat aFormat) +{ + VolatileBufferPtr vbufptr(aVBuf); + if (vbufptr.WasBufferPurged()) { + NS_WARNING("VolatileBuffer was purged"); + return false; + } + + int32_t stride = VolatileSurfaceStride(aSize, aFormat); + if (aFormat == SurfaceFormat::B8G8R8X8) { + // Skia doesn't support RGBX surfaces, so ensure the alpha value is set + // to opaque white. While it would be nice to only do this for Skia, + // imgFrame can run off main thread and past shutdown where + // we might not have gfxPlatform, so just memset everytime instead. + memset(vbufptr, 0xFF, stride * aSize.height); + } else if (aVBuf->OnHeap()) { + // We only need to memset it if the buffer was allocated on the heap. + // Otherwise, it's allocated via mmap and refers to a zeroed page and will + // be COW once it's written to. + memset(vbufptr, 0, stride * aSize.height); + } + + return true; +} + +// Returns true if an image of aWidth x aHeight is allowed and legal. +static bool +AllowedImageSize(int32_t aWidth, int32_t aHeight) +{ + // reject over-wide or over-tall images + const int32_t k64KLimit = 0x0000FFFF; + if (MOZ_UNLIKELY(aWidth > k64KLimit || aHeight > k64KLimit )) { + NS_WARNING("image too big"); + return false; + } + + // protect against invalid sizes + if (MOZ_UNLIKELY(aHeight <= 0 || aWidth <= 0)) { + return false; + } + + // check to make sure we don't overflow a 32-bit + CheckedInt32 requiredBytes = CheckedInt32(aWidth) * CheckedInt32(aHeight) * 4; + if (MOZ_UNLIKELY(!requiredBytes.isValid())) { + NS_WARNING("width or height too large"); + return false; + } +#if defined(XP_MACOSX) + // CoreGraphics is limited to images < 32K in *height*, so clamp all surfaces + // on the Mac to that height + if (MOZ_UNLIKELY(aHeight > SHRT_MAX)) { + NS_WARNING("image too big"); + return false; + } +#endif + return true; +} + +static bool AllowedImageAndFrameDimensions(const nsIntSize& aImageSize, + const nsIntRect& aFrameRect) +{ + if (!AllowedImageSize(aImageSize.width, aImageSize.height)) { + return false; + } + if (!AllowedImageSize(aFrameRect.width, aFrameRect.height)) { + return false; + } + nsIntRect imageRect(0, 0, aImageSize.width, aImageSize.height); + if (!imageRect.Contains(aFrameRect)) { + NS_WARNING("Animated image frame does not fit inside bounds of image"); + } + return true; +} + +imgFrame::imgFrame() + : mMonitor("imgFrame") + , mDecoded(0, 0, 0, 0) + , mLockCount(0) + , mTimeout(FrameTimeout::FromRawMilliseconds(100)) + , mDisposalMethod(DisposalMethod::NOT_SPECIFIED) + , mBlendMethod(BlendMethod::OVER) + , mHasNoAlpha(false) + , mAborted(false) + , mFinished(false) + , mOptimizable(false) + , mPalettedImageData(nullptr) + , mPaletteDepth(0) + , mNonPremult(false) + , mCompositingFailed(false) +{ +} + +imgFrame::~imgFrame() +{ +#ifdef DEBUG + MonitorAutoLock lock(mMonitor); + MOZ_ASSERT(mAborted || AreAllPixelsWritten()); + MOZ_ASSERT(mAborted || mFinished); +#endif + + free(mPalettedImageData); + mPalettedImageData = nullptr; +} + +nsresult +imgFrame::InitForDecoder(const nsIntSize& aImageSize, + const nsIntRect& aRect, + SurfaceFormat aFormat, + uint8_t aPaletteDepth /* = 0 */, + bool aNonPremult /* = false */) +{ + // Assert for properties that should be verified by decoders, + // warn for properties related to bad content. + if (!AllowedImageAndFrameDimensions(aImageSize, aRect)) { + NS_WARNING("Should have legal image size"); + mAborted = true; + return NS_ERROR_FAILURE; + } + + mImageSize = aImageSize; + mFrameRect = aRect; + + // We only allow a non-trivial frame rect (i.e., a frame rect that doesn't + // cover the entire image) for paletted animation frames. We never draw those + // frames directly; we just use FrameAnimator to composite them and produce a + // BGRA surface that we actually draw. We enforce this here to make sure that + // imgFrame::Draw(), which is responsible for drawing all other kinds of + // frames, never has to deal with a non-trivial frame rect. + if (aPaletteDepth == 0 && + !mFrameRect.IsEqualEdges(IntRect(IntPoint(), mImageSize))) { + MOZ_ASSERT_UNREACHABLE("Creating a non-paletted imgFrame with a " + "non-trivial frame rect"); + return NS_ERROR_FAILURE; + } + + mFormat = aFormat; + mPaletteDepth = aPaletteDepth; + mNonPremult = aNonPremult; + + if (aPaletteDepth != 0) { + // We're creating for a paletted image. + if (aPaletteDepth > 8) { + NS_WARNING("Should have legal palette depth"); + NS_ERROR("This Depth is not supported"); + mAborted = true; + return NS_ERROR_FAILURE; + } + + // Use the fallible allocator here. Paletted images always use 1 byte per + // pixel, so calculating the amount of memory we need is straightforward. + size_t dataSize = PaletteDataLength() + mFrameRect.Area(); + mPalettedImageData = static_cast(calloc(dataSize, sizeof(uint8_t))); + if (!mPalettedImageData) { + NS_WARNING("Call to calloc for paletted image data should succeed"); + } + NS_ENSURE_TRUE(mPalettedImageData, NS_ERROR_OUT_OF_MEMORY); + } else { + MOZ_ASSERT(!mImageSurface, "Called imgFrame::InitForDecoder() twice?"); + + mVBuf = AllocateBufferForImage(mFrameRect.Size(), mFormat); + if (!mVBuf) { + mAborted = true; + return NS_ERROR_OUT_OF_MEMORY; + } + + mImageSurface = CreateLockedSurface(mVBuf, mFrameRect.Size(), mFormat); + + if (!mImageSurface) { + NS_WARNING("Failed to create ImageSurface"); + mAborted = true; + return NS_ERROR_OUT_OF_MEMORY; + } + + if (!ClearSurface(mVBuf, mFrameRect.Size(), mFormat)) { + NS_WARNING("Could not clear allocated buffer"); + mAborted = true; + return NS_ERROR_OUT_OF_MEMORY; + } + } + + return NS_OK; +} + +nsresult +imgFrame::InitWithDrawable(gfxDrawable* aDrawable, + const nsIntSize& aSize, + const SurfaceFormat aFormat, + SamplingFilter aSamplingFilter, + uint32_t aImageFlags, + gfx::BackendType aBackend) +{ + // Assert for properties that should be verified by decoders, + // warn for properties related to bad content. + if (!AllowedImageSize(aSize.width, aSize.height)) { + NS_WARNING("Should have legal image size"); + mAborted = true; + return NS_ERROR_FAILURE; + } + + mImageSize = aSize; + mFrameRect = IntRect(IntPoint(0, 0), aSize); + + mFormat = aFormat; + mPaletteDepth = 0; + + RefPtr target; + + bool canUseDataSurface = + gfxPlatform::GetPlatform()->CanRenderContentToDataSurface(); + + if (canUseDataSurface) { + // It's safe to use data surfaces for content on this platform, so we can + // get away with using volatile buffers. + MOZ_ASSERT(!mImageSurface, "Called imgFrame::InitWithDrawable() twice?"); + + mVBuf = AllocateBufferForImage(mFrameRect.Size(), mFormat); + if (!mVBuf) { + mAborted = true; + return NS_ERROR_OUT_OF_MEMORY; + } + + int32_t stride = VolatileSurfaceStride(mFrameRect.Size(), mFormat); + VolatileBufferPtr ptr(mVBuf); + if (!ptr) { + mAborted = true; + return NS_ERROR_OUT_OF_MEMORY; + } + + mImageSurface = CreateLockedSurface(mVBuf, mFrameRect.Size(), mFormat); + + if (!mImageSurface) { + NS_WARNING("Failed to create ImageSurface"); + mAborted = true; + return NS_ERROR_OUT_OF_MEMORY; + } + + if (!ClearSurface(mVBuf, mFrameRect.Size(), mFormat)) { + NS_WARNING("Could not clear allocated buffer"); + mAborted = true; + return NS_ERROR_OUT_OF_MEMORY; + } + + target = gfxPlatform::CreateDrawTargetForData( + ptr, + mFrameRect.Size(), + stride, + mFormat); + } else { + // We can't use data surfaces for content, so we'll create an offscreen + // surface instead. This means if someone later calls RawAccessRef(), we + // may have to do an expensive readback, but we warned callers about that in + // the documentation for this method. + MOZ_ASSERT(!mOptSurface, "Called imgFrame::InitWithDrawable() twice?"); + + if (gfxPlatform::GetPlatform()->SupportsAzureContentForType(aBackend)) { + target = gfxPlatform::GetPlatform()-> + CreateDrawTargetForBackend(aBackend, mFrameRect.Size(), mFormat); + } else { + target = gfxPlatform::GetPlatform()-> + CreateOffscreenContentDrawTarget(mFrameRect.Size(), mFormat); + } + } + + if (!target || !target->IsValid()) { + mAborted = true; + return NS_ERROR_OUT_OF_MEMORY; + } + + // Draw using the drawable the caller provided. + RefPtr ctx = gfxContext::CreateOrNull(target); + MOZ_ASSERT(ctx); // Already checked the draw target above. + gfxUtils::DrawPixelSnapped(ctx, aDrawable, mFrameRect.Size(), + ImageRegion::Create(ThebesRect(mFrameRect)), + mFormat, aSamplingFilter, aImageFlags); + + if (canUseDataSurface && !mImageSurface) { + NS_WARNING("Failed to create VolatileDataSourceSurface"); + mAborted = true; + return NS_ERROR_OUT_OF_MEMORY; + } + + if (!canUseDataSurface) { + // We used an offscreen surface, which is an "optimized" surface from + // imgFrame's perspective. + mOptSurface = target->Snapshot(); + } + + // If we reach this point, we should regard ourselves as complete. + mDecoded = GetRect(); + mFinished = true; + +#ifdef DEBUG + MonitorAutoLock lock(mMonitor); + MOZ_ASSERT(AreAllPixelsWritten()); +#endif + + return NS_OK; +} + +bool +imgFrame::CanOptimizeOpaqueImage() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!ShutdownTracker::ShutdownHasStarted()); + mMonitor.AssertCurrentThreadOwns(); + + // If we're using a surface format with alpha but the image has no alpha, + // change the format. This doesn't change the underlying data at all, but + // allows DrawTargets to avoid blending when drawing known opaque images. + // This optimization is free and safe, so we always do it when we can except + // if we have a Skia backend. Skia doesn't support RGBX so ensure we don't + // optimize to a RGBX surface. + return mHasNoAlpha && mFormat == SurfaceFormat::B8G8R8A8 && mImageSurface && + (gfxPlatform::GetPlatform()->GetDefaultContentBackend() != BackendType::SKIA); +} + +nsresult +imgFrame::Optimize(DrawTarget* aTarget) +{ + MOZ_ASSERT(NS_IsMainThread()); + mMonitor.AssertCurrentThreadOwns(); + + if (mLockCount > 0 || !mOptimizable) { + // Don't optimize right now. + return NS_OK; + } + + // Check whether image optimization is disabled -- not thread safe! + static bool gDisableOptimize = false; + static bool hasCheckedOptimize = false; + if (!hasCheckedOptimize) { + if (PR_GetEnv("MOZ_DISABLE_IMAGE_OPTIMIZE")) { + gDisableOptimize = true; + } + hasCheckedOptimize = true; + } + + // Don't optimize during shutdown because gfxPlatform may not be available. + if (ShutdownTracker::ShutdownHasStarted()) { + return NS_OK; + } + + // This optimization is basically free, so we perform it even if optimization is disabled. + if (CanOptimizeOpaqueImage()) { + mFormat = SurfaceFormat::B8G8R8X8; + mImageSurface = CreateLockedSurface(mVBuf, mFrameRect.Size(), mFormat); + } + + if (gDisableOptimize) { + return NS_OK; + } + + if (mPalettedImageData || mOptSurface) { + return NS_OK; + } + + // XXX(seth): It's currently unclear if there's any reason why we can't + // optimize non-premult surfaces. We should look into removing this. + if (mNonPremult) { + return NS_OK; + } + + mOptSurface = gfxPlatform::GetPlatform() + ->ScreenReferenceDrawTarget()->OptimizeSourceSurface(mImageSurface); + if (mOptSurface == mImageSurface) { + mOptSurface = nullptr; + } + + if (mOptSurface) { + // There's no reason to keep our volatile buffer around at all if we have an + // optimized surface. Release our reference to it. This will leave + // |mVBufPtr| and |mImageSurface| as the only things keeping it alive, so + // it'll get freed below. + mVBuf = nullptr; + } + + // Release all strong references to our volatile buffer's memory. This will + // allow the operating system to free the memory if it needs to. + mVBufPtr = nullptr; + mImageSurface = nullptr; + mOptimizable = false; + + return NS_OK; +} + +DrawableFrameRef +imgFrame::DrawableRef() +{ + return DrawableFrameRef(this); +} + +RawAccessFrameRef +imgFrame::RawAccessRef() +{ + return RawAccessFrameRef(this); +} + +void +imgFrame::SetRawAccessOnly() +{ + AssertImageDataLocked(); + + // Lock our data and throw away the key. + LockImageData(); +} + + +imgFrame::SurfaceWithFormat +imgFrame::SurfaceForDrawing(bool aDoPartialDecode, + bool aDoTile, + ImageRegion& aRegion, + SourceSurface* aSurface) +{ + MOZ_ASSERT(NS_IsMainThread()); + mMonitor.AssertCurrentThreadOwns(); + + if (!aDoPartialDecode) { + return SurfaceWithFormat(new gfxSurfaceDrawable(aSurface, mImageSize), + mFormat); + } + + gfxRect available = gfxRect(mDecoded.x, mDecoded.y, mDecoded.width, + mDecoded.height); + + if (aDoTile) { + // Create a temporary surface. + // Give this surface an alpha channel because there are + // transparent pixels in the padding or undecoded area + RefPtr target = + gfxPlatform::GetPlatform()-> + CreateOffscreenContentDrawTarget(mImageSize, SurfaceFormat::B8G8R8A8); + if (!target) { + return SurfaceWithFormat(); + } + + SurfacePattern pattern(aSurface, + aRegion.GetExtendMode(), + Matrix::Translation(mDecoded.x, mDecoded.y)); + target->FillRect(ToRect(aRegion.Intersect(available).Rect()), pattern); + + RefPtr newsurf = target->Snapshot(); + return SurfaceWithFormat(new gfxSurfaceDrawable(newsurf, mImageSize), + target->GetFormat()); + } + + // Not tiling, and we have a surface, so we can account for + // a partial decode just by twiddling parameters. + aRegion = aRegion.Intersect(available); + IntSize availableSize(mDecoded.width, mDecoded.height); + + return SurfaceWithFormat(new gfxSurfaceDrawable(aSurface, availableSize), + mFormat); +} + +bool imgFrame::Draw(gfxContext* aContext, const ImageRegion& aRegion, + SamplingFilter aSamplingFilter, uint32_t aImageFlags) +{ + PROFILER_LABEL("imgFrame", "Draw", + js::ProfileEntry::Category::GRAPHICS); + + MOZ_ASSERT(NS_IsMainThread()); + NS_ASSERTION(!aRegion.Rect().IsEmpty(), "Drawing empty region!"); + NS_ASSERTION(!aRegion.IsRestricted() || + !aRegion.Rect().Intersect(aRegion.Restriction()).IsEmpty(), + "We must be allowed to sample *some* source pixels!"); + MOZ_ASSERT(mFrameRect.IsEqualEdges(IntRect(IntPoint(), mImageSize)), + "Directly drawing an image with a non-trivial frame rect!"); + + if (mPalettedImageData) { + MOZ_ASSERT_UNREACHABLE("Directly drawing a paletted image!"); + return false; + } + + MonitorAutoLock lock(mMonitor); + + // Possibly convert this image into a GPU texture, this may also cause our + // mImageSurface to be released and the OS to release the underlying memory. + Optimize(aContext->GetDrawTarget()); + + bool doPartialDecode = !AreAllPixelsWritten(); + + RefPtr surf = GetSourceSurfaceInternal(); + if (!surf) { + return false; + } + + gfxRect imageRect(0, 0, mImageSize.width, mImageSize.height); + bool doTile = !imageRect.Contains(aRegion.Rect()) && + !(aImageFlags & imgIContainer::FLAG_CLAMP); + + ImageRegion region(aRegion); + SurfaceWithFormat surfaceResult = + SurfaceForDrawing(doPartialDecode, doTile, region, surf); + + if (surfaceResult.IsValid()) { + gfxUtils::DrawPixelSnapped(aContext, surfaceResult.mDrawable, + imageRect.Size(), region, surfaceResult.mFormat, + aSamplingFilter, aImageFlags); + } + return true; +} + +nsresult +imgFrame::ImageUpdated(const nsIntRect& aUpdateRect) +{ + MonitorAutoLock lock(mMonitor); + return ImageUpdatedInternal(aUpdateRect); +} + +nsresult +imgFrame::ImageUpdatedInternal(const nsIntRect& aUpdateRect) +{ + mMonitor.AssertCurrentThreadOwns(); + + mDecoded.UnionRect(mDecoded, aUpdateRect); + + // Clamp to the frame rect to ensure that decoder bugs don't result in a + // decoded rect that extends outside the bounds of the frame rect. + mDecoded.IntersectRect(mDecoded, mFrameRect); + + return NS_OK; +} + +void +imgFrame::Finish(Opacity aFrameOpacity /* = Opacity::SOME_TRANSPARENCY */, + DisposalMethod aDisposalMethod /* = DisposalMethod::KEEP */, + FrameTimeout aTimeout + /* = FrameTimeout::FromRawMilliseconds(0) */, + BlendMethod aBlendMethod /* = BlendMethod::OVER */, + const Maybe& aBlendRect /* = Nothing() */) +{ + MonitorAutoLock lock(mMonitor); + MOZ_ASSERT(mLockCount > 0, "Image data should be locked"); + + if (aFrameOpacity == Opacity::FULLY_OPAQUE) { + mHasNoAlpha = true; + Telemetry::Accumulate(Telemetry::IMAGE_DECODE_OPAQUE_BGRA, + mFormat == SurfaceFormat::B8G8R8A8); + } + + mDisposalMethod = aDisposalMethod; + mTimeout = aTimeout; + mBlendMethod = aBlendMethod; + mBlendRect = aBlendRect; + ImageUpdatedInternal(GetRect()); + mFinished = true; + + // The image is now complete, wake up anyone who's waiting. + mMonitor.NotifyAll(); +} + +uint32_t +imgFrame::GetImageBytesPerRow() const +{ + mMonitor.AssertCurrentThreadOwns(); + + if (mVBuf) { + return mFrameRect.width * BytesPerPixel(mFormat); + } + + if (mPaletteDepth) { + return mFrameRect.width; + } + + return 0; +} + +uint32_t +imgFrame::GetImageDataLength() const +{ + return GetImageBytesPerRow() * mFrameRect.height; +} + +void +imgFrame::GetImageData(uint8_t** aData, uint32_t* aLength) const +{ + MonitorAutoLock lock(mMonitor); + GetImageDataInternal(aData, aLength); +} + +void +imgFrame::GetImageDataInternal(uint8_t** aData, uint32_t* aLength) const +{ + mMonitor.AssertCurrentThreadOwns(); + MOZ_ASSERT(mLockCount > 0, "Image data should be locked"); + + if (mImageSurface) { + *aData = mVBufPtr; + MOZ_ASSERT(*aData, + "mImageSurface is non-null, but mVBufPtr is null in GetImageData"); + } else if (mPalettedImageData) { + *aData = mPalettedImageData + PaletteDataLength(); + MOZ_ASSERT(*aData, + "mPalettedImageData is non-null, but result is null in GetImageData"); + } else { + MOZ_ASSERT(false, + "Have neither mImageSurface nor mPalettedImageData in GetImageData"); + *aData = nullptr; + } + + *aLength = GetImageDataLength(); +} + +uint8_t* +imgFrame::GetImageData() const +{ + uint8_t* data; + uint32_t length; + GetImageData(&data, &length); + return data; +} + +bool +imgFrame::GetIsPaletted() const +{ + return mPalettedImageData != nullptr; +} + +void +imgFrame::GetPaletteData(uint32_t** aPalette, uint32_t* length) const +{ + AssertImageDataLocked(); + + if (!mPalettedImageData) { + *aPalette = nullptr; + *length = 0; + } else { + *aPalette = (uint32_t*) mPalettedImageData; + *length = PaletteDataLength(); + } +} + +uint32_t* +imgFrame::GetPaletteData() const +{ + uint32_t* data; + uint32_t length; + GetPaletteData(&data, &length); + return data; +} + +nsresult +imgFrame::LockImageData() +{ + MonitorAutoLock lock(mMonitor); + + MOZ_ASSERT(mLockCount >= 0, "Unbalanced locks and unlocks"); + if (mLockCount < 0) { + return NS_ERROR_FAILURE; + } + + mLockCount++; + + // If we are not the first lock, there's nothing to do. + if (mLockCount != 1) { + return NS_OK; + } + + // If we're the first lock, but have an image surface, we're OK. + if (mImageSurface) { + mVBufPtr = mVBuf; + return NS_OK; + } + + // Paletted images don't have surfaces, so there's nothing to do. + if (mPalettedImageData) { + return NS_OK; + } + + MOZ_ASSERT_UNREACHABLE("It's illegal to re-lock an optimized imgFrame"); + return NS_ERROR_FAILURE; +} + +void +imgFrame::AssertImageDataLocked() const +{ +#ifdef DEBUG + MonitorAutoLock lock(mMonitor); + MOZ_ASSERT(mLockCount > 0, "Image data should be locked"); +#endif +} + +nsresult +imgFrame::UnlockImageData() +{ + MonitorAutoLock lock(mMonitor); + + MOZ_ASSERT(mLockCount > 0, "Unlocking an unlocked image!"); + if (mLockCount <= 0) { + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(mLockCount > 1 || mFinished || mAborted, + "Should have Finish()'d or aborted before unlocking"); + + mLockCount--; + + return NS_OK; +} + +void +imgFrame::SetOptimizable() +{ + AssertImageDataLocked(); + MonitorAutoLock lock(mMonitor); + mOptimizable = true; +} + +already_AddRefed +imgFrame::GetSourceSurface() +{ + MonitorAutoLock lock(mMonitor); + return GetSourceSurfaceInternal(); +} + +already_AddRefed +imgFrame::GetSourceSurfaceInternal() +{ + mMonitor.AssertCurrentThreadOwns(); + + if (mOptSurface) { + if (mOptSurface->IsValid()) { + RefPtr surf(mOptSurface); + return surf.forget(); + } else { + mOptSurface = nullptr; + } + } + + if (mImageSurface) { + RefPtr surf(mImageSurface); + return surf.forget(); + } + + if (!mVBuf) { + return nullptr; + } + + VolatileBufferPtr buf(mVBuf); + if (buf.WasBufferPurged()) { + return nullptr; + } + + return CreateLockedSurface(mVBuf, mFrameRect.Size(), mFormat); +} + +AnimationData +imgFrame::GetAnimationData() const +{ + MonitorAutoLock lock(mMonitor); + MOZ_ASSERT(mLockCount > 0, "Image data should be locked"); + + uint8_t* data; + if (mPalettedImageData) { + data = mPalettedImageData; + } else { + uint32_t length; + GetImageDataInternal(&data, &length); + } + + bool hasAlpha = mFormat == SurfaceFormat::B8G8R8A8; + + return AnimationData(data, PaletteDataLength(), mTimeout, GetRect(), + mBlendMethod, mBlendRect, mDisposalMethod, hasAlpha); +} + +void +imgFrame::Abort() +{ + MonitorAutoLock lock(mMonitor); + + mAborted = true; + + // Wake up anyone who's waiting. + mMonitor.NotifyAll(); +} + +bool +imgFrame::IsAborted() const +{ + MonitorAutoLock lock(mMonitor); + return mAborted; +} + +bool +imgFrame::IsFinished() const +{ + MonitorAutoLock lock(mMonitor); + return mFinished; +} + +void +imgFrame::WaitUntilFinished() const +{ + MonitorAutoLock lock(mMonitor); + + while (true) { + // Return if we're aborted or complete. + if (mAborted || mFinished) { + return; + } + + // Not complete yet, so we'll have to wait. + mMonitor.Wait(); + } +} + +bool +imgFrame::AreAllPixelsWritten() const +{ + mMonitor.AssertCurrentThreadOwns(); + return mDecoded.IsEqualInterior(mFrameRect); +} + +bool imgFrame::GetCompositingFailed() const +{ + MOZ_ASSERT(NS_IsMainThread()); + return mCompositingFailed; +} + +void +imgFrame::SetCompositingFailed(bool val) +{ + MOZ_ASSERT(NS_IsMainThread()); + mCompositingFailed = val; +} + +void +imgFrame::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + size_t& aHeapSizeOut, + size_t& aNonHeapSizeOut) const +{ + MonitorAutoLock lock(mMonitor); + + if (mPalettedImageData) { + aHeapSizeOut += aMallocSizeOf(mPalettedImageData); + } + if (mImageSurface) { + aHeapSizeOut += aMallocSizeOf(mImageSurface); + } + if (mOptSurface) { + aHeapSizeOut += aMallocSizeOf(mOptSurface); + } + + if (mVBuf) { + aHeapSizeOut += aMallocSizeOf(mVBuf); + aHeapSizeOut += mVBuf->HeapSizeOfExcludingThis(aMallocSizeOf); + aNonHeapSizeOut += mVBuf->NonHeapSizeOfExcludingThis(); + } +} + +} // namespace image +} // namespace mozilla diff --git a/image/imgFrame.h b/image/imgFrame.h new file mode 100644 index 000000000..e864aca7f --- /dev/null +++ b/image/imgFrame.h @@ -0,0 +1,599 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_imgFrame_h +#define mozilla_image_imgFrame_h + +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Monitor.h" +#include "mozilla/Move.h" +#include "mozilla/VolatileBuffer.h" +#include "gfxDrawable.h" +#include "imgIContainer.h" +#include "MainThreadUtils.h" + +namespace mozilla { +namespace image { + +class ImageRegion; +class DrawableFrameRef; +class RawAccessFrameRef; + +enum class BlendMethod : int8_t { + // All color components of the frame, including alpha, overwrite the current + // contents of the frame's output buffer region. + SOURCE, + + // The frame should be composited onto the output buffer based on its alpha, + // using a simple OVER operation. + OVER +}; + +enum class DisposalMethod : int8_t { + CLEAR_ALL = -1, // Clear the whole image, revealing what's underneath. + NOT_SPECIFIED, // Leave the frame and let the new frame draw on top. + KEEP, // Leave the frame and let the new frame draw on top. + CLEAR, // Clear the frame's area, revealing what's underneath. + RESTORE_PREVIOUS // Restore the previous (composited) frame. +}; + +enum class Opacity : uint8_t { + FULLY_OPAQUE, + SOME_TRANSPARENCY +}; + +/** + * FrameTimeout wraps a frame timeout value (measured in milliseconds) after + * first normalizing it. This normalization is necessary because some tools + * generate incorrect frame timeout values which we nevertheless have to + * support. For this reason, code that deals with frame timeouts should always + * use a FrameTimeout value rather than the raw value from the image header. + */ +struct FrameTimeout +{ + /** + * @return a FrameTimeout of zero. This should be used only for math + * involving FrameTimeout values. You can't obtain a zero FrameTimeout from + * FromRawMilliseconds(). + */ + static FrameTimeout Zero() { return FrameTimeout(0); } + + /// @return an infinite FrameTimeout. + static FrameTimeout Forever() { return FrameTimeout(-1); } + + /// @return a FrameTimeout obtained by normalizing a raw timeout value. + static FrameTimeout FromRawMilliseconds(int32_t aRawMilliseconds) + { + // Normalize all infinite timeouts to the same value. + if (aRawMilliseconds < 0) { + return FrameTimeout::Forever(); + } + + // Very small timeout values are problematic for two reasons: we don't want + // to burn energy redrawing animated images extremely fast, and broken tools + // generate these values when they actually want a "default" value, so such + // images won't play back right without normalization. For some context, + // see bug 890743, bug 125137, bug 139677, and bug 207059. The historical + // behavior of IE and Opera was: + // IE 6/Win: + // 10 - 50ms is normalized to 100ms. + // >50ms is used unnormalized. + // Opera 7 final/Win: + // 10ms is normalized to 100ms. + // >10ms is used unnormalized. + if (aRawMilliseconds >= 0 && aRawMilliseconds <= 10 ) { + return FrameTimeout(100); + } + + // The provided timeout value is OK as-is. + return FrameTimeout(aRawMilliseconds); + } + + bool operator==(const FrameTimeout& aOther) const + { + return mTimeout == aOther.mTimeout; + } + + bool operator!=(const FrameTimeout& aOther) const { return !(*this == aOther); } + + FrameTimeout operator+(const FrameTimeout& aOther) + { + if (*this == Forever() || aOther == Forever()) { + return Forever(); + } + + return FrameTimeout(mTimeout + aOther.mTimeout); + } + + FrameTimeout& operator+=(const FrameTimeout& aOther) + { + *this = *this + aOther; + return *this; + } + + /** + * @return this FrameTimeout's value in milliseconds. Illegal to call on a + * an infinite FrameTimeout value. + */ + uint32_t AsMilliseconds() const + { + if (*this == Forever()) { + MOZ_ASSERT_UNREACHABLE("Calling AsMilliseconds() on an infinite FrameTimeout"); + return 100; // Fail to something sane. + } + + return uint32_t(mTimeout); + } + + /** + * @return this FrameTimeout value encoded so that non-negative values + * represent a timeout in milliseconds, and -1 represents an infinite + * timeout. + * + * XXX(seth): This is a backwards compatibility hack that should be removed. + */ + int32_t AsEncodedValueDeprecated() const { return mTimeout; } + +private: + explicit FrameTimeout(int32_t aTimeout) + : mTimeout(aTimeout) + { } + + int32_t mTimeout; +}; + +/** + * AnimationData contains all of the information necessary for using an imgFrame + * as part of an animation. + * + * It includes pointers to the raw image data of the underlying imgFrame, but + * does not own that data. A RawAccessFrameRef for the underlying imgFrame must + * outlive the AnimationData for it to remain valid. + */ +struct AnimationData +{ + AnimationData(uint8_t* aRawData, uint32_t aPaletteDataLength, + FrameTimeout aTimeout, const nsIntRect& aRect, + BlendMethod aBlendMethod, const Maybe& aBlendRect, + DisposalMethod aDisposalMethod, bool aHasAlpha) + : mRawData(aRawData) + , mPaletteDataLength(aPaletteDataLength) + , mTimeout(aTimeout) + , mRect(aRect) + , mBlendMethod(aBlendMethod) + , mBlendRect(aBlendRect) + , mDisposalMethod(aDisposalMethod) + , mHasAlpha(aHasAlpha) + { } + + uint8_t* mRawData; + uint32_t mPaletteDataLength; + FrameTimeout mTimeout; + nsIntRect mRect; + BlendMethod mBlendMethod; + Maybe mBlendRect; + DisposalMethod mDisposalMethod; + bool mHasAlpha; +}; + +class imgFrame +{ + typedef gfx::Color Color; + typedef gfx::DataSourceSurface DataSourceSurface; + typedef gfx::DrawTarget DrawTarget; + typedef gfx::SamplingFilter SamplingFilter; + typedef gfx::IntPoint IntPoint; + typedef gfx::IntRect IntRect; + typedef gfx::IntSize IntSize; + typedef gfx::SourceSurface SourceSurface; + typedef gfx::SurfaceFormat SurfaceFormat; + +public: + MOZ_DECLARE_REFCOUNTED_TYPENAME(imgFrame) + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(imgFrame) + + imgFrame(); + + /** + * Initialize this imgFrame with an empty surface and prepare it for being + * written to by a decoder. + * + * This is appropriate for use with decoded images, but it should not be used + * when drawing content into an imgFrame, as it may use a different graphics + * backend than normal content drawing. + */ + nsresult InitForDecoder(const nsIntSize& aImageSize, + const nsIntRect& aRect, + SurfaceFormat aFormat, + uint8_t aPaletteDepth = 0, + bool aNonPremult = false); + + nsresult InitForDecoder(const nsIntSize& aSize, + SurfaceFormat aFormat, + uint8_t aPaletteDepth = 0) + { + return InitForDecoder(aSize, nsIntRect(0, 0, aSize.width, aSize.height), + aFormat, aPaletteDepth); + } + + + /** + * Initialize this imgFrame with a new surface and draw the provided + * gfxDrawable into it. + * + * This is appropriate to use when drawing content into an imgFrame, as it + * uses the same graphics backend as normal content drawing. The downside is + * that the underlying surface may not be stored in a volatile buffer on all + * platforms, and raw access to the surface (using RawAccessRef()) may be much + * more expensive than in the InitForDecoder() case. + * + * aBackend specifies the DrawTarget backend type this imgFrame is supposed + * to be drawn to. + */ + nsresult InitWithDrawable(gfxDrawable* aDrawable, + const nsIntSize& aSize, + const SurfaceFormat aFormat, + SamplingFilter aSamplingFilter, + uint32_t aImageFlags, + gfx::BackendType aBackend); + + DrawableFrameRef DrawableRef(); + RawAccessFrameRef RawAccessRef(); + + /** + * Make this imgFrame permanently available for raw access. + * + * This is irrevocable, and should be avoided whenever possible, since it + * prevents this imgFrame from being optimized and makes it impossible for its + * volatile buffer to be freed. + * + * It is an error to call this without already holding a RawAccessFrameRef to + * this imgFrame. + */ + void SetRawAccessOnly(); + + bool Draw(gfxContext* aContext, const ImageRegion& aRegion, + SamplingFilter aSamplingFilter, uint32_t aImageFlags); + + nsresult ImageUpdated(const nsIntRect& aUpdateRect); + + /** + * Mark this imgFrame as completely decoded, and set final options. + * + * You must always call either Finish() or Abort() before releasing the last + * RawAccessFrameRef pointing to an imgFrame. + * + * @param aFrameOpacity Whether this imgFrame is opaque. + * @param aDisposalMethod For animation frames, how this imgFrame is cleared + * from the compositing frame before the next frame is + * displayed. + * @param aTimeout For animation frames, the timeout before the next + * frame is displayed. + * @param aBlendMethod For animation frames, a blending method to be used + * when compositing this frame. + * @param aBlendRect For animation frames, if present, the subrect in + * which @aBlendMethod applies. Outside of this + * subrect, BlendMethod::OVER is always used. + */ + void Finish(Opacity aFrameOpacity = Opacity::SOME_TRANSPARENCY, + DisposalMethod aDisposalMethod = DisposalMethod::KEEP, + FrameTimeout aTimeout = FrameTimeout::FromRawMilliseconds(0), + BlendMethod aBlendMethod = BlendMethod::OVER, + const Maybe& aBlendRect = Nothing()); + + /** + * Mark this imgFrame as aborted. This informs the imgFrame that if it isn't + * completely decoded now, it never will be. + * + * You must always call either Finish() or Abort() before releasing the last + * RawAccessFrameRef pointing to an imgFrame. + */ + void Abort(); + + /** + * Returns true if this imgFrame has been aborted. + */ + bool IsAborted() const; + + /** + * Returns true if this imgFrame is completely decoded. + */ + bool IsFinished() const; + + /** + * Blocks until this imgFrame is either completely decoded, or is marked as + * aborted. + * + * Note that calling this on the main thread _blocks the main thread_. Be very + * careful in your use of this method to avoid excessive main thread jank or + * deadlock. + */ + void WaitUntilFinished() const; + + /** + * Returns the number of bytes per pixel this imgFrame requires. This is a + * worst-case value that does not take into account the effects of format + * changes caused by Optimize(), since an imgFrame is not optimized throughout + * its lifetime. + */ + uint32_t GetBytesPerPixel() const { return GetIsPaletted() ? 1 : 4; } + + IntSize GetImageSize() const { return mImageSize; } + IntRect GetRect() const { return mFrameRect; } + IntSize GetSize() const { return mFrameRect.Size(); } + void GetImageData(uint8_t** aData, uint32_t* length) const; + uint8_t* GetImageData() const; + + bool GetIsPaletted() const; + void GetPaletteData(uint32_t** aPalette, uint32_t* length) const; + uint32_t* GetPaletteData() const; + uint8_t GetPaletteDepth() const { return mPaletteDepth; } + + AnimationData GetAnimationData() const; + + bool GetCompositingFailed() const; + void SetCompositingFailed(bool val); + + void SetOptimizable(); + + already_AddRefed GetSourceSurface(); + + void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, size_t& aHeapSizeOut, + size_t& aNonHeapSizeOut) const; + +private: // methods + + ~imgFrame(); + + nsresult LockImageData(); + nsresult UnlockImageData(); + bool CanOptimizeOpaqueImage(); + nsresult Optimize(gfx::DrawTarget* aTarget); + + void AssertImageDataLocked() const; + + bool AreAllPixelsWritten() const; + nsresult ImageUpdatedInternal(const nsIntRect& aUpdateRect); + void GetImageDataInternal(uint8_t** aData, uint32_t* length) const; + uint32_t GetImageBytesPerRow() const; + uint32_t GetImageDataLength() const; + already_AddRefed GetSourceSurfaceInternal(); + + uint32_t PaletteDataLength() const + { + return mPaletteDepth ? (size_t(1) << mPaletteDepth) * sizeof(uint32_t) + : 0; + } + + struct SurfaceWithFormat { + RefPtr mDrawable; + SurfaceFormat mFormat; + SurfaceWithFormat() { } + SurfaceWithFormat(gfxDrawable* aDrawable, SurfaceFormat aFormat) + : mDrawable(aDrawable), mFormat(aFormat) + { } + bool IsValid() { return !!mDrawable; } + }; + + SurfaceWithFormat SurfaceForDrawing(bool aDoPartialDecode, + bool aDoTile, + ImageRegion& aRegion, + SourceSurface* aSurface); + +private: // data + friend class DrawableFrameRef; + friend class RawAccessFrameRef; + friend class UnlockImageDataRunnable; + + ////////////////////////////////////////////////////////////////////////////// + // Thread-safe mutable data, protected by mMonitor. + ////////////////////////////////////////////////////////////////////////////// + + mutable Monitor mMonitor; + + RefPtr mImageSurface; + RefPtr mOptSurface; + + RefPtr mVBuf; + VolatileBufferPtr mVBufPtr; + + nsIntRect mDecoded; + + //! Number of RawAccessFrameRefs currently alive for this imgFrame. + int32_t mLockCount; + + //! The timeout for this frame. + FrameTimeout mTimeout; + + DisposalMethod mDisposalMethod; + BlendMethod mBlendMethod; + Maybe mBlendRect; + SurfaceFormat mFormat; + + bool mHasNoAlpha; + bool mAborted; + bool mFinished; + bool mOptimizable; + + + ////////////////////////////////////////////////////////////////////////////// + // Effectively const data, only mutated in the Init methods. + ////////////////////////////////////////////////////////////////////////////// + + IntSize mImageSize; + IntRect mFrameRect; + + // The palette and image data for images that are paletted, since Cairo + // doesn't support these images. + // The paletted data comes first, then the image data itself. + // Total length is PaletteDataLength() + GetImageDataLength(). + uint8_t* mPalettedImageData; + uint8_t mPaletteDepth; + + bool mNonPremult; + + + ////////////////////////////////////////////////////////////////////////////// + // Main-thread-only mutable data. + ////////////////////////////////////////////////////////////////////////////// + + bool mCompositingFailed; +}; + +/** + * A reference to an imgFrame that holds the imgFrame's surface in memory, + * allowing drawing. If you have a DrawableFrameRef |ref| and |if (ref)| returns + * true, then calls to Draw() and GetSourceSurface() are guaranteed to succeed. + */ +class DrawableFrameRef final +{ +public: + DrawableFrameRef() { } + + explicit DrawableFrameRef(imgFrame* aFrame) + : mFrame(aFrame) + , mRef(aFrame->mVBuf) + { + if (mRef.WasBufferPurged()) { + mFrame = nullptr; + mRef = nullptr; + } + } + + DrawableFrameRef(DrawableFrameRef&& aOther) + : mFrame(aOther.mFrame.forget()) + , mRef(Move(aOther.mRef)) + { } + + DrawableFrameRef& operator=(DrawableFrameRef&& aOther) + { + MOZ_ASSERT(this != &aOther, "Self-moves are prohibited"); + mFrame = aOther.mFrame.forget(); + mRef = Move(aOther.mRef); + return *this; + } + + explicit operator bool() const { return bool(mFrame); } + + imgFrame* operator->() + { + MOZ_ASSERT(mFrame); + return mFrame; + } + + const imgFrame* operator->() const + { + MOZ_ASSERT(mFrame); + return mFrame; + } + + imgFrame* get() { return mFrame; } + const imgFrame* get() const { return mFrame; } + + void reset() + { + mFrame = nullptr; + mRef = nullptr; + } + +private: + DrawableFrameRef(const DrawableFrameRef& aOther) = delete; + + RefPtr mFrame; + VolatileBufferPtr mRef; +}; + +/** + * A reference to an imgFrame that holds the imgFrame's surface in memory in a + * format appropriate for access as raw data. If you have a RawAccessFrameRef + * |ref| and |if (ref)| is true, then calls to GetImageData() and + * GetPaletteData() are guaranteed to succeed. This guarantee is stronger than + * DrawableFrameRef, so everything that a valid DrawableFrameRef guarantees is + * also guaranteed by a valid RawAccessFrameRef. + * + * This may be considerably more expensive than is necessary just for drawing, + * so only use this when you need to read or write the raw underlying image data + * that the imgFrame holds. + * + * Once all an imgFrame's RawAccessFrameRefs go out of scope, new + * RawAccessFrameRefs cannot be created. + */ +class RawAccessFrameRef final +{ +public: + RawAccessFrameRef() { } + + explicit RawAccessFrameRef(imgFrame* aFrame) + : mFrame(aFrame) + { + MOZ_ASSERT(mFrame, "Need a frame"); + + if (NS_FAILED(mFrame->LockImageData())) { + mFrame->UnlockImageData(); + mFrame = nullptr; + } + } + + RawAccessFrameRef(RawAccessFrameRef&& aOther) + : mFrame(aOther.mFrame.forget()) + { } + + ~RawAccessFrameRef() + { + if (mFrame) { + mFrame->UnlockImageData(); + } + } + + RawAccessFrameRef& operator=(RawAccessFrameRef&& aOther) + { + MOZ_ASSERT(this != &aOther, "Self-moves are prohibited"); + + if (mFrame) { + mFrame->UnlockImageData(); + } + + mFrame = aOther.mFrame.forget(); + + return *this; + } + + explicit operator bool() const { return bool(mFrame); } + + imgFrame* operator->() + { + MOZ_ASSERT(mFrame); + return mFrame.get(); + } + + const imgFrame* operator->() const + { + MOZ_ASSERT(mFrame); + return mFrame; + } + + imgFrame* get() { return mFrame; } + const imgFrame* get() const { return mFrame; } + + void reset() + { + if (mFrame) { + mFrame->UnlockImageData(); + } + mFrame = nullptr; + } + +private: + RawAccessFrameRef(const RawAccessFrameRef& aOther) = delete; + + RefPtr mFrame; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_imgFrame_h diff --git a/image/imgICache.idl b/image/imgICache.idl new file mode 100644 index 000000000..2a12e2bb1 --- /dev/null +++ b/image/imgICache.idl @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface imgIRequest; +interface nsIDocument; +interface nsIDOMDocument; +interface nsIProperties; +interface nsIURI; + +/** + * imgICache interface + * + * @author Stuart Parmenter + * @version 0.1 + * @see imagelib2 + */ +[scriptable, builtinclass, uuid(bfdf23ff-378e-402e-8a6c-840f0c82b6c3)] +interface imgICache : nsISupports +{ + /** + * Evict images from the cache. + * + * @param chrome If TRUE, evict only chrome images. + * If FALSE, evict everything except chrome images. + */ + void clearCache(in boolean chrome); + + /** + * Find Properties + * Used to get properties such as 'type' and 'content-disposition' + * 'type' is a nsISupportsCString containing the images' mime type such as + * 'image/png' + * 'content-disposition' will be a nsISupportsCString containing the header + * If you call this before any data has been loaded from a URI, it will + * succeed, but come back empty. + * + * Hopefully this will be removed with bug 805119 + * + * @param uri The URI to look up. + * @param doc Optional pointer to the document that the cache entry belongs to. + * @returns NULL if the URL was not found in the cache + */ + [must_use] + nsIProperties findEntryProperties(in nsIURI uri, + [optional] in nsIDOMDocument doc); + + /** + * Make this cache instance respect private browsing notifications. This + * entails clearing the chrome and content caches whenever the + * last-pb-context-exited notification is observed. + */ + void respectPrivacyNotifications(); + + /** + * Clear the image cache for a document. Controlled documents are responsible + * for doing this manually when they get destroyed. + */ + [noscript, notxpcom] + void clearCacheForControlledDocument(in nsIDocument doc); +}; diff --git a/image/imgIContainer.idl b/image/imgIContainer.idl new file mode 100644 index 000000000..20c949037 --- /dev/null +++ b/image/imgIContainer.idl @@ -0,0 +1,549 @@ +/** -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{C++ +#include "DrawResult.h" +#include "gfxContext.h" +#include "gfxMatrix.h" +#include "gfxRect.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/Maybe.h" +#include "mozilla/RefPtr.h" +#include "nsRect.h" +#include "nsSize.h" +#include "limits.h" + +class nsIDocument; + +namespace mozilla { +namespace layers { +class LayerManager; +class ImageContainer; +} +} + +class nsIFrame; + +namespace mozilla { +class TimeStamp; +class SVGImageContext; +} + +namespace mozilla { +namespace image { + +class ImageRegion; +struct Orientation; + +} +} + +%} + +native DrawResult(mozilla::image::DrawResult); +[ptr] native gfxContext(gfxContext); +[ref] native gfxMatrix(gfxMatrix); +[ref] native gfxRect(gfxRect); +[ref] native gfxSize(gfxSize); +native SamplingFilter(mozilla::gfx::SamplingFilter); +[ref] native nsIntRect(nsIntRect); +native nsIntRectByVal(nsIntRect); +[ref] native nsIntSize(nsIntSize); +native nsSize(nsSize); +[ptr] native nsIFrame(nsIFrame); +native TempRefImageContainer(already_AddRefed); +[ref] native ImageRegion(mozilla::image::ImageRegion); +[ptr] native LayerManager(mozilla::layers::LayerManager); +native Orientation(mozilla::image::Orientation); +[ref] native TimeStamp(mozilla::TimeStamp); +[ref] native MaybeSVGImageContext(mozilla::Maybe); +native TempRefSourceSurface(already_AddRefed); +native TempRefImgIContainer(already_AddRefed); +native nsIntSizeByVal(nsIntSize); +[ptr] native nsIDocument(nsIDocument); + + +/** + * imgIContainer is the interface that represents an image. It allows + * access to frames as Thebes surfaces. It also allows drawing of images + * onto Thebes contexts. + * + * Internally, imgIContainer also manages animation of images. + */ +[scriptable, builtinclass, uuid(a8dbee24-ff86-4755-b40e-51175caf31af)] +interface imgIContainer : nsISupports +{ + /** + * The width of the container rectangle. In the case of any error, + * zero is returned, and an exception will be thrown. + */ + readonly attribute int32_t width; + + /** + * The height of the container rectangle. In the case of any error, + * zero is returned, and an exception will be thrown. + */ + readonly attribute int32_t height; + + /** + * The intrinsic size of this image in appunits. If the image has no intrinsic + * size in a dimension, -1 will be returned for that dimension. In the case of + * any error, an exception will be thrown. + */ + [noscript] readonly attribute nsSize intrinsicSize; + + /** + * The (dimensionless) intrinsic ratio of this image. In the case of any + * error, an exception will be thrown. + */ + [noscript] readonly attribute nsSize intrinsicRatio; + + /** + * Given a size at which this image will be displayed, and the drawing + * parameters affecting how it will be drawn, returns the image size which + * should be used to draw to produce the highest quality result. This is the + * appropriate size, for example, to use as an input to the pixel snapping + * algorithm. + * + * For best results the size returned by this method should not be cached. It + * can change over time due to changes in the internal state of the image. + * + * @param aDest The size of the destination rect into which this image will be + * drawn, in device pixels. + * @param aWhichFrame Frame specifier of the FRAME_* variety. + * @param aSamplingFilter The filter to be used if we're scaling the image. + * @param aFlags Flags of the FLAG_* variety + */ + [notxpcom, nostdcall] nsIntSizeByVal + optimalImageSizeForDest([const] in gfxSize aDest, in uint32_t aWhichFrame, + in SamplingFilter aSamplingFilter, in uint32_t aFlags); + + /** + * Enumerated values for the 'type' attribute (below). + */ + const unsigned short TYPE_RASTER = 0; + const unsigned short TYPE_VECTOR = 1; + + /** + * The type of this image (one of the TYPE_* values above). + */ + [infallible] readonly attribute unsigned short type; + + /** + * Whether this image is animated. You can only be guaranteed that querying + * this will not throw if STATUS_DECODE_COMPLETE is set on the imgIRequest. + * + * @throws NS_ERROR_NOT_AVAILABLE if the animated state cannot be determined. + */ + readonly attribute boolean animated; + + /** + * Flags for imgIContainer operations. + * + * Meanings: + * + * FLAG_NONE: Lack of flags. + * + * FLAG_SYNC_DECODE: Forces synchronous/non-progressive decode of all + * available data before the call returns. + * + * FLAG_SYNC_DECODE_IF_FAST: Like FLAG_SYNC_DECODE, but requests a sync decode + * be performed only if ImageLib estimates it can be completed very quickly. + * + * FLAG_ASYNC_NOTIFY: Send notifications asynchronously, even if we decode + * synchronously beause of FLAG_SYNC_DECODE or FLAG_SYNC_DECODE_IF_FAST. + * + * FLAG_DECODE_NO_PREMULTIPLY_ALPHA: Do not premultiply alpha if + * it's not already premultiplied in the image data. + * + * FLAG_DECODE_NO_COLORSPACE_CONVERSION: Do not do any colorspace conversion; + * ignore any embedded profiles, and don't convert to any particular + * destination space. + * + * FLAG_CLAMP: Extend the image to the fill area by clamping image sample + * coordinates instead of by tiling. This only affects 'draw'. + * + * FLAG_HIGH_QUALITY_SCALING: A hint as to whether this image should be + * scaled using the high quality scaler. Do not set this if not drawing to + * a window or not listening to invalidations. Passing this flag will do two + * things: 1) request a decode of the image at the size asked for by the + * caller if one isn't already started or complete, and 2) allows a decoded + * frame of any size (it could be neither the requested size, nor the + * intrinsic size) to be substituted. + * + * FLAG_WANT_DATA_SURFACE: Can be passed to GetFrame when the caller wants a + * DataSourceSurface instead of a hardware accelerated surface. This can be + * important for performance (by avoiding an upload to/readback from the GPU) + * when the caller knows they want a SourceSurface of type DATA. + * + * FLAG_BYPASS_SURFACE_CACHE: Forces drawing to happen rather than taking + * cached rendering from the surface cache. This is used when we are printing, + * for example, where we want the vector commands from VectorImages to end up + * in the PDF output rather than a cached rendering at screen resolution. + * + * FLAG_FORCE_PRESERVEASPECTRATIO_NONE: Force scaling this image + * non-uniformly if necessary. This flag is for vector image only. A raster + * image should ignore this flag. While drawing a vector image with this + * flag, do not force uniform scaling even if its root node has a + * preserveAspectRatio attribute that would otherwise require uniform + * scaling , such as xMinYMin/ xMidYMin. Always scale the graphic content of + * the given image non-uniformly if necessary such that the image's + * viewBox (if specified or implied by height/width attributes) exactly + * matches the viewport rectangle. + * + * FLAG_FORCE_UNIFORM_SCALING: Signal to ClippedImage::OptimalSizeForDest that + * its returned size can only scale the image's size *uniformly* (by the same + * factor in each dimension). We need this flag when painting border-image + * section with SVG image source-data, if the SVG image has no viewBox and no + * intrinsic size. In such a case, we synthesize a viewport for the SVG image + * (a "window into SVG space") based on the border image area, and we need to + * be sure we don't subsequently scale that viewport in a way that distorts + * its contents by stretching them more in one dimension than the other. + */ + const unsigned long FLAG_NONE = 0x0; + const unsigned long FLAG_SYNC_DECODE = 0x1; + const unsigned long FLAG_SYNC_DECODE_IF_FAST = 0x2; + const unsigned long FLAG_ASYNC_NOTIFY = 0x4; + const unsigned long FLAG_DECODE_NO_PREMULTIPLY_ALPHA = 0x8; + const unsigned long FLAG_DECODE_NO_COLORSPACE_CONVERSION = 0x10; + const unsigned long FLAG_CLAMP = 0x20; + const unsigned long FLAG_HIGH_QUALITY_SCALING = 0x40; + const unsigned long FLAG_WANT_DATA_SURFACE = 0x80; + const unsigned long FLAG_BYPASS_SURFACE_CACHE = 0x100; + const unsigned long FLAG_FORCE_PRESERVEASPECTRATIO_NONE = 0x200; + const unsigned long FLAG_FORCE_UNIFORM_SCALING = 0x400; + + /** + * A constant specifying the default set of decode flags (i.e., the default + * values for FLAG_DECODE_*). + */ + const unsigned long DECODE_FLAGS_DEFAULT = 0; + + /** + * Constants for specifying various "special" frames. + * + * FRAME_FIRST: The first frame + * FRAME_CURRENT: The current frame + * + * FRAME_MAX_VALUE should be set to the value of the maximum constant above, + * as it is used for ensuring that a valid value was passed in. + */ + const unsigned long FRAME_FIRST = 0; + const unsigned long FRAME_CURRENT = 1; + const unsigned long FRAME_MAX_VALUE = 1; + + /** + * Get a surface for the given frame. This may be a platform-native, + * optimized surface, so you cannot inspect its pixel data. If you + * need that, use SourceSurface::GetDataSurface. + * + * @param aWhichFrame Frame specifier of the FRAME_* variety. + * @param aFlags Flags of the FLAG_* variety + */ + [noscript, notxpcom] TempRefSourceSurface getFrame(in uint32_t aWhichFrame, + in uint32_t aFlags); + + /** + * Get a surface for the given frame at the specified size. Matching the + * requested size is best effort; it's not guaranteed that the surface you get + * will be a perfect match. (Some reasons you may get a surface of a different + * size include: if you requested upscaling, if downscale-during-decode is + * disabled, or if you didn't request the first frame.) + * + * @param aSize The desired size. + * @param aWhichFrame Frame specifier of the FRAME_* variety. + * @param aFlags Flags of the FLAG_* variety + */ + [noscript, notxpcom] TempRefSourceSurface getFrameAtSize([const] in nsIntSize aSize, + in uint32_t aWhichFrame, + in uint32_t aFlags); + + /** + * Returns true if this image will draw opaquely right now if asked to draw + * with FLAG_HIGH_QUALITY_SCALING and otherwise default flags. If this image + * (when decoded) is opaque but no decoded frames are available then + * willDrawOpaqueNow will return false. + */ + [noscript, notxpcom] boolean willDrawOpaqueNow(); + + /** + * @return true if getImageContainer() is expected to return a valid + * ImageContainer when passed the given @Manager and @Flags + * parameters. + */ + [noscript, notxpcom] boolean isImageContainerAvailable(in LayerManager aManager, + in uint32_t aFlags); + /** + * Attempts to create an ImageContainer (and Image) containing the current + * frame. + * + * Avoid calling this unless you're actually going to layerize this image. + * + * @param aManager The LayerManager which will be used to create the + * ImageContainer. + * @param aFlags Decoding / drawing flags (in other words, FLAG_* flags). + * Currently only FLAG_SYNC_DECODE and FLAG_SYNC_DECODE_IF_FAST + * are supported. + * @return An ImageContainer for the current frame, or nullptr if one could + * not be created. + */ + [noscript, notxpcom] TempRefImageContainer getImageContainer(in LayerManager aManager, + in uint32_t aFlags); + + /** + * Draw the requested frame of this image onto the context specified. + * + * Drawing an image involves scaling it to a certain size (which may be + * implemented as a "smart" scale by substituting an HQ-scaled frame or + * rendering at a high DPI), and then selecting a region of that image to + * draw. That region is drawn onto the graphics context and in the process + * transformed by the context matrix, which determines the final area that is + * filled. The basic process looks like this: + * + * +------------------+ + * | Image | + * | | + * | intrinsic width | + * | X | + * | intrinsic height | + * +------------------+ + * / \ + * / \ + * / (scale to aSize) \ + * / \ + * +----------------------------+ + * | | + * | Scaled Image | + * | aSize.width X aSize.height | + * | | + * | +---------+ | + * | | aRegion | | + * | +---------+ | + * +-------(---------(----------+ + * | | + * / \ + * | (transform | + * / by aContext \ + * | matrix) | + * / \ + * +---------------------+ + * | | + * | Fill Rect | + * | | + * +---------------------+ + * + * The region may extend outside of the scaled image's boundaries. It's + * actually a region in tiled image space, which is formed by tiling the + * scaled image infinitely in every direction. Drawing with a region larger + * than the scaled image thus causes the filled area to contain multiple tiled + * copies of the image, which looks like this: + * + * .................................................... + * : : : : + * : Tile : Tile : Tile : + * : +------------[aRegion]------------+ : + * :........|.......:................:........|.......: + * : | : : | : + * : Ti|le : Scaled Image : Ti|le : + * : | : : | : + * :........|.......:................:........|.......: + * : +---------------------------------+ : + * : Ti|le : Tile : Ti|le : + * : / : : \ : + * :......(.........:................:..........).....: + * | | + * / \ + * | (transform by aContext matrix) | + * / \ + * +---------------------------------------------+ + * | : : | + * |.....:.................................:.....| + * | : : | + * | : Tiled Fill : | + * | : : | + * |.....:.................................:.....| + * | : : | + * +---------------------------------------------+ + * + * + * @param aContext The Thebes context to draw the image to. + * @param aSize The size to which the image should be scaled before drawing. + * This requirement may be satisfied using HQ scaled frames, + * selecting from different resolution layers, drawing at a + * higher DPI, or just performing additional scaling on the + * graphics context. Callers can use optimalImageSizeForDest() + * to determine the best choice for this parameter if they have + * no special size requirements. + * @param aRegion The region in tiled image space which will be drawn onto the + * graphics context. aRegion is in the coordinate space of the + * image after it has been scaled to aSize - that is, the image + * is scaled first, and then aRegion is applied. When aFlags + * includes FLAG_CLAMP, the image will be extended to this area + * by clamping image sample coordinates. Otherwise, the image + * will be automatically tiled as necessary. aRegion can also + * optionally contain a second region which restricts the set + * of pixels we're allowed to sample from when drawing; this + * is only of use to callers which need to draw with pixel + * snapping. + * @param aWhichFrame Frame specifier of the FRAME_* variety. + * @param aSamplingFilter The filter to be used if we're scaling the image. + * @param aSVGContext If specified, SVG-related rendering context, such as + * overridden attributes on the image document's root + * node, and the size of the viewport that the full image + * would occupy. Ignored for raster images. + * @param aFlags Flags of the FLAG_* variety + * @return A DrawResult value indicating whether and to what degree the + * drawing operation was successful. + */ + [noscript, notxpcom] DrawResult + draw(in gfxContext aContext, + [const] in nsIntSize aSize, + [const] in ImageRegion aRegion, + in uint32_t aWhichFrame, + in SamplingFilter aSamplingFilter, + [const] in MaybeSVGImageContext aSVGContext, + in uint32_t aFlags); + + /* + * Ensures that an image is decoding. Calling this function guarantees that + * the image will at some point fire off decode notifications. Images that + * can be decoded "quickly" according to some heuristic will be decoded + * synchronously. + */ + [noscript] void startDecoding(); + + /* + * This method triggers decoding for an image, but unlike startDecoding() it + * enables the caller to provide more detailed information about the decode + * request. + * + * @param aSize The size to which the image should be scaled while decoding, + * if possible. If the image cannot be scaled to this size while + * being decoded, it will be decoded at its intrinsic size. + * @param aFlags Flags of the FLAG_* variety. Only the decode flags + * (FLAG_DECODE_*) and FLAG_SYNC_DECODE (which will + * synchronously decode images that can be decoded "quickly", + * just like startDecoding() does) are accepted; others will be + * ignored. + */ + [noscript] void requestDecodeForSize([const] in nsIntSize aSize, + in uint32_t aFlags); + + /** + * Increments the lock count on the image. An image will not be discarded + * as long as the lock count is nonzero. Note that it is still possible for + * the image to be undecoded if decode-on-draw is enabled and the image + * was never drawn. + * + * Upon instantiation images have a lock count of zero. + */ + void lockImage(); + + /** + * Decreases the lock count on the image. If the lock count drops to zero, + * the image is allowed to discard its frame data to save memory. + * + * Upon instantiation images have a lock count of zero. It is an error to + * call this method without first having made a matching lockImage() call. + * In other words, the lock count is not allowed to be negative. + */ + void unlockImage(); + + /** + * If this image is unlocked, discard its decoded data. If the image is + * locked or has already been discarded, do nothing. + */ + void requestDiscard(); + + /** + * Indicates that this imgIContainer has been triggered to update + * its internal animation state. Likely this should only be called + * from within nsImageFrame or objects of similar type. + */ + [notxpcom] void requestRefresh([const] in TimeStamp aTime); + + /** + * Animation mode Constants + * 0 = normal + * 1 = don't animate + * 2 = loop once + */ + const short kNormalAnimMode = 0; + const short kDontAnimMode = 1; + const short kLoopOnceAnimMode = 2; + + attribute unsigned short animationMode; + + /* Methods to control animation */ + void resetAnimation(); + + /* + * Returns an index for the requested animation frame (either FRAME_FIRST or + * FRAME_CURRENT). + * + * The units of the index aren't specified, and may vary between different + * types of images. What you can rely on is that on all occasions when + * getFrameIndex(FRAME_CURRENT) returns a certain value, + * draw(..FRAME_CURRENT..) will draw the same frame. The same holds for + * FRAME_FIRST as well. + * + * @param aWhichFrame Frame specifier of the FRAME_* variety. + */ + [notxpcom] float getFrameIndex(in uint32_t aWhichFrame); + + /* + * Returns the inherent orientation of the image, as described in the image's + * metadata (e.g. EXIF). + */ + [notxpcom] Orientation getOrientation(); + + /* + * Returns the delay, in ms, between the first and second frame. If this + * returns 0, there is no delay between first and second frame (i.e., this + * image could render differently whenever it draws). + * + * If this image is not animated, or not known to be animated (see attribute + * animated), returns -1. + */ + [notxpcom] int32_t getFirstFrameDelay(); + + /* + * If this is an animated image that hasn't started animating already, this + * sets the animation's start time to the indicated time. + * + * This has no effect if the image isn't animated or it has started animating + * already; it also has no effect if the image format doesn't care about + * animation start time. + * + * In all cases, animation does not actually begin until startAnimation(), + * resetAnimation(), or requestRefresh() is called for the first time. + */ + [notxpcom] void setAnimationStartTime([const] in TimeStamp aTime); + + /* + * Given an invalidation rect in the coordinate system used by the decoder, + * returns an invalidation rect in image space. + * + * This is the identity transformation in most cases, but the result can + * differ if the image is wrapped by an ImageWrapper that changes its size + * or orientation. + */ + [notxpcom] nsIntRectByVal + getImageSpaceInvalidationRect([const] in nsIntRect aRect); + + /* + * Removes any ImageWrappers and returns the unwrapped base image. + */ + [notxpcom, nostdcall] TempRefImgIContainer unwrap(); + + /* + * Propagate the use counters (if any) from this container to the passed in + * document. + */ + [noscript, notxpcom] void propagateUseCounters(in nsIDocument aDocument); +}; diff --git a/image/imgIContainerDebug.idl b/image/imgIContainerDebug.idl new file mode 100644 index 000000000..426ae1df8 --- /dev/null +++ b/image/imgIContainerDebug.idl @@ -0,0 +1,25 @@ +/** -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * This interface is used in debug builds (and only there) in + * order to let automatic tests running JavaScript access + * internal state of imgContainers. This lets us test + * things like animation. + */ +[scriptable, builtinclass, uuid(52cbb839-6e63-4a70-b21a-1db4ca706c49)] +interface imgIContainerDebug : nsISupports +{ + /** + * The # of frames this imgContainer has been notified about. + * That is equal to the # of times the animation timer has + * fired, and is usually equal to the # of frames actually + * drawn (but actual drawing might be disabled). + */ + readonly attribute uint32_t framesNotified; +}; diff --git a/image/imgIEncoder.idl b/image/imgIEncoder.idl new file mode 100644 index 000000000..8d6eae76e --- /dev/null +++ b/image/imgIEncoder.idl @@ -0,0 +1,135 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIAsyncInputStream.idl" +#include "nsIEventTarget.idl" + +/** + * imgIEncoder interface + */ +[scriptable, uuid(4baa2d6e-fee7-42df-ae3f-5fbebc0c267c)] +interface imgIEncoder : nsIAsyncInputStream +{ + // Possible values for outputOptions. Multiple values are semicolon-separated. + // + // PNG: + // ---- + // transparency=[yes|no|none] -- default: "yes" + // Overrides default from input format. "no" and "none" are equivalent. + // + // + // APNG: + // ----- + // The following options can be used with startImageEncode(): + // + // transparency=[yes|no|none] -- default: "yes" + // Overrides default from input format. "no" and "none" are equivalent. + // skipfirstframe=[yes|no] -- default: "no" + // Controls display of the first frame in animations. PNG-only clients + // always display the first frame (and only that frame). + // frames=# -- default: "1" + // Total number of frames in the image. The first frame, even if skipped, + // is always included in the count. + // plays=# -- default: "0" + // Number of times to play the animation sequence. "0" will repeat + // forever. + // + // The following options can be used for each frame, with addImageFrame(): + // + // transparency=[yes|no|none] -- default: "yes" + // Overrides default from input format. "no" and "none" are equivalent. + // delay=# -- default: "500" + // Number of milliseconds to display the frame, before moving to the next + // frame. + // dispose=[none|background|previous] -- default: "none" + // What to do with the image's canvas before rendering the next frame. + // See APNG spec. + // blend=[source|over] -- default: "source" + // How to render the new frame on the canvas. See APNG spec. + // xoffset=# -- default: "0" + // yoffset=# -- default: "0" + // Where to draw the frame, relative to the canvas. + // + // + // JPEG: + // ----- + // + // quality=# -- default: "92" + // Quality of compression, 0-100 (worst-best). + // Quality >= 90 prevents down-sampling of the color channels. + + + // Possible values for input format (note that not all image formats + // support saving alpha channels): + + // Input is RGB each pixel is represented by three bytes: + // R, G, and B (in that order, regardless of host endianness) + const uint32_t INPUT_FORMAT_RGB = 0; + + // Input is RGB each pixel is represented by four bytes: + // R, G, and B (in that order, regardless of host endianness). + // POST-MULTIPLIED alpha us used (50% transparent red is 0xff000080) + const uint32_t INPUT_FORMAT_RGBA = 1; + + // Input is host-endian ARGB: On big-endian machines each pixel is therefore + // ARGB, and for little-endian machiens (Intel) each pixel is BGRA + // (This is used by canvas to match it's internal representation) + // + // PRE-MULTIPLIED alpha is used (That is, 50% transparent red is 0x80800000, + // not 0x80ff0000 + const uint32_t INPUT_FORMAT_HOSTARGB = 2; + + /* data - list of bytes in the format specified by inputFormat + * width - width in pixels + * height - height in pixels + * stride - number of bytes per row in the image + * Normally (width*3) or (width*4), depending on your input format, + * but some data uses padding at the end of each row, which would + * be extra. + * inputFormat - one of INPUT_FORMAT_* specifying the format of data + * outputOptions - semicolon-delimited list of name=value pairs that can + * give options to the output encoder. Options are encoder- + * specific. Just give empty string for default behavior. + */ + void initFromData([array, size_is(length), const] in uint8_t data, + in unsigned long length, + in uint32_t width, + in uint32_t height, + in uint32_t stride, + in uint32_t inputFormat, + in AString outputOptions); + + /* + * For encoding images which may contain multiple frames, the 1-shot + * initFromData() interface is too simplistic. The alternative is to + * use startImageEncode(), call addImageFrame() one or more times, and + * then finish initialization with endImageEncode(). + * + * The arguments are basically the same as in initFromData(). + */ + void startImageEncode(in uint32_t width, + in uint32_t height, + in uint32_t inputFormat, + in AString outputOptions); + + void addImageFrame( [array, size_is(length), const] in uint8_t data, + in unsigned long length, + in uint32_t width, + in uint32_t height, + in uint32_t stride, + in uint32_t frameFormat, + in AString frameOptions); + + void endImageEncode(); + + /* + * Sometimes an encoder can contain another encoder and direct access + * to its buffer is necessary. It is only safe to assume that the buffer + * returned from getImageBuffer() is of size equal to getImageBufferUsed(). + */ + [noscript] unsigned long getImageBufferUsed(); + [noscript] charPtr getImageBuffer(); +}; diff --git a/image/imgILoader.idl b/image/imgILoader.idl new file mode 100644 index 000000000..36505da9d --- /dev/null +++ b/image/imgILoader.idl @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface imgINotificationObserver; +interface imgIRequest; + +interface nsIChannel; +interface nsILoadGroup; +interface nsIPrincipal; +interface nsIStreamListener; +interface nsIURI; + +interface nsISimpleEnumerator; + +#include "nsIRequest.idl" // for nsLoadFlags +#include "nsIContentPolicy.idl" // for nsContentPolicyType + +/** + * imgILoader interface + * + * @author Stuart Parmenter + * @version 0.3 + * @see imagelib2 + */ +[scriptable, builtinclass, uuid(e61377d2-910e-4c65-a64b-428d150e1fd1)] +interface imgILoader : nsISupports +{ + // Extra flags to pass to loadImage if you want a load to use CORS + // validation. + const unsigned long LOAD_CORS_ANONYMOUS = 1 << 16; + const unsigned long LOAD_CORS_USE_CREDENTIALS = 1 << 17; + + /** + * Start the load and decode of an image. + * @param aURI the URI to load + * @param aInitialDocumentURI the URI that 'initiated' the load -- used for + * 3rd party cookie blocking + * @param aReferrerURI the 'referring' URI + * @param aReferrerPolicy the policy to apply to sending referrers. + * examples: "default", "never", "always", "origin" + * (see W3C referrer-policy spec for valid policy strings) + * @param aLoadingPrincipal the principal of the loading document + * @param aLoadGroup Loadgroup to put the image load into + * @param aObserver the observer (may be null) + * @param aCX some random data + * @param aLoadFlags Load flags for the request + * @param aCacheKey cache key to use for a load if the original + * image came from a request that had post data + * @param aContentPolicyType [optional] the nsContentPolicyType to + * use for this load. Defaults to + * nsIContentPolicy::TYPE_IMAGE + + + * ImageLib does NOT keep a strong ref to the observer; this prevents + * reference cycles. This means that callers of loadImage should + * make sure to Cancel() the resulting request before the observer + * goes away. + */ + imgIRequest loadImageXPCOM(in nsIURI aURI, + in nsIURI aInitialDocumentURL, + in nsIURI aReferrerURI, + in AString aReferrerPolicy, + in nsIPrincipal aLoadingPrincipal, + in nsILoadGroup aLoadGroup, + in imgINotificationObserver aObserver, + in nsISupports aCX, + in nsLoadFlags aLoadFlags, + in nsISupports cacheKey, + [optional] + in nsContentPolicyType aContentPolicyType); + + /** + * Start the load and decode of an image. + * @param aChannel the channel to load the image from. This must + * already be opened before ths method is called, and there + * must have been no OnDataAvailable calls for it yet. + * @param aObserver the observer (may be null) + * @param cx some random data + * @param aListener [out] + * A listener that you must send the channel's notifications and data + * to. Can be null, in which case imagelib has found a cached image + * and is not interested in the data. @aChannel will be canceled for + * you in this case. + * + * ImageLib does NOT keep a strong ref to the observer; this prevents + * reference cycles. This means that callers of loadImageWithChannel should + * make sure to Cancel() the resulting request before the observer goes away. + */ + imgIRequest loadImageWithChannelXPCOM(in nsIChannel aChannel, + in imgINotificationObserver aObserver, + in nsISupports cx, + out nsIStreamListener aListener); +}; diff --git a/image/imgINotificationObserver.idl b/image/imgINotificationObserver.idl new file mode 100644 index 000000000..abf62535b --- /dev/null +++ b/image/imgINotificationObserver.idl @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface imgIRequest; + +%{C++ +#include "nsRect.h" +%} + +[ptr] native nsIntRect(nsIntRect); + +[scriptable, builtinclass, uuid(03da5641-a333-454a-a859-036d0bb683b7)] +interface imgINotificationObserver : nsISupports +{ + // GetWidth() and GetHeight() can now be used to retrieve the size of the + // image. + const long SIZE_AVAILABLE = 1; + + // A region of the image (indicated by the |aRect| argument to |notify|) has + // changed, and needs to be redrawn. This is triggered both for incremental + // rendering as the image gets decoded and for changes due to animation. + const long FRAME_UPDATE = 2; + + // The first frame of the image is now decoded and ready to draw. + const long FRAME_COMPLETE = 3; + + // The entire image has been loaded. That doesn't mean that it has been + // decoded, but it does mean that imgIContainer::Draw is guaranteed to succeed + // (modulo decode errors, at least) if you specify FLAG_SYNC_DECODE. + const long LOAD_COMPLETE = 4; + + // The entire image has been decoded. + const long DECODE_COMPLETE = 5; + + // The decoded version of the image has been discarded. Content should never + // change as a result of this notification - discarding is an implementation + // detail. This notification should normally only be observed by tests. + const long DISCARD = 6; + + // The image was drawn without being locked. This notification is part of the + // image locking mechanism that prevents visible images from being discarded; + // generally only image locking code needs to observe it. + const long UNLOCKED_DRAW = 7; + + // The image is animated. + const long IS_ANIMATED = 8; + + // The image is transparent. + const long HAS_TRANSPARENCY = 9; + + [noscript] void notify(in imgIRequest aProxy, in long aType, + [const] in nsIntRect aRect); +}; diff --git a/image/imgIOnloadBlocker.idl b/image/imgIOnloadBlocker.idl new file mode 100644 index 000000000..c4075330d --- /dev/null +++ b/image/imgIOnloadBlocker.idl @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface imgIRequest; + +[uuid(dc126d90-0ee0-4683-b942-2fa66e443abc)] +interface imgIOnloadBlocker : nsISupports +{ + /** + * blockOnload + * Called when it is appropriate to block onload for the given imgIRequest. + * + * @param aRequest + * The request that should block onload. + */ + void blockOnload(in imgIRequest aRequest); + + /** + * unblockOnload + * Called when it is appropriate to unblock onload for the given + * imgIRequest. + * + * @param aRequest + * The request that should unblock onload. + */ + void unblockOnload(in imgIRequest aRequest); +}; diff --git a/image/imgIRequest.idl b/image/imgIRequest.idl new file mode 100644 index 000000000..680bd6e3a --- /dev/null +++ b/image/imgIRequest.idl @@ -0,0 +1,206 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIRequest.idl" + +interface imgIContainer; +interface imgINotificationObserver; +interface nsIURI; +interface nsIPrincipal; + +/** + * imgIRequest interface + * + * @author Stuart Parmenter + * @version 0.1 + * @see imagelib2 + */ +[scriptable, builtinclass, uuid(db0a945c-3883-424a-98d0-2ee0523b0255)] +interface imgIRequest : nsIRequest +{ + /** + * the image container... + * @return the image object associated with the request. + * @attention NEED DOCS + */ + readonly attribute imgIContainer image; + + /** + * Bits set in the return value from imageStatus + * @name statusflags + * + * Meanings: + * + * STATUS_NONE: Nothing to report. + * + * STATUS_SIZE_AVAILABLE: We received enough image data + * from the network or filesystem that we know the width + * and height of the image, and have thus called SetSize() + * on the container. + * + * STATUS_LOAD_COMPLETE: The data has been fully loaded + * to memory, but not necessarily fully decoded. + * + * STATUS_ERROR: An error occurred loading the image. + * + * STATUS_FRAME_COMPLETE: The first frame has been + * completely decoded. + * + * STATUS_DECODE_COMPLETE: The whole image has been decoded. + * + * STATUS_IS_ANIMATED: The image is animated. + * + * STATUS_HAS_TRANSPARENCY: The image is partially or completely transparent. + */ + //@{ + const long STATUS_NONE = 0x0; + const long STATUS_SIZE_AVAILABLE = 0x1; + const long STATUS_LOAD_COMPLETE = 0x2; + const long STATUS_ERROR = 0x4; + const long STATUS_FRAME_COMPLETE = 0x8; + const long STATUS_DECODE_COMPLETE = 0x10; + const long STATUS_IS_ANIMATED = 0x20; + const long STATUS_HAS_TRANSPARENCY = 0x40; + //@} + + /** + * Status flags of the STATUS_* variety. + */ + readonly attribute unsigned long imageStatus; + + /* + * Actual error code that generated a STATUS_ERROR imageStatus + * (see xpcom/base/ErrorList.h) + */ + [noscript] readonly attribute nsresult imageErrorCode; + + /** + * The URI the image load was started with. Note that this might not be the + * actual URI for the image (e.g. if HTTP redirects happened during the + * load). + */ + readonly attribute nsIURI URI; + + /** + * The URI of the resource we ended up loading after all redirects, etc. + */ + readonly attribute nsIURI currentURI; + + readonly attribute imgINotificationObserver notificationObserver; + + readonly attribute string mimeType; + + /** + * Clone this request; the returned request will have aObserver as the + * observer. aObserver will be notified synchronously (before the clone() + * call returns) with all the notifications that have already been dispatched + * for this image load. + */ + imgIRequest clone(in imgINotificationObserver aObserver); + + /** + * The principal gotten from the channel the image was loaded from. + */ + readonly attribute nsIPrincipal imagePrincipal; + + /** + * Whether the request is multipart (ie, multipart/x-mixed-replace) + */ + readonly attribute bool multipart; + + /** + * CORS modes images can be loaded with. + * + * By default, all images are loaded with CORS_NONE and cannot be used + * cross-origin in context like WebGL. + * + * If an HTML img element has the crossorigin attribute set, the imgIRequest + * will be validated for cross-origin usage with CORS, and, if successful, + * will have its CORS mode set to the relevant type. + */ + //@{ + const long CORS_NONE = 1; + const long CORS_ANONYMOUS = 2; + const long CORS_USE_CREDENTIALS = 3; + //@} + + /** + * The CORS mode that this image was loaded with. + */ + readonly attribute long CORSMode; + + /** + * Cancels this request as in nsIRequest::Cancel(); further, also nulls out + * decoderObserver so it gets no further notifications from us. + * + * NOTE: You should not use this in any new code; instead, use cancel(). Note + * that cancel() is asynchronous, which means that some time after you call + * it, the listener/observer will get an OnStopRequest(). This means that, if + * you're the observer, you can't call cancel() from your destructor. + */ + void cancelAndForgetObserver(in nsresult aStatus); + + /** + * Requests a synchronous decode for the image. + * + * imgIContainer has a startDecoding() method, but callers may want to request + * a decode before the container has necessarily been instantiated. Calling + * startDecoding() on the imgIRequest simply forwards along the request if the + * container already exists, or calls it once the container becomes available + * if it does not yet exist. + */ + void startDecoding(); + + /** + * Locks an image. If the image does not exist yet, locks it once it becomes + * available. The lock persists for the lifetime of the imgIRequest (until + * unlockImage is called) even if the underlying image changes. + * + * If you don't call unlockImage() by the time this imgIRequest goes away, it + * will be called for you automatically. + * + * @see imgIContainer::lockImage for documentation of the underlying call. + */ + void lockImage(); + + /** + * Unlocks an image. + * + * @see imgIContainer::unlockImage for documentation of the underlying call. + */ + void unlockImage(); + + /** + * If this image is unlocked, discard the image's decoded data. If the image + * is locked or is already discarded, do nothing. + */ + void requestDiscard(); + + /** + * If this request is for an animated image, the method creates a new + * request which contains the current frame of the image. + * Otherwise returns the same request. + */ + imgIRequest getStaticRequest(); + + /** + * Requests that the image animate (if it has an animation). + * + * @see Image::IncrementAnimationConsumers for documentation of the + * underlying call. + */ + void incrementAnimationConsumers(); + + /** + * Tell the image it can forget about a request that the image animate. + * + * @see Image::DecrementAnimationConsumers for documentation of the + * underlying call. + */ + void decrementAnimationConsumers(); +}; + diff --git a/image/imgIScriptedNotificationObserver.idl b/image/imgIScriptedNotificationObserver.idl new file mode 100644 index 000000000..54769e38f --- /dev/null +++ b/image/imgIScriptedNotificationObserver.idl @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface imgIRequest; + +[scriptable, uuid(10be55b3-2029-41a7-a975-538efed250ed)] +interface imgIScriptedNotificationObserver : nsISupports +{ + void sizeAvailable(in imgIRequest aRequest); + void frameUpdate(in imgIRequest aRequest); + void frameComplete(in imgIRequest aRequest); + void loadComplete(in imgIRequest aRequest); + void decodeComplete(in imgIRequest aRequest); + void discard(in imgIRequest aRequest); + void isAnimated(in imgIRequest aRequest); + void hasTransparency(in imgIRequest aRequest); +}; diff --git a/image/imgITools.idl b/image/imgITools.idl new file mode 100644 index 000000000..111efa07c --- /dev/null +++ b/image/imgITools.idl @@ -0,0 +1,152 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIInputStream; +interface imgIContainer; +interface imgILoader; +interface imgICache; +interface nsIDOMDocument; +interface imgIScriptedNotificationObserver; +interface imgINotificationObserver; + +[scriptable, builtinclass, uuid(4c2383a4-931c-484d-8c4a-973590f66e3f)] +interface imgITools : nsISupports +{ + /** + * decodeImage + * Caller provides an input stream and mimetype. We read from the stream + * and decompress it (according to the specified mime type) and return + * the resulting imgIContainer. + * + * @param aStream + * An input stream for an encoded image file. + * @param aMimeType + * Type of image in the stream. + */ + imgIContainer decodeImage(in nsIInputStream aStream, + in ACString aMimeType); + + /** + * decodeImageData + * Caller provides an input stream and mimetype. We read from the stream + * and decompress it (according to the specified mime type) and return + * the resulting imgIContainer. + * + * This method is deprecated and will be removed at some time in the future; + * new code should use |decodeImage|. + * + * @param aStream + * An input stream for an encoded image file. + * @param aMimeType + * Type of image in the stream. + * @param aContainer + * An imgIContainer holding the decoded image will be returned via + * this parameter. It is an error to provide any initial value but + * |null|. + */ + [deprecated] void decodeImageData(in nsIInputStream aStream, + in ACString aMimeType, + inout imgIContainer aContainer); + + /** + * encodeImage + * Caller provides an image container, and the mime type it should be + * encoded to. We return an input stream for the encoded image data. + * + * @param aContainer + * An image container. + * @param aMimeType + * Type of encoded image desired (eg "image/png"). + * @param outputOptions + * Encoder-specific output options. + */ + nsIInputStream encodeImage(in imgIContainer aContainer, + in ACString aMimeType, + [optional] in AString outputOptions); + + /** + * encodeScaledImage + * Caller provides an image container, and the mime type it should be + * encoded to. We return an input stream for the encoded image data. + * The encoded image is scaled to the specified dimensions. + * + * @param aContainer + * An image container. + * @param aMimeType + * Type of encoded image desired (eg "image/png"). + * @param aWidth, aHeight + * The size (in pixels) desired for the resulting image. Specify 0 to + * use the given image's width or height. Values must be >= 0. + * @param outputOptions + * Encoder-specific output options. + */ + nsIInputStream encodeScaledImage(in imgIContainer aContainer, + in ACString aMimeType, + in long aWidth, + in long aHeight, + [optional] in AString outputOptions); + + /** + * getImgLoaderForDocument + * Retrieve an image loader that reflects the privacy status of the given + * document. + * + * @param doc + * A document. Must not be null. + */ + imgILoader getImgLoaderForDocument(in nsIDOMDocument doc); + + /** + * getImgLoaderForDocument + * Retrieve an image cache that reflects the privacy status of the given + * document. + * + * @param doc + * A document. Null is allowed, but must _only_ be passed + * when there is no way to obtain a relevant document for + * the current context in which a cache is desired. + */ + imgICache getImgCacheForDocument(in nsIDOMDocument doc); + + /** + * encodeCroppedImage + * Caller provides an image container, and the mime type it should be + * encoded to. We return an input stream for the encoded image data. + * The encoded image is cropped to the specified dimensions. + * + * The given offset and size must not exceed the image bounds. + * + * @param aContainer + * An image container. + * @param aMimeType + * Type of encoded image desired (eg "image/png"). + * @param aOffsetX, aOffsetY + * The crop offset (in pixels). Values must be >= 0. + * @param aWidth, aHeight + * The size (in pixels) desired for the resulting image. Specify 0 to + * use the given image's width or height. Values must be >= 0. + * @param outputOptions + * Encoder-specific output options. + */ + nsIInputStream encodeCroppedImage(in imgIContainer aContainer, + in ACString aMimeType, + in long aOffsetX, + in long aOffsetY, + in long aWidth, + in long aHeight, + [optional] in AString outputOptions); + + /** + * Create a wrapper around a scripted notification observer (ordinarily + * imgINotificationObserver cannot be implemented from scripts). + * + * @param aObserver The scripted observer to wrap + */ + imgINotificationObserver + createScriptedObserver(in imgIScriptedNotificationObserver aObserver); +}; diff --git a/image/imgLoader.cpp b/image/imgLoader.cpp new file mode 100644 index 000000000..3545c5be2 --- /dev/null +++ b/image/imgLoader.cpp @@ -0,0 +1,2993 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageLogging.h" +#include "imgLoader.h" + +#include "mozilla/Attributes.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Move.h" +#include "mozilla/Preferences.h" +#include "mozilla/ChaosMode.h" + +#include "nsImageModule.h" +#include "imgRequestProxy.h" + +#include "nsCOMPtr.h" + +#include "nsContentPolicyUtils.h" +#include "nsContentUtils.h" +#include "nsNetUtil.h" +#include "nsNetCID.h" +#include "nsIProtocolHandler.h" +#include "nsMimeTypes.h" +#include "nsStreamUtils.h" +#include "nsIHttpChannel.h" +#include "nsICacheInfoChannel.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIProgressEventSink.h" +#include "nsIChannelEventSink.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsIFileURL.h" +#include "nsIFile.h" +#include "nsCRT.h" +#include "nsINetworkPredictor.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/nsMixedContentBlocker.h" + +#include "nsIApplicationCache.h" +#include "nsIApplicationCacheContainer.h" + +#include "nsIMemoryReporter.h" +#include "DecoderFactory.h" +#include "Image.h" +#include "gfxPrefs.h" +#include "prtime.h" + +// we want to explore making the document own the load group +// so we can associate the document URI with the load group. +// until this point, we have an evil hack: +#include "nsIHttpChannelInternal.h" +#include "nsILoadContext.h" +#include "nsILoadGroupChild.h" +#include "nsIDOMDocument.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::image; +using namespace mozilla::net; + +MOZ_DEFINE_MALLOC_SIZE_OF(ImagesMallocSizeOf) + +class imgMemoryReporter final : public nsIMemoryReporter +{ + ~imgMemoryReporter() { } + +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + nsTArray chrome; + nsTArray content; + nsTArray uncached; + + for (uint32_t i = 0; i < mKnownLoaders.Length(); i++) { + for (auto iter = mKnownLoaders[i]->mChromeCache.Iter(); !iter.Done(); iter.Next()) { + imgCacheEntry* entry = iter.UserData(); + RefPtr req = entry->GetRequest(); + RecordCounterForRequest(req, &chrome, !entry->HasNoProxies()); + } + for (auto iter = mKnownLoaders[i]->mCache.Iter(); !iter.Done(); iter.Next()) { + imgCacheEntry* entry = iter.UserData(); + RefPtr req = entry->GetRequest(); + RecordCounterForRequest(req, &content, !entry->HasNoProxies()); + } + MutexAutoLock lock(mKnownLoaders[i]->mUncachedImagesMutex); + for (auto iter = mKnownLoaders[i]->mUncachedImages.Iter(); + !iter.Done(); + iter.Next()) { + nsPtrHashKey* entry = iter.Get(); + RefPtr req = entry->GetKey(); + RecordCounterForRequest(req, &uncached, req->HasConsumers()); + } + } + + // Note that we only need to anonymize content image URIs. + + ReportCounterArray(aHandleReport, aData, chrome, "images/chrome"); + + ReportCounterArray(aHandleReport, aData, content, "images/content", + aAnonymize); + + // Uncached images may be content or chrome, so anonymize them. + ReportCounterArray(aHandleReport, aData, uncached, "images/uncached", + aAnonymize); + + return NS_OK; + } + + static int64_t ImagesContentUsedUncompressedDistinguishedAmount() + { + size_t n = 0; + for (uint32_t i = 0; i < imgLoader::sMemReporter->mKnownLoaders.Length(); + i++) { + for (auto iter = imgLoader::sMemReporter->mKnownLoaders[i]->mCache.Iter(); + !iter.Done(); + iter.Next()) { + imgCacheEntry* entry = iter.UserData(); + if (entry->HasNoProxies()) { + continue; + } + + RefPtr req = entry->GetRequest(); + RefPtr image = req->GetImage(); + if (!image) { + continue; + } + + // Both this and EntryImageSizes measure images/content/raster/used/decoded + // memory. This function's measurement is secondary -- the result doesn't + // go in the "explicit" tree -- so we use moz_malloc_size_of instead of + // ImagesMallocSizeOf to prevent DMD from seeing it reported twice. + ImageMemoryCounter counter(image, moz_malloc_size_of, /* aIsUsed = */ true); + + n += counter.Values().DecodedHeap(); + n += counter.Values().DecodedNonHeap(); + } + } + return n; + } + + void RegisterLoader(imgLoader* aLoader) + { + mKnownLoaders.AppendElement(aLoader); + } + + void UnregisterLoader(imgLoader* aLoader) + { + mKnownLoaders.RemoveElement(aLoader); + } + +private: + nsTArray mKnownLoaders; + + struct MemoryTotal + { + MemoryTotal& operator+=(const ImageMemoryCounter& aImageCounter) + { + if (aImageCounter.Type() == imgIContainer::TYPE_RASTER) { + if (aImageCounter.IsUsed()) { + mUsedRasterCounter += aImageCounter.Values(); + } else { + mUnusedRasterCounter += aImageCounter.Values(); + } + } else if (aImageCounter.Type() == imgIContainer::TYPE_VECTOR) { + if (aImageCounter.IsUsed()) { + mUsedVectorCounter += aImageCounter.Values(); + } else { + mUnusedVectorCounter += aImageCounter.Values(); + } + } else { + MOZ_CRASH("Unexpected image type"); + } + + return *this; + } + + const MemoryCounter& UsedRaster() const { return mUsedRasterCounter; } + const MemoryCounter& UnusedRaster() const { return mUnusedRasterCounter; } + const MemoryCounter& UsedVector() const { return mUsedVectorCounter; } + const MemoryCounter& UnusedVector() const { return mUnusedVectorCounter; } + + private: + MemoryCounter mUsedRasterCounter; + MemoryCounter mUnusedRasterCounter; + MemoryCounter mUsedVectorCounter; + MemoryCounter mUnusedVectorCounter; + }; + + // Reports all images of a single kind, e.g. all used chrome images. + void ReportCounterArray(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, + nsTArray& aCounterArray, + const char* aPathPrefix, + bool aAnonymize = false) + { + MemoryTotal summaryTotal; + MemoryTotal nonNotableTotal; + + // Report notable images, and compute total and non-notable aggregate sizes. + for (uint32_t i = 0; i < aCounterArray.Length(); i++) { + ImageMemoryCounter& counter = aCounterArray[i]; + + if (aAnonymize) { + counter.URI().Truncate(); + counter.URI().AppendPrintf("", i); + } else { + // The URI could be an extremely long data: URI. Truncate if needed. + static const size_t max = 256; + if (counter.URI().Length() > max) { + counter.URI().Truncate(max); + counter.URI().AppendLiteral(" (truncated)"); + } + counter.URI().ReplaceChar('/', '\\'); + } + + summaryTotal += counter; + + if (counter.IsNotable()) { + ReportImage(aHandleReport, aData, aPathPrefix, counter); + } else { + nonNotableTotal += counter; + } + } + + // Report non-notable images in aggregate. + ReportTotal(aHandleReport, aData, /* aExplicit = */ true, + aPathPrefix, "/", nonNotableTotal); + + // Report a summary in aggregate, outside of the explicit tree. + ReportTotal(aHandleReport, aData, /* aExplicit = */ false, + aPathPrefix, "", summaryTotal); + } + + static void ReportImage(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, + const char* aPathPrefix, + const ImageMemoryCounter& aCounter) + { + nsAutoCString pathPrefix(NS_LITERAL_CSTRING("explicit/")); + pathPrefix.Append(aPathPrefix); + pathPrefix.Append(aCounter.Type() == imgIContainer::TYPE_RASTER + ? "/raster/" + : "/vector/"); + pathPrefix.Append(aCounter.IsUsed() ? "used/" : "unused/"); + pathPrefix.Append("image("); + pathPrefix.AppendInt(aCounter.IntrinsicSize().width); + pathPrefix.Append("x"); + pathPrefix.AppendInt(aCounter.IntrinsicSize().height); + pathPrefix.Append(", "); + + if (aCounter.URI().IsEmpty()) { + pathPrefix.Append(""); + } else { + pathPrefix.Append(aCounter.URI()); + } + + pathPrefix.Append(")/"); + + ReportSurfaces(aHandleReport, aData, pathPrefix, aCounter); + + ReportSourceValue(aHandleReport, aData, pathPrefix, aCounter.Values()); + } + + static void ReportSurfaces(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, + const nsACString& aPathPrefix, + const ImageMemoryCounter& aCounter) + { + for (const SurfaceMemoryCounter& counter : aCounter.Surfaces()) { + nsAutoCString surfacePathPrefix(aPathPrefix); + surfacePathPrefix.Append(counter.IsLocked() ? "locked/" : "unlocked/"); + surfacePathPrefix.Append("surface("); + surfacePathPrefix.AppendInt(counter.Key().Size().width); + surfacePathPrefix.Append("x"); + surfacePathPrefix.AppendInt(counter.Key().Size().height); + + if (counter.Type() == SurfaceMemoryCounterType::NORMAL) { + PlaybackType playback = counter.Key().Playback(); + surfacePathPrefix.Append(playback == PlaybackType::eAnimated + ? " (animation)" + : ""); + + if (counter.Key().Flags() != DefaultSurfaceFlags()) { + surfacePathPrefix.Append(", flags:"); + surfacePathPrefix.AppendInt(uint32_t(counter.Key().Flags()), + /* aRadix = */ 16); + } + } else if (counter.Type() == SurfaceMemoryCounterType::COMPOSITING) { + surfacePathPrefix.Append(", compositing frame"); + } else if (counter.Type() == SurfaceMemoryCounterType::COMPOSITING_PREV) { + surfacePathPrefix.Append(", compositing prev frame"); + } else { + MOZ_ASSERT_UNREACHABLE("Unknown counter type"); + } + + surfacePathPrefix.Append(")/"); + + ReportValues(aHandleReport, aData, surfacePathPrefix, counter.Values()); + } + } + + static void ReportTotal(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, + bool aExplicit, + const char* aPathPrefix, + const char* aPathInfix, + const MemoryTotal& aTotal) + { + nsAutoCString pathPrefix; + if (aExplicit) { + pathPrefix.Append("explicit/"); + } + pathPrefix.Append(aPathPrefix); + + nsAutoCString rasterUsedPrefix(pathPrefix); + rasterUsedPrefix.Append("/raster/used/"); + rasterUsedPrefix.Append(aPathInfix); + ReportValues(aHandleReport, aData, rasterUsedPrefix, aTotal.UsedRaster()); + + nsAutoCString rasterUnusedPrefix(pathPrefix); + rasterUnusedPrefix.Append("/raster/unused/"); + rasterUnusedPrefix.Append(aPathInfix); + ReportValues(aHandleReport, aData, rasterUnusedPrefix, + aTotal.UnusedRaster()); + + nsAutoCString vectorUsedPrefix(pathPrefix); + vectorUsedPrefix.Append("/vector/used/"); + vectorUsedPrefix.Append(aPathInfix); + ReportValues(aHandleReport, aData, vectorUsedPrefix, aTotal.UsedVector()); + + nsAutoCString vectorUnusedPrefix(pathPrefix); + vectorUnusedPrefix.Append("/vector/unused/"); + vectorUnusedPrefix.Append(aPathInfix); + ReportValues(aHandleReport, aData, vectorUnusedPrefix, + aTotal.UnusedVector()); + } + + static void ReportValues(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, + const nsACString& aPathPrefix, + const MemoryCounter& aCounter) + { + ReportSourceValue(aHandleReport, aData, aPathPrefix, aCounter); + + ReportValue(aHandleReport, aData, KIND_HEAP, aPathPrefix, + "decoded-heap", + "Decoded image data which is stored on the heap.", + aCounter.DecodedHeap()); + + ReportValue(aHandleReport, aData, KIND_NONHEAP, aPathPrefix, + "decoded-nonheap", + "Decoded image data which isn't stored on the heap.", + aCounter.DecodedNonHeap()); + } + + static void ReportSourceValue(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, + const nsACString& aPathPrefix, + const MemoryCounter& aCounter) + { + ReportValue(aHandleReport, aData, KIND_HEAP, aPathPrefix, + "source", + "Raster image source data and vector image documents.", + aCounter.Source()); + } + + static void ReportValue(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, + int32_t aKind, + const nsACString& aPathPrefix, + const char* aPathSuffix, + const char* aDescription, + size_t aValue) + { + if (aValue == 0) { + return; + } + + nsAutoCString desc(aDescription); + nsAutoCString path(aPathPrefix); + path.Append(aPathSuffix); + + aHandleReport->Callback(EmptyCString(), path, aKind, UNITS_BYTES, + aValue, desc, aData); + } + + static void RecordCounterForRequest(imgRequest* aRequest, + nsTArray* aArray, + bool aIsUsed) + { + RefPtr image = aRequest->GetImage(); + if (!image) { + return; + } + + ImageMemoryCounter counter(image, ImagesMallocSizeOf, aIsUsed); + + aArray->AppendElement(Move(counter)); + } +}; + +NS_IMPL_ISUPPORTS(imgMemoryReporter, nsIMemoryReporter) + +NS_IMPL_ISUPPORTS(nsProgressNotificationProxy, + nsIProgressEventSink, + nsIChannelEventSink, + nsIInterfaceRequestor) + +NS_IMETHODIMP +nsProgressNotificationProxy::OnProgress(nsIRequest* request, + nsISupports* ctxt, + int64_t progress, + int64_t progressMax) +{ + nsCOMPtr loadGroup; + request->GetLoadGroup(getter_AddRefs(loadGroup)); + + nsCOMPtr target; + NS_QueryNotificationCallbacks(mOriginalCallbacks, + loadGroup, + NS_GET_IID(nsIProgressEventSink), + getter_AddRefs(target)); + if (!target) { + return NS_OK; + } + return target->OnProgress(mImageRequest, ctxt, progress, progressMax); +} + +NS_IMETHODIMP +nsProgressNotificationProxy::OnStatus(nsIRequest* request, + nsISupports* ctxt, + nsresult status, + const char16_t* statusArg) +{ + nsCOMPtr loadGroup; + request->GetLoadGroup(getter_AddRefs(loadGroup)); + + nsCOMPtr target; + NS_QueryNotificationCallbacks(mOriginalCallbacks, + loadGroup, + NS_GET_IID(nsIProgressEventSink), + getter_AddRefs(target)); + if (!target) { + return NS_OK; + } + return target->OnStatus(mImageRequest, ctxt, status, statusArg); +} + +NS_IMETHODIMP +nsProgressNotificationProxy:: + AsyncOnChannelRedirect(nsIChannel* oldChannel, + nsIChannel* newChannel, + uint32_t flags, + nsIAsyncVerifyRedirectCallback* cb) +{ + // Tell the original original callbacks about it too + nsCOMPtr loadGroup; + newChannel->GetLoadGroup(getter_AddRefs(loadGroup)); + nsCOMPtr target; + NS_QueryNotificationCallbacks(mOriginalCallbacks, + loadGroup, + NS_GET_IID(nsIChannelEventSink), + getter_AddRefs(target)); + if (!target) { + cb->OnRedirectVerifyCallback(NS_OK); + return NS_OK; + } + + // Delegate to |target| if set, reusing |cb| + return target->AsyncOnChannelRedirect(oldChannel, newChannel, flags, cb); +} + +NS_IMETHODIMP +nsProgressNotificationProxy::GetInterface(const nsIID& iid, + void** result) +{ + if (iid.Equals(NS_GET_IID(nsIProgressEventSink))) { + *result = static_cast(this); + NS_ADDREF_THIS(); + return NS_OK; + } + if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) { + *result = static_cast(this); + NS_ADDREF_THIS(); + return NS_OK; + } + if (mOriginalCallbacks) { + return mOriginalCallbacks->GetInterface(iid, result); + } + return NS_NOINTERFACE; +} + +static void +NewRequestAndEntry(bool aForcePrincipalCheckForCacheEntry, imgLoader* aLoader, + const ImageCacheKey& aKey, + imgRequest** aRequest, imgCacheEntry** aEntry) +{ + RefPtr request = new imgRequest(aLoader, aKey); + RefPtr entry = + new imgCacheEntry(aLoader, request, aForcePrincipalCheckForCacheEntry); + aLoader->AddToUncachedImages(request); + request.forget(aRequest); + entry.forget(aEntry); +} + +static bool +ShouldRevalidateEntry(imgCacheEntry* aEntry, + nsLoadFlags aFlags, + bool aHasExpired) +{ + bool bValidateEntry = false; + + if (aFlags & nsIRequest::LOAD_BYPASS_CACHE) { + return false; + } + + if (aFlags & nsIRequest::VALIDATE_ALWAYS) { + bValidateEntry = true; + } else if (aEntry->GetMustValidate()) { + bValidateEntry = true; + } else if (aHasExpired) { + // The cache entry has expired... Determine whether the stale cache + // entry can be used without validation... + if (aFlags & (nsIRequest::VALIDATE_NEVER | + nsIRequest::VALIDATE_ONCE_PER_SESSION)) { + // VALIDATE_NEVER and VALIDATE_ONCE_PER_SESSION allow stale cache + // entries to be used unless they have been explicitly marked to + // indicate that revalidation is necessary. + bValidateEntry = false; + + } else if (!(aFlags & nsIRequest::LOAD_FROM_CACHE)) { + // LOAD_FROM_CACHE allows a stale cache entry to be used... Otherwise, + // the entry must be revalidated. + bValidateEntry = true; + } + } + + return bValidateEntry; +} + +/* Call content policies on cached images that went through a redirect */ +static bool +ShouldLoadCachedImage(imgRequest* aImgRequest, + nsISupports* aLoadingContext, + nsIPrincipal* aLoadingPrincipal, + nsContentPolicyType aPolicyType) +{ + /* Call content policies on cached images - Bug 1082837 + * Cached images are keyed off of the first uri in a redirect chain. + * Hence content policies don't get a chance to test the intermediate hops + * or the final desitnation. Here we test the final destination using + * mCurrentURI off of the imgRequest and passing it into content policies. + * For Mixed Content Blocker, we do an additional check to determine if any + * of the intermediary hops went through an insecure redirect with the + * mHadInsecureRedirect flag + */ + bool insecureRedirect = aImgRequest->HadInsecureRedirect(); + nsCOMPtr contentLocation; + aImgRequest->GetCurrentURI(getter_AddRefs(contentLocation)); + nsresult rv; + + int16_t decision = nsIContentPolicy::REJECT_REQUEST; + rv = NS_CheckContentLoadPolicy(aPolicyType, + contentLocation, + aLoadingPrincipal, + aLoadingContext, + EmptyCString(), //mime guess + nullptr, //aExtra + &decision, + nsContentUtils::GetContentPolicy(), + nsContentUtils::GetSecurityManager()); + if (NS_FAILED(rv) || !NS_CP_ACCEPTED(decision)) { + return false; + } + + // We call all Content Policies above, but we also have to call mcb + // individually to check the intermediary redirect hops are secure. + if (insecureRedirect) { + if (!nsContentUtils::IsSystemPrincipal(aLoadingPrincipal)) { + // Set the requestingLocation from the aLoadingPrincipal. + nsCOMPtr requestingLocation; + if (aLoadingPrincipal) { + rv = aLoadingPrincipal->GetURI(getter_AddRefs(requestingLocation)); + NS_ENSURE_SUCCESS(rv, false); + } + + // reset the decision for mixed content blocker check + decision = nsIContentPolicy::REJECT_REQUEST; + rv = nsMixedContentBlocker::ShouldLoad(insecureRedirect, + aPolicyType, + contentLocation, + requestingLocation, + aLoadingContext, + EmptyCString(), //mime guess + nullptr, + aLoadingPrincipal, + &decision); + if (NS_FAILED(rv) || !NS_CP_ACCEPTED(decision)) { + return false; + } + } + } + + bool sendPriming = false; + bool mixedContentWouldBlock = false; + rv = nsMixedContentBlocker::GetHSTSPrimingFromRequestingContext(contentLocation, + aLoadingContext, &sendPriming, &mixedContentWouldBlock); + if (NS_FAILED(rv)) { + return false; + } + if (sendPriming && mixedContentWouldBlock) { + // if either of the securty checks above would cause a priming request, we + // can't load this image from the cache, so go ahead and return false here + return false; + } + + return true; +} + +// Returns true if this request is compatible with the given CORS mode on the +// given loading principal, and false if the request may not be reused due +// to CORS. Also checks the Referrer Policy, since requests with different +// referrers/policies may generate different responses. +static bool +ValidateSecurityInfo(imgRequest* request, bool forcePrincipalCheck, + int32_t corsmode, nsIPrincipal* loadingPrincipal, + nsISupports* aCX, nsContentPolicyType aPolicyType, + ReferrerPolicy referrerPolicy) +{ + // If the entry's Referrer Policy doesn't match, we can't use this request. + // XXX: this will return false if an image has different referrer attributes, + // i.e. we currently don't use the cached image but reload the image with + // the new referrer policy bug 1174921 + if (referrerPolicy != request->GetReferrerPolicy()) { + return false; + } + + // If the entry's CORS mode doesn't match, or the CORS mode matches but the + // document principal isn't the same, we can't use this request. + if (request->GetCORSMode() != corsmode) { + return false; + } else if (request->GetCORSMode() != imgIRequest::CORS_NONE || + forcePrincipalCheck) { + nsCOMPtr otherprincipal = request->GetLoadingPrincipal(); + + // If we previously had a principal, but we don't now, we can't use this + // request. + if (otherprincipal && !loadingPrincipal) { + return false; + } + + if (otherprincipal && loadingPrincipal) { + bool equals = false; + otherprincipal->Equals(loadingPrincipal, &equals); + if (!equals) { + return false; + } + } + } + + // Content Policy Check on Cached Images + return ShouldLoadCachedImage(request, aCX, loadingPrincipal, aPolicyType); +} + +static nsresult +NewImageChannel(nsIChannel** aResult, + // If aForcePrincipalCheckForCacheEntry is true, then we will + // force a principal check even when not using CORS before + // assuming we have a cache hit on a cache entry that we + // create for this channel. This is an out param that should + // be set to true if this channel ends up depending on + // aLoadingPrincipal and false otherwise. + bool* aForcePrincipalCheckForCacheEntry, + nsIURI* aURI, + nsIURI* aInitialDocumentURI, + int32_t aCORSMode, + nsIURI* aReferringURI, + ReferrerPolicy aReferrerPolicy, + nsILoadGroup* aLoadGroup, + const nsCString& aAcceptHeader, + nsLoadFlags aLoadFlags, + nsContentPolicyType aPolicyType, + nsIPrincipal* aLoadingPrincipal, + nsISupports* aRequestingContext, + bool aRespectPrivacy) +{ + MOZ_ASSERT(aResult); + + nsresult rv; + nsCOMPtr newHttpChannel; + + nsCOMPtr callbacks; + + if (aLoadGroup) { + // Get the notification callbacks from the load group for the new channel. + // + // XXX: This is not exactly correct, because the network request could be + // referenced by multiple windows... However, the new channel needs + // something. So, using the 'first' notification callbacks is better + // than nothing... + // + aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); + } + + // Pass in a nullptr loadgroup because this is the underlying network + // request. This request may be referenced by several proxy image requests + // (possibly in different documents). + // If all of the proxy requests are canceled then this request should be + // canceled too. + // + aLoadFlags |= nsIChannel::LOAD_CLASSIFY_URI; + + nsCOMPtr requestingNode = do_QueryInterface(aRequestingContext); + + nsSecurityFlags securityFlags = + aCORSMode == imgIRequest::CORS_NONE + ? nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS + : nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS; + if (aCORSMode == imgIRequest::CORS_ANONYMOUS) { + securityFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN; + } else if (aCORSMode == imgIRequest::CORS_USE_CREDENTIALS) { + securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE; + } + securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME; + + // Note we are calling NS_NewChannelWithTriggeringPrincipal() here with a + // node and a principal. This is for things like background images that are + // specified by user stylesheets, where the document is being styled, but + // the principal is that of the user stylesheet. + if (requestingNode && aLoadingPrincipal) { + rv = NS_NewChannelWithTriggeringPrincipal(aResult, + aURI, + requestingNode, + aLoadingPrincipal, + securityFlags, + aPolicyType, + nullptr, // loadGroup + callbacks, + aLoadFlags); + + if (NS_FAILED(rv)) { + return rv; + } + + if (aPolicyType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) { + // If this is a favicon loading, we will use the originAttributes from the + // loadingPrincipal as the channel's originAttributes. This allows the favicon + // loading from XUL will use the correct originAttributes. + NeckoOriginAttributes neckoAttrs; + neckoAttrs.InheritFromDocToNecko(BasePrincipal::Cast(aLoadingPrincipal)->OriginAttributesRef()); + + nsCOMPtr loadInfo = (*aResult)->GetLoadInfo(); + rv = loadInfo->SetOriginAttributes(neckoAttrs); + } + } else { + // either we are loading something inside a document, in which case + // we should always have a requestingNode, or we are loading something + // outside a document, in which case the loadingPrincipal and + // triggeringPrincipal should always be the systemPrincipal. + // However, there are exceptions: one is Notifications which create a + // channel in the parent prcoess in which case we can't get a requestingNode. + rv = NS_NewChannel(aResult, + aURI, + nsContentUtils::GetSystemPrincipal(), + securityFlags, + aPolicyType, + nullptr, // loadGroup + callbacks, + aLoadFlags); + + if (NS_FAILED(rv)) { + return rv; + } + + // Use the OriginAttributes from the loading principal, if one is available, + // and adjust the private browsing ID based on what kind of load the caller + // has asked us to perform. + NeckoOriginAttributes neckoAttrs; + if (aLoadingPrincipal) { + neckoAttrs.InheritFromDocToNecko(BasePrincipal::Cast(aLoadingPrincipal)->OriginAttributesRef()); + } + neckoAttrs.mPrivateBrowsingId = aRespectPrivacy ? 1 : 0; + + nsCOMPtr loadInfo = (*aResult)->GetLoadInfo(); + rv = loadInfo->SetOriginAttributes(neckoAttrs); + } + + if (NS_FAILED(rv)) { + return rv; + } + + // only inherit if we have a principal + *aForcePrincipalCheckForCacheEntry = + aLoadingPrincipal && + nsContentUtils::ChannelShouldInheritPrincipal( + aLoadingPrincipal, + aURI, + /* aInheritForAboutBlank */ false, + /* aForceInherit */ false); + + // Initialize HTTP-specific attributes + newHttpChannel = do_QueryInterface(*aResult); + if (newHttpChannel) { + newHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"), + aAcceptHeader, + false); + + nsCOMPtr httpChannelInternal = + do_QueryInterface(newHttpChannel); + NS_ENSURE_TRUE(httpChannelInternal, NS_ERROR_UNEXPECTED); + httpChannelInternal->SetDocumentURI(aInitialDocumentURI); + newHttpChannel->SetReferrerWithPolicy(aReferringURI, aReferrerPolicy); + } + + // Image channels are loaded by default with reduced priority. + nsCOMPtr p = do_QueryInterface(*aResult); + if (p) { + uint32_t priority = nsISupportsPriority::PRIORITY_LOW; + + if (aLoadFlags & nsIRequest::LOAD_BACKGROUND) { + ++priority; // further reduce priority for background loads + } + + p->AdjustPriority(priority); + } + + // Create a new loadgroup for this new channel, using the old group as + // the parent. The indirection keeps the channel insulated from cancels, + // but does allow a way for this revalidation to be associated with at + // least one base load group for scheduling/caching purposes. + + nsCOMPtr loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID); + nsCOMPtr childLoadGroup = do_QueryInterface(loadGroup); + if (childLoadGroup) { + childLoadGroup->SetParentLoadGroup(aLoadGroup); + } + (*aResult)->SetLoadGroup(loadGroup); + + return NS_OK; +} + +static uint32_t +SecondsFromPRTime(PRTime prTime) +{ + return uint32_t(int64_t(prTime) / int64_t(PR_USEC_PER_SEC)); +} + +imgCacheEntry::imgCacheEntry(imgLoader* loader, imgRequest* request, + bool forcePrincipalCheck) + : mLoader(loader), + mRequest(request), + mDataSize(0), + mTouchedTime(SecondsFromPRTime(PR_Now())), + mLoadTime(SecondsFromPRTime(PR_Now())), + mExpiryTime(0), + mMustValidate(false), + // We start off as evicted so we don't try to update the cache. PutIntoCache + // will set this to false. + mEvicted(true), + mHasNoProxies(true), + mForcePrincipalCheck(forcePrincipalCheck) +{ } + +imgCacheEntry::~imgCacheEntry() +{ + LOG_FUNC(gImgLog, "imgCacheEntry::~imgCacheEntry()"); +} + +void +imgCacheEntry::Touch(bool updateTime /* = true */) +{ + LOG_SCOPE(gImgLog, "imgCacheEntry::Touch"); + + if (updateTime) { + mTouchedTime = SecondsFromPRTime(PR_Now()); + } + + UpdateCache(); +} + +void +imgCacheEntry::UpdateCache(int32_t diff /* = 0 */) +{ + // Don't update the cache if we've been removed from it or it doesn't care + // about our size or usage. + if (!Evicted() && HasNoProxies()) { + mLoader->CacheEntriesChanged(mRequest->IsChrome(), diff); + } +} + +void imgCacheEntry::UpdateLoadTime() +{ + mLoadTime = SecondsFromPRTime(PR_Now()); +} + +void +imgCacheEntry::SetHasNoProxies(bool hasNoProxies) +{ + if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) { + if (hasNoProxies) { + LOG_FUNC_WITH_PARAM(gImgLog, "imgCacheEntry::SetHasNoProxies true", + "uri", mRequest->CacheKey().Spec()); + } else { + LOG_FUNC_WITH_PARAM(gImgLog, "imgCacheEntry::SetHasNoProxies false", + "uri", mRequest->CacheKey().Spec()); + } + } + + mHasNoProxies = hasNoProxies; +} + +imgCacheQueue::imgCacheQueue() + : mDirty(false), + mSize(0) +{ } + +void +imgCacheQueue::UpdateSize(int32_t diff) +{ + mSize += diff; +} + +uint32_t +imgCacheQueue::GetSize() const +{ + return mSize; +} + +#include +using namespace std; + +void +imgCacheQueue::Remove(imgCacheEntry* entry) +{ + queueContainer::iterator it = find(mQueue.begin(), mQueue.end(), entry); + if (it != mQueue.end()) { + mSize -= (*it)->GetDataSize(); + mQueue.erase(it); + MarkDirty(); + } +} + +void +imgCacheQueue::Push(imgCacheEntry* entry) +{ + mSize += entry->GetDataSize(); + + RefPtr refptr(entry); + mQueue.push_back(refptr); + MarkDirty(); +} + +already_AddRefed +imgCacheQueue::Pop() +{ + if (mQueue.empty()) { + return nullptr; + } + if (IsDirty()) { + Refresh(); + } + + RefPtr entry = mQueue[0]; + std::pop_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries); + mQueue.pop_back(); + + mSize -= entry->GetDataSize(); + return entry.forget(); +} + +void +imgCacheQueue::Refresh() +{ + std::make_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries); + mDirty = false; +} + +void +imgCacheQueue::MarkDirty() +{ + mDirty = true; +} + +bool +imgCacheQueue::IsDirty() +{ + return mDirty; +} + +uint32_t +imgCacheQueue::GetNumElements() const +{ + return mQueue.size(); +} + +imgCacheQueue::iterator +imgCacheQueue::begin() +{ + return mQueue.begin(); +} + +imgCacheQueue::const_iterator +imgCacheQueue::begin() const +{ + return mQueue.begin(); +} + +imgCacheQueue::iterator +imgCacheQueue::end() +{ + return mQueue.end(); +} + +imgCacheQueue::const_iterator +imgCacheQueue::end() const +{ + return mQueue.end(); +} + +nsresult +imgLoader::CreateNewProxyForRequest(imgRequest* aRequest, + nsILoadGroup* aLoadGroup, + imgINotificationObserver* aObserver, + nsLoadFlags aLoadFlags, + imgRequestProxy** _retval) +{ + LOG_SCOPE_WITH_PARAM(gImgLog, "imgLoader::CreateNewProxyForRequest", + "imgRequest", aRequest); + + /* XXX If we move decoding onto separate threads, we should save off the + calling thread here and pass it off to |proxyRequest| so that it call + proxy calls to |aObserver|. + */ + + RefPtr proxyRequest = new imgRequestProxy(); + + /* It is important to call |SetLoadFlags()| before calling |Init()| because + |Init()| adds the request to the loadgroup. + */ + proxyRequest->SetLoadFlags(aLoadFlags); + + RefPtr uri; + aRequest->GetURI(getter_AddRefs(uri)); + + // init adds itself to imgRequest's list of observers + nsresult rv = proxyRequest->Init(aRequest, aLoadGroup, uri, aObserver); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + proxyRequest.forget(_retval); + return NS_OK; +} + +class imgCacheExpirationTracker final + : public nsExpirationTracker +{ + enum { TIMEOUT_SECONDS = 10 }; +public: + imgCacheExpirationTracker(); + +protected: + void NotifyExpired(imgCacheEntry* entry); +}; + +imgCacheExpirationTracker::imgCacheExpirationTracker() + : nsExpirationTracker(TIMEOUT_SECONDS * 1000, + "imgCacheExpirationTracker") +{ } + +void +imgCacheExpirationTracker::NotifyExpired(imgCacheEntry* entry) +{ + // Hold on to a reference to this entry, because the expiration tracker + // mechanism doesn't. + RefPtr kungFuDeathGrip(entry); + + if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) { + RefPtr req = entry->GetRequest(); + if (req) { + LOG_FUNC_WITH_PARAM(gImgLog, + "imgCacheExpirationTracker::NotifyExpired", + "entry", req->CacheKey().Spec()); + } + } + + // We can be called multiple times on the same entry. Don't do work multiple + // times. + if (!entry->Evicted()) { + entry->Loader()->RemoveFromCache(entry); + } + + entry->Loader()->VerifyCacheSizes(); +} + + +/////////////////////////////////////////////////////////////////////////////// +// imgLoader +/////////////////////////////////////////////////////////////////////////////// + +double imgLoader::sCacheTimeWeight; +uint32_t imgLoader::sCacheMaxSize; +imgMemoryReporter* imgLoader::sMemReporter; + +NS_IMPL_ISUPPORTS(imgLoader, imgILoader, nsIContentSniffer, imgICache, + nsISupportsWeakReference, nsIObserver) + +static imgLoader* gNormalLoader = nullptr; +static imgLoader* gPrivateBrowsingLoader = nullptr; + +/* static */ already_AddRefed +imgLoader::CreateImageLoader() +{ + // In some cases, such as xpctests, XPCOM modules are not automatically + // initialized. We need to make sure that our module is initialized before + // we hand out imgLoader instances and code starts using them. + mozilla::image::EnsureModuleInitialized(); + + RefPtr loader = new imgLoader(); + loader->Init(); + + return loader.forget(); +} + +imgLoader* +imgLoader::NormalLoader() +{ + if (!gNormalLoader) { + gNormalLoader = CreateImageLoader().take(); + } + return gNormalLoader; +} + +imgLoader* +imgLoader::PrivateBrowsingLoader() +{ + if (!gPrivateBrowsingLoader) { + gPrivateBrowsingLoader = CreateImageLoader().take(); + gPrivateBrowsingLoader->RespectPrivacyNotifications(); + } + return gPrivateBrowsingLoader; +} + +imgLoader::imgLoader() +: mUncachedImagesMutex("imgLoader::UncachedImages"), mRespectPrivacy(false) +{ + sMemReporter->AddRef(); + sMemReporter->RegisterLoader(this); +} + +imgLoader::~imgLoader() +{ + ClearChromeImageCache(); + ClearImageCache(); + { + // If there are any of our imgRequest's left they are in the uncached + // images set, so clear their pointer to us. + MutexAutoLock lock(mUncachedImagesMutex); + for (auto iter = mUncachedImages.Iter(); !iter.Done(); iter.Next()) { + nsPtrHashKey* entry = iter.Get(); + RefPtr req = entry->GetKey(); + req->ClearLoader(); + } + } + sMemReporter->UnregisterLoader(this); + sMemReporter->Release(); +} + +void +imgLoader::VerifyCacheSizes() +{ +#ifdef DEBUG + if (!mCacheTracker) { + return; + } + + uint32_t cachesize = mCache.Count() + mChromeCache.Count(); + uint32_t queuesize = + mCacheQueue.GetNumElements() + mChromeCacheQueue.GetNumElements(); + uint32_t trackersize = 0; + for (nsExpirationTracker::Iterator it(mCacheTracker.get()); + it.Next(); ){ + trackersize++; + } + MOZ_ASSERT(queuesize == trackersize, "Queue and tracker sizes out of sync!"); + MOZ_ASSERT(queuesize <= cachesize, "Queue has more elements than cache!"); +#endif +} + +imgLoader::imgCacheTable& +imgLoader::GetCache(bool aForChrome) +{ + return aForChrome ? mChromeCache : mCache; +} + +imgLoader::imgCacheTable& +imgLoader::GetCache(const ImageCacheKey& aKey) +{ + return GetCache(aKey.IsChrome()); +} + +imgCacheQueue& +imgLoader::GetCacheQueue(bool aForChrome) +{ + return aForChrome ? mChromeCacheQueue : mCacheQueue; + +} + +imgCacheQueue& +imgLoader::GetCacheQueue(const ImageCacheKey& aKey) +{ + return GetCacheQueue(aKey.IsChrome()); + +} + +void imgLoader::GlobalInit() +{ + sCacheTimeWeight = gfxPrefs::ImageCacheTimeWeight() / 1000.0; + int32_t cachesize = gfxPrefs::ImageCacheSize(); + sCacheMaxSize = cachesize > 0 ? cachesize : 0; + + sMemReporter = new imgMemoryReporter(); + RegisterStrongMemoryReporter(sMemReporter); + RegisterImagesContentUsedUncompressedDistinguishedAmount( + imgMemoryReporter::ImagesContentUsedUncompressedDistinguishedAmount); +} + +void imgLoader::ShutdownMemoryReporter() +{ + UnregisterImagesContentUsedUncompressedDistinguishedAmount(); + UnregisterStrongMemoryReporter(sMemReporter); +} + +nsresult +imgLoader::InitCache() +{ + nsCOMPtr os = mozilla::services::GetObserverService(); + if (!os) { + return NS_ERROR_FAILURE; + } + + os->AddObserver(this, "memory-pressure", false); + os->AddObserver(this, "chrome-flush-skin-caches", false); + os->AddObserver(this, "chrome-flush-caches", false); + os->AddObserver(this, "last-pb-context-exited", false); + os->AddObserver(this, "profile-before-change", false); + os->AddObserver(this, "xpcom-shutdown", false); + + mCacheTracker = MakeUnique(); + + return NS_OK; +} + +nsresult +imgLoader::Init() +{ + InitCache(); + + ReadAcceptHeaderPref(); + + Preferences::AddWeakObserver(this, "image.http.accept"); + + return NS_OK; +} + +NS_IMETHODIMP +imgLoader::RespectPrivacyNotifications() +{ + mRespectPrivacy = true; + return NS_OK; +} + +NS_IMETHODIMP +imgLoader::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + // We listen for pref change notifications... + if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { + if (!NS_strcmp(aData, u"image.http.accept")) { + ReadAcceptHeaderPref(); + } + + } else if (strcmp(aTopic, "memory-pressure") == 0) { + MinimizeCaches(); + } else if (strcmp(aTopic, "chrome-flush-skin-caches") == 0 || + strcmp(aTopic, "chrome-flush-caches") == 0) { + MinimizeCaches(); + ClearChromeImageCache(); + } else if (strcmp(aTopic, "last-pb-context-exited") == 0) { + if (mRespectPrivacy) { + ClearImageCache(); + ClearChromeImageCache(); + } + } else if (strcmp(aTopic, "profile-before-change") == 0) { + mCacheTracker = nullptr; + } else if (strcmp(aTopic, "xpcom-shutdown") == 0) { + mCacheTracker = nullptr; + ShutdownMemoryReporter(); + + } else { + // (Nothing else should bring us here) + MOZ_ASSERT(0, "Invalid topic received"); + } + + return NS_OK; +} + +void imgLoader::ReadAcceptHeaderPref() +{ + nsAdoptingCString accept = Preferences::GetCString("image.http.accept"); + if (accept) { + mAcceptHeader = accept; + } else { + mAcceptHeader = + IMAGE_PNG "," IMAGE_WILDCARD ";q=0.8," ANY_WILDCARD ";q=0.5"; + } +} + +NS_IMETHODIMP +imgLoader::ClearCache(bool chrome) +{ + if (XRE_IsParentProcess()) { + bool privateLoader = this == gPrivateBrowsingLoader; + for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) { + Unused << cp->SendClearImageCache(privateLoader, chrome); + } + } + + if (chrome) { + return ClearChromeImageCache(); + } else { + return ClearImageCache(); + } +} + +NS_IMETHODIMP +imgLoader::FindEntryProperties(nsIURI* uri, + nsIDOMDocument* aDOMDoc, + nsIProperties** _retval) +{ + *_retval = nullptr; + + nsCOMPtr doc = do_QueryInterface(aDOMDoc); + + PrincipalOriginAttributes attrs; + if (doc) { + nsCOMPtr principal = doc->NodePrincipal(); + if (principal) { + attrs = BasePrincipal::Cast(principal)->OriginAttributesRef(); + } + } + + nsresult rv; + ImageCacheKey key(uri, attrs, doc, rv); + NS_ENSURE_SUCCESS(rv, rv); + imgCacheTable& cache = GetCache(key); + + RefPtr entry; + if (cache.Get(key, getter_AddRefs(entry)) && entry) { + if (mCacheTracker && entry->HasNoProxies()) { + mCacheTracker->MarkUsed(entry); + } + + RefPtr request = entry->GetRequest(); + if (request) { + nsCOMPtr properties = request->Properties(); + properties.forget(_retval); + } + } + + return NS_OK; +} + +NS_IMETHODIMP_(void) +imgLoader::ClearCacheForControlledDocument(nsIDocument* aDoc) +{ + MOZ_ASSERT(aDoc); + AutoTArray, 128> entriesToBeRemoved; + imgCacheTable& cache = GetCache(false); + for (auto iter = cache.Iter(); !iter.Done(); iter.Next()) { + auto& key = iter.Key(); + if (key.ControlledDocument() == aDoc) { + entriesToBeRemoved.AppendElement(iter.Data()); + } + } + for (auto& entry : entriesToBeRemoved) { + if (!RemoveFromCache(entry)) { + NS_WARNING("Couldn't remove an entry from the cache in ClearCacheForControlledDocument()\n"); + } + } +} + +void +imgLoader::Shutdown() +{ + NS_IF_RELEASE(gNormalLoader); + gNormalLoader = nullptr; + NS_IF_RELEASE(gPrivateBrowsingLoader); + gPrivateBrowsingLoader = nullptr; +} + +nsresult +imgLoader::ClearChromeImageCache() +{ + return EvictEntries(mChromeCache); +} + +nsresult +imgLoader::ClearImageCache() +{ + return EvictEntries(mCache); +} + +void +imgLoader::MinimizeCaches() +{ + EvictEntries(mCacheQueue); + EvictEntries(mChromeCacheQueue); +} + +bool +imgLoader::PutIntoCache(const ImageCacheKey& aKey, imgCacheEntry* entry) +{ + imgCacheTable& cache = GetCache(aKey); + + LOG_STATIC_FUNC_WITH_PARAM(gImgLog, + "imgLoader::PutIntoCache", "uri", aKey.Spec()); + + // Check to see if this request already exists in the cache. If so, we'll + // replace the old version. + RefPtr tmpCacheEntry; + if (cache.Get(aKey, getter_AddRefs(tmpCacheEntry)) && tmpCacheEntry) { + MOZ_LOG(gImgLog, LogLevel::Debug, + ("[this=%p] imgLoader::PutIntoCache -- Element already in the cache", + nullptr)); + RefPtr tmpRequest = tmpCacheEntry->GetRequest(); + + // If it already exists, and we're putting the same key into the cache, we + // should remove the old version. + MOZ_LOG(gImgLog, LogLevel::Debug, + ("[this=%p] imgLoader::PutIntoCache -- Replacing cached element", + nullptr)); + + RemoveFromCache(aKey); + } else { + MOZ_LOG(gImgLog, LogLevel::Debug, + ("[this=%p] imgLoader::PutIntoCache --" + " Element NOT already in the cache", nullptr)); + } + + cache.Put(aKey, entry); + + // We can be called to resurrect an evicted entry. + if (entry->Evicted()) { + entry->SetEvicted(false); + } + + // If we're resurrecting an entry with no proxies, put it back in the + // tracker and queue. + if (entry->HasNoProxies()) { + nsresult addrv = NS_OK; + + if (mCacheTracker) { + addrv = mCacheTracker->AddObject(entry); + } + + if (NS_SUCCEEDED(addrv)) { + imgCacheQueue& queue = GetCacheQueue(aKey); + queue.Push(entry); + } + } + + RefPtr request = entry->GetRequest(); + request->SetIsInCache(true); + RemoveFromUncachedImages(request); + + return true; +} + +bool +imgLoader::SetHasNoProxies(imgRequest* aRequest, imgCacheEntry* aEntry) +{ + LOG_STATIC_FUNC_WITH_PARAM(gImgLog, + "imgLoader::SetHasNoProxies", "uri", + aRequest->CacheKey().Spec()); + + aEntry->SetHasNoProxies(true); + + if (aEntry->Evicted()) { + return false; + } + + imgCacheQueue& queue = GetCacheQueue(aRequest->IsChrome()); + + nsresult addrv = NS_OK; + + if (mCacheTracker) { + addrv = mCacheTracker->AddObject(aEntry); + } + + if (NS_SUCCEEDED(addrv)) { + queue.Push(aEntry); + } + + imgCacheTable& cache = GetCache(aRequest->IsChrome()); + CheckCacheLimits(cache, queue); + + return true; +} + +bool +imgLoader::SetHasProxies(imgRequest* aRequest) +{ + VerifyCacheSizes(); + + const ImageCacheKey& key = aRequest->CacheKey(); + imgCacheTable& cache = GetCache(key); + + LOG_STATIC_FUNC_WITH_PARAM(gImgLog, + "imgLoader::SetHasProxies", "uri", key.Spec()); + + RefPtr entry; + if (cache.Get(key, getter_AddRefs(entry)) && entry) { + // Make sure the cache entry is for the right request + RefPtr entryRequest = entry->GetRequest(); + if (entryRequest == aRequest && entry->HasNoProxies()) { + imgCacheQueue& queue = GetCacheQueue(key); + queue.Remove(entry); + + if (mCacheTracker) { + mCacheTracker->RemoveObject(entry); + } + + entry->SetHasNoProxies(false); + + return true; + } + } + + return false; +} + +void +imgLoader::CacheEntriesChanged(bool aForChrome, int32_t aSizeDiff /* = 0 */) +{ + imgCacheQueue& queue = GetCacheQueue(aForChrome); + queue.MarkDirty(); + queue.UpdateSize(aSizeDiff); +} + +void +imgLoader::CheckCacheLimits(imgCacheTable& cache, imgCacheQueue& queue) +{ + if (queue.GetNumElements() == 0) { + NS_ASSERTION(queue.GetSize() == 0, + "imgLoader::CheckCacheLimits -- incorrect cache size"); + } + + // Remove entries from the cache until we're back at our desired max size. + while (queue.GetSize() > sCacheMaxSize) { + // Remove the first entry in the queue. + RefPtr entry(queue.Pop()); + + NS_ASSERTION(entry, "imgLoader::CheckCacheLimits -- NULL entry pointer"); + + if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) { + RefPtr req = entry->GetRequest(); + if (req) { + LOG_STATIC_FUNC_WITH_PARAM(gImgLog, + "imgLoader::CheckCacheLimits", + "entry", req->CacheKey().Spec()); + } + } + + if (entry) { + RemoveFromCache(entry); + } + } +} + +bool +imgLoader::ValidateRequestWithNewChannel(imgRequest* request, + nsIURI* aURI, + nsIURI* aInitialDocumentURI, + nsIURI* aReferrerURI, + ReferrerPolicy aReferrerPolicy, + nsILoadGroup* aLoadGroup, + imgINotificationObserver* aObserver, + nsISupports* aCX, + nsLoadFlags aLoadFlags, + nsContentPolicyType aLoadPolicyType, + imgRequestProxy** aProxyRequest, + nsIPrincipal* aLoadingPrincipal, + int32_t aCORSMode) +{ + // now we need to insert a new channel request object inbetween the real + // request and the proxy that basically delays loading the image until it + // gets a 304 or figures out that this needs to be a new request + + nsresult rv; + + // If we're currently in the middle of validating this request, just hand + // back a proxy to it; the required work will be done for us. + if (request->GetValidator()) { + rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver, + aLoadFlags, aProxyRequest); + if (NS_FAILED(rv)) { + return false; + } + + if (*aProxyRequest) { + imgRequestProxy* proxy = static_cast(*aProxyRequest); + + // We will send notifications from imgCacheValidator::OnStartRequest(). + // In the mean time, we must defer notifications because we are added to + // the imgRequest's proxy list, and we can get extra notifications + // resulting from methods such as StartDecoding(). See bug 579122. + proxy->SetNotificationsDeferred(true); + + // Attach the proxy without notifying + request->GetValidator()->AddProxy(proxy); + } + + return NS_SUCCEEDED(rv); + + } else { + // We will rely on Necko to cache this request when it's possible, and to + // tell imgCacheValidator::OnStartRequest whether the request came from its + // cache. + nsCOMPtr newChannel; + bool forcePrincipalCheck; + rv = NewImageChannel(getter_AddRefs(newChannel), + &forcePrincipalCheck, + aURI, + aInitialDocumentURI, + aCORSMode, + aReferrerURI, + aReferrerPolicy, + aLoadGroup, + mAcceptHeader, + aLoadFlags, + aLoadPolicyType, + aLoadingPrincipal, + aCX, + mRespectPrivacy); + if (NS_FAILED(rv)) { + return false; + } + + RefPtr req; + rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver, + aLoadFlags, getter_AddRefs(req)); + if (NS_FAILED(rv)) { + return false; + } + + // Make sure that OnStatus/OnProgress calls have the right request set... + RefPtr progressproxy = + new nsProgressNotificationProxy(newChannel, req); + if (!progressproxy) { + return false; + } + + RefPtr hvc = + new imgCacheValidator(progressproxy, this, request, aCX, + forcePrincipalCheck); + + // Casting needed here to get past multiple inheritance. + nsCOMPtr listener = + do_QueryInterface(static_cast(hvc)); + NS_ENSURE_TRUE(listener, false); + + // We must set the notification callbacks before setting up the + // CORS listener, because that's also interested inthe + // notification callbacks. + newChannel->SetNotificationCallbacks(hvc); + + request->SetValidator(hvc); + + // We will send notifications from imgCacheValidator::OnStartRequest(). + // In the mean time, we must defer notifications because we are added to + // the imgRequest's proxy list, and we can get extra notifications + // resulting from methods such as StartDecoding(). See bug 579122. + req->SetNotificationsDeferred(true); + + // Add the proxy without notifying + hvc->AddProxy(req); + + mozilla::net::PredictorLearn(aURI, aInitialDocumentURI, + nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE, aLoadGroup); + + rv = newChannel->AsyncOpen2(listener); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + req.forget(aProxyRequest); + return true; + } +} + +bool +imgLoader::ValidateEntry(imgCacheEntry* aEntry, + nsIURI* aURI, + nsIURI* aInitialDocumentURI, + nsIURI* aReferrerURI, + ReferrerPolicy aReferrerPolicy, + nsILoadGroup* aLoadGroup, + imgINotificationObserver* aObserver, + nsISupports* aCX, + nsLoadFlags aLoadFlags, + nsContentPolicyType aLoadPolicyType, + bool aCanMakeNewChannel, + imgRequestProxy** aProxyRequest, + nsIPrincipal* aLoadingPrincipal, + int32_t aCORSMode) +{ + LOG_SCOPE(gImgLog, "imgLoader::ValidateEntry"); + + bool hasExpired; + uint32_t expirationTime = aEntry->GetExpiryTime(); + if (expirationTime <= SecondsFromPRTime(PR_Now())) { + hasExpired = true; + } else { + hasExpired = false; + } + + nsresult rv; + + // Special treatment for file URLs - aEntry has expired if file has changed + nsCOMPtr fileUrl(do_QueryInterface(aURI)); + if (fileUrl) { + uint32_t lastModTime = aEntry->GetLoadTime(); + + nsCOMPtr theFile; + rv = fileUrl->GetFile(getter_AddRefs(theFile)); + if (NS_SUCCEEDED(rv)) { + PRTime fileLastMod; + rv = theFile->GetLastModifiedTime(&fileLastMod); + if (NS_SUCCEEDED(rv)) { + // nsIFile uses millisec, NSPR usec + fileLastMod *= 1000; + hasExpired = SecondsFromPRTime((PRTime)fileLastMod) > lastModTime; + } + } + } + + RefPtr request(aEntry->GetRequest()); + + if (!request) { + return false; + } + + if (!ValidateSecurityInfo(request, aEntry->ForcePrincipalCheck(), + aCORSMode, aLoadingPrincipal, + aCX, aLoadPolicyType, aReferrerPolicy)) + return false; + + // data URIs are immutable and by their nature can't leak data, so we can + // just return true in that case. Doing so would mean that shift-reload + // doesn't reload data URI documents/images though (which is handy for + // debugging during gecko development) so we make an exception in that case. + nsAutoCString scheme; + aURI->GetScheme(scheme); + if (scheme.EqualsLiteral("data") && + !(aLoadFlags & nsIRequest::LOAD_BYPASS_CACHE)) { + return true; + } + + bool validateRequest = false; + + // If the request's loadId is the same as the aCX, then it is ok to use + // this one because it has already been validated for this context. + // + // XXX: nullptr seems to be a 'special' key value that indicates that NO + // validation is required. + // + void *key = (void*) aCX; + if (request->LoadId() != key) { + // If we would need to revalidate this entry, but we're being told to + // bypass the cache, we don't allow this entry to be used. + if (aLoadFlags & nsIRequest::LOAD_BYPASS_CACHE) { + return false; + } + + if (MOZ_UNLIKELY(ChaosMode::isActive(ChaosFeature::ImageCache))) { + if (ChaosMode::randomUint32LessThan(4) < 1) { + return false; + } + } + + // Determine whether the cache aEntry must be revalidated... + validateRequest = ShouldRevalidateEntry(aEntry, aLoadFlags, hasExpired); + + MOZ_LOG(gImgLog, LogLevel::Debug, + ("imgLoader::ValidateEntry validating cache entry. " + "validateRequest = %d", validateRequest)); + } else if (!key && MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) { + MOZ_LOG(gImgLog, LogLevel::Debug, + ("imgLoader::ValidateEntry BYPASSING cache validation for %s " + "because of NULL LoadID", aURI->GetSpecOrDefault().get())); + } + + // We can't use a cached request if it comes from a different + // application cache than this load is expecting. + nsCOMPtr appCacheContainer; + nsCOMPtr requestAppCache; + nsCOMPtr groupAppCache; + if ((appCacheContainer = do_GetInterface(request->GetRequest()))) { + appCacheContainer->GetApplicationCache(getter_AddRefs(requestAppCache)); + } + if ((appCacheContainer = do_QueryInterface(aLoadGroup))) { + appCacheContainer->GetApplicationCache(getter_AddRefs(groupAppCache)); + } + + if (requestAppCache != groupAppCache) { + MOZ_LOG(gImgLog, LogLevel::Debug, + ("imgLoader::ValidateEntry - Unable to use cached imgRequest " + "[request=%p] because of mismatched application caches\n", + address_of(request))); + return false; + } + + if (validateRequest && aCanMakeNewChannel) { + LOG_SCOPE(gImgLog, + "imgLoader::ValidateRequest |cache hit| must validate"); + + return ValidateRequestWithNewChannel(request, aURI, aInitialDocumentURI, + aReferrerURI, aReferrerPolicy, + aLoadGroup, aObserver, + aCX, aLoadFlags, aLoadPolicyType, + aProxyRequest, aLoadingPrincipal, + aCORSMode); + } + + return !validateRequest; +} + +bool +imgLoader::RemoveFromCache(const ImageCacheKey& aKey) +{ + LOG_STATIC_FUNC_WITH_PARAM(gImgLog, + "imgLoader::RemoveFromCache", "uri", aKey.Spec()); + + imgCacheTable& cache = GetCache(aKey); + imgCacheQueue& queue = GetCacheQueue(aKey); + + RefPtr entry; + if (cache.Get(aKey, getter_AddRefs(entry)) && entry) { + cache.Remove(aKey); + + MOZ_ASSERT(!entry->Evicted(), "Evicting an already-evicted cache entry!"); + + // Entries with no proxies are in the tracker. + if (entry->HasNoProxies()) { + if (mCacheTracker) { + mCacheTracker->RemoveObject(entry); + } + queue.Remove(entry); + } + + entry->SetEvicted(true); + + RefPtr request = entry->GetRequest(); + request->SetIsInCache(false); + AddToUncachedImages(request); + + return true; + + } else { + return false; + } +} + +bool +imgLoader::RemoveFromCache(imgCacheEntry* entry) +{ + LOG_STATIC_FUNC(gImgLog, "imgLoader::RemoveFromCache entry"); + + RefPtr request = entry->GetRequest(); + if (request) { + const ImageCacheKey& key = request->CacheKey(); + imgCacheTable& cache = GetCache(key); + imgCacheQueue& queue = GetCacheQueue(key); + + LOG_STATIC_FUNC_WITH_PARAM(gImgLog, + "imgLoader::RemoveFromCache", "entry's uri", + key.Spec()); + + cache.Remove(key); + + if (entry->HasNoProxies()) { + LOG_STATIC_FUNC(gImgLog, + "imgLoader::RemoveFromCache removing from tracker"); + if (mCacheTracker) { + mCacheTracker->RemoveObject(entry); + } + queue.Remove(entry); + } + + entry->SetEvicted(true); + request->SetIsInCache(false); + AddToUncachedImages(request); + + return true; + } + + return false; +} + +nsresult +imgLoader::EvictEntries(imgCacheTable& aCacheToClear) +{ + LOG_STATIC_FUNC(gImgLog, "imgLoader::EvictEntries table"); + + // We have to make a temporary, since RemoveFromCache removes the element + // from the queue, invalidating iterators. + nsTArray > entries; + for (auto iter = aCacheToClear.Iter(); !iter.Done(); iter.Next()) { + RefPtr& data = iter.Data(); + entries.AppendElement(data); + } + + for (uint32_t i = 0; i < entries.Length(); ++i) { + if (!RemoveFromCache(entries[i])) { + return NS_ERROR_FAILURE; + } + } + + MOZ_ASSERT(aCacheToClear.Count() == 0); + + return NS_OK; +} + +nsresult +imgLoader::EvictEntries(imgCacheQueue& aQueueToClear) +{ + LOG_STATIC_FUNC(gImgLog, "imgLoader::EvictEntries queue"); + + // We have to make a temporary, since RemoveFromCache removes the element + // from the queue, invalidating iterators. + nsTArray > entries(aQueueToClear.GetNumElements()); + for (imgCacheQueue::const_iterator i = aQueueToClear.begin(); + i != aQueueToClear.end(); ++i) { + entries.AppendElement(*i); + } + + for (uint32_t i = 0; i < entries.Length(); ++i) { + if (!RemoveFromCache(entries[i])) { + return NS_ERROR_FAILURE; + } + } + + MOZ_ASSERT(aQueueToClear.GetNumElements() == 0); + + return NS_OK; +} + +void +imgLoader::AddToUncachedImages(imgRequest* aRequest) +{ + MutexAutoLock lock(mUncachedImagesMutex); + mUncachedImages.PutEntry(aRequest); +} + +void +imgLoader::RemoveFromUncachedImages(imgRequest* aRequest) +{ + MutexAutoLock lock(mUncachedImagesMutex); + mUncachedImages.RemoveEntry(aRequest); +} + + +#define LOAD_FLAGS_CACHE_MASK (nsIRequest::LOAD_BYPASS_CACHE | \ + nsIRequest::LOAD_FROM_CACHE) + +#define LOAD_FLAGS_VALIDATE_MASK (nsIRequest::VALIDATE_ALWAYS | \ + nsIRequest::VALIDATE_NEVER | \ + nsIRequest::VALIDATE_ONCE_PER_SESSION) + +NS_IMETHODIMP +imgLoader::LoadImageXPCOM(nsIURI* aURI, + nsIURI* aInitialDocumentURI, + nsIURI* aReferrerURI, + const nsAString& aReferrerPolicy, + nsIPrincipal* aLoadingPrincipal, + nsILoadGroup* aLoadGroup, + imgINotificationObserver* aObserver, + nsISupports* aCX, + nsLoadFlags aLoadFlags, + nsISupports* aCacheKey, + nsContentPolicyType aContentPolicyType, + imgIRequest** _retval) +{ + // Optional parameter, so defaults to 0 (== TYPE_INVALID) + if (!aContentPolicyType) { + aContentPolicyType = nsIContentPolicy::TYPE_INTERNAL_IMAGE; + } + imgRequestProxy* proxy; + ReferrerPolicy refpol = ReferrerPolicyFromString(aReferrerPolicy); + nsCOMPtr node = do_QueryInterface(aCX); + nsCOMPtr doc = do_QueryInterface(aCX); + nsresult rv = LoadImage(aURI, + aInitialDocumentURI, + aReferrerURI, + refpol == mozilla::net::RP_Unset ? + mozilla::net::RP_Default : refpol, + aLoadingPrincipal, + aLoadGroup, + aObserver, + node, + doc, + aLoadFlags, + aCacheKey, + aContentPolicyType, + EmptyString(), + &proxy); + *_retval = proxy; + return rv; +} + +nsresult +imgLoader::LoadImage(nsIURI* aURI, + nsIURI* aInitialDocumentURI, + nsIURI* aReferrerURI, + ReferrerPolicy aReferrerPolicy, + nsIPrincipal* aLoadingPrincipal, + nsILoadGroup* aLoadGroup, + imgINotificationObserver* aObserver, + nsINode *aContext, + nsIDocument* aLoadingDocument, + nsLoadFlags aLoadFlags, + nsISupports* aCacheKey, + nsContentPolicyType aContentPolicyType, + const nsAString& initiatorType, + imgRequestProxy** _retval) +{ + VerifyCacheSizes(); + + NS_ASSERTION(aURI, "imgLoader::LoadImage -- NULL URI pointer"); + + if (!aURI) { + return NS_ERROR_NULL_POINTER; + } + + LOG_SCOPE_WITH_PARAM(gImgLog, "imgLoader::LoadImage", "aURI", + aURI->GetSpecOrDefault().get()); + + *_retval = nullptr; + + RefPtr request; + + nsresult rv; + nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL; + +#ifdef DEBUG + bool isPrivate = false; + + if (aLoadGroup) { + nsCOMPtr callbacks; + aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); + if (callbacks) { + nsCOMPtr loadContext = do_GetInterface(callbacks); + isPrivate = loadContext && loadContext->UsePrivateBrowsing(); + } + } + MOZ_ASSERT(isPrivate == mRespectPrivacy); +#endif + + // Get the default load flags from the loadgroup (if possible)... + if (aLoadGroup) { + aLoadGroup->GetLoadFlags(&requestFlags); + } + // + // Merge the default load flags with those passed in via aLoadFlags. + // Currently, *only* the caching, validation and background load flags + // are merged... + // + // The flags in aLoadFlags take precedence over the default flags! + // + if (aLoadFlags & LOAD_FLAGS_CACHE_MASK) { + // Override the default caching flags... + requestFlags = (requestFlags & ~LOAD_FLAGS_CACHE_MASK) | + (aLoadFlags & LOAD_FLAGS_CACHE_MASK); + } + if (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK) { + // Override the default validation flags... + requestFlags = (requestFlags & ~LOAD_FLAGS_VALIDATE_MASK) | + (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK); + } + if (aLoadFlags & nsIRequest::LOAD_BACKGROUND) { + // Propagate background loading... + requestFlags |= nsIRequest::LOAD_BACKGROUND; + } + + int32_t corsmode = imgIRequest::CORS_NONE; + if (aLoadFlags & imgILoader::LOAD_CORS_ANONYMOUS) { + corsmode = imgIRequest::CORS_ANONYMOUS; + } else if (aLoadFlags & imgILoader::LOAD_CORS_USE_CREDENTIALS) { + corsmode = imgIRequest::CORS_USE_CREDENTIALS; + } + + RefPtr entry; + + // Look in the cache for our URI, and then validate it. + // XXX For now ignore aCacheKey. We will need it in the future + // for correctly dealing with image load requests that are a result + // of post data. + PrincipalOriginAttributes attrs; + if (aLoadingPrincipal) { + attrs = BasePrincipal::Cast(aLoadingPrincipal)->OriginAttributesRef(); + } + ImageCacheKey key(aURI, attrs, aLoadingDocument, rv); + NS_ENSURE_SUCCESS(rv, rv); + imgCacheTable& cache = GetCache(key); + + if (cache.Get(key, getter_AddRefs(entry)) && entry) { + if (ValidateEntry(entry, aURI, aInitialDocumentURI, aReferrerURI, + aReferrerPolicy, aLoadGroup, aObserver, aLoadingDocument, + requestFlags, aContentPolicyType, true, _retval, + aLoadingPrincipal, corsmode)) { + request = entry->GetRequest(); + + // If this entry has no proxies, its request has no reference to the + // entry. + if (entry->HasNoProxies()) { + LOG_FUNC_WITH_PARAM(gImgLog, + "imgLoader::LoadImage() adding proxyless entry", "uri", key.Spec()); + MOZ_ASSERT(!request->HasCacheEntry(), + "Proxyless entry's request has cache entry!"); + request->SetCacheEntry(entry); + + if (mCacheTracker && entry->GetExpirationState()->IsTracked()) { + mCacheTracker->MarkUsed(entry); + } + } + + entry->Touch(); + + } else { + // We can't use this entry. We'll try to load it off the network, and if + // successful, overwrite the old entry in the cache with a new one. + entry = nullptr; + } + } + + // Keep the channel in this scope, so we can adjust its notificationCallbacks + // later when we create the proxy. + nsCOMPtr newChannel; + // If we didn't get a cache hit, we need to load from the network. + if (!request) { + LOG_SCOPE(gImgLog, "imgLoader::LoadImage |cache miss|"); + + bool forcePrincipalCheck; + rv = NewImageChannel(getter_AddRefs(newChannel), + &forcePrincipalCheck, + aURI, + aInitialDocumentURI, + corsmode, + aReferrerURI, + aReferrerPolicy, + aLoadGroup, + mAcceptHeader, + requestFlags, + aContentPolicyType, + aLoadingPrincipal, + aContext, + mRespectPrivacy); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(NS_UsePrivateBrowsing(newChannel) == mRespectPrivacy); + + NewRequestAndEntry(forcePrincipalCheck, this, key, + getter_AddRefs(request), + getter_AddRefs(entry)); + + MOZ_LOG(gImgLog, LogLevel::Debug, + ("[this=%p] imgLoader::LoadImage -- Created new imgRequest" + " [request=%p]\n", this, request.get())); + + nsCOMPtr channelLoadGroup; + newChannel->GetLoadGroup(getter_AddRefs(channelLoadGroup)); + rv = request->Init(aURI, aURI, /* aHadInsecureRedirect = */ false, + channelLoadGroup, newChannel, entry, aLoadingDocument, + aLoadingPrincipal, corsmode, aReferrerPolicy); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + + // Add the initiator type for this image load + nsCOMPtr timedChannel = do_QueryInterface(newChannel); + if (timedChannel) { + timedChannel->SetInitiatorType(initiatorType); + } + + // create the proxy listener + nsCOMPtr listener = new ProxyListener(request.get()); + + MOZ_LOG(gImgLog, LogLevel::Debug, + ("[this=%p] imgLoader::LoadImage -- Calling channel->AsyncOpen2()\n", + this)); + + mozilla::net::PredictorLearn(aURI, aInitialDocumentURI, + nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE, aLoadGroup); + + nsresult openRes = newChannel->AsyncOpen2(listener); + + if (NS_FAILED(openRes)) { + MOZ_LOG(gImgLog, LogLevel::Debug, + ("[this=%p] imgLoader::LoadImage -- AsyncOpen2() failed: 0x%x\n", + this, openRes)); + request->CancelAndAbort(openRes); + return openRes; + } + + // Try to add the new request into the cache. + PutIntoCache(key, entry); + } else { + LOG_MSG_WITH_PARAM(gImgLog, + "imgLoader::LoadImage |cache hit|", "request", request); + } + + + // If we didn't get a proxy when validating the cache entry, we need to + // create one. + if (!*_retval) { + // ValidateEntry() has three return values: "Is valid," "might be valid -- + // validating over network", and "not valid." If we don't have a _retval, + // we know ValidateEntry is not validating over the network, so it's safe + // to SetLoadId here because we know this request is valid for this context. + // + // Note, however, that this doesn't guarantee the behaviour we want (one + // URL maps to the same image on a page) if we load the same image in a + // different tab (see bug 528003), because its load id will get re-set, and + // that'll cause us to validate over the network. + request->SetLoadId(aLoadingDocument); + + LOG_MSG(gImgLog, "imgLoader::LoadImage", "creating proxy request."); + rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver, + requestFlags, _retval); + if (NS_FAILED(rv)) { + return rv; + } + + imgRequestProxy* proxy = *_retval; + + // Make sure that OnStatus/OnProgress calls have the right request set, if + // we did create a channel here. + if (newChannel) { + nsCOMPtr requestor( + new nsProgressNotificationProxy(newChannel, proxy)); + if (!requestor) { + return NS_ERROR_OUT_OF_MEMORY; + } + newChannel->SetNotificationCallbacks(requestor); + } + + // Note that it's OK to add here even if the request is done. If it is, + // it'll send a OnStopRequest() to the proxy in imgRequestProxy::Notify and + // the proxy will be removed from the loadgroup. + proxy->AddToLoadGroup(); + + // If we're loading off the network, explicitly don't notify our proxy, + // because necko (or things called from necko, such as imgCacheValidator) + // are going to call our notifications asynchronously, and we can't make it + // further asynchronous because observers might rely on imagelib completing + // its work between the channel's OnStartRequest and OnStopRequest. + if (!newChannel) { + proxy->NotifyListener(); + } + + return rv; + } + + NS_ASSERTION(*_retval, "imgLoader::LoadImage -- no return value"); + + return NS_OK; +} + +NS_IMETHODIMP +imgLoader::LoadImageWithChannelXPCOM(nsIChannel* channel, + imgINotificationObserver* aObserver, + nsISupports* aCX, + nsIStreamListener** listener, + imgIRequest** _retval) +{ + nsresult result; + imgRequestProxy* proxy; + result = LoadImageWithChannel(channel, + aObserver, + aCX, + listener, + &proxy); + *_retval = proxy; + return result; +} + +nsresult +imgLoader::LoadImageWithChannel(nsIChannel* channel, + imgINotificationObserver* aObserver, + nsISupports* aCX, + nsIStreamListener** listener, + imgRequestProxy** _retval) +{ + NS_ASSERTION(channel, + "imgLoader::LoadImageWithChannel -- NULL channel pointer"); + + MOZ_ASSERT(NS_UsePrivateBrowsing(channel) == mRespectPrivacy); + + RefPtr request; + + nsCOMPtr uri; + channel->GetURI(getter_AddRefs(uri)); + nsCOMPtr doc = do_QueryInterface(aCX); + + NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE); + nsCOMPtr loadInfo = channel->GetLoadInfo(); + + PrincipalOriginAttributes attrs; + if (loadInfo) { + attrs.InheritFromNecko(loadInfo->GetOriginAttributes()); + } + + nsresult rv; + ImageCacheKey key(uri, attrs, doc, rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL; + channel->GetLoadFlags(&requestFlags); + + RefPtr entry; + + if (requestFlags & nsIRequest::LOAD_BYPASS_CACHE) { + RemoveFromCache(key); + } else { + // Look in the cache for our URI, and then validate it. + // XXX For now ignore aCacheKey. We will need it in the future + // for correctly dealing with image load requests that are a result + // of post data. + imgCacheTable& cache = GetCache(key); + if (cache.Get(key, getter_AddRefs(entry)) && entry) { + // We don't want to kick off another network load. So we ask + // ValidateEntry to only do validation without creating a new proxy. If + // it says that the entry isn't valid any more, we'll only use the entry + // we're getting if the channel is loading from the cache anyways. + // + // XXX -- should this be changed? it's pretty much verbatim from the old + // code, but seems nonsensical. + // + // Since aCanMakeNewChannel == false, we don't need to pass content policy + // type/principal/etc + + nsCOMPtr loadInfo = channel->GetLoadInfo(); + // if there is a loadInfo, use the right contentType, otherwise + // default to the internal image type + nsContentPolicyType policyType = loadInfo + ? loadInfo->InternalContentPolicyType() + : nsIContentPolicy::TYPE_INTERNAL_IMAGE; + + if (ValidateEntry(entry, uri, nullptr, nullptr, RP_Default, + nullptr, aObserver, aCX, requestFlags, + policyType, false, nullptr, + nullptr, imgIRequest::CORS_NONE)) { + request = entry->GetRequest(); + } else { + nsCOMPtr cacheChan(do_QueryInterface(channel)); + bool bUseCacheCopy; + + if (cacheChan) { + cacheChan->IsFromCache(&bUseCacheCopy); + } else { + bUseCacheCopy = false; + } + + if (!bUseCacheCopy) { + entry = nullptr; + } else { + request = entry->GetRequest(); + } + } + + if (request && entry) { + // If this entry has no proxies, its request has no reference to + // the entry. + if (entry->HasNoProxies()) { + LOG_FUNC_WITH_PARAM(gImgLog, + "imgLoader::LoadImageWithChannel() adding proxyless entry", + "uri", key.Spec()); + MOZ_ASSERT(!request->HasCacheEntry(), + "Proxyless entry's request has cache entry!"); + request->SetCacheEntry(entry); + + if (mCacheTracker && entry->GetExpirationState()->IsTracked()) { + mCacheTracker->MarkUsed(entry); + } + } + } + } + } + + nsCOMPtr loadGroup; + channel->GetLoadGroup(getter_AddRefs(loadGroup)); + + // Filter out any load flags not from nsIRequest + requestFlags &= nsIRequest::LOAD_REQUESTMASK; + + rv = NS_OK; + if (request) { + // we have this in our cache already.. cancel the current (document) load + + // this should fire an OnStopRequest + channel->Cancel(NS_ERROR_PARSED_DATA_CACHED); + + *listener = nullptr; // give them back a null nsIStreamListener + + rv = CreateNewProxyForRequest(request, loadGroup, aObserver, + requestFlags, _retval); + static_cast(*_retval)->NotifyListener(); + } else { + // We use originalURI here to fulfil the imgIRequest contract on GetURI. + nsCOMPtr originalURI; + channel->GetOriginalURI(getter_AddRefs(originalURI)); + + // XXX(seth): We should be able to just use |key| here, except that |key| is + // constructed above with the *current URI* and not the *original URI*. I'm + // pretty sure this is a bug, and it's preventing us from ever getting a + // cache hit in LoadImageWithChannel when redirects are involved. + ImageCacheKey originalURIKey(originalURI, attrs, doc, rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Default to doing a principal check because we don't know who + // started that load and whether their principal ended up being + // inherited on the channel. + NewRequestAndEntry(/* aForcePrincipalCheckForCacheEntry = */ true, + this, originalURIKey, + getter_AddRefs(request), + getter_AddRefs(entry)); + + // No principal specified here, because we're not passed one. + // In LoadImageWithChannel, the redirects that may have been + // assoicated with this load would have gone through necko. + // We only have the final URI in ImageLib and hence don't know + // if the request went through insecure redirects. But if it did, + // the necko cache should have handled that (since all necko cache hits + // including the redirects will go through content policy). Hence, we + // can set aHadInsecureRedirect to false here. + rv = request->Init(originalURI, uri, /* aHadInsecureRedirect = */ false, + channel, channel, entry, aCX, nullptr, + imgIRequest::CORS_NONE, RP_Default); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr pl = + new ProxyListener(static_cast(request.get())); + pl.forget(listener); + + // Try to add the new request into the cache. + PutIntoCache(originalURIKey, entry); + + rv = CreateNewProxyForRequest(request, loadGroup, aObserver, + requestFlags, _retval); + + // Explicitly don't notify our proxy, because we're loading off the + // network, and necko (or things called from necko, such as + // imgCacheValidator) are going to call our notifications asynchronously, + // and we can't make it further asynchronous because observers might rely + // on imagelib completing its work between the channel's OnStartRequest and + // OnStopRequest. + } + + return rv; +} + +bool +imgLoader::SupportImageWithMimeType(const char* aMimeType, + AcceptedMimeTypes aAccept + /* = AcceptedMimeTypes::IMAGES */) +{ + nsAutoCString mimeType(aMimeType); + ToLowerCase(mimeType); + + if (aAccept == AcceptedMimeTypes::IMAGES_AND_DOCUMENTS && + mimeType.EqualsLiteral("image/svg+xml")) { + return true; + } + + DecoderType type = DecoderFactory::GetDecoderType(mimeType.get()); + return type != DecoderType::UNKNOWN; +} + +NS_IMETHODIMP +imgLoader::GetMIMETypeFromContent(nsIRequest* aRequest, + const uint8_t* aContents, + uint32_t aLength, + nsACString& aContentType) +{ + return GetMimeTypeFromContent((const char*)aContents, aLength, aContentType); +} + +/* static */ +nsresult +imgLoader::GetMimeTypeFromContent(const char* aContents, + uint32_t aLength, + nsACString& aContentType) +{ + /* Is it a GIF? */ + if (aLength >= 6 && (!nsCRT::strncmp(aContents, "GIF87a", 6) || + !nsCRT::strncmp(aContents, "GIF89a", 6))) { + aContentType.AssignLiteral(IMAGE_GIF); + + /* or a PNG? */ + } else if (aLength >= 8 && ((unsigned char)aContents[0]==0x89 && + (unsigned char)aContents[1]==0x50 && + (unsigned char)aContents[2]==0x4E && + (unsigned char)aContents[3]==0x47 && + (unsigned char)aContents[4]==0x0D && + (unsigned char)aContents[5]==0x0A && + (unsigned char)aContents[6]==0x1A && + (unsigned char)aContents[7]==0x0A)) { + aContentType.AssignLiteral(IMAGE_PNG); + + /* maybe a JPEG (JFIF)? */ + /* JFIF files start with SOI APP0 but older files can start with SOI DQT + * so we test for SOI followed by any marker, i.e. FF D8 FF + * this will also work for SPIFF JPEG files if they appear in the future. + * + * (JFIF is 0XFF 0XD8 0XFF 0XE0 0X4A 0X46 0X49 0X46 0X00) + */ + } else if (aLength >= 3 && + ((unsigned char)aContents[0])==0xFF && + ((unsigned char)aContents[1])==0xD8 && + ((unsigned char)aContents[2])==0xFF) { + aContentType.AssignLiteral(IMAGE_JPEG); + + /* or how about ART? */ + /* ART begins with JG (4A 47). Major version offset 2. + * Minor version offset 3. Offset 4 must be nullptr. + */ + } else if (aLength >= 5 && + ((unsigned char) aContents[0])==0x4a && + ((unsigned char) aContents[1])==0x47 && + ((unsigned char) aContents[4])==0x00 ) { + aContentType.AssignLiteral(IMAGE_ART); + + } else if (aLength >= 2 && !nsCRT::strncmp(aContents, "BM", 2)) { + aContentType.AssignLiteral(IMAGE_BMP); + + // ICOs always begin with a 2-byte 0 followed by a 2-byte 1. + // CURs begin with 2-byte 0 followed by 2-byte 2. + } else if (aLength >= 4 && (!memcmp(aContents, "\000\000\001\000", 4) || + !memcmp(aContents, "\000\000\002\000", 4))) { + aContentType.AssignLiteral(IMAGE_ICO); + + } else { + /* none of the above? I give up */ + return NS_ERROR_NOT_AVAILABLE; + } + + return NS_OK; +} + +/** + * proxy stream listener class used to handle multipart/x-mixed-replace + */ + +#include "nsIRequest.h" +#include "nsIStreamConverterService.h" + +NS_IMPL_ISUPPORTS(ProxyListener, + nsIStreamListener, + nsIThreadRetargetableStreamListener, + nsIRequestObserver) + +ProxyListener::ProxyListener(nsIStreamListener* dest) : + mDestListener(dest) +{ + /* member initializers and constructor code */ +} + +ProxyListener::~ProxyListener() +{ + /* destructor code */ +} + + +/** nsIRequestObserver methods **/ + +NS_IMETHODIMP +ProxyListener::OnStartRequest(nsIRequest* aRequest, nsISupports* ctxt) +{ + if (!mDestListener) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr channel(do_QueryInterface(aRequest)); + if (channel) { + // We need to set the initiator type for the image load + nsCOMPtr timedChannel = do_QueryInterface(channel); + if (timedChannel) { + nsAutoString type; + timedChannel->GetInitiatorType(type); + if (type.IsEmpty()) { + timedChannel->SetInitiatorType(NS_LITERAL_STRING("img")); + } + } + + nsAutoCString contentType; + nsresult rv = channel->GetContentType(contentType); + + if (!contentType.IsEmpty()) { + /* If multipart/x-mixed-replace content, we'll insert a MIME decoder + in the pipeline to handle the content and pass it along to our + original listener. + */ + if (NS_LITERAL_CSTRING("multipart/x-mixed-replace").Equals(contentType)) { + + nsCOMPtr convServ( + do_GetService("@mozilla.org/streamConverters;1", &rv)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr toListener(mDestListener); + nsCOMPtr fromListener; + + rv = convServ->AsyncConvertData("multipart/x-mixed-replace", + "*/*", + toListener, + nullptr, + getter_AddRefs(fromListener)); + if (NS_SUCCEEDED(rv)) { + mDestListener = fromListener; + } + } + } + } + } + + return mDestListener->OnStartRequest(aRequest, ctxt); +} + +NS_IMETHODIMP +ProxyListener::OnStopRequest(nsIRequest* aRequest, + nsISupports* ctxt, + nsresult status) +{ + if (!mDestListener) { + return NS_ERROR_FAILURE; + } + + return mDestListener->OnStopRequest(aRequest, ctxt, status); +} + +/** nsIStreamListener methods **/ + +NS_IMETHODIMP +ProxyListener::OnDataAvailable(nsIRequest* aRequest, nsISupports* ctxt, + nsIInputStream* inStr, uint64_t sourceOffset, + uint32_t count) +{ + if (!mDestListener) { + return NS_ERROR_FAILURE; + } + + return mDestListener->OnDataAvailable(aRequest, ctxt, inStr, + sourceOffset, count); +} + +/** nsThreadRetargetableStreamListener methods **/ +NS_IMETHODIMP +ProxyListener::CheckListenerChain() +{ + NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!"); + nsresult rv = NS_OK; + nsCOMPtr retargetableListener = + do_QueryInterface(mDestListener, &rv); + if (retargetableListener) { + rv = retargetableListener->CheckListenerChain(); + } + MOZ_LOG(gImgLog, LogLevel::Debug, + ("ProxyListener::CheckListenerChain %s [this=%p listener=%p rv=%x]", + (NS_SUCCEEDED(rv) ? "success" : "failure"), + this, (nsIStreamListener*)mDestListener, rv)); + return rv; +} + +/** + * http validate class. check a channel for a 304 + */ + +NS_IMPL_ISUPPORTS(imgCacheValidator, nsIStreamListener, nsIRequestObserver, + nsIThreadRetargetableStreamListener, + nsIChannelEventSink, nsIInterfaceRequestor, + nsIAsyncVerifyRedirectCallback) + +imgCacheValidator::imgCacheValidator(nsProgressNotificationProxy* progress, + imgLoader* loader, imgRequest* request, + nsISupports* aContext, + bool forcePrincipalCheckForCacheEntry) + : mProgressProxy(progress), + mRequest(request), + mContext(aContext), + mImgLoader(loader), + mHadInsecureRedirect(false) +{ + NewRequestAndEntry(forcePrincipalCheckForCacheEntry, loader, + mRequest->CacheKey(), + getter_AddRefs(mNewRequest), + getter_AddRefs(mNewEntry)); +} + +imgCacheValidator::~imgCacheValidator() +{ + if (mRequest) { + mRequest->SetValidator(nullptr); + } +} + +void +imgCacheValidator::AddProxy(imgRequestProxy* aProxy) +{ + // aProxy needs to be in the loadgroup since we're validating from + // the network. + aProxy->AddToLoadGroup(); + + mProxies.AppendObject(aProxy); +} + +/** nsIRequestObserver methods **/ + +NS_IMETHODIMP +imgCacheValidator::OnStartRequest(nsIRequest* aRequest, nsISupports* ctxt) +{ + // We may be holding on to a document, so ensure that it's released. + nsCOMPtr context = mContext.forget(); + + // If for some reason we don't still have an existing request (probably + // because OnStartRequest got delivered more than once), just bail. + if (!mRequest) { + MOZ_ASSERT_UNREACHABLE("OnStartRequest delivered more than once?"); + aRequest->Cancel(NS_BINDING_ABORTED); + return NS_ERROR_FAILURE; + } + + // If this request is coming from cache and has the same URI as our + // imgRequest, the request all our proxies are pointing at is valid, and all + // we have to do is tell them to notify their listeners. + nsCOMPtr cacheChan(do_QueryInterface(aRequest)); + nsCOMPtr channel(do_QueryInterface(aRequest)); + if (cacheChan && channel && !mRequest->CacheChanged(aRequest)) { + bool isFromCache = false; + cacheChan->IsFromCache(&isFromCache); + + nsCOMPtr channelURI; + channel->GetURI(getter_AddRefs(channelURI)); + + nsCOMPtr currentURI; + mRequest->GetCurrentURI(getter_AddRefs(currentURI)); + + bool sameURI = false; + if (channelURI && currentURI) { + channelURI->Equals(currentURI, &sameURI); + } + + if (isFromCache && sameURI) { + uint32_t count = mProxies.Count(); + for (int32_t i = count-1; i>=0; i--) { + imgRequestProxy* proxy = static_cast(mProxies[i]); + + // Proxies waiting on cache validation should be deferring + // notifications. Undefer them. + MOZ_ASSERT(proxy->NotificationsDeferred(), + "Proxies waiting on cache validation should be " + "deferring notifications!"); + proxy->SetNotificationsDeferred(false); + + // Notify synchronously, because we're already in OnStartRequest, an + // asynchronously-called function. + proxy->SyncNotifyListener(); + } + + // We don't need to load this any more. + aRequest->Cancel(NS_BINDING_ABORTED); + + mRequest->SetLoadId(context); + mRequest->SetValidator(nullptr); + + mRequest = nullptr; + + mNewRequest = nullptr; + mNewEntry = nullptr; + + return NS_OK; + } + } + + // We can't load out of cache. We have to create a whole new request for the + // data that's coming in off the channel. + nsCOMPtr uri; + { + RefPtr imageURL; + mRequest->GetURI(getter_AddRefs(imageURL)); + uri = imageURL->ToIURI(); + } + + if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) { + LOG_MSG_WITH_PARAM(gImgLog, + "imgCacheValidator::OnStartRequest creating new request", + "uri", uri->GetSpecOrDefault().get()); + } + + int32_t corsmode = mRequest->GetCORSMode(); + ReferrerPolicy refpol = mRequest->GetReferrerPolicy(); + nsCOMPtr loadingPrincipal = mRequest->GetLoadingPrincipal(); + + // Doom the old request's cache entry + mRequest->RemoveFromCache(); + + mRequest->SetValidator(nullptr); + mRequest = nullptr; + + // We use originalURI here to fulfil the imgIRequest contract on GetURI. + nsCOMPtr originalURI; + channel->GetOriginalURI(getter_AddRefs(originalURI)); + nsresult rv = + mNewRequest->Init(originalURI, uri, mHadInsecureRedirect, aRequest, channel, + mNewEntry, context, loadingPrincipal, corsmode, refpol); + if (NS_FAILED(rv)) { + return rv; + } + + mDestListener = new ProxyListener(mNewRequest); + + // Try to add the new request into the cache. Note that the entry must be in + // the cache before the proxies' ownership changes, because adding a proxy + // changes the caching behaviour for imgRequests. + mImgLoader->PutIntoCache(mNewRequest->CacheKey(), mNewEntry); + + uint32_t count = mProxies.Count(); + for (int32_t i = count-1; i>=0; i--) { + imgRequestProxy* proxy = static_cast(mProxies[i]); + proxy->ChangeOwner(mNewRequest); + + // Notify synchronously, because we're already in OnStartRequest, an + // asynchronously-called function. + proxy->SetNotificationsDeferred(false); + proxy->SyncNotifyListener(); + } + + mNewRequest = nullptr; + mNewEntry = nullptr; + + return mDestListener->OnStartRequest(aRequest, ctxt); +} + +NS_IMETHODIMP +imgCacheValidator::OnStopRequest(nsIRequest* aRequest, + nsISupports* ctxt, + nsresult status) +{ + // Be sure we've released the document that we may have been holding on to. + mContext = nullptr; + + if (!mDestListener) { + return NS_OK; + } + + return mDestListener->OnStopRequest(aRequest, ctxt, status); +} + +/** nsIStreamListener methods **/ + + +NS_IMETHODIMP +imgCacheValidator::OnDataAvailable(nsIRequest* aRequest, nsISupports* ctxt, + nsIInputStream* inStr, + uint64_t sourceOffset, uint32_t count) +{ + if (!mDestListener) { + // XXX see bug 113959 + uint32_t _retval; + inStr->ReadSegments(NS_DiscardSegment, nullptr, count, &_retval); + return NS_OK; + } + + return mDestListener->OnDataAvailable(aRequest, ctxt, inStr, sourceOffset, + count); +} + +/** nsIThreadRetargetableStreamListener methods **/ + +NS_IMETHODIMP +imgCacheValidator::CheckListenerChain() +{ + NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!"); + nsresult rv = NS_OK; + nsCOMPtr retargetableListener = + do_QueryInterface(mDestListener, &rv); + if (retargetableListener) { + rv = retargetableListener->CheckListenerChain(); + } + MOZ_LOG(gImgLog, LogLevel::Debug, + ("[this=%p] imgCacheValidator::CheckListenerChain -- rv %d=%s", + this, NS_SUCCEEDED(rv) ? "succeeded" : "failed", rv)); + return rv; +} + +/** nsIInterfaceRequestor methods **/ + +NS_IMETHODIMP +imgCacheValidator::GetInterface(const nsIID& aIID, void** aResult) +{ + if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { + return QueryInterface(aIID, aResult); + } + + return mProgressProxy->GetInterface(aIID, aResult); +} + +// These functions are materially the same as the same functions in imgRequest. +// We duplicate them because we're verifying whether cache loads are necessary, +// not unconditionally loading. + +/** nsIChannelEventSink methods **/ +NS_IMETHODIMP +imgCacheValidator:: + AsyncOnChannelRedirect(nsIChannel* oldChannel, + nsIChannel* newChannel, + uint32_t flags, + nsIAsyncVerifyRedirectCallback* callback) +{ + // Note all cache information we get from the old channel. + mNewRequest->SetCacheValidation(mNewEntry, oldChannel); + + // If the previous URI is a non-HTTPS URI, record that fact for later use by + // security code, which needs to know whether there is an insecure load at any + // point in the redirect chain. + nsCOMPtr oldURI; + bool isHttps = false; + bool isChrome = false; + bool schemeLocal = false; + if (NS_FAILED(oldChannel->GetURI(getter_AddRefs(oldURI))) || + NS_FAILED(oldURI->SchemeIs("https", &isHttps)) || + NS_FAILED(oldURI->SchemeIs("chrome", &isChrome)) || + NS_FAILED(NS_URIChainHasFlags(oldURI, + nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, + &schemeLocal)) || + (!isHttps && !isChrome && !schemeLocal)) { + mHadInsecureRedirect = true; + } + + // Prepare for callback + mRedirectCallback = callback; + mRedirectChannel = newChannel; + + return mProgressProxy->AsyncOnChannelRedirect(oldChannel, newChannel, flags, + this); +} + +NS_IMETHODIMP +imgCacheValidator::OnRedirectVerifyCallback(nsresult aResult) +{ + // If we've already been told to abort, just do so. + if (NS_FAILED(aResult)) { + mRedirectCallback->OnRedirectVerifyCallback(aResult); + mRedirectCallback = nullptr; + mRedirectChannel = nullptr; + return NS_OK; + } + + // make sure we have a protocol that returns data rather than opens + // an external application, e.g. mailto: + nsCOMPtr uri; + mRedirectChannel->GetURI(getter_AddRefs(uri)); + bool doesNotReturnData = false; + NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA, + &doesNotReturnData); + + nsresult result = NS_OK; + + if (doesNotReturnData) { + result = NS_ERROR_ABORT; + } + + mRedirectCallback->OnRedirectVerifyCallback(result); + mRedirectCallback = nullptr; + mRedirectChannel = nullptr; + return NS_OK; +} diff --git a/image/imgLoader.h b/image/imgLoader.h new file mode 100644 index 000000000..8e818a290 --- /dev/null +++ b/image/imgLoader.h @@ -0,0 +1,574 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_imgLoader_h +#define mozilla_image_imgLoader_h + +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" +#include "mozilla/UniquePtr.h" + +#include "imgILoader.h" +#include "imgICache.h" +#include "nsWeakReference.h" +#include "nsIContentSniffer.h" +#include "nsRefPtrHashtable.h" +#include "nsExpirationTracker.h" +#include "ImageCacheKey.h" +#include "imgRequest.h" +#include "nsIProgressEventSink.h" +#include "nsIChannel.h" +#include "nsIThreadRetargetableStreamListener.h" +#include "imgIRequest.h" +#include "mozilla/net/ReferrerPolicy.h" + +class imgLoader; +class imgRequestProxy; +class imgINotificationObserver; +class nsILoadGroup; +class imgCacheExpirationTracker; +class imgMemoryReporter; + +namespace mozilla { +namespace image { +class ImageURL; +} // namespace image +} // namespace mozilla + +class imgCacheEntry +{ +public: + imgCacheEntry(imgLoader* loader, imgRequest* request, + bool aForcePrincipalCheck); + ~imgCacheEntry(); + + nsrefcnt AddRef() + { + NS_PRECONDITION(int32_t(mRefCnt) >= 0, "illegal refcnt"); + MOZ_ASSERT(_mOwningThread.GetThread() == PR_GetCurrentThread(), + "imgCacheEntry addref isn't thread-safe!"); + ++mRefCnt; + NS_LOG_ADDREF(this, mRefCnt, "imgCacheEntry", sizeof(*this)); + return mRefCnt; + } + + nsrefcnt Release() + { + NS_PRECONDITION(0 != mRefCnt, "dup release"); + MOZ_ASSERT(_mOwningThread.GetThread() == PR_GetCurrentThread(), + "imgCacheEntry release isn't thread-safe!"); + --mRefCnt; + NS_LOG_RELEASE(this, mRefCnt, "imgCacheEntry"); + if (mRefCnt == 0) { + mRefCnt = 1; /* stabilize */ + delete this; + return 0; + } + return mRefCnt; + } + + uint32_t GetDataSize() const + { + return mDataSize; + } + void SetDataSize(uint32_t aDataSize) + { + int32_t oldsize = mDataSize; + mDataSize = aDataSize; + UpdateCache(mDataSize - oldsize); + } + + int32_t GetTouchedTime() const + { + return mTouchedTime; + } + void SetTouchedTime(int32_t time) + { + mTouchedTime = time; + Touch(/* updateTime = */ false); + } + + uint32_t GetLoadTime() const + { + return mLoadTime; + } + + void UpdateLoadTime(); + + int32_t GetExpiryTime() const + { + return mExpiryTime; + } + void SetExpiryTime(int32_t aExpiryTime) + { + mExpiryTime = aExpiryTime; + Touch(); + } + + bool GetMustValidate() const + { + return mMustValidate; + } + void SetMustValidate(bool aValidate) + { + mMustValidate = aValidate; + Touch(); + } + + already_AddRefed GetRequest() const + { + RefPtr req = mRequest; + return req.forget(); + } + + bool Evicted() const + { + return mEvicted; + } + + nsExpirationState* GetExpirationState() + { + return &mExpirationState; + } + + bool HasNoProxies() const + { + return mHasNoProxies; + } + + bool ForcePrincipalCheck() const + { + return mForcePrincipalCheck; + } + + imgLoader* Loader() const + { + return mLoader; + } + +private: // methods + friend class imgLoader; + friend class imgCacheQueue; + void Touch(bool updateTime = true); + void UpdateCache(int32_t diff = 0); + void SetEvicted(bool evict) + { + mEvicted = evict; + } + void SetHasNoProxies(bool hasNoProxies); + + // Private, unimplemented copy constructor. + imgCacheEntry(const imgCacheEntry&); + +private: // data + nsAutoRefCnt mRefCnt; + NS_DECL_OWNINGTHREAD + + imgLoader* mLoader; + RefPtr mRequest; + uint32_t mDataSize; + int32_t mTouchedTime; + uint32_t mLoadTime; + int32_t mExpiryTime; + nsExpirationState mExpirationState; + bool mMustValidate : 1; + bool mEvicted : 1; + bool mHasNoProxies : 1; + bool mForcePrincipalCheck : 1; +}; + +#include + +#define NS_IMGLOADER_CID \ +{ /* c1354898-e3fe-4602-88a7-c4520c21cb4e */ \ + 0xc1354898, \ + 0xe3fe, \ + 0x4602, \ + {0x88, 0xa7, 0xc4, 0x52, 0x0c, 0x21, 0xcb, 0x4e} \ +} + +class imgCacheQueue +{ +public: + imgCacheQueue(); + void Remove(imgCacheEntry*); + void Push(imgCacheEntry*); + void MarkDirty(); + bool IsDirty(); + already_AddRefed Pop(); + void Refresh(); + uint32_t GetSize() const; + void UpdateSize(int32_t diff); + uint32_t GetNumElements() const; + typedef std::vector > queueContainer; + typedef queueContainer::iterator iterator; + typedef queueContainer::const_iterator const_iterator; + + iterator begin(); + const_iterator begin() const; + iterator end(); + const_iterator end() const; + +private: + queueContainer mQueue; + bool mDirty; + uint32_t mSize; +}; + +enum class AcceptedMimeTypes : uint8_t { + IMAGES, + IMAGES_AND_DOCUMENTS, +}; + +class imgLoader final : public imgILoader, + public nsIContentSniffer, + public imgICache, + public nsSupportsWeakReference, + public nsIObserver +{ + virtual ~imgLoader(); + +public: + typedef mozilla::image::ImageCacheKey ImageCacheKey; + typedef mozilla::image::ImageURL ImageURL; + typedef nsRefPtrHashtable, + imgCacheEntry> imgCacheTable; + typedef nsTHashtable> imgSet; + typedef mozilla::net::ReferrerPolicy ReferrerPolicy; + typedef mozilla::Mutex Mutex; + + NS_DECL_ISUPPORTS + NS_DECL_IMGILOADER + NS_DECL_NSICONTENTSNIFFER + NS_DECL_IMGICACHE + NS_DECL_NSIOBSERVER + + /** + * Get the normal image loader instance that is used by gecko code, creating + * it if necessary. + */ + static imgLoader* NormalLoader(); + + /** + * Get the Private Browsing image loader instance that is used by gecko code, + * creating it if necessary. + */ + static imgLoader* PrivateBrowsingLoader(); + + /** + * Gecko code should use NormalLoader() or PrivateBrowsingLoader() to get the + * appropriate image loader. + * + * This constructor is public because the XPCOM module code that creates + * instances of "@mozilla.org/image/loader;1" / "@mozilla.org/image/cache;1" + * for nsIComponentManager.createInstance()/nsIServiceManager.getService() + * calls (now only made by add-ons) needs access to it. + * + * XXX We would like to get rid of the nsIServiceManager.getService (and + * nsIComponentManager.createInstance) method of creating imgLoader objects, + * but there are add-ons that are still using it. These add-ons don't + * actually do anything useful with the loaders that they create since nobody + * who creates an imgLoader using this method actually QIs to imgILoader and + * loads images. They all just QI to imgICache and either call clearCache() + * or findEntryProperties(). Since they're doing this on an imgLoader that + * has never loaded images, these calls are useless. It seems likely that + * the code that is doing this is just legacy code left over from a time when + * there was only one imgLoader instance for the entire process. (Nowadays + * the correct method to get an imgILoader/imgICache is to call + * imgITools::getImgCacheForDocument/imgITools::getImgLoaderForDocument.) + * All the same, even though what these add-ons are doing is a no-op, + * removing the nsIServiceManager.getService method of creating/getting an + * imgLoader objects would cause an exception in these add-ons that could + * break things. + */ + imgLoader(); + nsresult Init(); + + MOZ_MUST_USE nsresult LoadImage(nsIURI* aURI, + nsIURI* aInitialDocumentURI, + nsIURI* aReferrerURI, + ReferrerPolicy aReferrerPolicy, + nsIPrincipal* aLoadingPrincipal, + nsILoadGroup* aLoadGroup, + imgINotificationObserver* aObserver, + nsINode* aContext, + nsIDocument* aLoadingDocument, + nsLoadFlags aLoadFlags, + nsISupports* aCacheKey, + nsContentPolicyType aContentPolicyType, + const nsAString& initiatorType, + imgRequestProxy** _retval); + + MOZ_MUST_USE nsresult + LoadImageWithChannel(nsIChannel* channel, + imgINotificationObserver* aObserver, + nsISupports* aCX, + nsIStreamListener** listener, + imgRequestProxy** _retval); + + static nsresult GetMimeTypeFromContent(const char* aContents, + uint32_t aLength, + nsACString& aContentType); + + /** + * Returns true if the given mime type may be interpreted as an image. + * + * Some MIME types may be interpreted as both images and documents. (At the + * moment only "image/svg+xml" falls into this category, but there may be more + * in the future.) Callers which want this function to return true for such + * MIME types should pass AcceptedMimeTypes::IMAGES_AND_DOCUMENTS for + * @aAccept. + * + * @param aMimeType The MIME type to evaluate. + * @param aAcceptedMimeTypes Which kinds of MIME types to treat as images. + */ + static bool + SupportImageWithMimeType(const char* aMimeType, + AcceptedMimeTypes aAccept = + AcceptedMimeTypes::IMAGES); + + static void GlobalInit(); // for use by the factory + static void Shutdown(); // for use by the factory + static void ShutdownMemoryReporter(); + + nsresult ClearChromeImageCache(); + nsresult ClearImageCache(); + void MinimizeCaches(); + + nsresult InitCache(); + + bool RemoveFromCache(const ImageCacheKey& aKey); + bool RemoveFromCache(imgCacheEntry* entry); + + bool PutIntoCache(const ImageCacheKey& aKey, imgCacheEntry* aEntry); + + void AddToUncachedImages(imgRequest* aRequest); + void RemoveFromUncachedImages(imgRequest* aRequest); + + // Returns true if we should prefer evicting cache entry |two| over cache + // entry |one|. + // This mixes units in the worst way, but provides reasonable results. + inline static bool CompareCacheEntries(const RefPtr& one, + const RefPtr& two) + { + if (!one) { + return false; + } + if (!two) { + return true; + } + + const double sizeweight = 1.0 - sCacheTimeWeight; + + // We want large, old images to be evicted first (depending on their + // relative weights). Since a larger time is actually newer, we subtract + // time's weight, so an older image has a larger weight. + double oneweight = double(one->GetDataSize()) * sizeweight - + double(one->GetTouchedTime()) * sCacheTimeWeight; + double twoweight = double(two->GetDataSize()) * sizeweight - + double(two->GetTouchedTime()) * sCacheTimeWeight; + + return oneweight < twoweight; + } + + void VerifyCacheSizes(); + + // The image loader maintains a hash table of all imgCacheEntries. However, + // only some of them will be evicted from the cache: those who have no + // imgRequestProxies watching their imgRequests. + // + // Once an imgRequest has no imgRequestProxies, it should notify us by + // calling HasNoObservers(), and null out its cache entry pointer. + // + // Upon having a proxy start observing again, it should notify us by calling + // HasObservers(). The request's cache entry will be re-set before this + // happens, by calling imgRequest::SetCacheEntry() when an entry with no + // observers is re-requested. + bool SetHasNoProxies(imgRequest* aRequest, imgCacheEntry* aEntry); + bool SetHasProxies(imgRequest* aRequest); + +private: // methods + + static already_AddRefed CreateImageLoader(); + + bool ValidateEntry(imgCacheEntry* aEntry, nsIURI* aKey, + nsIURI* aInitialDocumentURI, nsIURI* aReferrerURI, + ReferrerPolicy aReferrerPolicy, + nsILoadGroup* aLoadGroup, + imgINotificationObserver* aObserver, nsISupports* aCX, + nsLoadFlags aLoadFlags, + nsContentPolicyType aContentPolicyType, + bool aCanMakeNewChannel, + imgRequestProxy** aProxyRequest, + nsIPrincipal* aLoadingPrincipal, + int32_t aCORSMode); + + bool ValidateRequestWithNewChannel(imgRequest* request, nsIURI* aURI, + nsIURI* aInitialDocumentURI, + nsIURI* aReferrerURI, + ReferrerPolicy aReferrerPolicy, + nsILoadGroup* aLoadGroup, + imgINotificationObserver* aObserver, + nsISupports* aCX, nsLoadFlags aLoadFlags, + nsContentPolicyType aContentPolicyType, + imgRequestProxy** aProxyRequest, + nsIPrincipal* aLoadingPrincipal, + int32_t aCORSMode); + + nsresult CreateNewProxyForRequest(imgRequest* aRequest, + nsILoadGroup* aLoadGroup, + imgINotificationObserver* aObserver, + nsLoadFlags aLoadFlags, + imgRequestProxy** _retval); + + void ReadAcceptHeaderPref(); + + nsresult EvictEntries(imgCacheTable& aCacheToClear); + nsresult EvictEntries(imgCacheQueue& aQueueToClear); + + imgCacheTable& GetCache(bool aForChrome); + imgCacheTable& GetCache(const ImageCacheKey& aKey); + imgCacheQueue& GetCacheQueue(bool aForChrome); + imgCacheQueue& GetCacheQueue(const ImageCacheKey& aKey); + void CacheEntriesChanged(bool aForChrome, int32_t aSizeDiff = 0); + void CheckCacheLimits(imgCacheTable& cache, imgCacheQueue& queue); + +private: // data + friend class imgCacheEntry; + friend class imgMemoryReporter; + + imgCacheTable mCache; + imgCacheQueue mCacheQueue; + + imgCacheTable mChromeCache; + imgCacheQueue mChromeCacheQueue; + + // Hash set of every imgRequest for this loader that isn't in mCache or + // mChromeCache. The union over all imgLoader's of mCache, mChromeCache, and + // mUncachedImages should be every imgRequest that is alive. These are weak + // pointers so we rely on the imgRequest destructor to remove itself. + imgSet mUncachedImages; + // The imgRequest can have refs to them held on non-main thread, so we need + // a mutex because we modify the uncached images set from the imgRequest + // destructor. + Mutex mUncachedImagesMutex; + + static double sCacheTimeWeight; + static uint32_t sCacheMaxSize; + static imgMemoryReporter* sMemReporter; + + nsCString mAcceptHeader; + + mozilla::UniquePtr mCacheTracker; + bool mRespectPrivacy; +}; + + + +/** + * proxy stream listener class used to handle multipart/x-mixed-replace + */ + +#include "nsCOMPtr.h" +#include "nsIStreamListener.h" +#include "nsIThreadRetargetableStreamListener.h" + +class ProxyListener : public nsIStreamListener + , public nsIThreadRetargetableStreamListener +{ +public: + explicit ProxyListener(nsIStreamListener* dest); + + /* additional members */ + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER + NS_DECL_NSIREQUESTOBSERVER + +private: + virtual ~ProxyListener(); + + nsCOMPtr mDestListener; +}; + +/** + * A class that implements nsIProgressEventSink and forwards all calls to it to + * the original notification callbacks of the channel. Also implements + * nsIInterfaceRequestor and gives out itself for nsIProgressEventSink calls, + * and forwards everything else to the channel's notification callbacks. + */ +class nsProgressNotificationProxy final + : public nsIProgressEventSink + , public nsIChannelEventSink + , public nsIInterfaceRequestor +{ + public: + nsProgressNotificationProxy(nsIChannel* channel, + imgIRequest* proxy) + : mImageRequest(proxy) { + channel->GetNotificationCallbacks(getter_AddRefs(mOriginalCallbacks)); + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIPROGRESSEVENTSINK + NS_DECL_NSICHANNELEVENTSINK + NS_DECL_NSIINTERFACEREQUESTOR + private: + ~nsProgressNotificationProxy() { } + + nsCOMPtr mOriginalCallbacks; + nsCOMPtr mImageRequest; +}; + +/** + * validate checker + */ + +#include "nsCOMArray.h" + +class imgCacheValidator : public nsIStreamListener, + public nsIThreadRetargetableStreamListener, + public nsIChannelEventSink, + public nsIInterfaceRequestor, + public nsIAsyncVerifyRedirectCallback +{ +public: + imgCacheValidator(nsProgressNotificationProxy* progress, imgLoader* loader, + imgRequest* aRequest, nsISupports* aContext, + bool forcePrincipalCheckForCacheEntry); + + void AddProxy(imgRequestProxy* aProxy); + + NS_DECL_ISUPPORTS + NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSICHANNELEVENTSINK + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK + +private: + virtual ~imgCacheValidator(); + + nsCOMPtr mDestListener; + RefPtr mProgressProxy; + nsCOMPtr mRedirectCallback; + nsCOMPtr mRedirectChannel; + + RefPtr mRequest; + nsCOMArray mProxies; + + RefPtr mNewRequest; + RefPtr mNewEntry; + + nsCOMPtr mContext; + + imgLoader* mImgLoader; + + bool mHadInsecureRedirect; +}; + +#endif // mozilla_image_imgLoader_h diff --git a/image/imgRequest.cpp b/image/imgRequest.cpp new file mode 100644 index 000000000..ba99779d3 --- /dev/null +++ b/image/imgRequest.cpp @@ -0,0 +1,1305 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "imgRequest.h" +#include "ImageLogging.h" + +#include "imgLoader.h" +#include "imgRequestProxy.h" +#include "DecodePool.h" +#include "ProgressTracker.h" +#include "ImageFactory.h" +#include "Image.h" +#include "MultipartImage.h" +#include "RasterImage.h" + +#include "nsIChannel.h" +#include "nsICacheInfoChannel.h" +#include "nsIDocument.h" +#include "nsIThreadRetargetableRequest.h" +#include "nsIInputStream.h" +#include "nsIMultiPartChannel.h" +#include "nsIHttpChannel.h" +#include "nsIApplicationCache.h" +#include "nsIApplicationCacheChannel.h" +#include "nsMimeTypes.h" + +#include "nsIInterfaceRequestorUtils.h" +#include "nsISupportsPrimitives.h" +#include "nsIScriptSecurityManager.h" +#include "nsContentUtils.h" + +#include "plstr.h" // PL_strcasestr(...) +#include "nsNetUtil.h" +#include "nsIProtocolHandler.h" +#include "imgIRequest.h" + +using namespace mozilla; +using namespace mozilla::image; + +#define LOG_TEST(level) (MOZ_LOG_TEST(gImgLog, (level))) + +NS_IMPL_ISUPPORTS(imgRequest, + nsIStreamListener, nsIRequestObserver, + nsIThreadRetargetableStreamListener, + nsIChannelEventSink, + nsIInterfaceRequestor, + nsIAsyncVerifyRedirectCallback) + +imgRequest::imgRequest(imgLoader* aLoader, const ImageCacheKey& aCacheKey) + : mLoader(aLoader) + , mCacheKey(aCacheKey) + , mLoadId(nullptr) + , mFirstProxy(nullptr) + , mValidator(nullptr) + , mInnerWindowId(0) + , mCORSMode(imgIRequest::CORS_NONE) + , mReferrerPolicy(mozilla::net::RP_Default) + , mImageErrorCode(NS_OK) + , mMutex("imgRequest") + , mProgressTracker(new ProgressTracker()) + , mIsMultiPartChannel(false) + , mGotData(false) + , mIsInCache(false) + , mDecodeRequested(false) + , mNewPartPending(false) + , mHadInsecureRedirect(false) +{ } + +imgRequest::~imgRequest() +{ + if (mLoader) { + mLoader->RemoveFromUncachedImages(this); + } + if (mURI) { + nsAutoCString spec; + mURI->GetSpec(spec); + LOG_FUNC_WITH_PARAM(gImgLog, "imgRequest::~imgRequest()", + "keyuri", spec.get()); + } else + LOG_FUNC(gImgLog, "imgRequest::~imgRequest()"); +} + +nsresult +imgRequest::Init(nsIURI *aURI, + nsIURI *aCurrentURI, + bool aHadInsecureRedirect, + nsIRequest *aRequest, + nsIChannel *aChannel, + imgCacheEntry *aCacheEntry, + nsISupports* aCX, + nsIPrincipal* aLoadingPrincipal, + int32_t aCORSMode, + ReferrerPolicy aReferrerPolicy) +{ + MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!"); + + LOG_FUNC(gImgLog, "imgRequest::Init"); + + MOZ_ASSERT(!mImage, "Multiple calls to init"); + MOZ_ASSERT(aURI, "No uri"); + MOZ_ASSERT(aCurrentURI, "No current uri"); + MOZ_ASSERT(aRequest, "No request"); + MOZ_ASSERT(aChannel, "No channel"); + + mProperties = do_CreateInstance("@mozilla.org/properties;1"); + + // Use ImageURL to ensure access to URI data off main thread. + nsresult rv; + mURI = new ImageURL(aURI, rv); + NS_ENSURE_SUCCESS(rv, rv); + + mCurrentURI = aCurrentURI; + mRequest = aRequest; + mChannel = aChannel; + mTimedChannel = do_QueryInterface(mChannel); + + mLoadingPrincipal = aLoadingPrincipal; + mCORSMode = aCORSMode; + mReferrerPolicy = aReferrerPolicy; + + // If the original URI and the current URI are different, check whether the + // original URI is secure. We deliberately don't take the current URI into + // account, as it needs to be handled using more complicated rules than + // earlier elements of the redirect chain. + if (aURI != aCurrentURI) { + bool isHttps = false; + bool isChrome = false; + bool schemeLocal = false; + if (NS_FAILED(aURI->SchemeIs("https", &isHttps)) || + NS_FAILED(aURI->SchemeIs("chrome", &isChrome)) || + NS_FAILED(NS_URIChainHasFlags( + aURI, + nsIProtocolHandler::URI_IS_LOCAL_RESOURCE , &schemeLocal)) || + (!isHttps && !isChrome && !schemeLocal)) { + mHadInsecureRedirect = true; + } + } + + // imgCacheValidator may have handled redirects before we were created, so we + // allow the caller to let us know if any redirects were insecure. + mHadInsecureRedirect = mHadInsecureRedirect || aHadInsecureRedirect; + + mChannel->GetNotificationCallbacks(getter_AddRefs(mPrevChannelSink)); + + NS_ASSERTION(mPrevChannelSink != this, + "Initializing with a channel that already calls back to us!"); + + mChannel->SetNotificationCallbacks(this); + + mCacheEntry = aCacheEntry; + mCacheEntry->UpdateLoadTime(); + + SetLoadId(aCX); + + // Grab the inner window ID of the loading document, if possible. + nsCOMPtr doc = do_QueryInterface(aCX); + if (doc) { + mInnerWindowId = doc->InnerWindowID(); + } + + return NS_OK; +} + +void +imgRequest::ClearLoader() { + mLoader = nullptr; +} + +already_AddRefed +imgRequest::GetProgressTracker() const +{ + MutexAutoLock lock(mMutex); + + if (mImage) { + MOZ_ASSERT(!mProgressTracker, + "Should have given mProgressTracker to mImage"); + return mImage->GetProgressTracker(); + } else { + MOZ_ASSERT(mProgressTracker, + "Should have mProgressTracker until we create mImage"); + RefPtr progressTracker = mProgressTracker; + MOZ_ASSERT(progressTracker); + return progressTracker.forget(); + } +} + +void imgRequest::SetCacheEntry(imgCacheEntry* entry) +{ + mCacheEntry = entry; +} + +bool +imgRequest::HasCacheEntry() const +{ + return mCacheEntry != nullptr; +} + +void +imgRequest::ResetCacheEntry() +{ + if (HasCacheEntry()) { + mCacheEntry->SetDataSize(0); + } +} + +void +imgRequest::AddProxy(imgRequestProxy* proxy) +{ + NS_PRECONDITION(proxy, "null imgRequestProxy passed in"); + LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequest::AddProxy", "proxy", proxy); + + if (!mFirstProxy) { + // Save a raw pointer to the first proxy we see, for use in the network + // priority logic. + mFirstProxy = proxy; + } + + // If we're empty before adding, we have to tell the loader we now have + // proxies. + RefPtr progressTracker = GetProgressTracker(); + if (progressTracker->ObserverCount() == 0) { + MOZ_ASSERT(mURI, "Trying to SetHasProxies without key uri."); + if (mLoader) { + mLoader->SetHasProxies(this); + } + } + + progressTracker->AddObserver(proxy); +} + +nsresult +imgRequest::RemoveProxy(imgRequestProxy* proxy, nsresult aStatus) +{ + LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequest::RemoveProxy", "proxy", proxy); + + // This will remove our animation consumers, so after removing + // this proxy, we don't end up without proxies with observers, but still + // have animation consumers. + proxy->ClearAnimationConsumers(); + + // Let the status tracker do its thing before we potentially call Cancel() + // below, because Cancel() may result in OnStopRequest being called back + // before Cancel() returns, leaving the image in a different state then the + // one it was in at this point. + RefPtr progressTracker = GetProgressTracker(); + if (!progressTracker->RemoveObserver(proxy)) { + return NS_OK; + } + + if (progressTracker->ObserverCount() == 0) { + // If we have no observers, there's nothing holding us alive. If we haven't + // been cancelled and thus removed from the cache, tell the image loader so + // we can be evicted from the cache. + if (mCacheEntry) { + MOZ_ASSERT(mURI, "Removing last observer without key uri."); + + if (mLoader) { + mLoader->SetHasNoProxies(this, mCacheEntry); + } + } else if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) { + nsAutoCString spec; + mURI->GetSpec(spec); + LOG_MSG_WITH_PARAM(gImgLog, + "imgRequest::RemoveProxy no cache entry", + "uri", spec.get()); + } + + /* If |aStatus| is a failure code, then cancel the load if it is still in + progress. Otherwise, let the load continue, keeping 'this' in the cache + with no observers. This way, if a proxy is destroyed without calling + cancel on it, it won't leak and won't leave a bad pointer in the observer + list. + */ + if (!(progressTracker->GetProgress() & FLAG_LAST_PART_COMPLETE) && + NS_FAILED(aStatus)) { + LOG_MSG(gImgLog, "imgRequest::RemoveProxy", + "load in progress. canceling"); + + this->Cancel(NS_BINDING_ABORTED); + } + + /* break the cycle from the cache entry. */ + mCacheEntry = nullptr; + } + + // If a proxy is removed for a reason other than its owner being + // changed, remove the proxy from the loadgroup. + if (aStatus != NS_IMAGELIB_CHANGING_OWNER) { + proxy->RemoveFromLoadGroup(true); + } + + return NS_OK; +} + +void +imgRequest::CancelAndAbort(nsresult aStatus) +{ + LOG_SCOPE(gImgLog, "imgRequest::CancelAndAbort"); + + Cancel(aStatus); + + // It's possible for the channel to fail to open after we've set our + // notification callbacks. In that case, make sure to break the cycle between + // the channel and us, because it won't. + if (mChannel) { + mChannel->SetNotificationCallbacks(mPrevChannelSink); + mPrevChannelSink = nullptr; + } +} + +class imgRequestMainThreadCancel : public Runnable +{ +public: + imgRequestMainThreadCancel(imgRequest* aImgRequest, nsresult aStatus) + : mImgRequest(aImgRequest) + , mStatus(aStatus) + { + MOZ_ASSERT(!NS_IsMainThread(), "Create me off main thread only!"); + MOZ_ASSERT(aImgRequest); + } + + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_IsMainThread(), "I should be running on the main thread!"); + mImgRequest->ContinueCancel(mStatus); + return NS_OK; + } +private: + RefPtr mImgRequest; + nsresult mStatus; +}; + +void +imgRequest::Cancel(nsresult aStatus) +{ + /* The Cancel() method here should only be called by this class. */ + LOG_SCOPE(gImgLog, "imgRequest::Cancel"); + + if (NS_IsMainThread()) { + ContinueCancel(aStatus); + } else { + NS_DispatchToMainThread(new imgRequestMainThreadCancel(this, aStatus)); + } +} + +void +imgRequest::ContinueCancel(nsresult aStatus) +{ + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr progressTracker = GetProgressTracker(); + progressTracker->SyncNotifyProgress(FLAG_HAS_ERROR | FLAG_ONLOAD_UNBLOCKED); + + RemoveFromCache(); + + if (mRequest && !(progressTracker->GetProgress() & FLAG_LAST_PART_COMPLETE)) { + mRequest->Cancel(aStatus); + } +} + +class imgRequestMainThreadEvict : public Runnable +{ +public: + explicit imgRequestMainThreadEvict(imgRequest* aImgRequest) + : mImgRequest(aImgRequest) + { + MOZ_ASSERT(!NS_IsMainThread(), "Create me off main thread only!"); + MOZ_ASSERT(aImgRequest); + } + + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_IsMainThread(), "I should be running on the main thread!"); + mImgRequest->ContinueEvict(); + return NS_OK; + } +private: + RefPtr mImgRequest; +}; + +// EvictFromCache() is written to allowed to get called from any thread +void +imgRequest::EvictFromCache() +{ + /* The EvictFromCache() method here should only be called by this class. */ + LOG_SCOPE(gImgLog, "imgRequest::EvictFromCache"); + + if (NS_IsMainThread()) { + ContinueEvict(); + } else { + NS_DispatchToMainThread(new imgRequestMainThreadEvict(this)); + } +} + +// Helper-method used by EvictFromCache() +void +imgRequest::ContinueEvict() +{ + MOZ_ASSERT(NS_IsMainThread()); + + RemoveFromCache(); +} + +void +imgRequest::StartDecoding() +{ + MutexAutoLock lock(mMutex); + mDecodeRequested = true; +} + +bool +imgRequest::IsDecodeRequested() const +{ + MutexAutoLock lock(mMutex); + return mDecodeRequested; +} + +nsresult imgRequest::GetURI(ImageURL** aURI) +{ + MOZ_ASSERT(aURI); + + LOG_FUNC(gImgLog, "imgRequest::GetURI"); + + if (mURI) { + *aURI = mURI; + NS_ADDREF(*aURI); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +nsresult +imgRequest::GetCurrentURI(nsIURI** aURI) +{ + MOZ_ASSERT(aURI); + + LOG_FUNC(gImgLog, "imgRequest::GetCurrentURI"); + + if (mCurrentURI) { + *aURI = mCurrentURI; + NS_ADDREF(*aURI); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +bool +imgRequest::IsChrome() const +{ + bool isChrome = false; + if (NS_WARN_IF(NS_FAILED(mURI->SchemeIs("chrome", &isChrome)))) { + return false; + } + return isChrome; +} + +nsresult +imgRequest::GetImageErrorCode() +{ + return mImageErrorCode; +} + +nsresult +imgRequest::GetSecurityInfo(nsISupports** aSecurityInfo) +{ + LOG_FUNC(gImgLog, "imgRequest::GetSecurityInfo"); + + // Missing security info means this is not a security load + // i.e. it is not an error when security info is missing + NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo); + return NS_OK; +} + +void +imgRequest::RemoveFromCache() +{ + LOG_SCOPE(gImgLog, "imgRequest::RemoveFromCache"); + + bool isInCache = false; + + { + MutexAutoLock lock(mMutex); + isInCache = mIsInCache; + } + + if (isInCache && mLoader) { + // mCacheEntry is nulled out when we have no more observers. + if (mCacheEntry) { + mLoader->RemoveFromCache(mCacheEntry); + } else { + mLoader->RemoveFromCache(mCacheKey); + } + } + + mCacheEntry = nullptr; +} + +bool +imgRequest::HasConsumers() const +{ + RefPtr progressTracker = GetProgressTracker(); + return progressTracker && progressTracker->ObserverCount() > 0; +} + +already_AddRefed +imgRequest::GetImage() const +{ + MutexAutoLock lock(mMutex); + RefPtr image = mImage; + return image.forget(); +} + +int32_t imgRequest::Priority() const +{ + int32_t priority = nsISupportsPriority::PRIORITY_NORMAL; + nsCOMPtr p = do_QueryInterface(mRequest); + if (p) { + p->GetPriority(&priority); + } + return priority; +} + +void +imgRequest::AdjustPriority(imgRequestProxy* proxy, int32_t delta) +{ + // only the first proxy is allowed to modify the priority of this image load. + // + // XXX(darin): this is probably not the most optimal algorithm as we may want + // to increase the priority of requests that have a lot of proxies. the key + // concern though is that image loads remain lower priority than other pieces + // of content such as link clicks, CSS, and JS. + // + if (!mFirstProxy || proxy != mFirstProxy) { + return; + } + + nsCOMPtr p = do_QueryInterface(mChannel); + if (p) { + p->AdjustPriority(delta); + } +} + +bool +imgRequest::HasTransferredData() const +{ + MutexAutoLock lock(mMutex); + return mGotData; +} + +void +imgRequest::SetIsInCache(bool aInCache) +{ + LOG_FUNC_WITH_PARAM(gImgLog, + "imgRequest::SetIsCacheable", "aInCache", aInCache); + MutexAutoLock lock(mMutex); + mIsInCache = aInCache; +} + +void +imgRequest::UpdateCacheEntrySize() +{ + if (!mCacheEntry) { + return; + } + + RefPtr image = GetImage(); + size_t size = image->SizeOfSourceWithComputedFallback(moz_malloc_size_of); + mCacheEntry->SetDataSize(size); +} + +void +imgRequest::SetCacheValidation(imgCacheEntry* aCacheEntry, nsIRequest* aRequest) +{ + /* get the expires info */ + if (aCacheEntry) { + nsCOMPtr cacheChannel(do_QueryInterface(aRequest)); + if (cacheChannel) { + uint32_t expiration = 0; + /* get the expiration time from the caching channel's token */ + if (NS_SUCCEEDED(cacheChannel->GetCacheTokenExpirationTime(&expiration))) { + // Expiration time defaults to 0. We set the expiration time on our + // entry if it hasn't been set yet. + if (aCacheEntry->GetExpiryTime() == 0) { + aCacheEntry->SetExpiryTime(expiration); + } + } + } + + // Determine whether the cache entry must be revalidated when we try to use + // it. Currently, only HTTP specifies this information... + nsCOMPtr httpChannel(do_QueryInterface(aRequest)); + if (httpChannel) { + bool bMustRevalidate = false; + + httpChannel->IsNoStoreResponse(&bMustRevalidate); + + if (!bMustRevalidate) { + httpChannel->IsNoCacheResponse(&bMustRevalidate); + } + + if (!bMustRevalidate) { + nsAutoCString cacheHeader; + + httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Cache-Control"), + cacheHeader); + if (PL_strcasestr(cacheHeader.get(), "must-revalidate")) { + bMustRevalidate = true; + } + } + + // Cache entries default to not needing to validate. We ensure that + // multiple calls to this function don't override an earlier decision to + // validate by making validation a one-way decision. + if (bMustRevalidate) { + aCacheEntry->SetMustValidate(bMustRevalidate); + } + } + } +} + +namespace { + +already_AddRefed +GetApplicationCache(nsIRequest* aRequest) +{ + nsresult rv; + + nsCOMPtr appCacheChan = + do_QueryInterface(aRequest); + if (!appCacheChan) { + return nullptr; + } + + bool fromAppCache; + rv = appCacheChan->GetLoadedFromApplicationCache(&fromAppCache); + NS_ENSURE_SUCCESS(rv, nullptr); + + if (!fromAppCache) { + return nullptr; + } + + nsCOMPtr appCache; + rv = appCacheChan->GetApplicationCache(getter_AddRefs(appCache)); + NS_ENSURE_SUCCESS(rv, nullptr); + + return appCache.forget(); +} + +} // namespace + +bool +imgRequest::CacheChanged(nsIRequest* aNewRequest) +{ + nsCOMPtr newAppCache = GetApplicationCache(aNewRequest); + + // Application cache not involved at all or the same app cache involved + // in both of the loads (original and new). + if (newAppCache == mApplicationCache) { + return false; + } + + // In a rare case it may happen that two objects still refer + // the same application cache version. + if (newAppCache && mApplicationCache) { + nsresult rv; + + nsAutoCString oldAppCacheClientId, newAppCacheClientId; + rv = mApplicationCache->GetClientID(oldAppCacheClientId); + NS_ENSURE_SUCCESS(rv, true); + rv = newAppCache->GetClientID(newAppCacheClientId); + NS_ENSURE_SUCCESS(rv, true); + + if (oldAppCacheClientId == newAppCacheClientId) { + return false; + } + } + + // When we get here, app caches differ or app cache is involved + // just in one of the loads what we also consider as a change + // in a loading cache. + return true; +} + +bool +imgRequest::GetMultipart() const +{ + MutexAutoLock lock(mMutex); + return mIsMultiPartChannel; +} + +bool +imgRequest::HadInsecureRedirect() const +{ + MutexAutoLock lock(mMutex); + return mHadInsecureRedirect; +} + +/** nsIRequestObserver methods **/ + +NS_IMETHODIMP +imgRequest::OnStartRequest(nsIRequest* aRequest, nsISupports* ctxt) +{ + LOG_SCOPE(gImgLog, "imgRequest::OnStartRequest"); + + RefPtr image; + + // Figure out if we're multipart. + nsCOMPtr multiPartChannel = do_QueryInterface(aRequest); + MOZ_ASSERT(multiPartChannel || !mIsMultiPartChannel, + "Stopped being multipart?"); { + MutexAutoLock lock(mMutex); + mNewPartPending = true; + image = mImage; + mIsMultiPartChannel = bool(multiPartChannel); + } + + // If we're not multipart, we shouldn't have an image yet. + if (image && !multiPartChannel) { + MOZ_ASSERT_UNREACHABLE("Already have an image for a non-multipart request"); + Cancel(NS_IMAGELIB_ERROR_FAILURE); + return NS_ERROR_FAILURE; + } + + /* + * If mRequest is null here, then we need to set it so that we'll be able to + * cancel it if our Cancel() method is called. Note that this can only + * happen for multipart channels. We could simply not null out mRequest for + * non-last parts, if GetIsLastPart() were reliable, but it's not. See + * https://bugzilla.mozilla.org/show_bug.cgi?id=339610 + */ + if (!mRequest) { + MOZ_ASSERT(multiPartChannel, "Should have mRequest unless we're multipart"); + nsCOMPtr baseChannel; + multiPartChannel->GetBaseChannel(getter_AddRefs(baseChannel)); + mRequest = baseChannel; + } + + nsCOMPtr channel(do_QueryInterface(aRequest)); + if (channel) { + channel->GetSecurityInfo(getter_AddRefs(mSecurityInfo)); + + /* Get our principal */ + nsCOMPtr + secMan = nsContentUtils::GetSecurityManager(); + if (secMan) { + nsresult rv = + secMan->GetChannelResultPrincipal(channel, getter_AddRefs(mPrincipal)); + if (NS_FAILED(rv)) { + return rv; + } + } + } + + SetCacheValidation(mCacheEntry, aRequest); + + mApplicationCache = GetApplicationCache(aRequest); + + // Shouldn't we be dead already if this gets hit? + // Probably multipart/x-mixed-replace... + RefPtr progressTracker = GetProgressTracker(); + if (progressTracker->ObserverCount() == 0) { + this->Cancel(NS_IMAGELIB_ERROR_FAILURE); + } + + // Try to retarget OnDataAvailable to a decode thread. + nsCOMPtr httpChannel = do_QueryInterface(aRequest); + nsCOMPtr retargetable = + do_QueryInterface(aRequest); + if (httpChannel && retargetable) { + nsAutoCString mimeType; + nsresult rv = httpChannel->GetContentType(mimeType); + if (NS_SUCCEEDED(rv) && !mimeType.EqualsLiteral(IMAGE_SVG_XML)) { + // Retarget OnDataAvailable to the DecodePool's IO thread. + nsCOMPtr target = + DecodePool::Singleton()->GetIOEventTarget(); + rv = retargetable->RetargetDeliveryTo(target); + } + MOZ_LOG(gImgLog, LogLevel::Warning, + ("[this=%p] imgRequest::OnStartRequest -- " + "RetargetDeliveryTo rv %d=%s\n", + this, rv, NS_SUCCEEDED(rv) ? "succeeded" : "failed")); + } + + return NS_OK; +} + +NS_IMETHODIMP +imgRequest::OnStopRequest(nsIRequest* aRequest, + nsISupports* ctxt, nsresult status) +{ + LOG_FUNC(gImgLog, "imgRequest::OnStopRequest"); + MOZ_ASSERT(NS_IsMainThread(), "Can't send notifications off-main-thread"); + + RefPtr image = GetImage(); + + RefPtr strongThis = this; + + if (mIsMultiPartChannel && mNewPartPending) { + OnDataAvailable(aRequest, ctxt, nullptr, 0, 0); + } + + // XXXldb What if this is a non-last part of a multipart request? + // xxx before we release our reference to mRequest, lets + // save the last status that we saw so that the + // imgRequestProxy will have access to it. + if (mRequest) { + mRequest = nullptr; // we no longer need the request + } + + // stop holding a ref to the channel, since we don't need it anymore + if (mChannel) { + mChannel->SetNotificationCallbacks(mPrevChannelSink); + mPrevChannelSink = nullptr; + mChannel = nullptr; + } + + bool lastPart = true; + nsCOMPtr mpchan(do_QueryInterface(aRequest)); + if (mpchan) { + mpchan->GetIsLastPart(&lastPart); + } + + bool isPartial = false; + if (image && (status == NS_ERROR_NET_PARTIAL_TRANSFER)) { + isPartial = true; + status = NS_OK; // fake happy face + } + + // Tell the image that it has all of the source data. Note that this can + // trigger a failure, since the image might be waiting for more non-optional + // data and this is the point where we break the news that it's not coming. + if (image) { + nsresult rv = image->OnImageDataComplete(aRequest, ctxt, status, lastPart); + + // If we got an error in the OnImageDataComplete() call, we don't want to + // proceed as if nothing bad happened. However, we also want to give + // precedence to failure status codes from necko, since presumably they're + // more meaningful. + if (NS_FAILED(rv) && NS_SUCCEEDED(status)) { + status = rv; + } + } + + // If the request went through, update the cache entry size. Otherwise, + // cancel the request, which removes us from the cache. + if (image && NS_SUCCEEDED(status) && !isPartial) { + // We update the cache entry size here because this is where we finish + // loading compressed source data, which is part of our size calculus. + UpdateCacheEntrySize(); + + } else if (isPartial) { + // Remove the partial image from the cache. + this->EvictFromCache(); + + } else { + mImageErrorCode = status; + + // if the error isn't "just" a partial transfer + // stops animations, removes from cache + this->Cancel(status); + } + + if (!image) { + // We have to fire the OnStopRequest notifications ourselves because there's + // no image capable of doing so. + Progress progress = + LoadCompleteProgress(lastPart, /* aError = */ false, status); + + RefPtr progressTracker = GetProgressTracker(); + progressTracker->SyncNotifyProgress(progress); + } + + mTimedChannel = nullptr; + return NS_OK; +} + +struct mimetype_closure +{ + nsACString* newType; +}; + +/* prototype for these defined below */ +static nsresult +sniff_mimetype_callback(nsIInputStream* in, void* closure, + const char* fromRawSegment, uint32_t toOffset, + uint32_t count, uint32_t* writeCount); + +/** nsThreadRetargetableStreamListener methods **/ +NS_IMETHODIMP +imgRequest::CheckListenerChain() +{ + // TODO Might need more checking here. + NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!"); + return NS_OK; +} + +/** nsIStreamListener methods **/ + +struct NewPartResult final +{ + explicit NewPartResult(Image* aExistingImage) + : mImage(aExistingImage) + , mIsFirstPart(!aExistingImage) + , mSucceeded(false) + , mShouldResetCacheEntry(false) + { } + + nsAutoCString mContentType; + nsAutoCString mContentDisposition; + RefPtr mImage; + const bool mIsFirstPart; + bool mSucceeded; + bool mShouldResetCacheEntry; +}; + +static NewPartResult +PrepareForNewPart(nsIRequest* aRequest, nsIInputStream* aInStr, uint32_t aCount, + ImageURL* aURI, bool aIsMultipart, Image* aExistingImage, + ProgressTracker* aProgressTracker, uint32_t aInnerWindowId) +{ + NewPartResult result(aExistingImage); + + if (aInStr) { + mimetype_closure closure; + closure.newType = &result.mContentType; + + // Look at the first few bytes and see if we can tell what the data is from + // that since servers tend to lie. :( + uint32_t out; + aInStr->ReadSegments(sniff_mimetype_callback, &closure, aCount, &out); + } + + nsCOMPtr chan(do_QueryInterface(aRequest)); + if (result.mContentType.IsEmpty()) { + nsresult rv = chan ? chan->GetContentType(result.mContentType) + : NS_ERROR_FAILURE; + if (NS_FAILED(rv)) { + MOZ_LOG(gImgLog, + LogLevel::Error, ("imgRequest::PrepareForNewPart -- " + "Content type unavailable from the channel\n")); + if (!aIsMultipart) { + return result; + } + } + } + + if (chan) { + chan->GetContentDispositionHeader(result.mContentDisposition); + } + + MOZ_LOG(gImgLog, LogLevel::Debug, + ("imgRequest::PrepareForNewPart -- Got content type %s\n", + result.mContentType.get())); + + // XXX If server lied about mimetype and it's SVG, we may need to copy + // the data and dispatch back to the main thread, AND tell the channel to + // dispatch there in the future. + + // Create the new image and give it ownership of our ProgressTracker. + if (aIsMultipart) { + // Create the ProgressTracker and image for this part. + RefPtr progressTracker = new ProgressTracker(); + RefPtr partImage = + ImageFactory::CreateImage(aRequest, progressTracker, result.mContentType, + aURI, /* aIsMultipart = */ true, + aInnerWindowId); + + if (result.mIsFirstPart) { + // First part for a multipart channel. Create the MultipartImage wrapper. + MOZ_ASSERT(aProgressTracker, "Shouldn't have given away tracker yet"); + result.mImage = + ImageFactory::CreateMultipartImage(partImage, aProgressTracker); + } else { + // Transition to the new part. + auto multipartImage = static_cast(aExistingImage); + multipartImage->BeginTransitionToPart(partImage); + + // Reset our cache entry size so it doesn't keep growing without bound. + result.mShouldResetCacheEntry = true; + } + } else { + MOZ_ASSERT(!aExistingImage, "New part for non-multipart channel?"); + MOZ_ASSERT(aProgressTracker, "Shouldn't have given away tracker yet"); + + // Create an image using our progress tracker. + result.mImage = + ImageFactory::CreateImage(aRequest, aProgressTracker, result.mContentType, + aURI, /* aIsMultipart = */ false, + aInnerWindowId); + } + + MOZ_ASSERT(result.mImage); + if (!result.mImage->HasError() || aIsMultipart) { + // We allow multipart images to fail to initialize (which generally + // indicates a bad content type) without cancelling the load, because + // subsequent parts might be fine. + result.mSucceeded = true; + } + + return result; +} + +class FinishPreparingForNewPartRunnable final : public Runnable +{ +public: + FinishPreparingForNewPartRunnable(imgRequest* aImgRequest, + NewPartResult&& aResult) + : mImgRequest(aImgRequest) + , mResult(aResult) + { + MOZ_ASSERT(aImgRequest); + } + + NS_IMETHOD Run() override + { + mImgRequest->FinishPreparingForNewPart(mResult); + return NS_OK; + } + +private: + RefPtr mImgRequest; + NewPartResult mResult; +}; + +void +imgRequest::FinishPreparingForNewPart(const NewPartResult& aResult) +{ + MOZ_ASSERT(NS_IsMainThread()); + + mContentType = aResult.mContentType; + + SetProperties(aResult.mContentType, aResult.mContentDisposition); + + if (aResult.mIsFirstPart) { + // Notify listeners that we have an image. + RefPtr progressTracker = GetProgressTracker(); + progressTracker->OnImageAvailable(); + MOZ_ASSERT(progressTracker->HasImage()); + } + + if (aResult.mShouldResetCacheEntry) { + ResetCacheEntry(); + } + + if (IsDecodeRequested()) { + aResult.mImage->StartDecoding(); + } +} + +NS_IMETHODIMP +imgRequest::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext, + nsIInputStream* aInStr, uint64_t aOffset, + uint32_t aCount) +{ + LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequest::OnDataAvailable", + "count", aCount); + + NS_ASSERTION(aRequest, "imgRequest::OnDataAvailable -- no request!"); + + RefPtr image; + RefPtr progressTracker; + bool isMultipart = false; + bool newPartPending = false; + + // Retrieve and update our state. + { + MutexAutoLock lock(mMutex); + mGotData = true; + image = mImage; + progressTracker = mProgressTracker; + isMultipart = mIsMultiPartChannel; + newPartPending = mNewPartPending; + mNewPartPending = false; + } + + // If this is a new part, we need to sniff its content type and create an + // appropriate image. + if (newPartPending) { + NewPartResult result = PrepareForNewPart(aRequest, aInStr, aCount, mURI, + isMultipart, image, + progressTracker, mInnerWindowId); + bool succeeded = result.mSucceeded; + + if (result.mImage) { + image = result.mImage; + + // Update our state to reflect this new part. + { + MutexAutoLock lock(mMutex); + mImage = image; + mProgressTracker = nullptr; + } + + // Some property objects are not threadsafe, and we need to send + // OnImageAvailable on the main thread, so finish on the main thread. + if (NS_IsMainThread()) { + FinishPreparingForNewPart(result); + } else { + nsCOMPtr runnable = + new FinishPreparingForNewPartRunnable(this, Move(result)); + NS_DispatchToMainThread(runnable); + } + } + + if (!succeeded) { + // Something went wrong; probably a content type issue. + Cancel(NS_IMAGELIB_ERROR_FAILURE); + return NS_BINDING_ABORTED; + } + } + + // Notify the image that it has new data. + if (aInStr) { + nsresult rv = + image->OnImageDataAvailable(aRequest, aContext, aInStr, aOffset, aCount); + + if (NS_FAILED(rv)) { + MOZ_LOG(gImgLog, LogLevel::Warning, + ("[this=%p] imgRequest::OnDataAvailable -- " + "copy to RasterImage failed\n", this)); + Cancel(NS_IMAGELIB_ERROR_FAILURE); + return NS_BINDING_ABORTED; + } + } + + return NS_OK; +} + +void +imgRequest::SetProperties(const nsACString& aContentType, + const nsACString& aContentDisposition) +{ + /* set our mimetype as a property */ + nsCOMPtr contentType = + do_CreateInstance("@mozilla.org/supports-cstring;1"); + if (contentType) { + contentType->SetData(aContentType); + mProperties->Set("type", contentType); + } + + /* set our content disposition as a property */ + if (!aContentDisposition.IsEmpty()) { + nsCOMPtr contentDisposition = + do_CreateInstance("@mozilla.org/supports-cstring;1"); + if (contentDisposition) { + contentDisposition->SetData(aContentDisposition); + mProperties->Set("content-disposition", contentDisposition); + } + } +} + +static nsresult +sniff_mimetype_callback(nsIInputStream* in, + void* data, + const char* fromRawSegment, + uint32_t toOffset, + uint32_t count, + uint32_t* writeCount) +{ + mimetype_closure* closure = static_cast(data); + + NS_ASSERTION(closure, "closure is null!"); + + if (count > 0) { + imgLoader::GetMimeTypeFromContent(fromRawSegment, count, *closure->newType); + } + + *writeCount = 0; + return NS_ERROR_FAILURE; +} + + +/** nsIInterfaceRequestor methods **/ + +NS_IMETHODIMP +imgRequest::GetInterface(const nsIID & aIID, void** aResult) +{ + if (!mPrevChannelSink || aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { + return QueryInterface(aIID, aResult); + } + + NS_ASSERTION(mPrevChannelSink != this, + "Infinite recursion - don't keep track of channel sinks that are us!"); + return mPrevChannelSink->GetInterface(aIID, aResult); +} + +/** nsIChannelEventSink methods **/ +NS_IMETHODIMP +imgRequest::AsyncOnChannelRedirect(nsIChannel* oldChannel, + nsIChannel* newChannel, uint32_t flags, + nsIAsyncVerifyRedirectCallback* callback) +{ + NS_ASSERTION(mRequest && mChannel, + "Got a channel redirect after we nulled out mRequest!"); + NS_ASSERTION(mChannel == oldChannel, + "Got a channel redirect for an unknown channel!"); + NS_ASSERTION(newChannel, "Got a redirect to a NULL channel!"); + + SetCacheValidation(mCacheEntry, oldChannel); + + // Prepare for callback + mRedirectCallback = callback; + mNewRedirectChannel = newChannel; + + nsCOMPtr sink(do_GetInterface(mPrevChannelSink)); + if (sink) { + nsresult rv = sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, + this); + if (NS_FAILED(rv)) { + mRedirectCallback = nullptr; + mNewRedirectChannel = nullptr; + } + return rv; + } + + (void) OnRedirectVerifyCallback(NS_OK); + return NS_OK; +} + +NS_IMETHODIMP +imgRequest::OnRedirectVerifyCallback(nsresult result) +{ + NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback"); + NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback"); + + if (NS_FAILED(result)) { + mRedirectCallback->OnRedirectVerifyCallback(result); + mRedirectCallback = nullptr; + mNewRedirectChannel = nullptr; + return NS_OK; + } + + mChannel = mNewRedirectChannel; + mTimedChannel = do_QueryInterface(mChannel); + mNewRedirectChannel = nullptr; + + if (LOG_TEST(LogLevel::Debug)) { + LOG_MSG_WITH_PARAM(gImgLog, + "imgRequest::OnChannelRedirect", "old", + mCurrentURI ? mCurrentURI->GetSpecOrDefault().get() + : ""); + } + + // If the previous URI is a non-HTTPS URI, record that fact for later use by + // security code, which needs to know whether there is an insecure load at any + // point in the redirect chain. + bool isHttps = false; + bool isChrome = false; + bool schemeLocal = false; + if (NS_FAILED(mCurrentURI->SchemeIs("https", &isHttps)) || + NS_FAILED(mCurrentURI->SchemeIs("chrome", &isChrome)) || + NS_FAILED(NS_URIChainHasFlags(mCurrentURI, + nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, + &schemeLocal)) || + (!isHttps && !isChrome && !schemeLocal)) { + MutexAutoLock lock(mMutex); + + // The csp directive upgrade-insecure-requests performs an internal redirect + // to upgrade all requests from http to https before any data is fetched from + // the network. Do not pollute mHadInsecureRedirect in case of such an internal + // redirect. + nsCOMPtr loadInfo = mChannel->GetLoadInfo(); + bool upgradeInsecureRequests = loadInfo ? loadInfo->GetUpgradeInsecureRequests() + : false; + if (!upgradeInsecureRequests) { + mHadInsecureRedirect = true; + } + } + + // Update the current URI. + mChannel->GetURI(getter_AddRefs(mCurrentURI)); + + if (LOG_TEST(LogLevel::Debug)) { + LOG_MSG_WITH_PARAM(gImgLog, "imgRequest::OnChannelRedirect", "new", + mCurrentURI ? mCurrentURI->GetSpecOrDefault().get() + : ""); + } + + // Make sure we have a protocol that returns data rather than opens an + // external application, e.g. 'mailto:'. + bool doesNotReturnData = false; + nsresult rv = + NS_URIChainHasFlags(mCurrentURI, + nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA, + &doesNotReturnData); + + if (NS_SUCCEEDED(rv) && doesNotReturnData) { + rv = NS_ERROR_ABORT; + } + + if (NS_FAILED(rv)) { + mRedirectCallback->OnRedirectVerifyCallback(rv); + mRedirectCallback = nullptr; + return NS_OK; + } + + mRedirectCallback->OnRedirectVerifyCallback(NS_OK); + mRedirectCallback = nullptr; + return NS_OK; +} diff --git a/image/imgRequest.h b/image/imgRequest.h new file mode 100644 index 000000000..c05e6a4a9 --- /dev/null +++ b/image/imgRequest.h @@ -0,0 +1,293 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_imgRequest_h +#define mozilla_image_imgRequest_h + +#include "nsIChannelEventSink.h" +#include "nsIInterfaceRequestor.h" +#include "nsIStreamListener.h" +#include "nsIThreadRetargetableStreamListener.h" +#include "nsIPrincipal.h" + +#include "nsCOMPtr.h" +#include "nsProxyRelease.h" +#include "nsStringGlue.h" +#include "nsError.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "mozilla/Mutex.h" +#include "mozilla/net/ReferrerPolicy.h" +#include "ImageCacheKey.h" + +class imgCacheValidator; +class imgLoader; +class imgRequestProxy; +class imgCacheEntry; +class nsIApplicationCache; +class nsIProperties; +class nsIRequest; +class nsITimedChannel; +class nsIURI; + +namespace mozilla { +namespace image { +class Image; +class ImageURL; +class ProgressTracker; +} // namespace image +} // namespace mozilla + +struct NewPartResult; + +class imgRequest final : public nsIStreamListener, + public nsIThreadRetargetableStreamListener, + public nsIChannelEventSink, + public nsIInterfaceRequestor, + public nsIAsyncVerifyRedirectCallback +{ + typedef mozilla::image::Image Image; + typedef mozilla::image::ImageCacheKey ImageCacheKey; + typedef mozilla::image::ImageURL ImageURL; + typedef mozilla::image::ProgressTracker ProgressTracker; + typedef mozilla::net::ReferrerPolicy ReferrerPolicy; + +public: + imgRequest(imgLoader* aLoader, const ImageCacheKey& aCacheKey); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSICHANNELEVENTSINK + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK + + MOZ_MUST_USE nsresult Init(nsIURI* aURI, + nsIURI* aCurrentURI, + bool aHadInsecureRedirect, + nsIRequest* aRequest, + nsIChannel* aChannel, + imgCacheEntry* aCacheEntry, + nsISupports* aCX, + nsIPrincipal* aLoadingPrincipal, + int32_t aCORSMode, + ReferrerPolicy aReferrerPolicy); + + void ClearLoader(); + + // Callers must call imgRequestProxy::Notify later. + void AddProxy(imgRequestProxy* proxy); + + nsresult RemoveProxy(imgRequestProxy* proxy, nsresult aStatus); + + // Cancel, but also ensure that all work done in Init() is undone. Call this + // only when the channel has failed to open, and so calling Cancel() on it + // won't be sufficient. + void CancelAndAbort(nsresult aStatus); + + // Called or dispatched by cancel for main thread only execution. + void ContinueCancel(nsresult aStatus); + + // Called or dispatched by EvictFromCache for main thread only execution. + void ContinueEvict(); + + // Request that we start decoding the image as soon as data becomes available. + void StartDecoding(); + + inline uint64_t InnerWindowID() const { + return mInnerWindowId; + } + + // Set the cache validation information (expiry time, whether we must + // validate, etc) on the cache entry based on the request information. + // If this function is called multiple times, the information set earliest + // wins. + static void SetCacheValidation(imgCacheEntry* aEntry, nsIRequest* aRequest); + + // Check if application cache of the original load is different from + // application cache of the new load. Also lack of application cache + // on one of the loads is considered a change of a loading cache since + // HTTP cache may contain a different data then app cache. + bool CacheChanged(nsIRequest* aNewRequest); + + bool GetMultipart() const; + + // Returns whether we went through an insecure (non-HTTPS) redirect at some + // point during loading. This does not consider the current URI. + bool HadInsecureRedirect() const; + + // The CORS mode for which we loaded this image. + int32_t GetCORSMode() const { return mCORSMode; } + + // The Referrer Policy in effect when loading this image. + ReferrerPolicy GetReferrerPolicy() const { return mReferrerPolicy; } + + // The principal for the document that loaded this image. Used when trying to + // validate a CORS image load. + already_AddRefed GetLoadingPrincipal() const + { + nsCOMPtr principal = mLoadingPrincipal; + return principal.forget(); + } + + // Return the ProgressTracker associated with this imgRequest. It may live + // in |mProgressTracker| or in |mImage.mProgressTracker|, depending on whether + // mImage has been instantiated yet. + already_AddRefed GetProgressTracker() const; + + /// Returns the Image associated with this imgRequest, if it's ready. + already_AddRefed GetImage() const; + + // Get the current principal of the image. No AddRefing. + inline nsIPrincipal* GetPrincipal() const { return mPrincipal.get(); } + + /// Get the ImageCacheKey associated with this request. + const ImageCacheKey& CacheKey() const { return mCacheKey; } + + // Resize the cache entry to 0 if it exists + void ResetCacheEntry(); + + // OK to use on any thread. + nsresult GetURI(ImageURL** aURI); + nsresult GetCurrentURI(nsIURI** aURI); + bool IsChrome() const; + + nsresult GetImageErrorCode(void); + + /// Returns true if we've received any data. + bool HasTransferredData() const; + + /// Returns a non-owning pointer to this imgRequest's MIME type. + const char* GetMimeType() const { return mContentType.get(); } + + /// @return the priority of the underlying network request, or + /// PRIORITY_NORMAL if it doesn't support nsISupportsPriority. + int32_t Priority() const; + + /// Adjust the priority of the underlying network request by @aDelta on behalf + /// of @aProxy. + void AdjustPriority(imgRequestProxy* aProxy, int32_t aDelta); + + /// Returns a weak pointer to the underlying request. + nsIRequest* GetRequest() const { return mRequest; } + + nsITimedChannel* GetTimedChannel() const { return mTimedChannel; } + + nsresult GetSecurityInfo(nsISupports** aSecurityInfoOut); + + imgCacheValidator* GetValidator() const { return mValidator; } + void SetValidator(imgCacheValidator* aValidator) { mValidator = aValidator; } + + void* LoadId() const { return mLoadId; } + void SetLoadId(void* aLoadId) { mLoadId = aLoadId; } + + /// Reset the cache entry after we've dropped our reference to it. Used by + /// imgLoader when our cache entry is re-requested after we've dropped our + /// reference to it. + void SetCacheEntry(imgCacheEntry* aEntry); + + /// Returns whether we've got a reference to the cache entry. + bool HasCacheEntry() const; + + /// Set whether this request is stored in the cache. If it isn't, regardless + /// of whether this request has a non-null mCacheEntry, this imgRequest won't + /// try to update or modify the image cache. + void SetIsInCache(bool aCacheable); + + void EvictFromCache(); + void RemoveFromCache(); + + // Sets properties for this image; will dispatch to main thread if needed. + void SetProperties(const nsACString& aContentType, + const nsACString& aContentDisposition); + + nsIProperties* Properties() const { return mProperties; } + + bool HasConsumers() const; + +private: + friend class FinishPreparingForNewPartRunnable; + + virtual ~imgRequest(); + + void FinishPreparingForNewPart(const NewPartResult& aResult); + + void Cancel(nsresult aStatus); + + // Update the cache entry size based on the image container. + void UpdateCacheEntrySize(); + + /// Returns true if StartDecoding() was called. + bool IsDecodeRequested() const; + + // Weak reference to parent loader; this request cannot outlive its owner. + imgLoader* mLoader; + nsCOMPtr mRequest; + // The original URI we were loaded with. This is the same as the URI we are + // keyed on in the cache. We store a string here to avoid off main thread + // refcounting issues with nsStandardURL. + RefPtr mURI; + // The URI of the resource we ended up loading after all redirects, etc. + nsCOMPtr mCurrentURI; + // The principal of the document which loaded this image. Used when + // validating for CORS. + nsCOMPtr mLoadingPrincipal; + // The principal of this image. + nsCOMPtr mPrincipal; + nsCOMPtr mProperties; + nsCOMPtr mSecurityInfo; + nsCOMPtr mChannel; + nsCOMPtr mPrevChannelSink; + nsCOMPtr mApplicationCache; + + nsCOMPtr mTimedChannel; + + nsCString mContentType; + + /* we hold on to this to this so long as we have observers */ + RefPtr mCacheEntry; + + /// The key under which this imgRequest is stored in the image cache. + ImageCacheKey mCacheKey; + + void* mLoadId; + + /// Raw pointer to the first proxy that was added to this imgRequest. Use only + /// pointer comparisons; there's no guarantee this will remain valid. + void* mFirstProxy; + + imgCacheValidator* mValidator; + nsCOMPtr mRedirectCallback; + nsCOMPtr mNewRedirectChannel; + + // The ID of the inner window origin, used for error reporting. + uint64_t mInnerWindowId; + + // The CORS mode (defined in imgIRequest) this image was loaded with. By + // default, imgIRequest::CORS_NONE. + int32_t mCORSMode; + + // The Referrer Policy (defined in ReferrerPolicy.h) used for this image. + ReferrerPolicy mReferrerPolicy; + + nsresult mImageErrorCode; + + mutable mozilla::Mutex mMutex; + + // Member variables protected by mMutex. Note that *all* flags in our bitfield + // are protected by mMutex; if you're adding a new flag that isn'protected, it + // must not be a part of this bitfield. + RefPtr mProgressTracker; + RefPtr mImage; + bool mIsMultiPartChannel : 1; + bool mGotData : 1; + bool mIsInCache : 1; + bool mDecodeRequested : 1; + bool mNewPartPending : 1; + bool mHadInsecureRedirect : 1; +}; + +#endif // mozilla_image_imgRequest_h diff --git a/image/imgRequestProxy.cpp b/image/imgRequestProxy.cpp new file mode 100644 index 000000000..4ccf1ff1d --- /dev/null +++ b/image/imgRequestProxy.cpp @@ -0,0 +1,1084 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageLogging.h" +#include "imgRequestProxy.h" +#include "imgIOnloadBlocker.h" +#include "imgLoader.h" +#include "Image.h" +#include "ImageOps.h" +#include "nsError.h" +#include "nsCRTGlue.h" +#include "imgINotificationObserver.h" + +using namespace mozilla::image; + +// The split of imgRequestProxy and imgRequestProxyStatic means that +// certain overridden functions need to be usable in the destructor. +// Since virtual functions can't be used in that way, this class +// provides a behavioural trait for each class to use instead. +class ProxyBehaviour +{ + public: + virtual ~ProxyBehaviour() {} + + virtual already_AddRefed GetImage() const = 0; + virtual bool HasImage() const = 0; + virtual already_AddRefed GetProgressTracker() const = 0; + virtual imgRequest* GetOwner() const = 0; + virtual void SetOwner(imgRequest* aOwner) = 0; +}; + +class RequestBehaviour : public ProxyBehaviour +{ + public: + RequestBehaviour() : mOwner(nullptr), mOwnerHasImage(false) {} + + virtual already_AddRefedGetImage() const override; + virtual bool HasImage() const override; + virtual already_AddRefed GetProgressTracker() const override; + + virtual imgRequest* GetOwner() const override { + return mOwner; + } + + virtual void SetOwner(imgRequest* aOwner) override { + mOwner = aOwner; + + if (mOwner) { + RefPtr ownerProgressTracker = GetProgressTracker(); + mOwnerHasImage = ownerProgressTracker && ownerProgressTracker->HasImage(); + } else { + mOwnerHasImage = false; + } + } + + private: + // We maintain the following invariant: + // The proxy is registered at most with a single imgRequest as an observer, + // and whenever it is, mOwner points to that object. This helps ensure that + // imgRequestProxy::~imgRequestProxy unregisters the proxy as an observer + // from whatever request it was registered with (if any). This, in turn, + // means that imgRequest::mObservers will not have any stale pointers in it. + RefPtr mOwner; + + bool mOwnerHasImage; +}; + +already_AddRefed +RequestBehaviour::GetImage() const +{ + if (!mOwnerHasImage) { + return nullptr; + } + RefPtr progressTracker = GetProgressTracker(); + return progressTracker->GetImage(); +} + +already_AddRefed +RequestBehaviour::GetProgressTracker() const +{ + // NOTE: It's possible that our mOwner has an Image that it didn't notify + // us about, if we were Canceled before its Image was constructed. + // (Canceling removes us as an observer, so mOwner has no way to notify us). + // That's why this method uses mOwner->GetProgressTracker() instead of just + // mOwner->mProgressTracker -- we might have a null mImage and yet have an + // mOwner with a non-null mImage (and a null mProgressTracker pointer). + return mOwner->GetProgressTracker(); +} + +NS_IMPL_ADDREF(imgRequestProxy) +NS_IMPL_RELEASE(imgRequestProxy) + +NS_INTERFACE_MAP_BEGIN(imgRequestProxy) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, imgIRequest) + NS_INTERFACE_MAP_ENTRY(imgIRequest) + NS_INTERFACE_MAP_ENTRY(nsIRequest) + NS_INTERFACE_MAP_ENTRY(nsISupportsPriority) + NS_INTERFACE_MAP_ENTRY(nsISecurityInfoProvider) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITimedChannel, + TimedChannel() != nullptr) +NS_INTERFACE_MAP_END + +imgRequestProxy::imgRequestProxy() : + mBehaviour(new RequestBehaviour), + mURI(nullptr), + mListener(nullptr), + mLoadFlags(nsIRequest::LOAD_NORMAL), + mLockCount(0), + mAnimationConsumers(0), + mCanceled(false), + mIsInLoadGroup(false), + mListenerIsStrongRef(false), + mDecodeRequested(false), + mDeferNotifications(false) +{ + /* member initializers and constructor code */ + +} + +imgRequestProxy::~imgRequestProxy() +{ + /* destructor code */ + NS_PRECONDITION(!mListener, + "Someone forgot to properly cancel this request!"); + + // Unlock the image the proper number of times if we're holding locks on + // it. Note that UnlockImage() decrements mLockCount each time it's called. + while (mLockCount) { + UnlockImage(); + } + + ClearAnimationConsumers(); + + // Explicitly set mListener to null to ensure that the RemoveProxy + // call below can't send |this| to an arbitrary listener while |this| + // is being destroyed. This is all belt-and-suspenders in view of the + // above assert. + NullOutListener(); + + if (GetOwner()) { + /* Call RemoveProxy with a successful status. This will keep the + channel, if still downloading data, from being canceled if 'this' is + the last observer. This allows the image to continue to download and + be cached even if no one is using it currently. + */ + mCanceled = true; + GetOwner()->RemoveProxy(this, NS_OK); + } +} + +nsresult +imgRequestProxy::Init(imgRequest* aOwner, + nsILoadGroup* aLoadGroup, + ImageURL* aURI, + imgINotificationObserver* aObserver) +{ + NS_PRECONDITION(!GetOwner() && !mListener, + "imgRequestProxy is already initialized"); + + LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequestProxy::Init", "request", + aOwner); + + MOZ_ASSERT(mAnimationConsumers == 0, "Cannot have animation before Init"); + + mBehaviour->SetOwner(aOwner); + mListener = aObserver; + // Make sure to addref mListener before the AddProxy call below, since + // that call might well want to release it if the imgRequest has + // already seen OnStopRequest. + if (mListener) { + mListenerIsStrongRef = true; + NS_ADDREF(mListener); + } + mLoadGroup = aLoadGroup; + mURI = aURI; + + // Note: AddProxy won't send all the On* notifications immediately + if (GetOwner()) { + GetOwner()->AddProxy(this); + } + + return NS_OK; +} + +nsresult +imgRequestProxy::ChangeOwner(imgRequest* aNewOwner) +{ + NS_PRECONDITION(GetOwner(), + "Cannot ChangeOwner on a proxy without an owner!"); + + if (mCanceled) { + // Ensure that this proxy has received all notifications to date + // before we clean it up when removing it from the old owner below. + SyncNotifyListener(); + } + + // If we're holding locks, unlock the old image. + // Note that UnlockImage decrements mLockCount each time it's called. + uint32_t oldLockCount = mLockCount; + while (mLockCount) { + UnlockImage(); + } + + // If we're holding animation requests, undo them. + uint32_t oldAnimationConsumers = mAnimationConsumers; + ClearAnimationConsumers(); + + GetOwner()->RemoveProxy(this, NS_IMAGELIB_CHANGING_OWNER); + + mBehaviour->SetOwner(aNewOwner); + + // If we were locked, apply the locks here + for (uint32_t i = 0; i < oldLockCount; i++) { + LockImage(); + } + + // If we had animation requests, restore them here. Note that we + // do this *after* RemoveProxy, which clears out animation consumers + // (see bug 601723). + for (uint32_t i = 0; i < oldAnimationConsumers; i++) { + IncrementAnimationConsumers(); + } + + GetOwner()->AddProxy(this); + + // If we'd previously requested a synchronous decode, request a decode on the + // new image. + if (mDecodeRequested) { + StartDecoding(); + } + + return NS_OK; +} + +void +imgRequestProxy::AddToLoadGroup() +{ + NS_ASSERTION(!mIsInLoadGroup, "Whaa, we're already in the loadgroup!"); + + if (!mIsInLoadGroup && mLoadGroup) { + mLoadGroup->AddRequest(this, nullptr); + mIsInLoadGroup = true; + } +} + +void +imgRequestProxy::RemoveFromLoadGroup(bool releaseLoadGroup) +{ + if (!mIsInLoadGroup) { + return; + } + + /* calling RemoveFromLoadGroup may cause the document to finish + loading, which could result in our death. We need to make sure + that we stay alive long enough to fight another battle... at + least until we exit this function. + */ + nsCOMPtr kungFuDeathGrip(this); + + mLoadGroup->RemoveRequest(this, nullptr, NS_OK); + mIsInLoadGroup = false; + + if (releaseLoadGroup) { + // We're done with the loadgroup, release it. + mLoadGroup = nullptr; + } +} + + +/** nsIRequest / imgIRequest methods **/ + +NS_IMETHODIMP +imgRequestProxy::GetName(nsACString& aName) +{ + aName.Truncate(); + + if (mURI) { + mURI->GetSpec(aName); + } + + return NS_OK; +} + +NS_IMETHODIMP +imgRequestProxy::IsPending(bool* _retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +imgRequestProxy::GetStatus(nsresult* aStatus) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +imgRequestProxy::Cancel(nsresult status) +{ + if (mCanceled) { + return NS_ERROR_FAILURE; + } + + LOG_SCOPE(gImgLog, "imgRequestProxy::Cancel"); + + mCanceled = true; + + nsCOMPtr ev = new imgCancelRunnable(this, status); + return NS_DispatchToCurrentThread(ev); +} + +void +imgRequestProxy::DoCancel(nsresult status) +{ + if (GetOwner()) { + GetOwner()->RemoveProxy(this, status); + } + + NullOutListener(); +} + +NS_IMETHODIMP +imgRequestProxy::CancelAndForgetObserver(nsresult aStatus) +{ + // If mCanceled is true but mListener is non-null, that means + // someone called Cancel() on us but the imgCancelRunnable is still + // pending. We still need to null out mListener before returning + // from this function in this case. That means we want to do the + // RemoveProxy call right now, because we need to deliver the + // onStopRequest. + if (mCanceled && !mListener) { + return NS_ERROR_FAILURE; + } + + LOG_SCOPE(gImgLog, "imgRequestProxy::CancelAndForgetObserver"); + + mCanceled = true; + + // Now cheat and make sure our removal from loadgroup happens async + bool oldIsInLoadGroup = mIsInLoadGroup; + mIsInLoadGroup = false; + + if (GetOwner()) { + GetOwner()->RemoveProxy(this, aStatus); + } + + mIsInLoadGroup = oldIsInLoadGroup; + + if (mIsInLoadGroup) { + NS_DispatchToCurrentThread(NewRunnableMethod(this, &imgRequestProxy::DoRemoveFromLoadGroup)); + } + + NullOutListener(); + + return NS_OK; +} + +NS_IMETHODIMP +imgRequestProxy::StartDecoding() +{ + // Flag this, so we know to transfer the request if our owner changes + mDecodeRequested = true; + + RefPtr image = GetImage(); + if (image) { + return image->StartDecoding(); + } + + if (GetOwner()) { + GetOwner()->StartDecoding(); + } + + return NS_OK; +} + +NS_IMETHODIMP +imgRequestProxy::LockImage() +{ + mLockCount++; + RefPtr image = GetImage(); + if (image) { + return image->LockImage(); + } + return NS_OK; +} + +NS_IMETHODIMP +imgRequestProxy::UnlockImage() +{ + MOZ_ASSERT(mLockCount > 0, "calling unlock but no locks!"); + + mLockCount--; + RefPtr image = GetImage(); + if (image) { + return image->UnlockImage(); + } + return NS_OK; +} + +NS_IMETHODIMP +imgRequestProxy::RequestDiscard() +{ + RefPtr image = GetImage(); + if (image) { + return image->RequestDiscard(); + } + return NS_OK; +} + +NS_IMETHODIMP +imgRequestProxy::IncrementAnimationConsumers() +{ + mAnimationConsumers++; + RefPtr image = GetImage(); + if (image) { + image->IncrementAnimationConsumers(); + } + return NS_OK; +} + +NS_IMETHODIMP +imgRequestProxy::DecrementAnimationConsumers() +{ + // We may get here if some responsible code called Increment, + // then called us, but we have meanwhile called ClearAnimationConsumers + // because we needed to get rid of them earlier (see + // imgRequest::RemoveProxy), and hence have nothing left to + // decrement. (In such a case we got rid of the animation consumers + // early, but not the observer.) + if (mAnimationConsumers > 0) { + mAnimationConsumers--; + RefPtr image = GetImage(); + if (image) { + image->DecrementAnimationConsumers(); + } + } + return NS_OK; +} + +void +imgRequestProxy::ClearAnimationConsumers() +{ + while (mAnimationConsumers > 0) { + DecrementAnimationConsumers(); + } +} + +NS_IMETHODIMP +imgRequestProxy::Suspend() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +imgRequestProxy::Resume() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +imgRequestProxy::GetLoadGroup(nsILoadGroup** loadGroup) +{ + NS_IF_ADDREF(*loadGroup = mLoadGroup.get()); + return NS_OK; +} +NS_IMETHODIMP +imgRequestProxy::SetLoadGroup(nsILoadGroup* loadGroup) +{ + mLoadGroup = loadGroup; + return NS_OK; +} + +NS_IMETHODIMP +imgRequestProxy::GetLoadFlags(nsLoadFlags* flags) +{ + *flags = mLoadFlags; + return NS_OK; +} +NS_IMETHODIMP +imgRequestProxy::SetLoadFlags(nsLoadFlags flags) +{ + mLoadFlags = flags; + return NS_OK; +} + +/** imgIRequest methods **/ + +NS_IMETHODIMP +imgRequestProxy::GetImage(imgIContainer** aImage) +{ + NS_ENSURE_TRUE(aImage, NS_ERROR_NULL_POINTER); + // It's possible that our owner has an image but hasn't notified us of it - + // that'll happen if we get Canceled before the owner instantiates its image + // (because Canceling unregisters us as a listener on mOwner). If we're + // in that situation, just grab the image off of mOwner. + RefPtr image = GetImage(); + nsCOMPtr imageToReturn; + if (image) { + imageToReturn = do_QueryInterface(image); + } + if (!imageToReturn && GetOwner()) { + imageToReturn = GetOwner()->GetImage(); + } + if (!imageToReturn) { + return NS_ERROR_FAILURE; + } + + imageToReturn.swap(*aImage); + + return NS_OK; +} + +NS_IMETHODIMP +imgRequestProxy::GetImageStatus(uint32_t* aStatus) +{ + RefPtr progressTracker = GetProgressTracker(); + *aStatus = progressTracker->GetImageStatus(); + + return NS_OK; +} + +NS_IMETHODIMP +imgRequestProxy::GetImageErrorCode(nsresult* aStatus) +{ + if (!GetOwner()) { + return NS_ERROR_FAILURE; + } + + *aStatus = GetOwner()->GetImageErrorCode(); + + return NS_OK; +} + +NS_IMETHODIMP +imgRequestProxy::GetURI(nsIURI** aURI) +{ + MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI"); + nsCOMPtr uri = mURI->ToIURI(); + uri.forget(aURI); + return NS_OK; +} + +nsresult +imgRequestProxy::GetCurrentURI(nsIURI** aURI) +{ + if (!GetOwner()) { + return NS_ERROR_FAILURE; + } + + return GetOwner()->GetCurrentURI(aURI); +} + +nsresult +imgRequestProxy::GetURI(ImageURL** aURI) +{ + if (!mURI) { + return NS_ERROR_FAILURE; + } + + NS_ADDREF(*aURI = mURI); + + return NS_OK; +} + +NS_IMETHODIMP +imgRequestProxy::GetNotificationObserver(imgINotificationObserver** aObserver) +{ + *aObserver = mListener; + NS_IF_ADDREF(*aObserver); + return NS_OK; +} + +NS_IMETHODIMP +imgRequestProxy::GetMimeType(char** aMimeType) +{ + if (!GetOwner()) { + return NS_ERROR_FAILURE; + } + + const char* type = GetOwner()->GetMimeType(); + if (!type) { + return NS_ERROR_FAILURE; + } + + *aMimeType = NS_strdup(type); + + return NS_OK; +} + +static imgRequestProxy* NewProxy(imgRequestProxy* /*aThis*/) +{ + return new imgRequestProxy(); +} + +imgRequestProxy* NewStaticProxy(imgRequestProxy* aThis) +{ + nsCOMPtr currentPrincipal; + aThis->GetImagePrincipal(getter_AddRefs(currentPrincipal)); + RefPtr image = aThis->GetImage(); + return new imgRequestProxyStatic(image, currentPrincipal); +} + +NS_IMETHODIMP +imgRequestProxy::Clone(imgINotificationObserver* aObserver, + imgIRequest** aClone) +{ + nsresult result; + imgRequestProxy* proxy; + result = Clone(aObserver, &proxy); + *aClone = proxy; + return result; +} + +nsresult imgRequestProxy::Clone(imgINotificationObserver* aObserver, + imgRequestProxy** aClone) +{ + return PerformClone(aObserver, NewProxy, aClone); +} + +nsresult +imgRequestProxy::PerformClone(imgINotificationObserver* aObserver, + imgRequestProxy* (aAllocFn)(imgRequestProxy*), + imgRequestProxy** aClone) +{ + NS_PRECONDITION(aClone, "Null out param"); + + LOG_SCOPE(gImgLog, "imgRequestProxy::Clone"); + + *aClone = nullptr; + RefPtr clone = aAllocFn(this); + + // It is important to call |SetLoadFlags()| before calling |Init()| because + // |Init()| adds the request to the loadgroup. + // When a request is added to a loadgroup, its load flags are merged + // with the load flags of the loadgroup. + // XXXldb That's not true anymore. Stuff from imgLoader adds the + // request to the loadgroup. + clone->SetLoadFlags(mLoadFlags); + nsresult rv = clone->Init(mBehaviour->GetOwner(), mLoadGroup, + mURI, aObserver); + if (NS_FAILED(rv)) { + return rv; + } + + if (GetOwner() && GetOwner()->GetValidator()) { + clone->SetNotificationsDeferred(true); + GetOwner()->GetValidator()->AddProxy(clone); + } + + // Assign to *aClone before calling Notify so that if the caller expects to + // only be notified for requests it's already holding pointers to it won't be + // surprised. + NS_ADDREF(*aClone = clone); + + // This is wrong!!! We need to notify asynchronously, but there's code that + // assumes that we don't. This will be fixed in bug 580466. + clone->SyncNotifyListener(); + + return NS_OK; +} + +NS_IMETHODIMP +imgRequestProxy::GetImagePrincipal(nsIPrincipal** aPrincipal) +{ + if (!GetOwner()) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr principal = GetOwner()->GetPrincipal(); + principal.forget(aPrincipal); + return NS_OK; +} + +NS_IMETHODIMP +imgRequestProxy::GetMultipart(bool* aMultipart) +{ + if (!GetOwner()) { + return NS_ERROR_FAILURE; + } + + *aMultipart = GetOwner()->GetMultipart(); + + return NS_OK; +} + +NS_IMETHODIMP +imgRequestProxy::GetCORSMode(int32_t* aCorsMode) +{ + if (!GetOwner()) { + return NS_ERROR_FAILURE; + } + + *aCorsMode = GetOwner()->GetCORSMode(); + + return NS_OK; +} + +/** nsISupportsPriority methods **/ + +NS_IMETHODIMP +imgRequestProxy::GetPriority(int32_t* priority) +{ + NS_ENSURE_STATE(GetOwner()); + *priority = GetOwner()->Priority(); + return NS_OK; +} + +NS_IMETHODIMP +imgRequestProxy::SetPriority(int32_t priority) +{ + NS_ENSURE_STATE(GetOwner() && !mCanceled); + GetOwner()->AdjustPriority(this, priority - GetOwner()->Priority()); + return NS_OK; +} + +NS_IMETHODIMP +imgRequestProxy::AdjustPriority(int32_t priority) +{ + // We don't require |!mCanceled| here. This may be called even if we're + // cancelled, because it's invoked as part of the process of removing an image + // from the load group. + NS_ENSURE_STATE(GetOwner()); + GetOwner()->AdjustPriority(this, priority); + return NS_OK; +} + +/** nsISecurityInfoProvider methods **/ + +NS_IMETHODIMP +imgRequestProxy::GetSecurityInfo(nsISupports** _retval) +{ + if (GetOwner()) { + return GetOwner()->GetSecurityInfo(_retval); + } + + *_retval = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +imgRequestProxy::GetHasTransferredData(bool* hasData) +{ + if (GetOwner()) { + *hasData = GetOwner()->HasTransferredData(); + } else { + // The safe thing to do is to claim we have data + *hasData = true; + } + return NS_OK; +} + +static const char* +NotificationTypeToString(int32_t aType) +{ + switch(aType) + { + case imgINotificationObserver::SIZE_AVAILABLE: return "SIZE_AVAILABLE"; + case imgINotificationObserver::FRAME_UPDATE: return "FRAME_UPDATE"; + case imgINotificationObserver::FRAME_COMPLETE: return "FRAME_COMPLETE"; + case imgINotificationObserver::LOAD_COMPLETE: return "LOAD_COMPLETE"; + case imgINotificationObserver::DECODE_COMPLETE: return "DECODE_COMPLETE"; + case imgINotificationObserver::DISCARD: return "DISCARD"; + case imgINotificationObserver::UNLOCKED_DRAW: return "UNLOCKED_DRAW"; + case imgINotificationObserver::IS_ANIMATED: return "IS_ANIMATED"; + case imgINotificationObserver::HAS_TRANSPARENCY: return "HAS_TRANSPARENCY"; + default: + NS_NOTREACHED("Notification list should be exhaustive"); + return "(unknown notification)"; + } +} + +void +imgRequestProxy::Notify(int32_t aType, const mozilla::gfx::IntRect* aRect) +{ + MOZ_ASSERT(aType != imgINotificationObserver::LOAD_COMPLETE, + "Should call OnLoadComplete"); + + LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::Notify", "type", + NotificationTypeToString(aType)); + + if (!mListener || mCanceled) { + return; + } + + // Make sure the listener stays alive while we notify. + nsCOMPtr listener(mListener); + + listener->Notify(this, aType, aRect); +} + +void +imgRequestProxy::OnLoadComplete(bool aLastPart) +{ + if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) { + nsAutoCString name; + GetName(name); + LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::OnLoadComplete", + "name", name.get()); + } + + // There's all sorts of stuff here that could kill us (the OnStopRequest call + // on the listener, the removal from the loadgroup, the release of the + // listener, etc). Don't let them do it. + nsCOMPtr kungFuDeathGrip(this); + + if (mListener && !mCanceled) { + // Hold a ref to the listener while we call it, just in case. + nsCOMPtr listener(mListener); + listener->Notify(this, imgINotificationObserver::LOAD_COMPLETE, nullptr); + } + + // If we're expecting more data from a multipart channel, re-add ourself + // to the loadgroup so that the document doesn't lose track of the load. + // If the request is already a background request and there's more data + // coming, we can just leave the request in the loadgroup as-is. + if (aLastPart || (mLoadFlags & nsIRequest::LOAD_BACKGROUND) == 0) { + RemoveFromLoadGroup(aLastPart); + // More data is coming, so change the request to be a background request + // and put it back in the loadgroup. + if (!aLastPart) { + mLoadFlags |= nsIRequest::LOAD_BACKGROUND; + AddToLoadGroup(); + } + } + + if (mListenerIsStrongRef && aLastPart) { + NS_PRECONDITION(mListener, "How did that happen?"); + // Drop our strong ref to the listener now that we're done with + // everything. Note that this can cancel us and other fun things + // like that. Don't add anything in this method after this point. + imgINotificationObserver* obs = mListener; + mListenerIsStrongRef = false; + NS_RELEASE(obs); + } +} + +void +imgRequestProxy::BlockOnload() +{ + if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) { + nsAutoCString name; + GetName(name); + LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::BlockOnload", + "name", name.get()); + } + + nsCOMPtr blocker = do_QueryInterface(mListener); + if (blocker) { + blocker->BlockOnload(this); + } +} + +void +imgRequestProxy::UnblockOnload() +{ + if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) { + nsAutoCString name; + GetName(name); + LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::UnblockOnload", + "name", name.get()); + } + + nsCOMPtr blocker = do_QueryInterface(mListener); + if (blocker) { + blocker->UnblockOnload(this); + } +} + +void +imgRequestProxy::NullOutListener() +{ + // If we have animation consumers, then they don't matter anymore + if (mListener) { + ClearAnimationConsumers(); + } + + if (mListenerIsStrongRef) { + // Releasing could do weird reentery stuff, so just play it super-safe + nsCOMPtr obs; + obs.swap(mListener); + mListenerIsStrongRef = false; + } else { + mListener = nullptr; + } +} + +NS_IMETHODIMP +imgRequestProxy::GetStaticRequest(imgIRequest** aReturn) +{ + imgRequestProxy* proxy; + nsresult result = GetStaticRequest(&proxy); + *aReturn = proxy; + return result; +} + +nsresult +imgRequestProxy::GetStaticRequest(imgRequestProxy** aReturn) +{ + *aReturn = nullptr; + RefPtr image = GetImage(); + + bool animated; + if (!image || (NS_SUCCEEDED(image->GetAnimated(&animated)) && !animated)) { + // Early exit - we're not animated, so we don't have to do anything. + NS_ADDREF(*aReturn = this); + return NS_OK; + } + + // Check for errors in the image. Callers code rely on GetStaticRequest + // failing in this case, though with FrozenImage there's no technical reason + // for it anymore. + if (image->HasError()) { + return NS_ERROR_FAILURE; + } + + // We are animated. We need to create a frozen version of this image. + RefPtr frozenImage = ImageOps::Freeze(image); + + // Create a static imgRequestProxy with our new extracted frame. + nsCOMPtr currentPrincipal; + GetImagePrincipal(getter_AddRefs(currentPrincipal)); + RefPtr req = new imgRequestProxyStatic(frozenImage, + currentPrincipal); + req->Init(nullptr, nullptr, mURI, nullptr); + + NS_ADDREF(*aReturn = req); + + return NS_OK; +} + +void +imgRequestProxy::NotifyListener() +{ + // It would be nice to notify the observer directly in the status tracker + // instead of through the proxy, but there are several places we do extra + // processing when we receive notifications (like OnStopRequest()), and we + // need to check mCanceled everywhere too. + + RefPtr progressTracker = GetProgressTracker(); + if (GetOwner()) { + // Send the notifications to our listener asynchronously. + progressTracker->Notify(this); + } else { + // We don't have an imgRequest, so we can only notify the clone of our + // current state, but we still have to do that asynchronously. + MOZ_ASSERT(HasImage(), + "if we have no imgRequest, we should have an Image"); + progressTracker->NotifyCurrentState(this); + } +} + +void +imgRequestProxy::SyncNotifyListener() +{ + // It would be nice to notify the observer directly in the status tracker + // instead of through the proxy, but there are several places we do extra + // processing when we receive notifications (like OnStopRequest()), and we + // need to check mCanceled everywhere too. + + RefPtr progressTracker = GetProgressTracker(); + progressTracker->SyncNotify(this); +} + +void +imgRequestProxy::SetHasImage() +{ + RefPtr progressTracker = GetProgressTracker(); + MOZ_ASSERT(progressTracker); + RefPtr image = progressTracker->GetImage(); + MOZ_ASSERT(image); + + // Force any private status related to the owner to reflect + // the presence of an image; + mBehaviour->SetOwner(mBehaviour->GetOwner()); + + // Apply any locks we have + for (uint32_t i = 0; i < mLockCount; ++i) { + image->LockImage(); + } + + // Apply any animation consumers we have + for (uint32_t i = 0; i < mAnimationConsumers; i++) { + image->IncrementAnimationConsumers(); + } +} + +already_AddRefed +imgRequestProxy::GetProgressTracker() const +{ + return mBehaviour->GetProgressTracker(); +} + +already_AddRefed +imgRequestProxy::GetImage() const +{ + return mBehaviour->GetImage(); +} + +bool +RequestBehaviour::HasImage() const +{ + if (!mOwnerHasImage) { + return false; + } + RefPtr progressTracker = GetProgressTracker(); + return progressTracker ? progressTracker->HasImage() : false; +} + +bool +imgRequestProxy::HasImage() const +{ + return mBehaviour->HasImage(); +} + +imgRequest* +imgRequestProxy::GetOwner() const +{ + return mBehaviour->GetOwner(); +} + +////////////////// imgRequestProxyStatic methods + +class StaticBehaviour : public ProxyBehaviour +{ +public: + explicit StaticBehaviour(mozilla::image::Image* aImage) : mImage(aImage) {} + + virtual already_AddRefed + GetImage() const override { + RefPtr image = mImage; + return image.forget(); + } + + virtual bool HasImage() const override { + return mImage; + } + + virtual already_AddRefed GetProgressTracker() + const override { + return mImage->GetProgressTracker(); + } + + virtual imgRequest* GetOwner() const override { + return nullptr; + } + + virtual void SetOwner(imgRequest* aOwner) override { + MOZ_ASSERT(!aOwner, + "We shouldn't be giving static requests a non-null owner."); + } + +private: + // Our image. We have to hold a strong reference here, because that's normally + // the job of the underlying request. + RefPtr mImage; +}; + +imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image* aImage, + nsIPrincipal* aPrincipal) +: mPrincipal(aPrincipal) +{ + mBehaviour = mozilla::MakeUnique(aImage); +} + +NS_IMETHODIMP +imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal** aPrincipal) +{ + if (!mPrincipal) { + return NS_ERROR_FAILURE; + } + + NS_ADDREF(*aPrincipal = mPrincipal); + + return NS_OK; +} + +nsresult +imgRequestProxyStatic::Clone(imgINotificationObserver* aObserver, + imgRequestProxy** aClone) +{ + return PerformClone(aObserver, NewStaticProxy, aClone); +} diff --git a/image/imgRequestProxy.h b/image/imgRequestProxy.h new file mode 100644 index 000000000..558b7eaaf --- /dev/null +++ b/image/imgRequestProxy.h @@ -0,0 +1,248 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_imgRequestProxy_h +#define mozilla_image_imgRequestProxy_h + +#include "imgIRequest.h" +#include "nsISecurityInfoProvider.h" + +#include "nsILoadGroup.h" +#include "nsISupportsPriority.h" +#include "nsITimedChannel.h" +#include "nsCOMPtr.h" +#include "nsThreadUtils.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/gfx/Rect.h" + +#include "imgRequest.h" +#include "IProgressObserver.h" + +#define NS_IMGREQUESTPROXY_CID \ +{ /* 20557898-1dd2-11b2-8f65-9c462ee2bc95 */ \ + 0x20557898, \ + 0x1dd2, \ + 0x11b2, \ + {0x8f, 0x65, 0x9c, 0x46, 0x2e, 0xe2, 0xbc, 0x95} \ +} + +class imgINotificationObserver; +class imgStatusNotifyRunnable; +class ProxyBehaviour; + +namespace mozilla { +namespace image { +class Image; +class ImageURL; +class ProgressTracker; +} // namespace image +} // namespace mozilla + +class imgRequestProxy : public imgIRequest, + public mozilla::image::IProgressObserver, + public nsISupportsPriority, + public nsISecurityInfoProvider, + public nsITimedChannel +{ +protected: + virtual ~imgRequestProxy(); + +public: + typedef mozilla::image::Image Image; + typedef mozilla::image::ImageURL ImageURL; + typedef mozilla::image::ProgressTracker ProgressTracker; + + MOZ_DECLARE_REFCOUNTED_TYPENAME(imgRequestProxy) + NS_DECL_ISUPPORTS + NS_DECL_IMGIREQUEST + NS_DECL_NSIREQUEST + NS_DECL_NSISUPPORTSPRIORITY + NS_DECL_NSISECURITYINFOPROVIDER + // nsITimedChannel declared below + + imgRequestProxy(); + + // Callers to Init or ChangeOwner are required to call NotifyListener after + // (although not immediately after) doing so. + nsresult Init(imgRequest* aOwner, + nsILoadGroup* aLoadGroup, + ImageURL* aURI, + imgINotificationObserver* aObserver); + + nsresult ChangeOwner(imgRequest* aNewOwner); // this will change mOwner. + // Do not call this if the + // previous owner has already + // sent notifications out! + + void AddToLoadGroup(); + void RemoveFromLoadGroup(bool releaseLoadGroup); + + inline bool HasObserver() const { + return mListener != nullptr; + } + + // Asynchronously notify this proxy's listener of the current state of the + // image, and, if we have an imgRequest mOwner, any status changes that + // happen between the time this function is called and the time the + // notification is scheduled. + void NotifyListener(); + + // Synchronously notify this proxy's listener of the current state of the + // image. Only use this function if you are currently servicing an + // asynchronously-called function. + void SyncNotifyListener(); + + // imgINotificationObserver methods: + virtual void Notify(int32_t aType, + const mozilla::gfx::IntRect* aRect = nullptr) override; + virtual void OnLoadComplete(bool aLastPart) override; + + // imgIOnloadBlocker methods: + virtual void BlockOnload() override; + virtual void UnblockOnload() override; + + // Other, internal-only methods: + virtual void SetHasImage() override; + + // Whether we want notifications from ProgressTracker to be deferred until + // an event it has scheduled has been fired. + virtual bool NotificationsDeferred() const override + { + return mDeferNotifications; + } + virtual void SetNotificationsDeferred(bool aDeferNotifications) override + { + mDeferNotifications = aDeferNotifications; + } + + // Removes all animation consumers that were created with + // IncrementAnimationConsumers. This is necessary since we need + // to do it before the proxy itself is destroyed. See + // imgRequest::RemoveProxy + void ClearAnimationConsumers(); + + virtual nsresult Clone(imgINotificationObserver* aObserver, + imgRequestProxy** aClone); + nsresult GetStaticRequest(imgRequestProxy** aReturn); + + nsresult GetURI(ImageURL** aURI); + +protected: + friend class mozilla::image::ProgressTracker; + friend class imgStatusNotifyRunnable; + + class imgCancelRunnable; + friend class imgCancelRunnable; + + class imgCancelRunnable : public mozilla::Runnable + { + public: + imgCancelRunnable(imgRequestProxy* owner, nsresult status) + : mOwner(owner), mStatus(status) + { } + + NS_IMETHOD Run() override { + mOwner->DoCancel(mStatus); + return NS_OK; + } + + private: + RefPtr mOwner; + nsresult mStatus; + }; + + /* Finish up canceling ourselves */ + void DoCancel(nsresult status); + + /* Do the proper refcount management to null out mListener */ + void NullOutListener(); + + void DoRemoveFromLoadGroup() { + RemoveFromLoadGroup(true); + } + + // Return the ProgressTracker associated with mOwner and/or mImage. It may + // live either on mOwner or mImage, depending on whether + // (a) we have an mOwner at all + // (b) whether mOwner has instantiated its image yet + already_AddRefed GetProgressTracker() const; + + nsITimedChannel* TimedChannel() + { + if (!GetOwner()) { + return nullptr; + } + return GetOwner()->GetTimedChannel(); + } + + already_AddRefed GetImage() const; + bool HasImage() const; + imgRequest* GetOwner() const; + + nsresult PerformClone(imgINotificationObserver* aObserver, + imgRequestProxy* (aAllocFn)(imgRequestProxy*), + imgRequestProxy** aClone); + +public: + NS_FORWARD_SAFE_NSITIMEDCHANNEL(TimedChannel()) + +protected: + mozilla::UniquePtr mBehaviour; + +private: + friend class imgCacheValidator; + friend imgRequestProxy* NewStaticProxy(imgRequestProxy* aThis); + + // The URI of our request. + RefPtr mURI; + + // mListener is only promised to be a weak ref (see imgILoader.idl), + // but we actually keep a strong ref to it until we've seen our + // first OnStopRequest. + imgINotificationObserver* MOZ_UNSAFE_REF("Observers must call Cancel() or " + "CancelAndForgetObserver() before " + "they are destroyed") mListener; + + nsCOMPtr mLoadGroup; + + nsLoadFlags mLoadFlags; + uint32_t mLockCount; + uint32_t mAnimationConsumers; + bool mCanceled; + bool mIsInLoadGroup; + bool mListenerIsStrongRef; + bool mDecodeRequested; + + // Whether we want to defer our notifications by the non-virtual Observer + // interfaces as image loads proceed. + bool mDeferNotifications; +}; + +// Used for static image proxies for which no requests are available, so +// certain behaviours must be overridden to compensate. +class imgRequestProxyStatic : public imgRequestProxy +{ + +public: + imgRequestProxyStatic(Image* aImage, nsIPrincipal* aPrincipal); + + NS_IMETHOD GetImagePrincipal(nsIPrincipal** aPrincipal) override; + + using imgRequestProxy::Clone; + + virtual nsresult Clone(imgINotificationObserver* aObserver, + imgRequestProxy** aClone) override; + +protected: + friend imgRequestProxy* NewStaticProxy(imgRequestProxy*); + + // Our principal. We have to cache it, rather than accessing the underlying + // request on-demand, because static proxies don't have an underlying request. + nsCOMPtr mPrincipal; +}; + +#endif // mozilla_image_imgRequestProxy_h diff --git a/image/imgTools.cpp b/image/imgTools.cpp new file mode 100644 index 000000000..29905c1ab --- /dev/null +++ b/image/imgTools.cpp @@ -0,0 +1,356 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "imgTools.h" + +#include "gfxUtils.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/RefPtr.h" +#include "nsCOMPtr.h" +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsError.h" +#include "imgLoader.h" +#include "imgICache.h" +#include "imgIContainer.h" +#include "imgIEncoder.h" +#include "nsStreamUtils.h" +#include "nsContentUtils.h" +#include "ImageFactory.h" +#include "Image.h" +#include "ScriptedNotificationObserver.h" +#include "imgIScriptedNotificationObserver.h" +#include "gfxPlatform.h" + +using namespace mozilla::gfx; + +namespace mozilla { +namespace image { +/* ========== imgITools implementation ========== */ + + + +NS_IMPL_ISUPPORTS(imgTools, imgITools) + +imgTools::imgTools() +{ + /* member initializers and constructor code */ +} + +imgTools::~imgTools() +{ + /* destructor code */ +} + +NS_IMETHODIMP +imgTools::DecodeImageData(nsIInputStream* aInStr, + const nsACString& aMimeType, + imgIContainer** aContainer) +{ + MOZ_ASSERT(*aContainer == nullptr, + "Cannot provide an existing image container to DecodeImageData"); + + return DecodeImage(aInStr, aMimeType, aContainer); +} + +NS_IMETHODIMP +imgTools::DecodeImage(nsIInputStream* aInStr, + const nsACString& aMimeType, + imgIContainer** aContainer) +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv; + + NS_ENSURE_ARG_POINTER(aInStr); + + // Create a new image container to hold the decoded data. + nsAutoCString mimeType(aMimeType); + RefPtr image = ImageFactory::CreateAnonymousImage(mimeType); + RefPtr tracker = image->GetProgressTracker(); + + if (image->HasError()) { + return NS_ERROR_FAILURE; + } + + // Prepare the input stream. + nsCOMPtr inStream = aInStr; + if (!NS_InputStreamIsBuffered(aInStr)) { + nsCOMPtr bufStream; + rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream), aInStr, 1024); + if (NS_SUCCEEDED(rv)) { + inStream = bufStream; + } + } + + // Figure out how much data we've been passed. + uint64_t length; + rv = inStream->Available(&length); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(length <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG); + + // Send the source data to the Image. + rv = image->OnImageDataAvailable(nullptr, nullptr, inStream, 0, + uint32_t(length)); + NS_ENSURE_SUCCESS(rv, rv); + + // Let the Image know we've sent all the data. + rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true); + tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE); + NS_ENSURE_SUCCESS(rv, rv); + + // All done. + NS_ADDREF(*aContainer = image.get()); + return NS_OK; +} + +/** + * This takes a DataSourceSurface rather than a SourceSurface because some + * of the callers have a DataSourceSurface and we don't want to call + * GetDataSurface on such surfaces since that may incure a conversion to + * SurfaceType::DATA which we don't need. + */ +static nsresult +EncodeImageData(DataSourceSurface* aDataSurface, + const nsACString& aMimeType, + const nsAString& aOutputOptions, + nsIInputStream** aStream) +{ + MOZ_ASSERT(aDataSurface->GetFormat() == SurfaceFormat::B8G8R8A8, + "We're assuming B8G8R8A8"); + + // Get an image encoder for the media type + nsAutoCString encoderCID( + NS_LITERAL_CSTRING("@mozilla.org/image/encoder;2?type=") + aMimeType); + + nsCOMPtr encoder = do_CreateInstance(encoderCID.get()); + if (!encoder) { + return NS_IMAGELIB_ERROR_NO_ENCODER; + } + + DataSourceSurface::MappedSurface map; + if (!aDataSurface->Map(DataSourceSurface::MapType::READ, &map)) { + return NS_ERROR_FAILURE; + } + + IntSize size = aDataSurface->GetSize(); + uint32_t dataLength = map.mStride * size.height; + + // Encode the bitmap + nsresult rv = encoder->InitFromData(map.mData, + dataLength, + size.width, + size.height, + map.mStride, + imgIEncoder::INPUT_FORMAT_HOSTARGB, + aOutputOptions); + aDataSurface->Unmap(); + NS_ENSURE_SUCCESS(rv, rv); + + encoder.forget(aStream); + return NS_OK; +} + +NS_IMETHODIMP +imgTools::EncodeImage(imgIContainer* aContainer, + const nsACString& aMimeType, + const nsAString& aOutputOptions, + nsIInputStream** aStream) +{ + // Use frame 0 from the image container. + RefPtr frame = + aContainer->GetFrame(imgIContainer::FRAME_FIRST, + imgIContainer::FLAG_SYNC_DECODE); + NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); + + RefPtr dataSurface; + + if (frame->GetFormat() == SurfaceFormat::B8G8R8A8) { + dataSurface = frame->GetDataSurface(); + } else { + // Convert format to SurfaceFormat::B8G8R8A8 + dataSurface = gfxUtils:: + CopySurfaceToDataSourceSurfaceWithFormat(frame, + SurfaceFormat::B8G8R8A8); + } + + NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE); + + return EncodeImageData(dataSurface, aMimeType, aOutputOptions, aStream); +} + +NS_IMETHODIMP +imgTools::EncodeScaledImage(imgIContainer* aContainer, + const nsACString& aMimeType, + int32_t aScaledWidth, + int32_t aScaledHeight, + const nsAString& aOutputOptions, + nsIInputStream** aStream) +{ + NS_ENSURE_ARG(aScaledWidth >= 0 && aScaledHeight >= 0); + + // If no scaled size is specified, we'll just encode the image at its + // original size (no scaling). + if (aScaledWidth == 0 && aScaledHeight == 0) { + return EncodeImage(aContainer, aMimeType, aOutputOptions, aStream); + } + + // Retrieve the image's size. + int32_t imageWidth = 0; + int32_t imageHeight = 0; + aContainer->GetWidth(&imageWidth); + aContainer->GetHeight(&imageHeight); + + // If the given width or height is zero we'll replace it with the image's + // original dimensions. + IntSize scaledSize(aScaledWidth == 0 ? imageWidth : aScaledWidth, + aScaledHeight == 0 ? imageHeight : aScaledHeight); + + // Use frame 0 from the image container. + RefPtr frame = + aContainer->GetFrameAtSize(scaledSize, + imgIContainer::FRAME_FIRST, + imgIContainer::FLAG_HIGH_QUALITY_SCALING | + imgIContainer::FLAG_SYNC_DECODE); + NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); + + RefPtr dataSurface = + Factory::CreateDataSourceSurface(scaledSize, SurfaceFormat::B8G8R8A8); + if (NS_WARN_IF(!dataSurface)) { + return NS_ERROR_FAILURE; + } + + DataSourceSurface::MappedSurface map; + if (!dataSurface->Map(DataSourceSurface::MapType::WRITE, &map)) { + return NS_ERROR_FAILURE; + } + + RefPtr dt = + Factory::CreateDrawTargetForData(BackendType::CAIRO, + map.mData, + dataSurface->GetSize(), + map.mStride, + SurfaceFormat::B8G8R8A8); + if (!dt) { + gfxWarning() << "imgTools::EncodeImage failed in CreateDrawTargetForData"; + return NS_ERROR_OUT_OF_MEMORY; + } + + IntSize frameSize = frame->GetSize(); + dt->DrawSurface(frame, + Rect(0, 0, scaledSize.width, scaledSize.height), + Rect(0, 0, frameSize.width, frameSize.height), + DrawSurfaceOptions(), + DrawOptions(1.0f, CompositionOp::OP_SOURCE)); + + dataSurface->Unmap(); + + return EncodeImageData(dataSurface, aMimeType, aOutputOptions, aStream); +} + +NS_IMETHODIMP +imgTools::EncodeCroppedImage(imgIContainer* aContainer, + const nsACString& aMimeType, + int32_t aOffsetX, + int32_t aOffsetY, + int32_t aWidth, + int32_t aHeight, + const nsAString& aOutputOptions, + nsIInputStream** aStream) +{ + NS_ENSURE_ARG(aOffsetX >= 0 && aOffsetY >= 0 && aWidth >= 0 && aHeight >= 0); + + // Offsets must be zero when no width and height are given or else we're out + // of bounds. + NS_ENSURE_ARG(aWidth + aHeight > 0 || aOffsetX + aOffsetY == 0); + + // If no size is specified then we'll preserve the image's original dimensions + // and don't need to crop. + if (aWidth == 0 && aHeight == 0) { + return EncodeImage(aContainer, aMimeType, aOutputOptions, aStream); + } + + // Use frame 0 from the image container. + RefPtr frame = + aContainer->GetFrame(imgIContainer::FRAME_FIRST, + imgIContainer::FLAG_SYNC_DECODE); + NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); + + int32_t frameWidth = frame->GetSize().width; + int32_t frameHeight = frame->GetSize().height; + + // If the given width or height is zero we'll replace it with the image's + // original dimensions. + if (aWidth == 0) { + aWidth = frameWidth; + } else if (aHeight == 0) { + aHeight = frameHeight; + } + + // Check that the given crop rectangle is within image bounds. + NS_ENSURE_ARG(frameWidth >= aOffsetX + aWidth && + frameHeight >= aOffsetY + aHeight); + + RefPtr dataSurface = + Factory::CreateDataSourceSurface(IntSize(aWidth, aHeight), + SurfaceFormat::B8G8R8A8, + /* aZero = */ true); + if (NS_WARN_IF(!dataSurface)) { + return NS_ERROR_FAILURE; + } + + DataSourceSurface::MappedSurface map; + if (!dataSurface->Map(DataSourceSurface::MapType::WRITE, &map)) { + return NS_ERROR_FAILURE; + } + + RefPtr dt = + Factory::CreateDrawTargetForData(BackendType::CAIRO, + map.mData, + dataSurface->GetSize(), + map.mStride, + SurfaceFormat::B8G8R8A8); + if (!dt) { + gfxWarning() << + "imgTools::EncodeCroppedImage failed in CreateDrawTargetForData"; + return NS_ERROR_OUT_OF_MEMORY; + } + dt->CopySurface(frame, + IntRect(aOffsetX, aOffsetY, aWidth, aHeight), + IntPoint(0, 0)); + + dataSurface->Unmap(); + + return EncodeImageData(dataSurface, aMimeType, aOutputOptions, aStream); +} + +NS_IMETHODIMP +imgTools::CreateScriptedObserver(imgIScriptedNotificationObserver* aInner, + imgINotificationObserver** aObserver) +{ + NS_ADDREF(*aObserver = new ScriptedNotificationObserver(aInner)); + return NS_OK; +} + +NS_IMETHODIMP +imgTools::GetImgLoaderForDocument(nsIDOMDocument* aDoc, imgILoader** aLoader) +{ + nsCOMPtr doc = do_QueryInterface(aDoc); + NS_IF_ADDREF(*aLoader = nsContentUtils::GetImgLoaderForDocument(doc)); + return NS_OK; +} + +NS_IMETHODIMP +imgTools::GetImgCacheForDocument(nsIDOMDocument* aDoc, imgICache** aCache) +{ + nsCOMPtr loader; + nsresult rv = GetImgLoaderForDocument(aDoc, getter_AddRefs(loader)); + NS_ENSURE_SUCCESS(rv, rv); + return CallQueryInterface(loader, aCache); +} + +} // namespace image +} // namespace mozilla diff --git a/image/imgTools.h b/image/imgTools.h new file mode 100644 index 000000000..52d9d7443 --- /dev/null +++ b/image/imgTools.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_imgITools_h +#define mozilla_image_imgITools_h + +#include "imgITools.h" + +#define NS_IMGTOOLS_CID \ +{ /* 3d8fa16d-c9e1-4b50-bdef-2c7ae249967a */ \ + 0x3d8fa16d, \ + 0xc9e1, \ + 0x4b50, \ + {0xbd, 0xef, 0x2c, 0x7a, 0xe2, 0x49, 0x96, 0x7a} \ +} + +namespace mozilla { +namespace image { + +class imgTools final : public imgITools +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_IMGITOOLS + + imgTools(); + +private: + virtual ~imgTools(); +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_imgITools_h diff --git a/image/moz.build b/image/moz.build new file mode 100644 index 000000000..7e7e0fe70 --- /dev/null +++ b/image/moz.build @@ -0,0 +1,119 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS += ['build', 'decoders', 'encoders'] +if CONFIG['ENABLE_TESTS']: + DIRS += ['test/gtest'] + +with Files('**'): + BUG_COMPONENT = ('Core', 'ImageLib') + +BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini'] + +MOCHITEST_MANIFESTS += ['test/mochitest/mochitest.ini'] + +MOCHITEST_CHROME_MANIFESTS += ['test/mochitest/chrome.ini'] + +XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini'] + +XPIDL_SOURCES += [ + 'imgICache.idl', + 'imgIContainer.idl', + 'imgIContainerDebug.idl', + 'imgIEncoder.idl', + 'imgILoader.idl', + 'imgINotificationObserver.idl', + 'imgIOnloadBlocker.idl', + 'imgIRequest.idl', + 'imgIScriptedNotificationObserver.idl', + 'imgITools.idl', + 'nsIIconURI.idl', +] + +XPIDL_MODULE = 'imglib2' + +EXPORTS += [ + 'DrawResult.h', + 'ImageCacheKey.h', + 'ImageLogging.h', + 'ImageOps.h', + 'ImageRegion.h', + 'imgLoader.h', + 'imgRequest.h', + 'imgRequestProxy.h', + 'IProgressObserver.h', + 'Orientation.h', + 'SurfaceCacheUtils.h', +] + +UNIFIED_SOURCES += [ + 'AnimationSurfaceProvider.cpp', + 'ClippedImage.cpp', + 'DecodedSurfaceProvider.cpp', + 'DecodePool.cpp', + 'Decoder.cpp', + 'DecoderFactory.cpp', + 'DynamicImage.cpp', + 'FrameAnimator.cpp', + 'FrozenImage.cpp', + 'IDecodingTask.cpp', + 'Image.cpp', + 'ImageCacheKey.cpp', + 'ImageFactory.cpp', + 'ImageOps.cpp', + 'ImageWrapper.cpp', + 'imgFrame.cpp', + 'imgTools.cpp', + 'MultipartImage.cpp', + 'OrientedImage.cpp', + 'ScriptedNotificationObserver.cpp', + 'ShutdownTracker.cpp', + 'SourceBuffer.cpp', + 'SurfaceCache.cpp', + 'SurfaceCacheUtils.cpp', + 'SurfacePipe.cpp', + 'SVGDocumentWrapper.cpp', + 'VectorImage.cpp', +] +if CONFIG['MOZ_ENABLE_SKIA']: + UNIFIED_SOURCES += [ 'Downscaler.cpp'] + +# These files can't be unified because of ImageLogging.h #include order issues. +SOURCES += [ + 'imgLoader.cpp', + 'imgRequest.cpp', + 'imgRequestProxy.cpp', + 'ProgressTracker.cpp', + 'RasterImage.cpp', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + # Because SVGDocumentWrapper.cpp includes "mozilla/dom/SVGSVGElement.h" + '/dom/base', + '/dom/svg', + # Access to Skia headers for Downscaler + '/gfx/2d', + # We need to instantiate the decoders + '/image/decoders', + # Because VectorImage.cpp includes nsSVGUtils.h and nsSVGEffects.h + '/layout/svg', + # For URI-related functionality + '/netwerk/base', + # DecodePool uses thread-related facilities. + '/xpcom/threads', +] + +# Because imgFrame.cpp includes "cairo.h" +CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS'] + +LOCAL_INCLUDES += CONFIG['SKIA_INCLUDES'] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/image/nsIIconURI.idl b/image/nsIIconURI.idl new file mode 100644 index 000000000..d6f393aad --- /dev/null +++ b/image/nsIIconURI.idl @@ -0,0 +1,84 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIURL.idl" + + /** + * nsIIconURI + * + * This interface derives from nsIURI, to provide additional information + * about moz-icon URIs. + * + * What *is* a moz-icon URI you ask? Well, it has the following syntax: + * + * moz-icon:[ | // | //stock/]? + * ['?'[]] + * + * is a valid URL spec. + * + * is any filename with an extension, e.g. "dummy.html". + * If the file you want an icon for isn't known to exist, you can use this + * instead of a URL and just place a dummy file name with the extension or + * content type you want. + * + * is the name of a platform-dependant stock icon. + * + * Legal parameter value pairs are listed below: + * + * Parameter: size + * Values: [ | button | toolbar | toolbarsmall | menu | + * dialog] + * Description: If integer, this is the desired size in square pixels of + * the icon + * Else, use the OS default for the specified keyword context. + * + * Parameter: state + * Values: [normal | disabled] + * Description: The state of the icon. + * + * Parameter: contentType + * Values: + * Description: The mime type we want an icon for. This is ignored by + * stock images. + */ + +[scriptable, uuid(f8fe5ef2-5f2b-43f3-857d-5b64d192c427)] +interface nsIMozIconURI : nsIURI +{ + /// iconFile: the file URL contained within this moz-icon url, or null. + attribute nsIURL iconURL; + + /// imageSize: The image area in square pixels, defaults to 16 if unspecified. + attribute unsigned long imageSize; + + /// stockIcon: The stock icon name requested from the OS. + readonly attribute ACString stockIcon; + + /// iconSize: The stock icon size requested from the OS. + readonly attribute ACString iconSize; + + /// iconState: The stock icon state requested from the OS. + readonly attribute ACString iconState; + + /// contentType: A valid mime type, or the empty string. + attribute ACString contentType; + + /// fileExtension: The file extension of the file which we are looking up. + readonly attribute ACString fileExtension; +}; + +%{C++ + +// CID for nsMozIconURI, if implemented on this platform. +#define NS_MOZICONURI_CID \ +{ \ + 0x43a88e0e, \ + 0x2d37, \ + 0x11d5, \ + { 0x99, 0x7, 0x0, 0x10, 0x83, 0x1, 0xe, 0x9b } \ +} + +%} diff --git a/image/test/browser/animated.gif b/image/test/browser/animated.gif new file mode 100644 index 000000000..eb034e150 Binary files /dev/null and b/image/test/browser/animated.gif differ diff --git a/image/test/browser/animated2.gif b/image/test/browser/animated2.gif new file mode 100644 index 000000000..053eaae68 Binary files /dev/null and b/image/test/browser/animated2.gif differ diff --git a/image/test/browser/big.png b/image/test/browser/big.png new file mode 100644 index 000000000..94e7eb6db Binary files /dev/null and b/image/test/browser/big.png differ diff --git a/image/test/browser/browser.ini b/image/test/browser/browser.ini new file mode 100644 index 000000000..d5e94e575 --- /dev/null +++ b/image/test/browser/browser.ini @@ -0,0 +1,14 @@ +[DEFAULT] +support-files = + animated.gif + animated2.gif + big.png + head.js + image.html + imageX2.html + +[browser_bug666317.js] +skip-if = true || e10s # Bug 1207012 - Permaorange from an uncaught exception that isn't actually turning the suite orange until it hits beta, Bug 948194 - Decoded Images seem to not be discarded on memory-pressure notification with e10s enabled +[browser_image.js] +skip-if = true # Bug 987616 +[browser_docshell_type_editor.js] diff --git a/image/test/browser/browser_bug666317.js b/image/test/browser/browser_bug666317.js new file mode 100644 index 000000000..2bd2d0615 --- /dev/null +++ b/image/test/browser/browser_bug666317.js @@ -0,0 +1,140 @@ +waitForExplicitFinish(); + +var pageSource = + '' + + '' + + ''; + +var oldDiscardingPref, oldTab, newTab; +var prefBranch = Cc["@mozilla.org/preferences-service;1"] + .getService(Ci.nsIPrefService) + .getBranch('image.mem.'); + +var gWaitingForDiscard = false; +var gScriptedObserver; +var gClonedRequest; + +function ImageObserver(decodeCallback, discardCallback) { + this.decodeComplete = function onDecodeComplete(aRequest) { + decodeCallback(); + } + + this.discard = function onDiscard(request) + { + if (!gWaitingForDiscard) { + return; + } + + this.synchronous = false; + discardCallback(); + } + + this.synchronous = true; +} + +function currentRequest() { + let img = gBrowser.getBrowserForTab(newTab).contentWindow + .document.getElementById('testImg'); + img.QueryInterface(Ci.nsIImageLoadingContent); + return img.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST); +} + +function isImgDecoded() { + let request = currentRequest(); + return request.imageStatus & Ci.imgIRequest.STATUS_DECODE_COMPLETE ? true : false; +} + +// Ensure that the image is decoded by drawing it to a canvas. +function forceDecodeImg() { + let doc = gBrowser.getBrowserForTab(newTab).contentWindow.document; + let img = doc.getElementById('testImg'); + let canvas = doc.createElement('canvas'); + let ctx = canvas.getContext('2d'); + ctx.drawImage(img, 0, 0); +} + +function runAfterAsyncEvents(aCallback) { + function handlePostMessage(aEvent) { + if (aEvent.data == 'next') { + window.removeEventListener('message', handlePostMessage, false); + aCallback(); + } + } + + window.addEventListener('message', handlePostMessage, false); + + // We'll receive the 'message' event after everything else that's currently in + // the event queue (which is a stronger guarantee than setTimeout, because + // setTimeout events may be coalesced). This lets us ensure that we run + // aCallback *after* any asynchronous events are delivered. + window.postMessage('next', '*'); +} + +function test() { + // Enable the discarding pref. + oldDiscardingPref = prefBranch.getBoolPref('discardable'); + prefBranch.setBoolPref('discardable', true); + + // Create and focus a new tab. + oldTab = gBrowser.selectedTab; + newTab = gBrowser.addTab('data:text/html,' + pageSource); + gBrowser.selectedTab = newTab; + + // Run step2 after the tab loads. + gBrowser.getBrowserForTab(newTab) + .addEventListener("pageshow", step2); +} + +function step2() { + // Create the image observer. + var observer = + new ImageObserver(() => runAfterAsyncEvents(step3), // DECODE_COMPLETE + () => runAfterAsyncEvents(step5)); // DISCARD + gScriptedObserver = Cc["@mozilla.org/image/tools;1"] + .getService(Ci.imgITools) + .createScriptedObserver(observer); + + // Clone the current imgIRequest with our new observer. + var request = currentRequest(); + gClonedRequest = request.clone(gScriptedObserver); + + // Check that the image is decoded. + forceDecodeImg(); + + // The DECODE_COMPLETE notification is delivered asynchronously. ImageObserver will + // eventually call step3. +} + +function step3() { + ok(isImgDecoded(), 'Image should initially be decoded.'); + + // Focus the old tab, then fire a memory-pressure notification. This should + // cause the decoded image in the new tab to be discarded. + gBrowser.selectedTab = oldTab; + + // Allow time to process the tab change. + runAfterAsyncEvents(step4); +} + +function step4() { + gWaitingForDiscard = true; + + var os = Cc["@mozilla.org/observer-service;1"] + .getService(Ci.nsIObserverService); + os.notifyObservers(null, 'memory-pressure', 'heap-minimize'); + + // The DISCARD notification is delivered asynchronously. ImageObserver will + // eventually call step5. (Or else, sadly, the test will time out.) +} + +function step5() { + ok(true, 'Image should be discarded.'); + + // And we're done. + gBrowser.removeTab(newTab); + prefBranch.setBoolPref('discardable', oldDiscardingPref); + + gClonedRequest.cancelAndForgetObserver(0); + + finish(); +} diff --git a/image/test/browser/browser_docshell_type_editor.js b/image/test/browser/browser_docshell_type_editor.js new file mode 100644 index 000000000..8ac98924f --- /dev/null +++ b/image/test/browser/browser_docshell_type_editor.js @@ -0,0 +1,92 @@ + +"use strict"; + +const Ci = Components.interfaces; +const SIMPLE_HTML = "data:text/html,"; + +// The following URI is *not* accessible to content, hence loading that URI +// from an unprivileged site should be blocked. If docshell is of appType +// APP_TYPE_EDITOR however the load should be allowed. +// >> chrome://devtools/content/framework/dev-edition-promo/dev-edition-logo.png + +add_task(function* () { + info("docshell of appType APP_TYPE_EDITOR can access privileged images."); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: SIMPLE_HTML + }, function* (browser) { + yield ContentTask.spawn(browser, null, function* () { + let rootDocShell = docShell.QueryInterface(Ci.nsIDocShellTreeItem) + .rootTreeItem + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDocShell); + let defaultAppType = rootDocShell.appType; + + rootDocShell.appType = Ci.nsIDocShell.APP_TYPE_EDITOR; + + is(rootDocShell.appType, Ci.nsIDocShell.APP_TYPE_EDITOR, + "sanity check: appType after update should be type editor"); + + return new Promise(resolve => { + let doc = content.document; + let image = doc.createElement("img"); + image.onload = function() { + ok(true, "APP_TYPE_EDITOR is allowed to load privileged image"); + // restore appType of rootDocShell before moving on to the next test + rootDocShell.appType = defaultAppType; + resolve(); + } + image.onerror = function() { + ok(false, "APP_TYPE_EDITOR is allowed to load privileged image"); + // restore appType of rootDocShell before moving on to the next test + rootDocShell.appType = defaultAppType; + resolve(); + } + doc.body.appendChild(image); + image.src = "chrome://devtools/content/framework/dev-edition-promo/dev-edition-logo.png"; + }); + }); + }); +}); + +add_task(function* () { + info("docshell of appType APP_TYPE_UNKNOWN can *not* access privileged images."); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: SIMPLE_HTML + }, function* (browser) { + yield ContentTask.spawn(browser, null, function* () { + let rootDocShell = docShell.QueryInterface(Ci.nsIDocShellTreeItem) + .rootTreeItem + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDocShell); + let defaultAppType = rootDocShell.appType; + + rootDocShell.appType = Ci.nsIDocShell.APP_TYPE_UNKNOWN; + + is(rootDocShell.appType, Ci.nsIDocShell.APP_TYPE_UNKNOWN, + "sanity check: appType of docshell should be unknown"); + + return new Promise(resolve => { + let doc = content.document; + let image = doc.createElement("img"); + image.onload = function() { + ok(false, "APP_TYPE_UNKNOWN is *not* allowed to acces privileged image"); + // restore appType of rootDocShell before moving on to the next test + rootDocShell.appType = defaultAppType; + resolve(); + } + image.onerror = function() { + ok(true, "APP_TYPE_UNKNOWN is *not* allowed to acces privileged image"); + // restore appType of rootDocShell before moving on to the next test + rootDocShell.appType = defaultAppType; + resolve(); + } + doc.body.appendChild(image); + image.src = "chrome://devtools/content/framework/dev-edition-promo/dev-edition-logo.png"; + }); + }); + }); +}); diff --git a/image/test/browser/browser_image.js b/image/test/browser/browser_image.js new file mode 100644 index 000000000..9527726c3 --- /dev/null +++ b/image/test/browser/browser_image.js @@ -0,0 +1,195 @@ +waitForExplicitFinish(); +requestLongerTimeout(2); // see bug 660123 -- this test is slow on Mac. + +// A hold on the current timer, so it doens't get GCed out from +// under us +var gTimer; + +// Browsing to a new URL - pushing us into the bfcache - should cause +// animations to stop, and resume when we return +function testBFCache() { + function theTest() { + var abort = false; + var chances, gImage, gFrames; + gBrowser.selectedTab = gBrowser.addTab(TESTROOT + "image.html"); + gBrowser.selectedBrowser.addEventListener("pageshow", function () { + gBrowser.selectedBrowser.removeEventListener("pageshow", arguments.callee, true); + var window = gBrowser.contentWindow; + // If false, we are in an optimized build, and we abort this and + // all further tests + if (!actOnMozImage(window.document, "img1", function(image) { + gImage = image; + gFrames = gImage.framesNotified; + })) { + gBrowser.removeCurrentTab(); + abort = true; + } + goer.next(); + }, true); + yield; + if (abort) { + finish(); + yield; // optimized build + } + + // Let animation run for a bit + chances = 120; + do { + gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + gTimer.initWithCallback(function() { + if (gImage.framesNotified >= 20) { + goer.send(true); + } else { + chances--; + goer.send(chances == 0); // maybe if we wait a bit, it will happen + } + }, 500, Ci.nsITimer.TYPE_ONE_SHOT); + } while (!(yield)); + is(chances > 0, true, "Must have animated a few frames so far"); + + // Browse elsewhere; push our animating page into the bfcache + gBrowser.loadURI("about:blank"); + + // Wait a bit for page to fully load, then wait a while and + // see that no animation occurs. + gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + gTimer.initWithCallback(function() { + gFrames = gImage.framesNotified; + gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + gTimer.initWithCallback(function() { + // Might have a few stray frames, until other page totally loads + var additionalFrames = gImage.framesNotified - gFrames; + is(additionalFrames == 0, true, "Must have not animated in bfcache! Got " + additionalFrames + " additional frames"); + goer.next(); + }, 4000, Ci.nsITimer.TYPE_ONE_SHOT); // 4 seconds - expect 40 frames + }, 0, Ci.nsITimer.TYPE_ONE_SHOT); // delay of 0 - wait for next event loop + yield; + + // Go back + gBrowser.goBack(); + + chances = 120; + do { + gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + gTimer.initWithCallback(function() { + if (gImage.framesNotified - gFrames >= 20) { + goer.send(true); + } else { + chances--; + goer.send(chances == 0); // maybe if we wait a bit, it will happen + } + }, 500, Ci.nsITimer.TYPE_ONE_SHOT); + } while (!(yield)); + is(chances > 0, true, "Must have animated once out of bfcache!"); + + // Finally, check that the css background image has essentially the same + // # of frames, implying that it animated at the same times as the regular + // image. We can easily retrieve regular images through their HTML image + // elements, which is what we did before. For the background image, we + // create a regular image now, and read the current frame count. + var doc = gBrowser.selectedBrowser.contentWindow.document; + var div = doc.getElementById("background_div"); + div.innerHTML += ''; + actOnMozImage(doc, "img3", function(image) { + is(Math.abs(image.framesNotified - gImage.framesNotified)/gImage.framesNotified < 0.5, true, + "Must have also animated the background image, and essentially the same # of frames. " + + "Regular image got " + gImage.framesNotified + " frames but background image got " + image.framesNotified); + }); + + gBrowser.removeCurrentTab(); + + nextTest(); + } + + var goer = theTest(); + goer.next(); +} + +// Check that imgContainers are shared on the same page and +// between tabs +function testSharedContainers() { + function theTest() { + var gImages = []; + var gFrames; + + gBrowser.selectedTab = gBrowser.addTab(TESTROOT + "image.html"); + gBrowser.selectedBrowser.addEventListener("pageshow", function () { + gBrowser.selectedBrowser.removeEventListener("pageshow", arguments.callee, true); + actOnMozImage(gBrowser.contentDocument, "img1", function(image) { + gImages[0] = image; + gFrames = image.framesNotified; // May in theory have frames from last test + // in this counter - so subtract them out + }); + goer.next(); + }, true); + yield; + + // Load next tab somewhat later + gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + gTimer.initWithCallback(function() { + goer.next(); + }, 1500, Ci.nsITimer.TYPE_ONE_SHOT); + yield; + + gBrowser.selectedTab = gBrowser.addTab(TESTROOT + "imageX2.html"); + gBrowser.selectedBrowser.addEventListener("pageshow", function () { + gBrowser.selectedBrowser.removeEventListener("pageshow", arguments.callee, true); + [1,2].forEach(function(i) { + actOnMozImage(gBrowser.contentDocument, "img"+i, function(image) { + gImages[i] = image; + }); + }); + goer.next(); + }, true); + yield; + + var chances = 120; + do { + gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + gTimer.initWithCallback(function() { + if (gImages[0].framesNotified - gFrames >= 10) { + goer.send(true); + } else { + chances--; + goer.send(chances == 0); // maybe if we wait a bit, it will happen + } + }, 500, Ci.nsITimer.TYPE_ONE_SHOT); + } while (!(yield)); + is(chances > 0, true, "Must have been animating while showing several images"); + + // Check they all have the same frame counts + var theFrames = null; + [0,1,2].forEach(function(i) { + var frames = gImages[i].framesNotified; + if (theFrames == null) { + theFrames = frames; + } else { + is(theFrames, frames, "Sharing the same imgContainer means *exactly* the same frame counts!"); + } + }); + + gBrowser.removeCurrentTab(); + gBrowser.removeCurrentTab(); + + nextTest(); + } + + var goer = theTest(); + goer.next(); +} + +var tests = [testBFCache, testSharedContainers]; + +function nextTest() { + if (tests.length == 0) { + finish(); + return; + } + tests.shift()(); +} + +function test() { + ignoreAllUncaughtExceptions(); + nextTest(); +} + diff --git a/image/test/browser/head.js b/image/test/browser/head.js new file mode 100644 index 000000000..91a5f5793 --- /dev/null +++ b/image/test/browser/head.js @@ -0,0 +1,26 @@ +const RELATIVE_DIR = "image/test/browser/"; +const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR; +const TESTROOT2 = "http://example.org/browser/" + RELATIVE_DIR; + +var chrome_root = getRootDirectory(gTestPath); +const CHROMEROOT = chrome_root; + +function getImageLoading(doc, id) { + var htmlImg = doc.getElementById(id); + return htmlImg.QueryInterface(Ci.nsIImageLoadingContent); +} + +// Tries to get the Moz debug image, imgIContainerDebug. Only works +// in a debug build. If we succeed, we call func(). +function actOnMozImage(doc, id, func) { + var imgContainer = getImageLoading(doc, id).getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST).image; + var mozImage; + try { + mozImage = imgContainer.QueryInterface(Ci.imgIContainerDebug); + } + catch (e) { + return false; + } + func(mozImage); + return true; +} diff --git a/image/test/browser/image.html b/image/test/browser/image.html new file mode 100644 index 000000000..298bf1bdc --- /dev/null +++ b/image/test/browser/image.html @@ -0,0 +1,24 @@ + + + + + Imagelib2 animation tests + + + + +

    Page with image

    + +
    + + + diff --git a/image/test/browser/imageX2.html b/image/test/browser/imageX2.html new file mode 100644 index 000000000..bdacd0888 --- /dev/null +++ b/image/test/browser/imageX2.html @@ -0,0 +1,15 @@ + + + + + Imagelib2 animation tests + + +

    Page with images

    + +
    + + + + diff --git a/image/test/crashtests/1205923-1.html b/image/test/crashtests/1205923-1.html new file mode 100644 index 000000000..456fc51b6 --- /dev/null +++ b/image/test/crashtests/1205923-1.html @@ -0,0 +1,36 @@ + + + + + + diff --git a/image/test/crashtests/1210745-1.gif b/image/test/crashtests/1210745-1.gif new file mode 100644 index 000000000..92bcf7222 Binary files /dev/null and b/image/test/crashtests/1210745-1.gif differ diff --git a/image/test/crashtests/1212954-1.svg b/image/test/crashtests/1212954-1.svg new file mode 100644 index 000000000..83dd7b9c7 --- /dev/null +++ b/image/test/crashtests/1212954-1.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/image/test/crashtests/1235605.gif b/image/test/crashtests/1235605.gif new file mode 100644 index 000000000..e7c3ea0b8 Binary files /dev/null and b/image/test/crashtests/1235605.gif differ diff --git a/image/test/crashtests/1241728-1.html b/image/test/crashtests/1241728-1.html new file mode 100644 index 000000000..126c02e62 --- /dev/null +++ b/image/test/crashtests/1241728-1.html @@ -0,0 +1,17 @@ + + + + + + + + \ No newline at end of file diff --git a/image/test/crashtests/1241729-1.bmp b/image/test/crashtests/1241729-1.bmp new file mode 100644 index 000000000..e6f36d039 Binary files /dev/null and b/image/test/crashtests/1241729-1.bmp differ diff --git a/image/test/crashtests/1241729-1.html b/image/test/crashtests/1241729-1.html new file mode 100644 index 000000000..47f23134b --- /dev/null +++ b/image/test/crashtests/1241729-1.html @@ -0,0 +1,5 @@ + + + + + diff --git a/image/test/crashtests/1242093-1.html b/image/test/crashtests/1242093-1.html new file mode 100644 index 000000000..3eab166ef --- /dev/null +++ b/image/test/crashtests/1242093-1.html @@ -0,0 +1,22 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/image/test/crashtests/1242778-1.png b/image/test/crashtests/1242778-1.png new file mode 100644 index 000000000..4504d54e4 Binary files /dev/null and b/image/test/crashtests/1242778-1.png differ diff --git a/image/test/crashtests/1249576-1.png b/image/test/crashtests/1249576-1.png new file mode 100644 index 000000000..637dafbc2 Binary files /dev/null and b/image/test/crashtests/1249576-1.png differ diff --git a/image/test/crashtests/1251091-1.html b/image/test/crashtests/1251091-1.html new file mode 100644 index 000000000..520a393b4 --- /dev/null +++ b/image/test/crashtests/1251091-1.html @@ -0,0 +1,51 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/image/test/crashtests/1251091-1.png b/image/test/crashtests/1251091-1.png new file mode 100644 index 000000000..078b19a56 Binary files /dev/null and b/image/test/crashtests/1251091-1.png differ diff --git a/image/test/crashtests/1253362-1.html b/image/test/crashtests/1253362-1.html new file mode 100644 index 000000000..fdee850aa --- /dev/null +++ b/image/test/crashtests/1253362-1.html @@ -0,0 +1,11 @@ + + + + + + + +
    + + + diff --git a/image/test/crashtests/256-height.ico b/image/test/crashtests/256-height.ico new file mode 100644 index 000000000..6a3c5c194 Binary files /dev/null and b/image/test/crashtests/256-height.ico differ diff --git a/image/test/crashtests/256-width.ico b/image/test/crashtests/256-width.ico new file mode 100644 index 000000000..a82983ce4 Binary files /dev/null and b/image/test/crashtests/256-width.ico differ diff --git a/image/test/crashtests/463696.bmp b/image/test/crashtests/463696.bmp new file mode 100644 index 000000000..ec80d5412 Binary files /dev/null and b/image/test/crashtests/463696.bmp differ diff --git a/image/test/crashtests/523528-1.gif b/image/test/crashtests/523528-1.gif new file mode 100644 index 000000000..abadca7ad Binary files /dev/null and b/image/test/crashtests/523528-1.gif differ diff --git a/image/test/crashtests/523528-2.gif b/image/test/crashtests/523528-2.gif new file mode 100644 index 000000000..5be3bd46f Binary files /dev/null and b/image/test/crashtests/523528-2.gif differ diff --git a/image/test/crashtests/570451.png b/image/test/crashtests/570451.png new file mode 100644 index 000000000..c49f2d11f Binary files /dev/null and b/image/test/crashtests/570451.png differ diff --git a/image/test/crashtests/681190.html b/image/test/crashtests/681190.html new file mode 100644 index 000000000..b513d5ac6 --- /dev/null +++ b/image/test/crashtests/681190.html @@ -0,0 +1,10 @@ + + + + + + + diff --git a/image/test/crashtests/694165-1.xhtml b/image/test/crashtests/694165-1.xhtml new file mode 100644 index 000000000..1e340a0f2 --- /dev/null +++ b/image/test/crashtests/694165-1.xhtml @@ -0,0 +1,510 @@ + + +]> + + + + diff --git a/image/test/crashtests/732319-1.html b/image/test/crashtests/732319-1.html new file mode 100644 index 000000000..b9d9c6de8 --- /dev/null +++ b/image/test/crashtests/732319-1.html @@ -0,0 +1,2 @@ + + diff --git a/image/test/crashtests/83804-1.gif b/image/test/crashtests/83804-1.gif new file mode 100644 index 000000000..3967c703f Binary files /dev/null and b/image/test/crashtests/83804-1.gif differ diff --git a/image/test/crashtests/844403-1.html b/image/test/crashtests/844403-1.html new file mode 100644 index 000000000..5da6c9021 --- /dev/null +++ b/image/test/crashtests/844403-1.html @@ -0,0 +1,10 @@ + + + diff --git a/image/test/crashtests/856616.gif b/image/test/crashtests/856616.gif new file mode 100644 index 000000000..0fac81101 Binary files /dev/null and b/image/test/crashtests/856616.gif differ diff --git a/image/test/crashtests/89341-1.gif b/image/test/crashtests/89341-1.gif new file mode 100644 index 000000000..14b3892d1 Binary files /dev/null and b/image/test/crashtests/89341-1.gif differ diff --git a/image/test/crashtests/944353.jpg b/image/test/crashtests/944353.jpg new file mode 100644 index 000000000..fd81c5826 Binary files /dev/null and b/image/test/crashtests/944353.jpg differ diff --git a/image/test/crashtests/colormap-range.gif b/image/test/crashtests/colormap-range.gif new file mode 100644 index 000000000..887add653 Binary files /dev/null and b/image/test/crashtests/colormap-range.gif differ diff --git a/image/test/crashtests/crashtests.list b/image/test/crashtests/crashtests.list new file mode 100644 index 000000000..799c37314 --- /dev/null +++ b/image/test/crashtests/crashtests.list @@ -0,0 +1,51 @@ +# Bug 668068 - Maximum (256) width and height icons that we currently interpret as 0-width and 0-height. +load 256-height.ico +load 256-width.ico + +load 83804-1.gif +load 89341-1.gif +load 463696.bmp +load 570451.png +skip-if(Android) load 694165-1.xhtml +load 681190.html +load 732319-1.html +load 844403-1.html +load 856616.gif +load 944353.jpg +load 1205923-1.html +# Ensure we handle detecting that an image is animated, then failing to decode +# it. (See bug 1210745.) +load 1210745-1.gif +load 1212954-1.svg +load 1235605.gif +load 1241728-1.html +load 1241729-1.html +load 1242093-1.html +load 1242778-1.png +load 1249576-1.png +load 1253362-1.html +load colormap-range.gif +HTTP load delayedframe.sjs # A 3-frame animated GIF with an inordinate delay between the second and third frame + +# Animated gifs with a very large canvas, but tiny actual content. +load delaytest.html?523528-1.gif +load delaytest.html?523528-2.gif + +# Bug 1160801 - Ensure that we handle invalid disposal types. +load invalid-disposal-method-1.gif +load invalid-disposal-method-2.gif +load invalid-disposal-method-3.gif + +load invalid-icc-profile.jpg # This would have exposed the leak discovered in bug 642902 + +# Ensure we handle ICO directory entries which specify the wrong size for the contained resource. +load invalid_ico_height.ico +load invalid_ico_width.ico + +# Bug 525326 - Test image sizes of 65535x65535 which is larger than we allow) +load invalid-size.gif +load invalid-size-second-frame.gif + +load multiple-png-hassize.ico # Bug 863958 - This icon's size is such that it leads to multiple writes to the PNG decoder after we've gotten our size. +asserts(0-2) load ownerdiscard.html # Bug 807211 +load truncated-second-frame.png # Bug 863975 diff --git a/image/test/crashtests/delayedframe.sjs b/image/test/crashtests/delayedframe.sjs new file mode 100644 index 000000000..31eb1eff8 --- /dev/null +++ b/image/test/crashtests/delayedframe.sjs @@ -0,0 +1,44 @@ +function getFileStream(filename) +{ + // Get the location of this sjs file, and then use that to figure out where + // to find where our other files are. + var self = Components.classes["@mozilla.org/file/local;1"] + .createInstance(Components.interfaces.nsILocalFile); + self.initWithPath(getState("__LOCATION__")); + var file = self.parent; + file.append(filename); + dump(file.path + "\n"); + + var fileStream = Components.classes['@mozilla.org/network/file-input-stream;1'] + .createInstance(Components.interfaces.nsIFileInputStream); + fileStream.init(file, 1, 0, false); + + return fileStream; +} + +var gTimer; + +function handleRequest(request, response) +{ + response.processAsync(); + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "image/gif", false); + + var firststream = getFileStream("threeframes-start.gif"); + response.bodyOutputStream.writeFrom(firststream, firststream.available()) + firststream.close(); + + gTimer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer); + gTimer.initWithCallback(function() + { + var secondstream = getFileStream("threeframes-end.gif"); + response.bodyOutputStream.writeFrom(secondstream, secondstream.available()) + secondstream.close(); + response.finish(); + + // This time needs to be longer than the animation timer in + // threeframes-start.gif. That's specified as 100ms; just use 5 seconds as + // a reasonable upper bound. Since this is just a crashtest, timeouts + // aren't a big deal. + }, 5 * 1000 /* milliseconds */, Components.interfaces.nsITimer.TYPE_ONE_SHOT); +} diff --git a/image/test/crashtests/delaytest.html b/image/test/crashtests/delaytest.html new file mode 100644 index 000000000..00cd1ebd1 --- /dev/null +++ b/image/test/crashtests/delaytest.html @@ -0,0 +1,44 @@ + + + +Delayed image reftest wrapper + + + + + + diff --git a/image/test/crashtests/discardframe.htm b/image/test/crashtests/discardframe.htm new file mode 100644 index 000000000..5ced0029c --- /dev/null +++ b/image/test/crashtests/discardframe.htm @@ -0,0 +1 @@ + diff --git a/image/test/crashtests/ie.png b/image/test/crashtests/ie.png new file mode 100644 index 000000000..74c4a1a32 Binary files /dev/null and b/image/test/crashtests/ie.png differ diff --git a/image/test/crashtests/invalid-disposal-method-1.gif b/image/test/crashtests/invalid-disposal-method-1.gif new file mode 100644 index 000000000..30c61de18 Binary files /dev/null and b/image/test/crashtests/invalid-disposal-method-1.gif differ diff --git a/image/test/crashtests/invalid-disposal-method-2.gif b/image/test/crashtests/invalid-disposal-method-2.gif new file mode 100644 index 000000000..66158d81a Binary files /dev/null and b/image/test/crashtests/invalid-disposal-method-2.gif differ diff --git a/image/test/crashtests/invalid-disposal-method-3.gif b/image/test/crashtests/invalid-disposal-method-3.gif new file mode 100644 index 000000000..0da072377 Binary files /dev/null and b/image/test/crashtests/invalid-disposal-method-3.gif differ diff --git a/image/test/crashtests/invalid-icc-profile.jpg b/image/test/crashtests/invalid-icc-profile.jpg new file mode 100644 index 000000000..938c7713c Binary files /dev/null and b/image/test/crashtests/invalid-icc-profile.jpg differ diff --git a/image/test/crashtests/invalid-size-second-frame.gif b/image/test/crashtests/invalid-size-second-frame.gif new file mode 100644 index 000000000..22005ae4c Binary files /dev/null and b/image/test/crashtests/invalid-size-second-frame.gif differ diff --git a/image/test/crashtests/invalid-size.gif b/image/test/crashtests/invalid-size.gif new file mode 100644 index 000000000..665ca9b5d Binary files /dev/null and b/image/test/crashtests/invalid-size.gif differ diff --git a/image/test/crashtests/invalid_ico_height.ico b/image/test/crashtests/invalid_ico_height.ico new file mode 100644 index 000000000..50d684227 Binary files /dev/null and b/image/test/crashtests/invalid_ico_height.ico differ diff --git a/image/test/crashtests/invalid_ico_width.ico b/image/test/crashtests/invalid_ico_width.ico new file mode 100644 index 000000000..4ace07c16 Binary files /dev/null and b/image/test/crashtests/invalid_ico_width.ico differ diff --git a/image/test/crashtests/multiple-png-hassize.ico b/image/test/crashtests/multiple-png-hassize.ico new file mode 100644 index 000000000..694422001 Binary files /dev/null and b/image/test/crashtests/multiple-png-hassize.ico differ diff --git a/image/test/crashtests/ownerdiscard.html b/image/test/crashtests/ownerdiscard.html new file mode 100644 index 000000000..8d4a619fc --- /dev/null +++ b/image/test/crashtests/ownerdiscard.html @@ -0,0 +1,49 @@ + + + +
    + + +
    + + + diff --git a/image/test/crashtests/threeframes-end.gif b/image/test/crashtests/threeframes-end.gif new file mode 100644 index 000000000..baf6a418c Binary files /dev/null and b/image/test/crashtests/threeframes-end.gif differ diff --git a/image/test/crashtests/threeframes-start.gif b/image/test/crashtests/threeframes-start.gif new file mode 100644 index 000000000..bc641a316 Binary files /dev/null and b/image/test/crashtests/threeframes-start.gif differ diff --git a/image/test/crashtests/truncated-second-frame.png b/image/test/crashtests/truncated-second-frame.png new file mode 100644 index 000000000..0aef5e44d Binary files /dev/null and b/image/test/crashtests/truncated-second-frame.png differ diff --git a/image/test/crashtests/unsized-svg.svg b/image/test/crashtests/unsized-svg.svg new file mode 100644 index 000000000..714efc7ef --- /dev/null +++ b/image/test/crashtests/unsized-svg.svg @@ -0,0 +1 @@ + diff --git a/image/test/gtest/Common.cpp b/image/test/gtest/Common.cpp new file mode 100644 index 000000000..5a24bbb14 --- /dev/null +++ b/image/test/gtest/Common.cpp @@ -0,0 +1,673 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Common.h" + +#include + +#include "nsDirectoryServiceDefs.h" +#include "nsIDirectoryService.h" +#include "nsIFile.h" +#include "nsIInputStream.h" +#include "nsIProperties.h" +#include "nsNetUtil.h" +#include "mozilla/RefPtr.h" +#include "nsStreamUtils.h" +#include "nsString.h" + +namespace mozilla { +namespace image { + +using namespace gfx; + +using std::abs; +using std::vector; + +/////////////////////////////////////////////////////////////////////////////// +// General Helpers +/////////////////////////////////////////////////////////////////////////////// + +// These macros work like gtest's ASSERT_* macros, except that they can be used +// in functions that return values. +#define ASSERT_TRUE_OR_RETURN(e, rv) \ + EXPECT_TRUE(e); \ + if (!(e)) { \ + return rv; \ + } + +#define ASSERT_EQ_OR_RETURN(a, b, rv) \ + EXPECT_EQ(a, b); \ + if ((a) != (b)) { \ + return rv; \ + } + +#define ASSERT_GE_OR_RETURN(a, b, rv) \ + EXPECT_GE(a, b); \ + if (!((a) >= (b))) { \ + return rv; \ + } + +#define ASSERT_LE_OR_RETURN(a, b, rv) \ + EXPECT_LE(a, b); \ + if (!((a) <= (b))) { \ + return rv; \ + } + +#define ASSERT_LT_OR_RETURN(a, b, rv) \ + EXPECT_LT(a, b); \ + if (!((a) < (b))) { \ + return rv; \ + } + +already_AddRefed +LoadFile(const char* aRelativePath) +{ + nsresult rv; + + nsCOMPtr dirService = + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID); + ASSERT_TRUE_OR_RETURN(dirService != nullptr, nullptr); + + // Retrieve the current working directory. + nsCOMPtr file; + rv = dirService->Get(NS_OS_CURRENT_WORKING_DIR, + NS_GET_IID(nsIFile), getter_AddRefs(file)); + ASSERT_TRUE_OR_RETURN(NS_SUCCEEDED(rv), nullptr); + + // Construct the final path by appending the working path to the current + // working directory. + file->AppendNative(nsDependentCString(aRelativePath)); + + // Construct an input stream for the requested file. + nsCOMPtr inputStream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), file); + ASSERT_TRUE_OR_RETURN(NS_SUCCEEDED(rv), nullptr); + + // Ensure the resulting input stream is buffered. + if (!NS_InputStreamIsBuffered(inputStream)) { + nsCOMPtr bufStream; + rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream), + inputStream, 1024); + ASSERT_TRUE_OR_RETURN(NS_SUCCEEDED(rv), nullptr); + inputStream = bufStream; + } + + return inputStream.forget(); +} + +bool +IsSolidColor(SourceSurface* aSurface, + BGRAColor aColor, + uint8_t aFuzz /* = 0 */) +{ + IntSize size = aSurface->GetSize(); + return RectIsSolidColor(aSurface, IntRect(0, 0, size.width, size.height), + aColor, aFuzz); +} + +bool +IsSolidPalettedColor(Decoder* aDecoder, uint8_t aColor) +{ + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + return PalettedRectIsSolidColor(aDecoder, currentFrame->GetRect(), aColor); +} + +bool +RowsAreSolidColor(SourceSurface* aSurface, + int32_t aStartRow, + int32_t aRowCount, + BGRAColor aColor, + uint8_t aFuzz /* = 0 */) +{ + IntSize size = aSurface->GetSize(); + return RectIsSolidColor(aSurface, IntRect(0, aStartRow, size.width, aRowCount), + aColor, aFuzz); +} + +bool +PalettedRowsAreSolidColor(Decoder* aDecoder, + int32_t aStartRow, + int32_t aRowCount, + uint8_t aColor) +{ + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + IntRect frameRect = currentFrame->GetRect(); + IntRect solidColorRect(frameRect.x, aStartRow, frameRect.width, aRowCount); + return PalettedRectIsSolidColor(aDecoder, solidColorRect, aColor); +} + +bool +RectIsSolidColor(SourceSurface* aSurface, + const IntRect& aRect, + BGRAColor aColor, + uint8_t aFuzz /* = 0 */) +{ + IntSize surfaceSize = aSurface->GetSize(); + IntRect rect = + aRect.Intersect(IntRect(0, 0, surfaceSize.width, surfaceSize.height)); + + RefPtr dataSurface = aSurface->GetDataSurface(); + ASSERT_TRUE_OR_RETURN(dataSurface != nullptr, false); + + ASSERT_EQ_OR_RETURN(dataSurface->Stride(), surfaceSize.width * 4, false); + + DataSourceSurface::ScopedMap mapping(dataSurface, + DataSourceSurface::MapType::READ); + ASSERT_TRUE_OR_RETURN(mapping.IsMapped(), false); + + uint8_t* data = dataSurface->GetData(); + ASSERT_TRUE_OR_RETURN(data != nullptr, false); + + int32_t rowLength = dataSurface->Stride(); + for (int32_t row = rect.y; row < rect.YMost(); ++row) { + for (int32_t col = rect.x; col < rect.XMost(); ++col) { + int32_t i = row * rowLength + col * 4; + if (aFuzz != 0) { + ASSERT_LE_OR_RETURN(abs(aColor.mBlue - data[i + 0]), aFuzz, false); + ASSERT_LE_OR_RETURN(abs(aColor.mGreen - data[i + 1]), aFuzz, false); + ASSERT_LE_OR_RETURN(abs(aColor.mRed - data[i + 2]), aFuzz, false); + ASSERT_LE_OR_RETURN(abs(aColor.mAlpha - data[i + 3]), aFuzz, false); + } else { + ASSERT_EQ_OR_RETURN(aColor.mBlue, data[i + 0], false); + ASSERT_EQ_OR_RETURN(aColor.mGreen, data[i + 1], false); + ASSERT_EQ_OR_RETURN(aColor.mRed, data[i + 2], false); + ASSERT_EQ_OR_RETURN(aColor.mAlpha, data[i + 3], false); + } + } + } + + return true; +} + +bool +PalettedRectIsSolidColor(Decoder* aDecoder, const IntRect& aRect, uint8_t aColor) +{ + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + uint8_t* imageData; + uint32_t imageLength; + currentFrame->GetImageData(&imageData, &imageLength); + ASSERT_TRUE_OR_RETURN(imageData, false); + + // Clamp to the frame rect. If any pixels outside the frame rect are included, + // we immediately fail, because such pixels don't have any "color" in the + // sense this function measures - they're transparent, and that doesn't + // necessarily correspond to any color palette index at all. + IntRect frameRect = currentFrame->GetRect(); + ASSERT_EQ_OR_RETURN(imageLength, uint32_t(frameRect.Area()), false); + IntRect rect = aRect.Intersect(frameRect); + ASSERT_EQ_OR_RETURN(rect.Area(), aRect.Area(), false); + + // Translate |rect| by |frameRect.TopLeft()| to reflect the fact that the + // frame rect's offset doesn't actually mean anything in terms of the + // in-memory representation of the surface. The image data starts at the upper + // left corner of the frame rect, in other words. + rect -= frameRect.TopLeft(); + + // Walk through the image data and make sure that the entire rect has the + // palette index |aColor|. + int32_t rowLength = frameRect.width; + for (int32_t row = rect.y; row < rect.YMost(); ++row) { + for (int32_t col = rect.x; col < rect.XMost(); ++col) { + int32_t i = row * rowLength + col; + ASSERT_EQ_OR_RETURN(aColor, imageData[i], false); + } + } + + return true; +} + +bool +RowHasPixels(SourceSurface* aSurface, + int32_t aRow, + const vector& aPixels) +{ + ASSERT_GE_OR_RETURN(aRow, 0, false); + + IntSize surfaceSize = aSurface->GetSize(); + ASSERT_EQ_OR_RETURN(aPixels.size(), size_t(surfaceSize.width), false); + ASSERT_LT_OR_RETURN(aRow, surfaceSize.height, false); + + RefPtr dataSurface = aSurface->GetDataSurface(); + ASSERT_TRUE_OR_RETURN(dataSurface, false); + + ASSERT_EQ_OR_RETURN(dataSurface->Stride(), surfaceSize.width * 4, false); + + DataSourceSurface::ScopedMap mapping(dataSurface, + DataSourceSurface::MapType::READ); + ASSERT_TRUE_OR_RETURN(mapping.IsMapped(), false); + + uint8_t* data = dataSurface->GetData(); + ASSERT_TRUE_OR_RETURN(data != nullptr, false); + + int32_t rowLength = dataSurface->Stride(); + for (int32_t col = 0; col < surfaceSize.width; ++col) { + int32_t i = aRow * rowLength + col * 4; + ASSERT_EQ_OR_RETURN(aPixels[col].mBlue, data[i + 0], false); + ASSERT_EQ_OR_RETURN(aPixels[col].mGreen, data[i + 1], false); + ASSERT_EQ_OR_RETURN(aPixels[col].mRed, data[i + 2], false); + ASSERT_EQ_OR_RETURN(aPixels[col].mAlpha, data[i + 3], false); + } + + return true; +} + + +/////////////////////////////////////////////////////////////////////////////// +// SurfacePipe Helpers +/////////////////////////////////////////////////////////////////////////////// + +already_AddRefed +CreateTrivialDecoder() +{ + gfxPrefs::GetSingleton(); + DecoderType decoderType = DecoderFactory::GetDecoderType("image/gif"); + NotNull> sourceBuffer = WrapNotNull(new SourceBuffer()); + RefPtr decoder = + DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, Nothing(), + DefaultSurfaceFlags()); + return decoder.forget(); +} + +void +AssertCorrectPipelineFinalState(SurfaceFilter* aFilter, + const gfx::IntRect& aInputSpaceRect, + const gfx::IntRect& aOutputSpaceRect) +{ + EXPECT_TRUE(aFilter->IsSurfaceFinished()); + Maybe invalidRect = aFilter->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isSome()); + EXPECT_EQ(aInputSpaceRect, invalidRect->mInputSpaceRect); + EXPECT_EQ(aOutputSpaceRect, invalidRect->mOutputSpaceRect); +} + +void +CheckGeneratedImage(Decoder* aDecoder, + const IntRect& aRect, + uint8_t aFuzz /* = 0 */) +{ + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSourceSurface(); + const IntSize surfaceSize = surface->GetSize(); + + // This diagram shows how the surface is divided into regions that the code + // below tests for the correct content. The output rect is the bounds of the + // region labeled 'C'. + // + // +---------------------------+ + // | A | + // +---------+--------+--------+ + // | B | C | D | + // +---------+--------+--------+ + // | E | + // +---------------------------+ + + // Check that the output rect itself is green. (Region 'C'.) + EXPECT_TRUE(RectIsSolidColor(surface, aRect, BGRAColor::Green(), aFuzz)); + + // Check that the area above the output rect is transparent. (Region 'A'.) + EXPECT_TRUE(RectIsSolidColor(surface, + IntRect(0, 0, surfaceSize.width, aRect.y), + BGRAColor::Transparent(), aFuzz)); + + // Check that the area to the left of the output rect is transparent. (Region 'B'.) + EXPECT_TRUE(RectIsSolidColor(surface, + IntRect(0, aRect.y, aRect.x, aRect.YMost()), + BGRAColor::Transparent(), aFuzz)); + + // Check that the area to the right of the output rect is transparent. (Region 'D'.) + const int32_t widthOnRight = surfaceSize.width - aRect.XMost(); + EXPECT_TRUE(RectIsSolidColor(surface, + IntRect(aRect.XMost(), aRect.y, widthOnRight, aRect.YMost()), + BGRAColor::Transparent(), aFuzz)); + + // Check that the area below the output rect is transparent. (Region 'E'.) + const int32_t heightBelow = surfaceSize.height - aRect.YMost(); + EXPECT_TRUE(RectIsSolidColor(surface, + IntRect(0, aRect.YMost(), surfaceSize.width, heightBelow), + BGRAColor::Transparent(), aFuzz)); +} + +void +CheckGeneratedPalettedImage(Decoder* aDecoder, const IntRect& aRect) +{ + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + IntSize imageSize = currentFrame->GetImageSize(); + + // This diagram shows how the surface is divided into regions that the code + // below tests for the correct content. The output rect is the bounds of the + // region labeled 'C'. + // + // +---------------------------+ + // | A | + // +---------+--------+--------+ + // | B | C | D | + // +---------+--------+--------+ + // | E | + // +---------------------------+ + + // Check that the output rect itself is all 255's. (Region 'C'.) + EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder, aRect, 255)); + + // Check that the area above the output rect is all 0's. (Region 'A'.) + EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder, + IntRect(0, 0, imageSize.width, aRect.y), + 0)); + + // Check that the area to the left of the output rect is all 0's. (Region 'B'.) + EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder, + IntRect(0, aRect.y, aRect.x, aRect.YMost()), + 0)); + + // Check that the area to the right of the output rect is all 0's. (Region 'D'.) + const int32_t widthOnRight = imageSize.width - aRect.XMost(); + EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder, + IntRect(aRect.XMost(), aRect.y, widthOnRight, aRect.YMost()), + 0)); + + // Check that the area below the output rect is transparent. (Region 'E'.) + const int32_t heightBelow = imageSize.height - aRect.YMost(); + EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder, + IntRect(0, aRect.YMost(), imageSize.width, heightBelow), + 0)); +} + +void +CheckWritePixels(Decoder* aDecoder, + SurfaceFilter* aFilter, + Maybe aOutputRect /* = Nothing() */, + Maybe aInputRect /* = Nothing() */, + Maybe aInputWriteRect /* = Nothing() */, + Maybe aOutputWriteRect /* = Nothing() */, + uint8_t aFuzz /* = 0 */) +{ + IntRect outputRect = aOutputRect.valueOr(IntRect(0, 0, 100, 100)); + IntRect inputRect = aInputRect.valueOr(IntRect(0, 0, 100, 100)); + IntRect inputWriteRect = aInputWriteRect.valueOr(inputRect); + IntRect outputWriteRect = aOutputWriteRect.valueOr(outputRect); + + // Fill the image. + int32_t count = 0; + auto result = aFilter->WritePixels([&] { + ++count; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(inputWriteRect.width * inputWriteRect.height, count); + + AssertCorrectPipelineFinalState(aFilter, inputRect, outputRect); + + // Attempt to write more data and make sure nothing changes. + const int32_t oldCount = count; + result = aFilter->WritePixels([&] { + ++count; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + EXPECT_EQ(oldCount, count); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_TRUE(aFilter->IsSurfaceFinished()); + Maybe invalidRect = aFilter->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + // Attempt to advance to the next row and make sure nothing changes. + aFilter->AdvanceRow(); + EXPECT_TRUE(aFilter->IsSurfaceFinished()); + invalidRect = aFilter->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + // Check that the generated image is correct. + CheckGeneratedImage(aDecoder, outputWriteRect, aFuzz); +} + +void +CheckPalettedWritePixels(Decoder* aDecoder, + SurfaceFilter* aFilter, + Maybe aOutputRect /* = Nothing() */, + Maybe aInputRect /* = Nothing() */, + Maybe aInputWriteRect /* = Nothing() */, + Maybe aOutputWriteRect /* = Nothing() */, + uint8_t aFuzz /* = 0 */) +{ + IntRect outputRect = aOutputRect.valueOr(IntRect(0, 0, 100, 100)); + IntRect inputRect = aInputRect.valueOr(IntRect(0, 0, 100, 100)); + IntRect inputWriteRect = aInputWriteRect.valueOr(inputRect); + IntRect outputWriteRect = aOutputWriteRect.valueOr(outputRect); + + // Fill the image. + int32_t count = 0; + auto result = aFilter->WritePixels([&] { + ++count; + return AsVariant(uint8_t(255)); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(inputWriteRect.width * inputWriteRect.height, count); + + AssertCorrectPipelineFinalState(aFilter, inputRect, outputRect); + + // Attempt to write more data and make sure nothing changes. + const int32_t oldCount = count; + result = aFilter->WritePixels([&] { + ++count; + return AsVariant(uint8_t(255)); + }); + EXPECT_EQ(oldCount, count); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_TRUE(aFilter->IsSurfaceFinished()); + Maybe invalidRect = aFilter->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + // Attempt to advance to the next row and make sure nothing changes. + aFilter->AdvanceRow(); + EXPECT_TRUE(aFilter->IsSurfaceFinished()); + invalidRect = aFilter->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + // Check that the generated image is correct. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + uint8_t* imageData; + uint32_t imageLength; + currentFrame->GetImageData(&imageData, &imageLength); + ASSERT_TRUE(imageData != nullptr); + ASSERT_EQ(outputWriteRect.width * outputWriteRect.height, int32_t(imageLength)); + for (uint32_t i = 0; i < imageLength; ++i) { + ASSERT_EQ(uint8_t(255), imageData[i]); + } +} + + +/////////////////////////////////////////////////////////////////////////////// +// Test Data +/////////////////////////////////////////////////////////////////////////////// + +ImageTestCase GreenPNGTestCase() +{ + return ImageTestCase("green.png", "image/png", IntSize(100, 100)); +} + +ImageTestCase GreenGIFTestCase() +{ + return ImageTestCase("green.gif", "image/gif", IntSize(100, 100)); +} + +ImageTestCase GreenJPGTestCase() +{ + return ImageTestCase("green.jpg", "image/jpeg", IntSize(100, 100), + TEST_CASE_IS_FUZZY); +} + +ImageTestCase GreenBMPTestCase() +{ + return ImageTestCase("green.bmp", "image/bmp", IntSize(100, 100)); +} + +ImageTestCase GreenICOTestCase() +{ + // This ICO contains a 32-bit BMP, and we use a BMP's alpha data by default + // when the BMP is embedded in an ICO, so it's transparent. + return ImageTestCase("green.ico", "image/x-icon", IntSize(100, 100), + TEST_CASE_IS_TRANSPARENT); +} + +ImageTestCase GreenIconTestCase() +{ + return ImageTestCase("green.icon", "image/icon", IntSize(100, 100), + TEST_CASE_IS_TRANSPARENT); +} + +ImageTestCase GreenFirstFrameAnimatedGIFTestCase() +{ + return ImageTestCase("first-frame-green.gif", "image/gif", IntSize(100, 100), + TEST_CASE_IS_ANIMATED); +} + +ImageTestCase GreenFirstFrameAnimatedPNGTestCase() +{ + return ImageTestCase("first-frame-green.png", "image/png", IntSize(100, 100), + TEST_CASE_IS_TRANSPARENT | TEST_CASE_IS_ANIMATED); +} + +ImageTestCase CorruptTestCase() +{ + return ImageTestCase("corrupt.jpg", "image/jpeg", IntSize(100, 100), + TEST_CASE_HAS_ERROR); +} + +ImageTestCase CorruptBMPWithTruncatedHeader() +{ + // This BMP has a header which is truncated right between the BIH and the + // bitfields, which is a particularly error-prone place w.r.t. the BMP decoder + // state machine. + return ImageTestCase("invalid-truncated-metadata.bmp", "image/bmp", + IntSize(100, 100), TEST_CASE_HAS_ERROR); +} + +ImageTestCase CorruptICOWithBadBMPWidthTestCase() +{ + // This ICO contains a BMP icon which has a width that doesn't match the size + // listed in the corresponding ICO directory entry. + return ImageTestCase("corrupt-with-bad-bmp-width.ico", "image/x-icon", + IntSize(100, 100), TEST_CASE_HAS_ERROR); +} + +ImageTestCase CorruptICOWithBadBMPHeightTestCase() +{ + // This ICO contains a BMP icon which has a height that doesn't match the size + // listed in the corresponding ICO directory entry. + return ImageTestCase("corrupt-with-bad-bmp-height.ico", "image/x-icon", + IntSize(100, 100), TEST_CASE_HAS_ERROR); +} + +ImageTestCase TransparentPNGTestCase() +{ + return ImageTestCase("transparent.png", "image/png", IntSize(32, 32), + TEST_CASE_IS_TRANSPARENT); +} + +ImageTestCase TransparentGIFTestCase() +{ + return ImageTestCase("transparent.gif", "image/gif", IntSize(16, 16), + TEST_CASE_IS_TRANSPARENT); +} + +ImageTestCase FirstFramePaddingGIFTestCase() +{ + return ImageTestCase("transparent.gif", "image/gif", IntSize(16, 16), + TEST_CASE_IS_TRANSPARENT); +} + +ImageTestCase TransparentIfWithinICOBMPTestCase(TestCaseFlags aFlags) +{ + // This is a BMP that is only transparent when decoded as if it is within an + // ICO file. (Note: aFlags needs to be set to TEST_CASE_DEFAULT_FLAGS or + // TEST_CASE_IS_TRANSPARENT accordingly.) + return ImageTestCase("transparent-if-within-ico.bmp", "image/bmp", + IntSize(32, 32), aFlags); +} + +ImageTestCase RLE4BMPTestCase() +{ + return ImageTestCase("rle4.bmp", "image/bmp", IntSize(320, 240), + TEST_CASE_IS_TRANSPARENT); +} + +ImageTestCase RLE8BMPTestCase() +{ + return ImageTestCase("rle8.bmp", "image/bmp", IntSize(32, 32), + TEST_CASE_IS_TRANSPARENT); +} + +ImageTestCase NoFrameDelayGIFTestCase() +{ + // This is an invalid (or at least, questionably valid) GIF that's animated + // even though it specifies a frame delay of zero. It's animated, but it's not + // marked TEST_CASE_IS_ANIMATED because the metadata decoder can't detect that + // it's animated. + return ImageTestCase("no-frame-delay.gif", "image/gif", IntSize(100, 100)); +} + +ImageTestCase ExtraImageSubBlocksAnimatedGIFTestCase() +{ + // This is a corrupt GIF that has extra image sub blocks between the first and + // second frame. + return ImageTestCase("animated-with-extra-image-sub-blocks.gif", "image/gif", + IntSize(100, 100)); +} + +ImageTestCase DownscaledPNGTestCase() +{ + // This testcase (and all the other "downscaled") testcases) consists of 25 + // lines of green, followed by 25 lines of red, followed by 25 lines of green, + // followed by 25 more lines of red. It's intended that tests downscale it + // from 100x100 to 20x20, so we specify a 20x20 output size. + return ImageTestCase("downscaled.png", "image/png", IntSize(100, 100), + IntSize(20, 20)); +} + +ImageTestCase DownscaledGIFTestCase() +{ + return ImageTestCase("downscaled.gif", "image/gif", IntSize(100, 100), + IntSize(20, 20)); +} + +ImageTestCase DownscaledJPGTestCase() +{ + return ImageTestCase("downscaled.jpg", "image/jpeg", IntSize(100, 100), + IntSize(20, 20)); +} + +ImageTestCase DownscaledBMPTestCase() +{ + return ImageTestCase("downscaled.bmp", "image/bmp", IntSize(100, 100), + IntSize(20, 20)); +} + +ImageTestCase DownscaledICOTestCase() +{ + return ImageTestCase("downscaled.ico", "image/x-icon", IntSize(100, 100), + IntSize(20, 20), TEST_CASE_IS_TRANSPARENT); +} + +ImageTestCase DownscaledIconTestCase() +{ + return ImageTestCase("downscaled.icon", "image/icon", IntSize(100, 100), + IntSize(20, 20), TEST_CASE_IS_TRANSPARENT); +} + +ImageTestCase DownscaledTransparentICOWithANDMaskTestCase() +{ + // This test case is an ICO with AND mask transparency. We want to ensure that + // we can downscale it without crashing or triggering ASAN failures, but its + // content isn't simple to verify, so for now we don't check the output. + return ImageTestCase("transparent-ico-with-and-mask.ico", "image/x-icon", + IntSize(32, 32), IntSize(20, 20), + TEST_CASE_IS_TRANSPARENT | TEST_CASE_IGNORE_OUTPUT); +} + +ImageTestCase TruncatedSmallGIFTestCase() +{ + return ImageTestCase("green-1x1-truncated.gif", "image/gif", IntSize(1, 1)); +} + +} // namespace image +} // namespace mozilla diff --git a/image/test/gtest/Common.h b/image/test/gtest/Common.h new file mode 100644 index 000000000..79bed9fc1 --- /dev/null +++ b/image/test/gtest/Common.h @@ -0,0 +1,419 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_test_gtest_Common_h +#define mozilla_image_test_gtest_Common_h + +#include + +#include "gtest/gtest.h" + +#include "mozilla/Maybe.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/gfx/2D.h" +#include "Decoder.h" +#include "gfxColor.h" +#include "imgITools.h" +#include "nsCOMPtr.h" +#include "SurfacePipe.h" +#include "SurfacePipeFactory.h" + +class nsIInputStream; + +namespace mozilla { +namespace image { + +/////////////////////////////////////////////////////////////////////////////// +// Types +/////////////////////////////////////////////////////////////////////////////// + +enum TestCaseFlags +{ + TEST_CASE_DEFAULT_FLAGS = 0, + TEST_CASE_IS_FUZZY = 1 << 0, + TEST_CASE_HAS_ERROR = 1 << 1, + TEST_CASE_IS_TRANSPARENT = 1 << 2, + TEST_CASE_IS_ANIMATED = 1 << 3, + TEST_CASE_IGNORE_OUTPUT = 1 << 4, +}; + +struct ImageTestCase +{ + ImageTestCase(const char* aPath, + const char* aMimeType, + gfx::IntSize aSize, + uint32_t aFlags = TEST_CASE_DEFAULT_FLAGS) + : mPath(aPath) + , mMimeType(aMimeType) + , mSize(aSize) + , mOutputSize(aSize) + , mFlags(aFlags) + { } + + ImageTestCase(const char* aPath, + const char* aMimeType, + gfx::IntSize aSize, + gfx::IntSize aOutputSize, + uint32_t aFlags = TEST_CASE_DEFAULT_FLAGS) + : mPath(aPath) + , mMimeType(aMimeType) + , mSize(aSize) + , mOutputSize(aOutputSize) + , mFlags(aFlags) + { } + + const char* mPath; + const char* mMimeType; + gfx::IntSize mSize; + gfx::IntSize mOutputSize; + uint32_t mFlags; +}; + +struct BGRAColor +{ + BGRAColor() : BGRAColor(0, 0, 0, 0) { } + + BGRAColor(uint8_t aBlue, uint8_t aGreen, uint8_t aRed, uint8_t aAlpha) + : mBlue(aBlue) + , mGreen(aGreen) + , mRed(aRed) + , mAlpha(aAlpha) + { } + + static BGRAColor Green() { return BGRAColor(0x00, 0xFF, 0x00, 0xFF); } + static BGRAColor Red() { return BGRAColor(0x00, 0x00, 0xFF, 0xFF); } + static BGRAColor Blue() { return BGRAColor(0xFF, 0x00, 0x00, 0xFF); } + static BGRAColor Transparent() { return BGRAColor(0x00, 0x00, 0x00, 0x00); } + + uint32_t AsPixel() const { return gfxPackedPixel(mAlpha, mRed, mGreen, mBlue); } + + uint8_t mBlue; + uint8_t mGreen; + uint8_t mRed; + uint8_t mAlpha; +}; + + +/////////////////////////////////////////////////////////////////////////////// +// General Helpers +/////////////////////////////////////////////////////////////////////////////// + +/** + * A RAII class that ensure that ImageLib services are available. Any tests that + * require ImageLib to be initialized (for example, any test that uses the + * SurfaceCache; see image::EnsureModuleInitialized() for the full list) can + * use this class to ensure that ImageLib services are available. Failure to do + * so can result in strange, non-deterministic failures. + */ +struct AutoInitializeImageLib +{ + AutoInitializeImageLib() + { + // Ensure that ImageLib services are initialized. + nsCOMPtr imgTools = do_CreateInstance("@mozilla.org/image/tools;1"); + EXPECT_TRUE(imgTools != nullptr); + } +}; + +/// Loads a file from the current directory. @return an nsIInputStream for it. +already_AddRefed LoadFile(const char* aRelativePath); + +/** + * @returns true if every pixel of @aSurface is @aColor. + * + * If @aFuzz is nonzero, a tolerance of @aFuzz is allowed in each color + * component. This may be necessary for tests that involve JPEG images or + * downscaling. + */ +bool IsSolidColor(gfx::SourceSurface* aSurface, + BGRAColor aColor, + uint8_t aFuzz = 0); + +/** + * @returns true if every pixel of @aDecoder's surface has the palette index + * specified by @aColor. + */ +bool IsSolidPalettedColor(Decoder* aDecoder, uint8_t aColor); + +/** + * @returns true if every pixel in the range of rows specified by @aStartRow and + * @aRowCount of @aSurface is @aColor. + * + * If @aFuzz is nonzero, a tolerance of @aFuzz is allowed in each color + * component. This may be necessary for tests that involve JPEG images or + * downscaling. + */ +bool RowsAreSolidColor(gfx::SourceSurface* aSurface, + int32_t aStartRow, + int32_t aRowCount, + BGRAColor aColor, + uint8_t aFuzz = 0); + +/** + * @returns true if every pixel in the range of rows specified by @aStartRow and + * @aRowCount of @aDecoder's surface has the palette index specified by @aColor. + */ +bool PalettedRowsAreSolidColor(Decoder* aDecoder, + int32_t aStartRow, + int32_t aRowCount, + uint8_t aColor); + +/** + * @returns true if every pixel in the rect specified by @aRect is @aColor. + * + * If @aFuzz is nonzero, a tolerance of @aFuzz is allowed in each color + * component. This may be necessary for tests that involve JPEG images or + * downscaling. + */ +bool RectIsSolidColor(gfx::SourceSurface* aSurface, + const gfx::IntRect& aRect, + BGRAColor aColor, + uint8_t aFuzz = 0); + +/** + * @returns true if every pixel in the rect specified by @aRect has the palette + * index specified by @aColor. + */ +bool PalettedRectIsSolidColor(Decoder* aDecoder, + const gfx::IntRect& aRect, + uint8_t aColor); + +/** + * @returns true if the pixels in @aRow of @aSurface match the pixels given in + * @aPixels. + */ +bool RowHasPixels(gfx::SourceSurface* aSurface, + int32_t aRow, + const std::vector& aPixels); + +// ExpectNoResume is an IResumable implementation for use by tests that expect +// Resume() to never get called. +class ExpectNoResume final : public IResumable +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ExpectNoResume, override) + + void Resume() override { FAIL() << "Resume() should not get called"; } + +private: + ~ExpectNoResume() override { } +}; + +// CountResumes is an IResumable implementation for use by tests that expect +// Resume() to get called a certain number of times. +class CountResumes : public IResumable +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CountResumes, override) + + CountResumes() : mCount(0) { } + + void Resume() override { mCount++; } + uint32_t Count() const { return mCount; } + +private: + ~CountResumes() override { } + + uint32_t mCount; +}; + + +/////////////////////////////////////////////////////////////////////////////// +// SurfacePipe Helpers +/////////////////////////////////////////////////////////////////////////////// + +/** + * Creates a decoder with no data associated with, suitable for testing code + * that requires a decoder to initialize or to allocate surfaces but doesn't + * actually need the decoder to do any decoding. + * + * XXX(seth): We only need this because SurfaceSink and PalettedSurfaceSink + * defer to the decoder for surface allocation. Once all decoders use + * SurfacePipe we won't need to do that anymore and we can remove this function. + */ +already_AddRefed CreateTrivialDecoder(); + +/** + * Creates a pipeline of SurfaceFilters from a list of Config structs and passes + * it to the provided lambda @aFunc. Assertions that the pipeline is constructly + * correctly and cleanup of any allocated surfaces is handled automatically. + * + * @param aDecoder The decoder to use for allocating surfaces. + * @param aFunc The lambda function to pass the filter pipeline to. + * @param aConfigs The configuration for the pipeline. + */ +template +void WithFilterPipeline(Decoder* aDecoder, Func aFunc, Configs... aConfigs) +{ + auto pipe = MakeUnique::Type>(); + nsresult rv = pipe->Configure(aConfigs...); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + aFunc(aDecoder, pipe.get()); + + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + if (currentFrame) { + currentFrame->Finish(); + } +} + +/** + * Creates a pipeline of SurfaceFilters from a list of Config structs and + * asserts that configuring it fails. Cleanup of any allocated surfaces is + * handled automatically. + * + * @param aDecoder The decoder to use for allocating surfaces. + * @param aConfigs The configuration for the pipeline. + */ +template +void AssertConfiguringPipelineFails(Decoder* aDecoder, Configs... aConfigs) +{ + auto pipe = MakeUnique::Type>(); + nsresult rv = pipe->Configure(aConfigs...); + + // Callers expect configuring the pipeline to fail. + ASSERT_TRUE(NS_FAILED(rv)); + + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + if (currentFrame) { + currentFrame->Finish(); + } +} + +/** + * Asserts that the provided filter pipeline is in the correct final state, + * which is to say, the entire surface has been written to (IsSurfaceFinished() + * returns true) and the invalid rects are as expected. + * + * @param aFilter The filter pipeline to check. + * @param aInputSpaceRect The expect invalid rect, in input space. + * @param aoutputSpaceRect The expect invalid rect, in output space. + */ +void AssertCorrectPipelineFinalState(SurfaceFilter* aFilter, + const gfx::IntRect& aInputSpaceRect, + const gfx::IntRect& aOutputSpaceRect); + +/** + * Checks a generated image for correctness. Reports any unexpected deviation + * from the expected image as GTest failures. + * + * @param aDecoder The decoder which contains the image. The decoder's current + * frame will be checked. + * @param aRect The region in the space of the output surface that the filter + * pipeline will actually write to. It's expected that pixels in + * this region are green, while pixels outside this region are + * transparent. + * @param aFuzz The amount of fuzz to use in pixel comparisons. + */ +void CheckGeneratedImage(Decoder* aDecoder, + const gfx::IntRect& aRect, + uint8_t aFuzz = 0); + +/** + * Checks a generated paletted image for correctness. Reports any unexpected + * deviation from the expected image as GTest failures. + * + * @param aDecoder The decoder which contains the image. The decoder's current + * frame will be checked. + * @param aRect The region in the space of the output surface that the filter + * pipeline will actually write to. It's expected that pixels in + * this region have a palette index of 255, while pixels outside + * this region have a palette index of 0. + */ +void CheckGeneratedPalettedImage(Decoder* aDecoder, const gfx::IntRect& aRect); + +/** + * Tests the result of calling WritePixels() using the provided SurfaceFilter + * pipeline. The pipeline must be a normal (i.e., non-paletted) pipeline. + * + * The arguments are specified in the an order intended to minimize the number + * of arguments that most test cases need to pass. + * + * @param aDecoder The decoder whose current frame will be written to. + * @param aFilter The SurfaceFilter pipeline to use. + * @param aOutputRect The region in the space of the output surface that will be + * invalidated by the filter pipeline. Defaults to + * (0, 0, 100, 100). + * @param aInputRect The region in the space of the input image that will be + * invalidated by the filter pipeline. Defaults to + * (0, 0, 100, 100). + * @param aInputWriteRect The region in the space of the input image that the + * filter pipeline will allow writes to. Note the + * difference from @aInputRect: @aInputRect is the actual + * region invalidated, while @aInputWriteRect is the + * region that is written to. These can differ in cases + * where the input is not clipped to the size of the image. + * Defaults to the entire input rect. + * @param aOutputWriteRect The region in the space of the output surface that + * the filter pipeline will actually write to. It's + * expected that pixels in this region are green, while + * pixels outside this region are transparent. Defaults + * to the entire output rect. + */ +void CheckWritePixels(Decoder* aDecoder, + SurfaceFilter* aFilter, + Maybe aOutputRect = Nothing(), + Maybe aInputRect = Nothing(), + Maybe aInputWriteRect = Nothing(), + Maybe aOutputWriteRect = Nothing(), + uint8_t aFuzz = 0); + +/** + * Tests the result of calling WritePixels() using the provided SurfaceFilter + * pipeline. The pipeline must be a paletted pipeline. + * @see CheckWritePixels() for documentation of the arguments. + */ +void CheckPalettedWritePixels(Decoder* aDecoder, + SurfaceFilter* aFilter, + Maybe aOutputRect = Nothing(), + Maybe aInputRect = Nothing(), + Maybe aInputWriteRect = Nothing(), + Maybe aOutputWriteRect = Nothing(), + uint8_t aFuzz = 0); + + +/////////////////////////////////////////////////////////////////////////////// +// Test Data +/////////////////////////////////////////////////////////////////////////////// + +ImageTestCase GreenPNGTestCase(); +ImageTestCase GreenGIFTestCase(); +ImageTestCase GreenJPGTestCase(); +ImageTestCase GreenBMPTestCase(); +ImageTestCase GreenICOTestCase(); +ImageTestCase GreenIconTestCase(); + +ImageTestCase GreenFirstFrameAnimatedGIFTestCase(); +ImageTestCase GreenFirstFrameAnimatedPNGTestCase(); + +ImageTestCase CorruptTestCase(); +ImageTestCase CorruptBMPWithTruncatedHeader(); +ImageTestCase CorruptICOWithBadBMPWidthTestCase(); +ImageTestCase CorruptICOWithBadBMPHeightTestCase(); + +ImageTestCase TransparentPNGTestCase(); +ImageTestCase TransparentGIFTestCase(); +ImageTestCase FirstFramePaddingGIFTestCase(); +ImageTestCase NoFrameDelayGIFTestCase(); +ImageTestCase ExtraImageSubBlocksAnimatedGIFTestCase(); + +ImageTestCase TransparentBMPWhenBMPAlphaEnabledTestCase(); +ImageTestCase RLE4BMPTestCase(); +ImageTestCase RLE8BMPTestCase(); + +ImageTestCase DownscaledPNGTestCase(); +ImageTestCase DownscaledGIFTestCase(); +ImageTestCase DownscaledJPGTestCase(); +ImageTestCase DownscaledBMPTestCase(); +ImageTestCase DownscaledICOTestCase(); +ImageTestCase DownscaledIconTestCase(); +ImageTestCase DownscaledTransparentICOWithANDMaskTestCase(); + +ImageTestCase TruncatedSmallGIFTestCase(); + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_test_gtest_Common_h diff --git a/image/test/gtest/TestADAM7InterpolatingFilter.cpp b/image/test/gtest/TestADAM7InterpolatingFilter.cpp new file mode 100644 index 000000000..d9dab4346 --- /dev/null +++ b/image/test/gtest/TestADAM7InterpolatingFilter.cpp @@ -0,0 +1,671 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include + +#include "gtest/gtest.h" + +#include "mozilla/gfx/2D.h" +#include "mozilla/Maybe.h" +#include "Common.h" +#include "Decoder.h" +#include "DecoderFactory.h" +#include "SourceBuffer.h" +#include "SurfaceFilters.h" +#include "SurfacePipe.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; + +using std::generate; +using std::vector; + +template void +WithADAM7InterpolatingFilter(const IntSize& aSize, Func aFunc) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(bool(decoder)); + + WithFilterPipeline(decoder, Forward(aFunc), + ADAM7InterpolatingConfig { }, + SurfaceConfig { decoder, 0, aSize, + SurfaceFormat::B8G8R8A8, false }); +} + +void +AssertConfiguringADAM7InterpolatingFilterFails(const IntSize& aSize) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(bool(decoder)); + + AssertConfiguringPipelineFails(decoder, + ADAM7InterpolatingConfig { }, + SurfaceConfig { decoder, 0, aSize, + SurfaceFormat::B8G8R8A8, false }); +} + +uint8_t +InterpolateByte(uint8_t aByteA, uint8_t aByteB, float aWeight) +{ + return uint8_t(aByteA * aWeight + aByteB * (1.0f - aWeight)); +} + +BGRAColor +InterpolateColors(BGRAColor aColor1, BGRAColor aColor2, float aWeight) +{ + return BGRAColor(InterpolateByte(aColor1.mBlue, aColor2.mBlue, aWeight), + InterpolateByte(aColor1.mGreen, aColor2.mGreen, aWeight), + InterpolateByte(aColor1.mRed, aColor2.mRed, aWeight), + InterpolateByte(aColor1.mAlpha, aColor2.mAlpha, aWeight)); +} + +enum class ShouldInterpolate +{ + eYes, + eNo +}; + +BGRAColor +HorizontallyInterpolatedPixel(uint32_t aCol, + uint32_t aWidth, + const vector& aWeights, + ShouldInterpolate aShouldInterpolate, + const vector& aColors) +{ + // We cycle through the vector of weights forever. + float weight = aWeights[aCol % aWeights.size()]; + + // Find the columns of the two final pixels for this set of weights. + uint32_t finalPixel1 = aCol - aCol % aWeights.size(); + uint32_t finalPixel2 = finalPixel1 + aWeights.size(); + + // If |finalPixel2| is past the end of the row, that means that there is no + // final pixel after the pixel at |finalPixel1|. In that case, we just want to + // duplicate |finalPixel1|'s color until the end of the row. We can do that by + // setting |finalPixel2| equal to |finalPixel1| so that the interpolation has + // no effect. + if (finalPixel2 >= aWidth) { + finalPixel2 = finalPixel1; + } + + // We cycle through the vector of colors forever (subject to the above + // constraint about the end of the row). + BGRAColor color1 = aColors[finalPixel1 % aColors.size()]; + BGRAColor color2 = aColors[finalPixel2 % aColors.size()]; + + // If we're not interpolating, we treat all pixels which aren't final as + // transparent. Since the number of weights we have is equal to the stride + // between final pixels, we can check if |aCol| is a final pixel by checking + // whether |aCol| is a multiple of |aWeights.size()|. + if (aShouldInterpolate == ShouldInterpolate::eNo) { + return aCol % aWeights.size() == 0 ? color1 + : BGRAColor::Transparent(); + } + + // Interpolate. + return InterpolateColors(color1, color2, weight); +} + +vector& +InterpolationWeights(int32_t aStride) +{ + // Precalculated interpolation weights. These are used to interpolate + // between final pixels or between important rows. Although no interpolation + // is actually applied to the previous final pixel or important row value, + // the arrays still start with 1.0f, which is always skipped, primarily + // because otherwise |stride1Weights| would have zero elements. + static vector stride8Weights = + { 1.0f, 7 / 8.0f, 6 / 8.0f, 5 / 8.0f, 4 / 8.0f, 3 / 8.0f, 2 / 8.0f, 1 / 8.0f }; + static vector stride4Weights = { 1.0f, 3 / 4.0f, 2 / 4.0f, 1 / 4.0f }; + static vector stride2Weights = { 1.0f, 1 / 2.0f }; + static vector stride1Weights = { 1.0f }; + + switch (aStride) { + case 8: return stride8Weights; + case 4: return stride4Weights; + case 2: return stride2Weights; + case 1: return stride1Weights; + default: + MOZ_CRASH(); + } +} + +int32_t +ImportantRowStride(uint8_t aPass) +{ + // The stride between important rows for each pass, with a dummy value for + // the nonexistent pass 0 and for pass 8, since the tests run an extra pass to + // make sure nothing breaks. + static int32_t strides[] = { 1, 8, 8, 4, 4, 2, 2, 1, 1 }; + + return strides[aPass]; +} + +size_t +FinalPixelStride(uint8_t aPass) +{ + // The stride between the final pixels in important rows for each pass, with + // a dummy value for the nonexistent pass 0 and for pass 8, since the tests + // run an extra pass to make sure nothing breaks. + static size_t strides[] = { 1, 8, 4, 4, 2, 2, 1, 1, 1 }; + + return strides[aPass]; +} + +bool +IsImportantRow(int32_t aRow, uint8_t aPass) +{ + return aRow % ImportantRowStride(aPass) == 0; +} + +/** + * ADAM7 breaks up the image into 8x8 blocks. On each of the 7 passes, a new + * set of pixels in each block receives their final values, according to the + * following pattern: + * + * 1 6 4 6 2 6 4 6 + * 7 7 7 7 7 7 7 7 + * 5 6 5 6 5 6 5 6 + * 7 7 7 7 7 7 7 7 + * 3 6 4 6 3 6 4 6 + * 7 7 7 7 7 7 7 7 + * 5 6 5 6 5 6 5 6 + * 7 7 7 7 7 7 7 7 + * + * This function produces a row of pixels @aWidth wide, suitable for testing + * horizontal interpolation on pass @aPass. The pattern of pixels used is + * determined by @aPass and @aRow, which determine which pixels are final + * according to the table above, and @aColors, from which the pixel values + * are selected. + * + * There are two different behaviors: if |eNo| is passed for + * @aShouldInterpolate, non-final pixels are treated as transparent. If |eNo| + * is passed, non-final pixels get interpolated in from the surrounding final + * pixels. The intention is that |eNo| is passed to generate input which will + * be run through ADAM7InterpolatingFilter, and |eYes| is passed to generate + * reference data to check that the filter is performing horizontal + * interpolation correctly. + * + * This function does not perform vertical interpolation. Rows which aren't on + * the current pass are filled with transparent pixels. + * + * @return a vector representing a row of pixels. + */ +vector +ADAM7HorizontallyInterpolatedRow(uint8_t aPass, + uint32_t aRow, + uint32_t aWidth, + ShouldInterpolate aShouldInterpolate, + const vector& aColors) +{ + EXPECT_GT(aPass, 0); + EXPECT_LE(aPass, 8); + EXPECT_GT(aColors.size(), 0u); + + vector result(aWidth); + + if (IsImportantRow(aRow, aPass)) { + vector& weights = InterpolationWeights(FinalPixelStride(aPass)); + + // Compute the horizontally interpolated row. + uint32_t col = 0; + generate(result.begin(), result.end(), [&]{ + return HorizontallyInterpolatedPixel(col++, aWidth, weights, + aShouldInterpolate, aColors); + }); + } else { + // This is an unimportant row; just make the entire thing transparent. + generate(result.begin(), result.end(), []{ + return BGRAColor::Transparent(); + }); + } + + EXPECT_EQ(result.size(), size_t(aWidth)); + + return result; +} + +WriteState +WriteUninterpolatedPixels(SurfaceFilter* aFilter, + const IntSize& aSize, + uint8_t aPass, + const vector& aColors) +{ + WriteState result = WriteState::NEED_MORE_DATA; + + for (int32_t row = 0; row < aSize.height; ++row) { + // Compute uninterpolated pixels for this row. + vector pixels = + Move(ADAM7HorizontallyInterpolatedRow(aPass, row, aSize.width, + ShouldInterpolate::eNo, aColors)); + + // Write them to the surface. + auto pixelIterator = pixels.cbegin(); + result = aFilter->WritePixelsToRow([&]{ + return AsVariant((*pixelIterator++).AsPixel()); + }); + + if (result != WriteState::NEED_MORE_DATA) { + break; + } + } + + return result; +} + +bool +CheckHorizontallyInterpolatedImage(Decoder* aDecoder, + const IntSize& aSize, + uint8_t aPass, + const vector& aColors) +{ + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSourceSurface(); + + for (int32_t row = 0; row < aSize.height; ++row) { + if (!IsImportantRow(row, aPass)) { + continue; // Don't check rows which aren't important on this pass. + } + + // Compute the expected pixels, *with* interpolation to match what the + // filter should have done. + vector expectedPixels = + Move(ADAM7HorizontallyInterpolatedRow(aPass, row, aSize.width, + ShouldInterpolate::eYes, aColors)); + + if (!RowHasPixels(surface, row, expectedPixels)) { + return false; + } + } + + return true; +} + +void +CheckHorizontalInterpolation(const IntSize& aSize, + const vector& aColors) +{ + const IntRect surfaceRect(IntPoint(0, 0), aSize); + + WithADAM7InterpolatingFilter(aSize, + [&](Decoder* aDecoder, SurfaceFilter* aFilter) { + // We check horizontal interpolation behavior for each pass individually. In + // addition to the normal 7 passes that ADAM7 includes, we also check an + // eighth pass to verify that nothing breaks if extra data is written. + for (uint8_t pass = 1; pass <= 8; ++pass) { + // Write our color pattern to the surface. We don't perform any + // interpolation when writing to the filter so that we can check that the + // filter itself *does*. + WriteState result = + WriteUninterpolatedPixels(aFilter, aSize, pass, aColors); + + EXPECT_EQ(WriteState::FINISHED, result); + AssertCorrectPipelineFinalState(aFilter, surfaceRect, surfaceRect); + + // Check that the generated image matches the expected pattern, with + // interpolation applied. + EXPECT_TRUE(CheckHorizontallyInterpolatedImage(aDecoder, aSize, + pass, aColors)); + + // Prepare for the next pass. + aFilter->ResetToFirstRow(); + } + }); +} + +BGRAColor +ADAM7RowColor(int32_t aRow, + uint8_t aPass, + const vector& aColors) +{ + EXPECT_LT(0, aPass); + EXPECT_GE(8, aPass); + EXPECT_LT(0u, aColors.size()); + + // If this is an important row, select the color from the provided vector of + // colors, which we cycle through infinitely. If not, just fill the row with + // transparent pixels. + return IsImportantRow(aRow, aPass) ? aColors[aRow % aColors.size()] + : BGRAColor::Transparent(); +} + +WriteState +WriteRowColorPixels(SurfaceFilter* aFilter, + const IntSize& aSize, + uint8_t aPass, + const vector& aColors) +{ + WriteState result = WriteState::NEED_MORE_DATA; + + for (int32_t row = 0; row < aSize.height; ++row) { + const uint32_t color = ADAM7RowColor(row, aPass, aColors).AsPixel(); + + // Fill the surface with |color| pixels. + result = aFilter->WritePixelsToRow([&]{ return AsVariant(color); }); + + if (result != WriteState::NEED_MORE_DATA) { + break; + } + } + + return result; +} + +bool +CheckVerticallyInterpolatedImage(Decoder* aDecoder, + const IntSize& aSize, + uint8_t aPass, + const vector& aColors) +{ + vector& weights = InterpolationWeights(ImportantRowStride(aPass)); + + for (int32_t row = 0; row < aSize.height; ++row) { + // Vertically interpolation takes place between two important rows. The + // separation between the important rows is determined by the stride of this + // pass. When there is no "next" important row because we'd run off the + // bottom of the image, we use the same row for both. This matches + // ADAM7InterpolatingFilter's behavior of duplicating the last important row + // since there isn't another important row to vertically interpolate it + // with. + const int32_t stride = ImportantRowStride(aPass); + const int32_t prevImportantRow = row - row % stride; + const int32_t maybeNextImportantRow = prevImportantRow + stride; + const int32_t nextImportantRow = maybeNextImportantRow < aSize.height + ? maybeNextImportantRow + : prevImportantRow; + + // Retrieve the colors for the important rows we're going to interpolate. + const BGRAColor prevImportantRowColor = + ADAM7RowColor(prevImportantRow, aPass, aColors); + const BGRAColor nextImportantRowColor = + ADAM7RowColor(nextImportantRow, aPass, aColors); + + // The weight we'll use for interpolation is also determined by the stride. + // A row halfway between two important rows should have pixels that have a + // 50% contribution from each of the important rows, for example. + const float weight = weights[row % stride]; + const BGRAColor interpolatedColor = + InterpolateColors(prevImportantRowColor, nextImportantRowColor, weight); + + // Generate a row of expected pixels. Every pixel in the row is always the + // same color since we're only testing vertical interpolation between + // solid-colored rows. + vector expectedPixels(aSize.width); + generate(expectedPixels.begin(), expectedPixels.end(), [&]{ + return interpolatedColor; + }); + + // Check that the pixels match. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSourceSurface(); + if (!RowHasPixels(surface, row, expectedPixels)) { + return false; + } + } + + return true; +} + +void +CheckVerticalInterpolation(const IntSize& aSize, + const vector& aColors) +{ + const IntRect surfaceRect(IntPoint(0, 0), aSize); + + WithADAM7InterpolatingFilter(aSize, + [&](Decoder* aDecoder, SurfaceFilter* aFilter) { + for (uint8_t pass = 1; pass <= 8; ++pass) { + // Write a pattern of rows to the surface. Important rows will receive a + // color selected from |aColors|; unimportant rows will be transparent. + WriteState result = WriteRowColorPixels(aFilter, aSize, pass, aColors); + + EXPECT_EQ(WriteState::FINISHED, result); + AssertCorrectPipelineFinalState(aFilter, surfaceRect, surfaceRect); + + // Check that the generated image matches the expected pattern, with + // interpolation applied. + EXPECT_TRUE(CheckVerticallyInterpolatedImage(aDecoder, aSize, + pass, aColors)); + + // Prepare for the next pass. + aFilter->ResetToFirstRow(); + } + }); +} + +void +CheckInterpolation(const IntSize& aSize, const vector& aColors) +{ + CheckHorizontalInterpolation(aSize, aColors); + CheckVerticalInterpolation(aSize, aColors); +} + +void +CheckADAM7InterpolatingWritePixels(const IntSize& aSize) +{ + // This test writes 8 passes of green pixels (the seven ADAM7 passes, plus one + // extra to make sure nothing goes wrong if we write too much input) and verifies + // that the output is a solid green surface each time. Because all the pixels + // are the same color, interpolation doesn't matter; we test the correctness + // of the interpolation algorithm itself separately. + WithADAM7InterpolatingFilter(aSize, + [&](Decoder* aDecoder, SurfaceFilter* aFilter) { + IntRect rect(IntPoint(0, 0), aSize); + + for (int32_t pass = 1; pass <= 8; ++pass) { + // We only actually write up to the last important row for each pass, + // because that row unambiguously determines the remaining rows. + const int32_t lastRow = aSize.height - 1; + const int32_t lastImportantRow = + lastRow - (lastRow % ImportantRowStride(pass)); + const IntRect inputWriteRect(0, 0, aSize.width, lastImportantRow + 1); + + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(rect), + /* aInputRect = */ Some(rect), + /* aInputWriteRect = */ Some(inputWriteRect)); + + aFilter->ResetToFirstRow(); + EXPECT_FALSE(aFilter->IsSurfaceFinished()); + Maybe invalidRect = aFilter->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + } + }); +} + +TEST(ImageADAM7InterpolatingFilter, WritePixels100_100) +{ + CheckADAM7InterpolatingWritePixels(IntSize(100, 100)); +} + +TEST(ImageADAM7InterpolatingFilter, WritePixels99_99) +{ + CheckADAM7InterpolatingWritePixels(IntSize(99, 99)); +} + +TEST(ImageADAM7InterpolatingFilter, WritePixels66_33) +{ + CheckADAM7InterpolatingWritePixels(IntSize(66, 33)); +} + +TEST(ImageADAM7InterpolatingFilter, WritePixels33_66) +{ + CheckADAM7InterpolatingWritePixels(IntSize(33, 66)); +} + +TEST(ImageADAM7InterpolatingFilter, WritePixels15_15) +{ + CheckADAM7InterpolatingWritePixels(IntSize(15, 15)); +} + +TEST(ImageADAM7InterpolatingFilter, WritePixels9_9) +{ + CheckADAM7InterpolatingWritePixels(IntSize(9, 9)); +} + +TEST(ImageADAM7InterpolatingFilter, WritePixels8_8) +{ + CheckADAM7InterpolatingWritePixels(IntSize(8, 8)); +} + +TEST(ImageADAM7InterpolatingFilter, WritePixels7_7) +{ + CheckADAM7InterpolatingWritePixels(IntSize(7, 7)); +} + +TEST(ImageADAM7InterpolatingFilter, WritePixels3_3) +{ + CheckADAM7InterpolatingWritePixels(IntSize(3, 3)); +} + +TEST(ImageADAM7InterpolatingFilter, WritePixels1_1) +{ + CheckADAM7InterpolatingWritePixels(IntSize(1, 1)); +} + +TEST(ImageADAM7InterpolatingFilter, TrivialInterpolation48_48) +{ + CheckInterpolation(IntSize(48, 48), { BGRAColor::Green() }); +} + +TEST(ImageADAM7InterpolatingFilter, InterpolationOutput33_17) +{ + // We check interpolation using irregular patterns to make sure that the + // interpolation will look different for different passes. + CheckInterpolation(IntSize(33, 17), { + BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), + BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(), + BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue(), BGRAColor::Blue(), + BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Blue(), + BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), + BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue(), + BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(), + BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Blue() + }); +} + +TEST(ImageADAM7InterpolatingFilter, InterpolationOutput32_16) +{ + CheckInterpolation(IntSize(32, 16), { + BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), + BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(), + BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue(), BGRAColor::Blue(), + BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Blue(), + BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), + BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue(), + BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(), + BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Blue() + }); +} + +TEST(ImageADAM7InterpolatingFilter, InterpolationOutput31_15) +{ + CheckInterpolation(IntSize(31, 15), { + BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), + BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(), + BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue(), BGRAColor::Blue(), + BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Blue(), + BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), + BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue(), + BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(), + BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Blue() + }); +} + +TEST(ImageADAM7InterpolatingFilter, InterpolationOutput17_33) +{ + CheckInterpolation(IntSize(17, 33), { + BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), + BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), + BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(), + BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue() + }); +} + +TEST(ImageADAM7InterpolatingFilter, InterpolationOutput16_32) +{ + CheckInterpolation(IntSize(16, 32), { + BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), + BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), + BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(), + BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue() + }); +} + +TEST(ImageADAM7InterpolatingFilter, InterpolationOutput15_31) +{ + CheckInterpolation(IntSize(15, 31), { + BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), + BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), + BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(), + BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue() + }); +} + +TEST(ImageADAM7InterpolatingFilter, InterpolationOutput9_9) +{ + CheckInterpolation(IntSize(9, 9), { + BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(), + BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue() + }); +} + +TEST(ImageADAM7InterpolatingFilter, InterpolationOutput8_8) +{ + CheckInterpolation(IntSize(8, 8), { + BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(), + BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue() + }); +} + +TEST(ImageADAM7InterpolatingFilter, InterpolationOutput7_7) +{ + CheckInterpolation(IntSize(7, 7), { + BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(), + BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue() + }); +} + +TEST(ImageADAM7InterpolatingFilter, InterpolationOutput3_3) +{ + CheckInterpolation(IntSize(3, 3), { + BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Blue(), BGRAColor::Red() + }); +} + +TEST(ImageADAM7InterpolatingFilter, InterpolationOutput1_1) +{ + CheckInterpolation(IntSize(1, 1), { BGRAColor::Blue() }); +} + +TEST(ImageADAM7InterpolatingFilter, ADAM7InterpolationFailsFor0_0) +{ + // A 0x0 input size is invalid, so configuration should fail. + AssertConfiguringADAM7InterpolatingFilterFails(IntSize(0, 0)); +} + +TEST(ImageADAM7InterpolatingFilter, ADAM7InterpolationFailsForMinus1_Minus1) +{ + // A negative input size is invalid, so configuration should fail. + AssertConfiguringADAM7InterpolatingFilterFails(IntSize(-1, -1)); +} + +TEST(ImageADAM7InterpolatingFilter, ConfiguringPalettedADAM7InterpolatingFilterFails) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + // ADAM7InterpolatingFilter does not support paletted images, so configuration + // should fail. + AssertConfiguringPipelineFails(decoder, + ADAM7InterpolatingConfig { }, + PalettedSurfaceConfig { decoder, 0, IntSize(100, 100), + IntRect(0, 0, 50, 50), + SurfaceFormat::B8G8R8A8, 8, + false }); +} diff --git a/image/test/gtest/TestCopyOnWrite.cpp b/image/test/gtest/TestCopyOnWrite.cpp new file mode 100644 index 000000000..0d420b672 --- /dev/null +++ b/image/test/gtest/TestCopyOnWrite.cpp @@ -0,0 +1,235 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" + +#include "CopyOnWrite.h" + +using namespace mozilla; +using namespace mozilla::image; + +struct ValueStats +{ + int32_t mCopies = 0; + int32_t mFrees = 0; + int32_t mCalls = 0; + int32_t mConstCalls = 0; + int32_t mSerial = 0; +}; + +struct Value +{ + NS_INLINE_DECL_REFCOUNTING(Value) + + explicit Value(ValueStats& aStats) + : mStats(aStats) + , mSerial(mStats.mSerial++) + { } + + Value(const Value& aOther) + : mStats(aOther.mStats) + , mSerial(mStats.mSerial++) + { + mStats.mCopies++; + } + + void Go() { mStats.mCalls++; } + void Go() const { mStats.mConstCalls++; } + + int32_t Serial() const { return mSerial; } + +protected: + ~Value() { mStats.mFrees++; } + +private: + ValueStats& mStats; + int32_t mSerial; +}; + +TEST(ImageCopyOnWrite, Read) +{ + ValueStats stats; + + { + CopyOnWrite cow(new Value(stats)); + + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_TRUE(cow.CanRead()); + + cow.Read([&](const Value* aValue) { + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_EQ(0, aValue->Serial()); + EXPECT_TRUE(cow.CanRead()); + EXPECT_TRUE(cow.CanWrite()); + + aValue->Go(); + + EXPECT_EQ(0, stats.mCalls); + EXPECT_EQ(1, stats.mConstCalls); + }); + + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_EQ(0, stats.mCalls); + EXPECT_EQ(1, stats.mConstCalls); + } + + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(1, stats.mFrees); +} + +TEST(ImageCopyOnWrite, RecursiveRead) +{ + ValueStats stats; + + { + CopyOnWrite cow(new Value(stats)); + + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_TRUE(cow.CanRead()); + + cow.Read([&](const Value* aValue) { + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_EQ(0, aValue->Serial()); + EXPECT_TRUE(cow.CanRead()); + EXPECT_TRUE(cow.CanWrite()); + + // Make sure that Read() inside a Read() succeeds. + cow.Read([&](const Value* aValue) { + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_EQ(0, aValue->Serial()); + EXPECT_TRUE(cow.CanRead()); + EXPECT_TRUE(cow.CanWrite()); + + aValue->Go(); + + EXPECT_EQ(0, stats.mCalls); + EXPECT_EQ(1, stats.mConstCalls); + }, []() { + // This gets called if we can't read. We shouldn't get here. + EXPECT_TRUE(false); + }); + }); + + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_EQ(0, stats.mCalls); + EXPECT_EQ(1, stats.mConstCalls); + } + + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(1, stats.mFrees); +} + +TEST(ImageCopyOnWrite, Write) +{ + ValueStats stats; + + { + CopyOnWrite cow(new Value(stats)); + + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_TRUE(cow.CanRead()); + EXPECT_TRUE(cow.CanWrite()); + + cow.Write([&](Value* aValue) { + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_EQ(0, aValue->Serial()); + EXPECT_TRUE(!cow.CanRead()); + EXPECT_TRUE(!cow.CanWrite()); + + aValue->Go(); + + EXPECT_EQ(1, stats.mCalls); + EXPECT_EQ(0, stats.mConstCalls); + }); + + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_EQ(1, stats.mCalls); + EXPECT_EQ(0, stats.mConstCalls); + } + + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(1, stats.mFrees); +} + +TEST(ImageCopyOnWrite, WriteRecursive) +{ + ValueStats stats; + + { + CopyOnWrite cow(new Value(stats)); + + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_TRUE(cow.CanRead()); + EXPECT_TRUE(cow.CanWrite()); + + cow.Read([&](const Value* aValue) { + EXPECT_EQ(0, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_EQ(0, aValue->Serial()); + EXPECT_TRUE(cow.CanRead()); + EXPECT_TRUE(cow.CanWrite()); + + // Make sure Write() inside a Read() succeeds. + cow.Write([&](Value* aValue) { + EXPECT_EQ(1, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_EQ(1, aValue->Serial()); + EXPECT_TRUE(!cow.CanRead()); + EXPECT_TRUE(!cow.CanWrite()); + + aValue->Go(); + + EXPECT_EQ(1, stats.mCalls); + EXPECT_EQ(0, stats.mConstCalls); + + // Make sure Read() inside a Write() fails. + cow.Read([](const Value* aValue) { + // This gets called if we can read. We shouldn't get here. + EXPECT_TRUE(false); + }, []() { + // This gets called if we can't read. We *should* get here. + EXPECT_TRUE(true); + }); + + // Make sure Write() inside a Write() fails. + cow.Write([](Value* aValue) { + // This gets called if we can write. We shouldn't get here. + EXPECT_TRUE(false); + }, []() { + // This gets called if we can't write. We *should* get here. + EXPECT_TRUE(true); + }); + }, []() { + // This gets called if we can't write. We shouldn't get here. + EXPECT_TRUE(false); + }); + + aValue->Go(); + + EXPECT_EQ(1, stats.mCopies); + EXPECT_EQ(0, stats.mFrees); + EXPECT_EQ(1, stats.mCalls); + EXPECT_EQ(1, stats.mConstCalls); + }); + + EXPECT_EQ(1, stats.mCopies); + EXPECT_EQ(1, stats.mFrees); + EXPECT_EQ(1, stats.mCalls); + EXPECT_EQ(1, stats.mConstCalls); + } + + EXPECT_EQ(1, stats.mCopies); + EXPECT_EQ(2, stats.mFrees); +} diff --git a/image/test/gtest/TestDecodeToSurface.cpp b/image/test/gtest/TestDecodeToSurface.cpp new file mode 100644 index 000000000..bd52e7590 --- /dev/null +++ b/image/test/gtest/TestDecodeToSurface.cpp @@ -0,0 +1,123 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" + +#include "Common.h" +#include "imgIContainer.h" +#include "imgITools.h" +#include "ImageOps.h" +#include "mozilla/gfx/2D.h" +#include "nsComponentManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsIInputStream.h" +#include "nsIRunnable.h" +#include "nsIThread.h" +#include "mozilla/RefPtr.h" +#include "nsString.h" +#include "nsThreadUtils.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; + +class DecodeToSurfaceRunnable : public Runnable +{ +public: + DecodeToSurfaceRunnable(RefPtr& aSurface, + nsIInputStream* aInputStream, + const ImageTestCase& aTestCase) + : mSurface(aSurface) + , mInputStream(aInputStream) + , mTestCase(aTestCase) + { } + + NS_IMETHOD Run() override + { + Go(); + return NS_OK; + } + + void Go() + { + mSurface = + ImageOps::DecodeToSurface(mInputStream, + nsDependentCString(mTestCase.mMimeType), + imgIContainer::DECODE_FLAGS_DEFAULT); + ASSERT_TRUE(mSurface != nullptr); + + EXPECT_EQ(SurfaceType::DATA, mSurface->GetType()); + EXPECT_TRUE(mSurface->GetFormat() == SurfaceFormat::B8G8R8X8 || + mSurface->GetFormat() == SurfaceFormat::B8G8R8A8); + EXPECT_EQ(mTestCase.mSize, mSurface->GetSize()); + + EXPECT_TRUE(IsSolidColor(mSurface, BGRAColor::Green(), + mTestCase.mFlags & TEST_CASE_IS_FUZZY ? 1 : 0)); + } + +private: + RefPtr& mSurface; + nsCOMPtr mInputStream; + ImageTestCase mTestCase; +}; + +static void +RunDecodeToSurface(const ImageTestCase& aTestCase) +{ + nsCOMPtr inputStream = LoadFile(aTestCase.mPath); + ASSERT_TRUE(inputStream != nullptr); + + nsCOMPtr thread; + nsresult rv = NS_NewThread(getter_AddRefs(thread), nullptr); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + // We run the DecodeToSurface tests off-main-thread to ensure that + // DecodeToSurface doesn't require any main-thread-only code. + RefPtr surface; + nsCOMPtr runnable = + new DecodeToSurfaceRunnable(surface, inputStream, aTestCase); + thread->Dispatch(runnable, nsIThread::DISPATCH_SYNC); + + thread->Shutdown(); + + // Explicitly release the SourceSurface on the main thread. + surface = nullptr; +} + +class ImageDecodeToSurface : public ::testing::Test +{ +protected: + AutoInitializeImageLib mInit; +}; + +TEST_F(ImageDecodeToSurface, PNG) { RunDecodeToSurface(GreenPNGTestCase()); } +TEST_F(ImageDecodeToSurface, GIF) { RunDecodeToSurface(GreenGIFTestCase()); } +TEST_F(ImageDecodeToSurface, JPG) { RunDecodeToSurface(GreenJPGTestCase()); } +TEST_F(ImageDecodeToSurface, BMP) { RunDecodeToSurface(GreenBMPTestCase()); } +TEST_F(ImageDecodeToSurface, ICO) { RunDecodeToSurface(GreenICOTestCase()); } +TEST_F(ImageDecodeToSurface, Icon) { RunDecodeToSurface(GreenIconTestCase()); } + +TEST_F(ImageDecodeToSurface, AnimatedGIF) +{ + RunDecodeToSurface(GreenFirstFrameAnimatedGIFTestCase()); +} + +TEST_F(ImageDecodeToSurface, AnimatedPNG) +{ + RunDecodeToSurface(GreenFirstFrameAnimatedPNGTestCase()); +} + +TEST_F(ImageDecodeToSurface, Corrupt) +{ + ImageTestCase testCase = CorruptTestCase(); + + nsCOMPtr inputStream = LoadFile(testCase.mPath); + ASSERT_TRUE(inputStream != nullptr); + + RefPtr surface = + ImageOps::DecodeToSurface(inputStream, + nsDependentCString(testCase.mMimeType), + imgIContainer::DECODE_FLAGS_DEFAULT); + EXPECT_TRUE(surface == nullptr); +} diff --git a/image/test/gtest/TestDecoders.cpp b/image/test/gtest/TestDecoders.cpp new file mode 100644 index 000000000..58caa77a2 --- /dev/null +++ b/image/test/gtest/TestDecoders.cpp @@ -0,0 +1,669 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" + +#include "Common.h" +#include "Decoder.h" +#include "DecoderFactory.h" +#include "decoders/nsBMPDecoder.h" +#include "IDecodingTask.h" +#include "imgIContainer.h" +#include "imgITools.h" +#include "ImageFactory.h" +#include "mozilla/gfx/2D.h" +#include "nsComponentManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsIInputStream.h" +#include "nsIRunnable.h" +#include "nsIThread.h" +#include "mozilla/RefPtr.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsThreadUtils.h" +#include "ProgressTracker.h" +#include "SourceBuffer.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; + +static already_AddRefed +CheckDecoderState(const ImageTestCase& aTestCase, Decoder* aDecoder) +{ + EXPECT_TRUE(aDecoder->GetDecodeDone()); + EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_HAS_ERROR), + aDecoder->HasError()); + + // Verify that the decoder made the expected progress. + Progress progress = aDecoder->TakeProgress(); + EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_HAS_ERROR), + bool(progress & FLAG_HAS_ERROR)); + + if (aTestCase.mFlags & TEST_CASE_HAS_ERROR) { + return nullptr; // That's all we can check for bad images. + } + + EXPECT_TRUE(bool(progress & FLAG_SIZE_AVAILABLE)); + EXPECT_TRUE(bool(progress & FLAG_DECODE_COMPLETE)); + EXPECT_TRUE(bool(progress & FLAG_FRAME_COMPLETE)); + EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_TRANSPARENT), + bool(progress & FLAG_HAS_TRANSPARENCY)); + EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_ANIMATED), + bool(progress & FLAG_IS_ANIMATED)); + + // The decoder should get the correct size. + IntSize size = aDecoder->Size(); + EXPECT_EQ(aTestCase.mSize.width, size.width); + EXPECT_EQ(aTestCase.mSize.height, size.height); + + // Get the current frame, which is always the first frame of the image + // because CreateAnonymousDecoder() forces a first-frame-only decode. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSourceSurface(); + + // Verify that the resulting surfaces matches our expectations. + EXPECT_EQ(SurfaceType::DATA, surface->GetType()); + EXPECT_TRUE(surface->GetFormat() == SurfaceFormat::B8G8R8X8 || + surface->GetFormat() == SurfaceFormat::B8G8R8A8); + EXPECT_EQ(aTestCase.mOutputSize, surface->GetSize()); + + return surface.forget(); +} + +static void +CheckDecoderResults(const ImageTestCase& aTestCase, Decoder* aDecoder) +{ + RefPtr surface = CheckDecoderState(aTestCase, aDecoder); + if (!surface) { + return; + } + + if (aTestCase.mFlags & TEST_CASE_IGNORE_OUTPUT) { + return; + } + + // Check the output. + EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green(), + aTestCase.mFlags & TEST_CASE_IS_FUZZY ? 1 : 0)); +} + +template +void WithSingleChunkDecode(const ImageTestCase& aTestCase, + const Maybe& aOutputSize, + Func aResultChecker) +{ + nsCOMPtr inputStream = LoadFile(aTestCase.mPath); + ASSERT_TRUE(inputStream != nullptr); + + // Figure out how much data we have. + uint64_t length; + nsresult rv = inputStream->Available(&length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + // Write the data into a SourceBuffer. + NotNull> sourceBuffer = WrapNotNull(new SourceBuffer()); + sourceBuffer->ExpectLength(length); + rv = sourceBuffer->AppendFromInputStream(inputStream, length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + sourceBuffer->Complete(NS_OK); + + // Create a decoder. + DecoderType decoderType = + DecoderFactory::GetDecoderType(aTestCase.mMimeType); + RefPtr decoder = + DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, aOutputSize, + DefaultSurfaceFlags()); + ASSERT_TRUE(decoder != nullptr); + RefPtr task = new AnonymousDecodingTask(WrapNotNull(decoder)); + + // Run the full decoder synchronously. + task->Run(); + + // Call the lambda to verify the expected results. + aResultChecker(decoder); +} + +static void +CheckDecoderSingleChunk(const ImageTestCase& aTestCase) +{ + WithSingleChunkDecode(aTestCase, Nothing(), [&](Decoder* aDecoder) { + CheckDecoderResults(aTestCase, aDecoder); + }); +} + +static void +CheckDecoderMultiChunk(const ImageTestCase& aTestCase) +{ + nsCOMPtr inputStream = LoadFile(aTestCase.mPath); + ASSERT_TRUE(inputStream != nullptr); + + // Figure out how much data we have. + uint64_t length; + nsresult rv = inputStream->Available(&length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + // Create a SourceBuffer and a decoder. + NotNull> sourceBuffer = WrapNotNull(new SourceBuffer()); + sourceBuffer->ExpectLength(length); + DecoderType decoderType = + DecoderFactory::GetDecoderType(aTestCase.mMimeType); + RefPtr decoder = + DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, Nothing(), + DefaultSurfaceFlags()); + ASSERT_TRUE(decoder != nullptr); + RefPtr task = new AnonymousDecodingTask(WrapNotNull(decoder)); + + for (uint64_t read = 0; read < length ; ++read) { + uint64_t available = 0; + rv = inputStream->Available(&available); + ASSERT_TRUE(available > 0); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + rv = sourceBuffer->AppendFromInputStream(inputStream, 1); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + task->Run(); + } + + sourceBuffer->Complete(NS_OK); + task->Run(); + + CheckDecoderResults(aTestCase, decoder); +} + +static void +CheckDownscaleDuringDecode(const ImageTestCase& aTestCase) +{ + // This function expects that |aTestCase| consists of 25 lines of green, + // followed by 25 lines of red, followed by 25 lines of green, followed by 25 + // more lines of red. We'll downscale it from 100x100 to 20x20. + IntSize outputSize(20, 20); + + WithSingleChunkDecode(aTestCase, Some(outputSize), [&](Decoder* aDecoder) { + RefPtr surface = CheckDecoderState(aTestCase, aDecoder); + + // There are no downscale-during-decode tests that have TEST_CASE_HAS_ERROR + // set, so we expect to always get a surface here. + EXPECT_TRUE(surface != nullptr); + + if (aTestCase.mFlags & TEST_CASE_IGNORE_OUTPUT) { + return; + } + + // Check that the downscaled image is correct. Note that we skip rows near + // the transitions between colors, since the downscaler does not produce a + // sharp boundary at these points. Even some of the rows we test need a + // small amount of fuzz; this is just the nature of Lanczos downscaling. + EXPECT_TRUE(RowsAreSolidColor(surface, 0, 4, BGRAColor::Green(), /* aFuzz = */ 47)); + EXPECT_TRUE(RowsAreSolidColor(surface, 6, 3, BGRAColor::Red(), /* aFuzz = */ 27)); + EXPECT_TRUE(RowsAreSolidColor(surface, 11, 3, BGRAColor::Green(), /* aFuzz = */ 47)); + EXPECT_TRUE(RowsAreSolidColor(surface, 16, 4, BGRAColor::Red(), /* aFuzz = */ 27)); + }); +} + +class ImageDecoders : public ::testing::Test +{ +protected: + AutoInitializeImageLib mInit; +}; + +TEST_F(ImageDecoders, PNGSingleChunk) +{ + CheckDecoderSingleChunk(GreenPNGTestCase()); +} + +TEST_F(ImageDecoders, PNGMultiChunk) +{ + CheckDecoderMultiChunk(GreenPNGTestCase()); +} + +TEST_F(ImageDecoders, PNGDownscaleDuringDecode) +{ + CheckDownscaleDuringDecode(DownscaledPNGTestCase()); +} + +TEST_F(ImageDecoders, GIFSingleChunk) +{ + CheckDecoderSingleChunk(GreenGIFTestCase()); +} + +TEST_F(ImageDecoders, GIFMultiChunk) +{ + CheckDecoderMultiChunk(GreenGIFTestCase()); +} + +TEST_F(ImageDecoders, GIFDownscaleDuringDecode) +{ + CheckDownscaleDuringDecode(DownscaledGIFTestCase()); +} + +TEST_F(ImageDecoders, JPGSingleChunk) +{ + CheckDecoderSingleChunk(GreenJPGTestCase()); +} + +TEST_F(ImageDecoders, JPGMultiChunk) +{ + CheckDecoderMultiChunk(GreenJPGTestCase()); +} + +TEST_F(ImageDecoders, JPGDownscaleDuringDecode) +{ + CheckDownscaleDuringDecode(DownscaledJPGTestCase()); +} + +TEST_F(ImageDecoders, BMPSingleChunk) +{ + CheckDecoderSingleChunk(GreenBMPTestCase()); +} + +TEST_F(ImageDecoders, BMPMultiChunk) +{ + CheckDecoderMultiChunk(GreenBMPTestCase()); +} + +TEST_F(ImageDecoders, BMPDownscaleDuringDecode) +{ + CheckDownscaleDuringDecode(DownscaledBMPTestCase()); +} + +TEST_F(ImageDecoders, ICOSingleChunk) +{ + CheckDecoderSingleChunk(GreenICOTestCase()); +} + +TEST_F(ImageDecoders, ICOMultiChunk) +{ + CheckDecoderMultiChunk(GreenICOTestCase()); +} + +TEST_F(ImageDecoders, ICODownscaleDuringDecode) +{ + CheckDownscaleDuringDecode(DownscaledICOTestCase()); +} + +TEST_F(ImageDecoders, ICOWithANDMaskDownscaleDuringDecode) +{ + CheckDownscaleDuringDecode(DownscaledTransparentICOWithANDMaskTestCase()); +} + +TEST_F(ImageDecoders, IconSingleChunk) +{ + CheckDecoderSingleChunk(GreenIconTestCase()); +} + +TEST_F(ImageDecoders, IconMultiChunk) +{ + CheckDecoderMultiChunk(GreenIconTestCase()); +} + +TEST_F(ImageDecoders, IconDownscaleDuringDecode) +{ + CheckDownscaleDuringDecode(DownscaledIconTestCase()); +} + +TEST_F(ImageDecoders, AnimatedGIFSingleChunk) +{ + CheckDecoderSingleChunk(GreenFirstFrameAnimatedGIFTestCase()); +} + +TEST_F(ImageDecoders, AnimatedGIFMultiChunk) +{ + CheckDecoderMultiChunk(GreenFirstFrameAnimatedGIFTestCase()); +} + +TEST_F(ImageDecoders, AnimatedPNGSingleChunk) +{ + CheckDecoderSingleChunk(GreenFirstFrameAnimatedPNGTestCase()); +} + +TEST_F(ImageDecoders, AnimatedPNGMultiChunk) +{ + CheckDecoderMultiChunk(GreenFirstFrameAnimatedPNGTestCase()); +} + +TEST_F(ImageDecoders, CorruptSingleChunk) +{ + CheckDecoderSingleChunk(CorruptTestCase()); +} + +TEST_F(ImageDecoders, CorruptMultiChunk) +{ + CheckDecoderMultiChunk(CorruptTestCase()); +} + +TEST_F(ImageDecoders, CorruptBMPWithTruncatedHeaderSingleChunk) +{ + CheckDecoderSingleChunk(CorruptBMPWithTruncatedHeader()); +} + +TEST_F(ImageDecoders, CorruptBMPWithTruncatedHeaderMultiChunk) +{ + CheckDecoderMultiChunk(CorruptBMPWithTruncatedHeader()); +} + +TEST_F(ImageDecoders, CorruptICOWithBadBMPWidthSingleChunk) +{ + CheckDecoderSingleChunk(CorruptICOWithBadBMPWidthTestCase()); +} + +TEST_F(ImageDecoders, CorruptICOWithBadBMPWidthMultiChunk) +{ + CheckDecoderMultiChunk(CorruptICOWithBadBMPWidthTestCase()); +} + +TEST_F(ImageDecoders, CorruptICOWithBadBMPHeightSingleChunk) +{ + CheckDecoderSingleChunk(CorruptICOWithBadBMPHeightTestCase()); +} + +TEST_F(ImageDecoders, CorruptICOWithBadBMPHeightMultiChunk) +{ + CheckDecoderMultiChunk(CorruptICOWithBadBMPHeightTestCase()); +} + +TEST_F(ImageDecoders, AnimatedGIFWithFRAME_FIRST) +{ + ImageTestCase testCase = GreenFirstFrameAnimatedGIFTestCase(); + + // Verify that we can decode this test case and retrieve the first frame using + // imgIContainer::FRAME_FIRST. This ensures that we correctly trigger a + // single-frame decode rather than an animated decode when + // imgIContainer::FRAME_FIRST is requested. + + // Create an image. + RefPtr image = + ImageFactory::CreateAnonymousImage(nsDependentCString(testCase.mMimeType)); + ASSERT_TRUE(!image->HasError()); + + nsCOMPtr inputStream = LoadFile(testCase.mPath); + ASSERT_TRUE(inputStream); + + // Figure out how much data we have. + uint64_t length; + nsresult rv = inputStream->Available(&length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + // Write the data into the image. + rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0, + static_cast(length)); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + // Let the image know we've sent all the data. + rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + RefPtr tracker = image->GetProgressTracker(); + tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE); + + // Lock the image so its surfaces don't disappear during the test. + image->LockImage(); + + // Use GetFrame() to force a sync decode of the image, specifying FRAME_FIRST + // to ensure that we don't get an animated decode. + RefPtr surface = + image->GetFrame(imgIContainer::FRAME_FIRST, + imgIContainer::FLAG_SYNC_DECODE); + + // Ensure that the image's metadata meets our expectations. + IntSize imageSize(0, 0); + rv = image->GetWidth(&imageSize.width); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + rv = image->GetHeight(&imageSize.height); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + + EXPECT_EQ(testCase.mSize.width, imageSize.width); + EXPECT_EQ(testCase.mSize.height, imageSize.height); + + Progress imageProgress = tracker->GetProgress(); + + EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false); + EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true); + + // Ensure that we decoded the static version of the image. + { + LookupResult result = + SurfaceCache::Lookup(ImageKey(image.get()), + RasterSurfaceKey(imageSize, + DefaultSurfaceFlags(), + PlaybackType::eStatic)); + ASSERT_EQ(MatchType::EXACT, result.Type()); + EXPECT_TRUE(bool(result.Surface())); + } + + // Ensure that we didn't decode the animated version of the image. + { + LookupResult result = + SurfaceCache::Lookup(ImageKey(image.get()), + RasterSurfaceKey(imageSize, + DefaultSurfaceFlags(), + PlaybackType::eAnimated)); + ASSERT_EQ(MatchType::NOT_FOUND, result.Type()); + } + + // Use GetFrame() to force a sync decode of the image, this time specifying + // FRAME_CURRENT to ensure that we get an animated decode. + RefPtr animatedSurface = + image->GetFrame(imgIContainer::FRAME_CURRENT, + imgIContainer::FLAG_SYNC_DECODE); + + // Ensure that we decoded both frames of the animated version of the image. + { + LookupResult result = + SurfaceCache::Lookup(ImageKey(image.get()), + RasterSurfaceKey(imageSize, + DefaultSurfaceFlags(), + PlaybackType::eAnimated)); + ASSERT_EQ(MatchType::EXACT, result.Type()); + + EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0))); + EXPECT_TRUE(bool(result.Surface())); + + EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(1))); + EXPECT_TRUE(bool(result.Surface())); + } + + // Ensure that the static version is still around. + { + LookupResult result = + SurfaceCache::Lookup(ImageKey(image.get()), + RasterSurfaceKey(imageSize, + DefaultSurfaceFlags(), + PlaybackType::eStatic)); + ASSERT_EQ(MatchType::EXACT, result.Type()); + EXPECT_TRUE(bool(result.Surface())); + } +} + +TEST_F(ImageDecoders, AnimatedGIFWithFRAME_CURRENT) +{ + ImageTestCase testCase = GreenFirstFrameAnimatedGIFTestCase(); + + // Verify that we can decode this test case and retrieve the entire sequence + // of frames using imgIContainer::FRAME_CURRENT. This ensures that we + // correctly trigger an animated decode rather than a single-frame decode when + // imgIContainer::FRAME_CURRENT is requested. + + // Create an image. + RefPtr image = + ImageFactory::CreateAnonymousImage(nsDependentCString(testCase.mMimeType)); + ASSERT_TRUE(!image->HasError()); + + nsCOMPtr inputStream = LoadFile(testCase.mPath); + ASSERT_TRUE(inputStream); + + // Figure out how much data we have. + uint64_t length; + nsresult rv = inputStream->Available(&length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + // Write the data into the image. + rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0, + static_cast(length)); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + // Let the image know we've sent all the data. + rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + RefPtr tracker = image->GetProgressTracker(); + tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE); + + // Lock the image so its surfaces don't disappear during the test. + image->LockImage(); + + // Use GetFrame() to force a sync decode of the image, specifying + // FRAME_CURRENT to ensure we get an animated decode. + RefPtr surface = + image->GetFrame(imgIContainer::FRAME_CURRENT, + imgIContainer::FLAG_SYNC_DECODE); + + // Ensure that the image's metadata meets our expectations. + IntSize imageSize(0, 0); + rv = image->GetWidth(&imageSize.width); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + rv = image->GetHeight(&imageSize.height); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + + EXPECT_EQ(testCase.mSize.width, imageSize.width); + EXPECT_EQ(testCase.mSize.height, imageSize.height); + + Progress imageProgress = tracker->GetProgress(); + + EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false); + EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true); + + // Ensure that we decoded both frames of the animated version of the image. + { + LookupResult result = + SurfaceCache::Lookup(ImageKey(image.get()), + RasterSurfaceKey(imageSize, + DefaultSurfaceFlags(), + PlaybackType::eAnimated)); + ASSERT_EQ(MatchType::EXACT, result.Type()); + + EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0))); + EXPECT_TRUE(bool(result.Surface())); + + EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(1))); + EXPECT_TRUE(bool(result.Surface())); + } + + // Ensure that we didn't decode the static version of the image. + { + LookupResult result = + SurfaceCache::Lookup(ImageKey(image.get()), + RasterSurfaceKey(imageSize, + DefaultSurfaceFlags(), + PlaybackType::eStatic)); + ASSERT_EQ(MatchType::NOT_FOUND, result.Type()); + } + + // Use GetFrame() to force a sync decode of the image, this time specifying + // FRAME_FIRST to ensure that we get a single-frame decode. + RefPtr animatedSurface = + image->GetFrame(imgIContainer::FRAME_FIRST, + imgIContainer::FLAG_SYNC_DECODE); + + // Ensure that we decoded the static version of the image. + { + LookupResult result = + SurfaceCache::Lookup(ImageKey(image.get()), + RasterSurfaceKey(imageSize, + DefaultSurfaceFlags(), + PlaybackType::eStatic)); + ASSERT_EQ(MatchType::EXACT, result.Type()); + EXPECT_TRUE(bool(result.Surface())); + } + + // Ensure that both frames of the animated version are still around. + { + LookupResult result = + SurfaceCache::Lookup(ImageKey(image.get()), + RasterSurfaceKey(imageSize, + DefaultSurfaceFlags(), + PlaybackType::eAnimated)); + ASSERT_EQ(MatchType::EXACT, result.Type()); + + EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0))); + EXPECT_TRUE(bool(result.Surface())); + + EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(1))); + EXPECT_TRUE(bool(result.Surface())); + } +} + +TEST_F(ImageDecoders, AnimatedGIFWithExtraImageSubBlocks) +{ + ImageTestCase testCase = ExtraImageSubBlocksAnimatedGIFTestCase(); + + // Verify that we can decode this test case and get two frames, even though + // there are extra image sub blocks between the first and second frame. The + // extra data shouldn't confuse the decoder or cause the decode to fail. + + // Create an image. + RefPtr image = + ImageFactory::CreateAnonymousImage(nsDependentCString(testCase.mMimeType)); + ASSERT_TRUE(!image->HasError()); + + nsCOMPtr inputStream = LoadFile(testCase.mPath); + ASSERT_TRUE(inputStream); + + // Figure out how much data we have. + uint64_t length; + nsresult rv = inputStream->Available(&length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + // Write the data into the image. + rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0, + static_cast(length)); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + // Let the image know we've sent all the data. + rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + RefPtr tracker = image->GetProgressTracker(); + tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE); + + // Use GetFrame() to force a sync decode of the image. + RefPtr surface = + image->GetFrame(imgIContainer::FRAME_CURRENT, + imgIContainer::FLAG_SYNC_DECODE); + + // Ensure that the image's metadata meets our expectations. + IntSize imageSize(0, 0); + rv = image->GetWidth(&imageSize.width); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + rv = image->GetHeight(&imageSize.height); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + + EXPECT_EQ(testCase.mSize.width, imageSize.width); + EXPECT_EQ(testCase.mSize.height, imageSize.height); + + Progress imageProgress = tracker->GetProgress(); + + EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false); + EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true); + + // Ensure that we decoded both frames of the image. + LookupResult result = + SurfaceCache::Lookup(ImageKey(image.get()), + RasterSurfaceKey(imageSize, + DefaultSurfaceFlags(), + PlaybackType::eAnimated)); + ASSERT_EQ(MatchType::EXACT, result.Type()); + + EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0))); + EXPECT_TRUE(bool(result.Surface())); + + EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(1))); + EXPECT_TRUE(bool(result.Surface())); +} + +TEST_F(ImageDecoders, TruncatedSmallGIFSingleChunk) +{ + CheckDecoderSingleChunk(TruncatedSmallGIFTestCase()); +} diff --git a/image/test/gtest/TestDeinterlacingFilter.cpp b/image/test/gtest/TestDeinterlacingFilter.cpp new file mode 100644 index 000000000..30cad7993 --- /dev/null +++ b/image/test/gtest/TestDeinterlacingFilter.cpp @@ -0,0 +1,672 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" + +#include "mozilla/gfx/2D.h" +#include "Common.h" +#include "Decoder.h" +#include "DecoderFactory.h" +#include "SourceBuffer.h" +#include "SurfaceFilters.h" +#include "SurfacePipe.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; + +template void +WithDeinterlacingFilter(const IntSize& aSize, + bool aProgressiveDisplay, + Func aFunc) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(bool(decoder)); + + WithFilterPipeline(decoder, Forward(aFunc), + DeinterlacingConfig { aProgressiveDisplay }, + SurfaceConfig { decoder, 0, aSize, + SurfaceFormat::B8G8R8A8, false }); +} + +template void +WithPalettedDeinterlacingFilter(const IntSize& aSize, + Func aFunc) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + WithFilterPipeline(decoder, Forward(aFunc), + DeinterlacingConfig { /* mProgressiveDisplay = */ true }, + PalettedSurfaceConfig { decoder, 0, aSize, + IntRect(0, 0, 100, 100), + SurfaceFormat::B8G8R8A8, 8, + false }); +} + +void +AssertConfiguringDeinterlacingFilterFails(const IntSize& aSize) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + AssertConfiguringPipelineFails(decoder, + DeinterlacingConfig { /* mProgressiveDisplay = */ true}, + SurfaceConfig { decoder, 0, aSize, + SurfaceFormat::B8G8R8A8, false }); +} + +class ImageDeinterlacingFilter : public ::testing::Test +{ +protected: + AutoInitializeImageLib mInit; +}; + +TEST_F(ImageDeinterlacingFilter, WritePixels100_100) +{ + WithDeinterlacingFilter(IntSize(100, 100), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100))); + }); +} + +TEST_F(ImageDeinterlacingFilter, WritePixels99_99) +{ + WithDeinterlacingFilter(IntSize(99, 99), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 99, 99)), + /* aInputRect = */ Some(IntRect(0, 0, 99, 99))); + }); +} + +TEST_F(ImageDeinterlacingFilter, WritePixels8_8) +{ + WithDeinterlacingFilter(IntSize(8, 8), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 8, 8)), + /* aInputRect = */ Some(IntRect(0, 0, 8, 8))); + }); +} + +TEST_F(ImageDeinterlacingFilter, WritePixels7_7) +{ + WithDeinterlacingFilter(IntSize(7, 7), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 7, 7)), + /* aInputRect = */ Some(IntRect(0, 0, 7, 7))); + }); +} + +TEST_F(ImageDeinterlacingFilter, WritePixels3_3) +{ + WithDeinterlacingFilter(IntSize(3, 3), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 3, 3)), + /* aInputRect = */ Some(IntRect(0, 0, 3, 3))); + }); +} + +TEST_F(ImageDeinterlacingFilter, WritePixels1_1) +{ + WithDeinterlacingFilter(IntSize(1, 1), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 1, 1)), + /* aInputRect = */ Some(IntRect(0, 0, 1, 1))); + }); +} + +TEST_F(ImageDeinterlacingFilter, PalettedWritePixels) +{ + WithPalettedDeinterlacingFilter(IntSize(100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckPalettedWritePixels(aDecoder, aFilter); + }); +} + +TEST_F(ImageDeinterlacingFilter, WritePixelsNonProgressiveOutput51_52) +{ + WithDeinterlacingFilter(IntSize(51, 52), /* aProgressiveDisplay = */ false, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Fill the image. The output should be green for even rows and red for odd + // rows but we need to write the rows in the order that the deinterlacer + // expects them. + uint32_t count = 0; + auto result = aFilter->WritePixels([&]() { + uint32_t row = count / 51; // Integer division. + ++count; + + // Note that we use a switch statement here, even though it's quite + // verbose, because it's useful to have the mappings between input and + // output rows available when debugging these tests. + + switch (row) { + // First pass. Output rows are positioned at 8n + 0. + case 0: // Output row 0. + case 1: // Output row 8. + case 2: // Output row 16. + case 3: // Output row 24. + case 4: // Output row 32. + case 5: // Output row 40. + case 6: // Output row 48. + return AsVariant(BGRAColor::Green().AsPixel()); + + // Second pass. Rows are positioned at 8n + 4. + case 7: // Output row 4. + case 8: // Output row 12. + case 9: // Output row 20. + case 10: // Output row 28. + case 11: // Output row 36. + case 12: // Output row 44. + return AsVariant(BGRAColor::Green().AsPixel()); + + // Third pass. Rows are positioned at 4n + 2. + case 13: // Output row 2. + case 14: // Output row 6. + case 15: // Output row 10. + case 16: // Output row 14. + case 17: // Output row 18. + case 18: // Output row 22. + case 19: // Output row 26. + case 20: // Output row 30. + case 21: // Output row 34. + case 22: // Output row 38. + case 23: // Output row 42. + case 24: // Output row 46. + case 25: // Output row 50. + return AsVariant(BGRAColor::Green().AsPixel()); + + // Fourth pass. Rows are positioned at 2n + 1. + case 26: // Output row 1. + case 27: // Output row 3. + case 28: // Output row 5. + case 29: // Output row 7. + case 30: // Output row 9. + case 31: // Output row 11. + case 32: // Output row 13. + case 33: // Output row 15. + case 34: // Output row 17. + case 35: // Output row 19. + case 36: // Output row 21. + case 37: // Output row 23. + case 38: // Output row 25. + case 39: // Output row 27. + case 40: // Output row 29. + case 41: // Output row 31. + case 42: // Output row 33. + case 43: // Output row 35. + case 44: // Output row 37. + case 45: // Output row 39. + case 46: // Output row 41. + case 47: // Output row 43. + case 48: // Output row 45. + case 49: // Output row 47. + case 50: // Output row 49. + case 51: // Output row 51. + return AsVariant(BGRAColor::Red().AsPixel()); + + default: + MOZ_ASSERT_UNREACHABLE("Unexpected row"); + return AsVariant(BGRAColor::Transparent().AsPixel()); + } + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(51u * 52u, count); + + AssertCorrectPipelineFinalState(aFilter, + IntRect(0, 0, 51, 52), + IntRect(0, 0, 51, 52)); + + // Check that the generated image is correct. As mentioned above, we expect + // even rows to be green and odd rows to be red. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSourceSurface(); + + for (uint32_t row = 0; row < 52; ++row) { + EXPECT_TRUE(RowsAreSolidColor(surface, row, 1, + row % 2 == 0 ? BGRAColor::Green() + : BGRAColor::Red())); + } + }); +} + +TEST_F(ImageDeinterlacingFilter, WritePixelsOutput20_20) +{ + WithDeinterlacingFilter(IntSize(20, 20), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Fill the image. The output should be green for even rows and red for odd + // rows but we need to write the rows in the order that the deinterlacer + // expects them. + uint32_t count = 0; + auto result = aFilter->WritePixels([&]() { + uint32_t row = count / 20; // Integer division. + ++count; + + // Note that we use a switch statement here, even though it's quite + // verbose, because it's useful to have the mappings between input and + // output rows available when debugging these tests. + + switch (row) { + // First pass. Output rows are positioned at 8n + 0. + case 0: // Output row 0. + case 1: // Output row 8. + case 2: // Output row 16. + return AsVariant(BGRAColor::Green().AsPixel()); + + // Second pass. Rows are positioned at 8n + 4. + case 3: // Output row 4. + case 4: // Output row 12. + return AsVariant(BGRAColor::Green().AsPixel()); + + // Third pass. Rows are positioned at 4n + 2. + case 5: // Output row 2. + case 6: // Output row 6. + case 7: // Output row 10. + case 8: // Output row 14. + case 9: // Output row 18. + return AsVariant(BGRAColor::Green().AsPixel()); + + // Fourth pass. Rows are positioned at 2n + 1. + case 10: // Output row 1. + case 11: // Output row 3. + case 12: // Output row 5. + case 13: // Output row 7. + case 14: // Output row 9. + case 15: // Output row 11. + case 16: // Output row 13. + case 17: // Output row 15. + case 18: // Output row 17. + case 19: // Output row 19. + return AsVariant(BGRAColor::Red().AsPixel()); + + default: + MOZ_ASSERT_UNREACHABLE("Unexpected row"); + return AsVariant(BGRAColor::Transparent().AsPixel()); + } + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(20u * 20u, count); + + AssertCorrectPipelineFinalState(aFilter, + IntRect(0, 0, 20, 20), + IntRect(0, 0, 20, 20)); + + // Check that the generated image is correct. As mentioned above, we expect + // even rows to be green and odd rows to be red. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSourceSurface(); + + for (uint32_t row = 0; row < 20; ++row) { + EXPECT_TRUE(RowsAreSolidColor(surface, row, 1, + row % 2 == 0 ? BGRAColor::Green() + : BGRAColor::Red())); + } + }); +} + +TEST_F(ImageDeinterlacingFilter, WritePixelsOutput7_7) +{ + WithDeinterlacingFilter(IntSize(7, 7), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Fill the image. The output should be a repeating pattern of two green + // rows followed by two red rows but we need to write the rows in the order + // that the deinterlacer expects them. + uint32_t count = 0; + auto result = aFilter->WritePixels([&]() { + uint32_t row = count / 7; // Integer division. + ++count; + + switch (row) { + // First pass. Output rows are positioned at 8n + 0. + case 0: // Output row 0. + return AsVariant(BGRAColor::Green().AsPixel()); + + // Second pass. Rows are positioned at 8n + 4. + case 1: // Output row 4. + return AsVariant(BGRAColor::Green().AsPixel()); + + // Third pass. Rows are positioned at 4n + 2. + case 2: // Output row 2. + case 3: // Output row 6. + return AsVariant(BGRAColor::Red().AsPixel()); + + // Fourth pass. Rows are positioned at 2n + 1. + case 4: // Output row 1. + return AsVariant(BGRAColor::Green().AsPixel()); + + case 5: // Output row 3. + return AsVariant(BGRAColor::Red().AsPixel()); + + case 6: // Output row 5. + return AsVariant(BGRAColor::Green().AsPixel()); + + default: + MOZ_ASSERT_UNREACHABLE("Unexpected row"); + return AsVariant(BGRAColor::Transparent().AsPixel()); + } + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(7u * 7u, count); + + AssertCorrectPipelineFinalState(aFilter, + IntRect(0, 0, 7, 7), + IntRect(0, 0, 7, 7)); + + // Check that the generated image is correct. As mentioned above, we expect + // two green rows, followed by two red rows, then two green rows, etc. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSourceSurface(); + + for (uint32_t row = 0; row < 7; ++row) { + BGRAColor color = row == 0 || row == 1 || row == 4 || row == 5 + ? BGRAColor::Green() + : BGRAColor::Red(); + EXPECT_TRUE(RowsAreSolidColor(surface, row, 1, color)); + } + }); +} + +TEST_F(ImageDeinterlacingFilter, WritePixelsOutput3_3) +{ + WithDeinterlacingFilter(IntSize(3, 3), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Fill the image. The output should be green, red, green in that order, but + // we need to write the rows in the order that the deinterlacer expects + // them. + uint32_t count = 0; + auto result = aFilter->WritePixels([&]() { + uint32_t row = count / 3; // Integer division. + ++count; + + switch (row) { + // First pass. Output rows are positioned at 8n + 0. + case 0: // Output row 0. + return AsVariant(BGRAColor::Green().AsPixel()); + + // Second pass. Rows are positioned at 8n + 4. + // No rows for this pass. + + // Third pass. Rows are positioned at 4n + 2. + case 1: // Output row 2. + return AsVariant(BGRAColor::Green().AsPixel()); + + // Fourth pass. Rows are positioned at 2n + 1. + case 2: // Output row 1. + return AsVariant(BGRAColor::Red().AsPixel()); + + default: + MOZ_ASSERT_UNREACHABLE("Unexpected row"); + return AsVariant(BGRAColor::Transparent().AsPixel()); + } + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(3u * 3u, count); + + AssertCorrectPipelineFinalState(aFilter, + IntRect(0, 0, 3, 3), + IntRect(0, 0, 3, 3)); + + // Check that the generated image is correct. As mentioned above, we expect + // green, red, green in that order. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSourceSurface(); + + for (uint32_t row = 0; row < 3; ++row) { + EXPECT_TRUE(RowsAreSolidColor(surface, row, 1, + row == 0 || row == 2 ? BGRAColor::Green() + : BGRAColor::Red())); + } + }); +} + +TEST_F(ImageDeinterlacingFilter, WritePixelsOutput1_1) +{ + WithDeinterlacingFilter(IntSize(1, 1), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Fill the image. The output should be a single red row. + uint32_t count = 0; + auto result = aFilter->WritePixels([&]() { + ++count; + return AsVariant(BGRAColor::Red().AsPixel()); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(1u, count); + + AssertCorrectPipelineFinalState(aFilter, + IntRect(0, 0, 1, 1), + IntRect(0, 0, 1, 1)); + + // Check that the generated image is correct. As mentioned above, we expect + // a single red row. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSourceSurface(); + + EXPECT_TRUE(RowsAreSolidColor(surface, 0, 1, BGRAColor::Red())); + }); +} + +void +WriteRowAndCheckInterlacerOutput(Decoder* aDecoder, + SurfaceFilter* aFilter, + BGRAColor aColor, + WriteState aNextState, + IntRect aInvalidRect, + uint32_t aFirstHaeberliRow, + uint32_t aLastHaeberliRow) +{ + uint32_t count = 0; + + auto result = aFilter->WritePixels([&]() -> NextPixel { + if (count < 7) { + ++count; + return AsVariant(aColor.AsPixel()); + } + return AsVariant(WriteState::NEED_MORE_DATA); + }); + + EXPECT_EQ(aNextState, result); + EXPECT_EQ(7u, count); + + // Assert that we got the expected invalidation region. + Maybe invalidRect = aFilter->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isSome()); + EXPECT_EQ(aInvalidRect, invalidRect->mInputSpaceRect); + EXPECT_EQ(aInvalidRect, invalidRect->mOutputSpaceRect); + + // Check that the portion of the image generated so far is correct. The rows + // from aFirstHaeberliRow to aLastHaeberliRow should be filled with aColor. + // Note that this is not the same as the set of rows in aInvalidRect, because + // after writing a row the deinterlacer seeks to the next row to write, which + // may involve copying previously-written rows in the buffer to the output + // even though they don't change in this pass. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSourceSurface(); + + for (uint32_t row = aFirstHaeberliRow; row <= aLastHaeberliRow; ++row) { + EXPECT_TRUE(RowsAreSolidColor(surface, row, 1, aColor)); + } +} + +TEST_F(ImageDeinterlacingFilter, WritePixelsIntermediateOutput7_7) +{ + WithDeinterlacingFilter(IntSize(7, 7), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Fill the image. The output should be a repeating pattern of two green + // rows followed by two red rows but we need to write the rows in the order + // that the deinterlacer expects them. + + // First pass. Output rows are positioned at 8n + 0. + + // Output row 0. The invalid rect is the entire image because this is the + // end of the first pass. + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(), + WriteState::NEED_MORE_DATA, + IntRect(0, 0, 7, 7), 0, 4); + + // Second pass. Rows are positioned at 8n + 4. + + // Output row 4. The invalid rect is the entire image because this is the + // end of the second pass. + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(), + WriteState::NEED_MORE_DATA, + IntRect(0, 0, 7, 7), 1, 4); + + // Third pass. Rows are positioned at 4n + 2. + + // Output row 2. The invalid rect contains the Haeberli rows for this output + // row (rows 2 and 3) as well as the rows that we copy from previous passes + // when seeking to the next output row (rows 4 and 5). + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Red(), + WriteState::NEED_MORE_DATA, + IntRect(0, 2, 7, 4), 2, 3); + + // Output row 6. The invalid rect is the entire image because this is the + // end of the third pass. + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Red(), + WriteState::NEED_MORE_DATA, + IntRect(0, 0, 7, 7), 6, 6); + + // Fourth pass. Rows are positioned at 2n + 1. + + // Output row 1. The invalid rect contains the Haeberli rows for this output + // row (just row 1) as well as the rows that we copy from previous passes + // when seeking to the next output row (row 2). + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(), + WriteState::NEED_MORE_DATA, + IntRect(0, 1, 7, 2), 1, 1); + + // Output row 3. The invalid rect contains the Haeberli rows for this output + // row (just row 3) as well as the rows that we copy from previous passes + // when seeking to the next output row (row 4). + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Red(), + WriteState::NEED_MORE_DATA, + IntRect(0, 3, 7, 2), 3, 3); + + // Output row 5. The invalid rect contains the Haeberli rows for this output + // row (just row 5) as well as the rows that we copy from previous passes + // when seeking to the next output row (row 6). + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(), + WriteState::FINISHED, + IntRect(0, 5, 7, 2), 5, 5); + + // Assert that we're in the expected final state. + EXPECT_TRUE(aFilter->IsSurfaceFinished()); + Maybe invalidRect = aFilter->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + // Check that the generated image is correct. As mentioned above, we expect + // two green rows, followed by two red rows, then two green rows, etc. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSourceSurface(); + + for (uint32_t row = 0; row < 7; ++row) { + BGRAColor color = row == 0 || row == 1 || row == 4 || row == 5 + ? BGRAColor::Green() + : BGRAColor::Red(); + EXPECT_TRUE(RowsAreSolidColor(surface, row, 1, color)); + } + }); +} + +TEST_F(ImageDeinterlacingFilter, WritePixelsNonProgressiveIntermediateOutput7_7) +{ + WithDeinterlacingFilter(IntSize(7, 7), /* aProgressiveDisplay = */ false, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Fill the image. The output should be a repeating pattern of two green + // rows followed by two red rows but we need to write the rows in the order + // that the deinterlacer expects them. + + // First pass. Output rows are positioned at 8n + 0. + + // Output row 0. The invalid rect is the entire image because this is the + // end of the first pass. + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(), + WriteState::NEED_MORE_DATA, + IntRect(0, 0, 7, 7), 0, 0); + + // Second pass. Rows are positioned at 8n + 4. + + // Output row 4. The invalid rect is the entire image because this is the + // end of the second pass. + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(), + WriteState::NEED_MORE_DATA, + IntRect(0, 0, 7, 7), 4, 4); + + // Third pass. Rows are positioned at 4n + 2. + + // Output row 2. The invalid rect contains the Haeberli rows for this output + // row (rows 2 and 3) as well as the rows that we copy from previous passes + // when seeking to the next output row (rows 4 and 5). + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Red(), + WriteState::NEED_MORE_DATA, + IntRect(0, 2, 7, 4), 2, 2); + + // Output row 6. The invalid rect is the entire image because this is the + // end of the third pass. + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Red(), + WriteState::NEED_MORE_DATA, + IntRect(0, 0, 7, 7), 6, 6); + + // Fourth pass. Rows are positioned at 2n + 1. + + // Output row 1. The invalid rect contains the Haeberli rows for this output + // row (just row 1) as well as the rows that we copy from previous passes + // when seeking to the next output row (row 2). + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(), + WriteState::NEED_MORE_DATA, + IntRect(0, 1, 7, 2), 1, 1); + + // Output row 3. The invalid rect contains the Haeberli rows for this output + // row (just row 3) as well as the rows that we copy from previous passes + // when seeking to the next output row (row 4). + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Red(), + WriteState::NEED_MORE_DATA, + IntRect(0, 3, 7, 2), 3, 3); + + // Output row 5. The invalid rect contains the Haeberli rows for this output + // row (just row 5) as well as the rows that we copy from previous passes + // when seeking to the next output row (row 6). + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(), + WriteState::FINISHED, + IntRect(0, 5, 7, 2), 5, 5); + + // Assert that we're in the expected final state. + EXPECT_TRUE(aFilter->IsSurfaceFinished()); + Maybe invalidRect = aFilter->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + // Check that the generated image is correct. As mentioned above, we expect + // two green rows, followed by two red rows, then two green rows, etc. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSourceSurface(); + + for (uint32_t row = 0; row < 7; ++row) { + BGRAColor color = row == 0 || row == 1 || row == 4 || row == 5 + ? BGRAColor::Green() + : BGRAColor::Red(); + EXPECT_TRUE(RowsAreSolidColor(surface, row, 1, color)); + } + }); +} + + +TEST_F(ImageDeinterlacingFilter, DeinterlacingFailsFor0_0) +{ + // A 0x0 input size is invalid, so configuration should fail. + AssertConfiguringDeinterlacingFilterFails(IntSize(0, 0)); +} + +TEST_F(ImageDeinterlacingFilter, DeinterlacingFailsForMinus1_Minus1) +{ + // A negative input size is invalid, so configuration should fail. + AssertConfiguringDeinterlacingFilterFails(IntSize(-1, -1)); +} diff --git a/image/test/gtest/TestDownscalingFilter.cpp b/image/test/gtest/TestDownscalingFilter.cpp new file mode 100644 index 000000000..596becab0 --- /dev/null +++ b/image/test/gtest/TestDownscalingFilter.cpp @@ -0,0 +1,231 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" + +#include "mozilla/gfx/2D.h" +#include "Common.h" +#include "Decoder.h" +#include "DecoderFactory.h" +#include "SourceBuffer.h" +#include "SurfaceFilters.h" +#include "SurfacePipe.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; + +template void +WithDownscalingFilter(const IntSize& aInputSize, + const IntSize& aOutputSize, + Func aFunc) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + WithFilterPipeline(decoder, Forward(aFunc), + DownscalingConfig { aInputSize, + SurfaceFormat::B8G8R8A8 }, + SurfaceConfig { decoder, 0, aOutputSize, + SurfaceFormat::B8G8R8A8, false }); +} + +void +AssertConfiguringDownscalingFilterFails(const IntSize& aInputSize, + const IntSize& aOutputSize) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + AssertConfiguringPipelineFails(decoder, + DownscalingConfig { aInputSize, + SurfaceFormat::B8G8R8A8 }, + SurfaceConfig { decoder, 0, aOutputSize, + SurfaceFormat::B8G8R8A8, false }); +} + +TEST(ImageDownscalingFilter, WritePixels100_100to99_99) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(99, 99), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 99, 99))); + }); +} + +TEST(ImageDownscalingFilter, WritePixels100_100to33_33) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(33, 33), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 33, 33))); + }); +} + +TEST(ImageDownscalingFilter, WritePixels100_100to1_1) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(1, 1), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 1, 1))); + }); +} + +TEST(ImageDownscalingFilter, WritePixels100_100to33_99) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(33, 99), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 33, 99))); + }); +} + +TEST(ImageDownscalingFilter, WritePixels100_100to99_33) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(99, 33), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 99, 33))); + }); +} + +TEST(ImageDownscalingFilter, WritePixels100_100to99_1) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(99, 1), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 99, 1))); + }); +} + +TEST(ImageDownscalingFilter, WritePixels100_100to1_99) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(1, 99), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 1, 99))); + }); +} + +TEST(ImageDownscalingFilter, DownscalingFailsFor100_100to101_101) +{ + // Upscaling is disallowed. + AssertConfiguringDownscalingFilterFails(IntSize(100, 100), IntSize(101, 101)); +} + +TEST(ImageDownscalingFilter, DownscalingFailsFor100_100to100_100) +{ + // "Scaling" to the same size is disallowed. + AssertConfiguringDownscalingFilterFails(IntSize(100, 100), IntSize(100, 100)); +} + +TEST(ImageDownscalingFilter, DownscalingFailsFor0_0toMinus1_Minus1) +{ + // A 0x0 input size is disallowed. + AssertConfiguringDownscalingFilterFails(IntSize(0, 0), IntSize(-1, -1)); +} + +TEST(ImageDownscalingFilter, DownscalingFailsForMinus1_Minus1toMinus2_Minus2) +{ + // A negative input size is disallowed. + AssertConfiguringDownscalingFilterFails(IntSize(-1, -1), IntSize(-2, -2)); +} + +TEST(ImageDownscalingFilter, DownscalingFailsFor100_100to0_0) +{ + // A 0x0 output size is disallowed. + AssertConfiguringDownscalingFilterFails(IntSize(100, 100), IntSize(0, 0)); +} + +TEST(ImageDownscalingFilter, DownscalingFailsFor100_100toMinus1_Minus1) +{ + // A negative output size is disallowed. + AssertConfiguringDownscalingFilterFails(IntSize(100, 100), IntSize(-1, -1)); +} + +TEST(ImageDownscalingFilter, WritePixelsOutput100_100to20_20) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(20, 20), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Fill the image. It consists of 25 lines of green, followed by 25 lines of + // red, followed by 25 lines of green, followed by 25 more lines of red. + uint32_t count = 0; + auto result = aFilter->WritePixels([&]() -> NextPixel { + uint32_t color = (count <= 25 * 100) || (count > 50 * 100 && count <= 75 * 100) + ? BGRAColor::Green().AsPixel() + : BGRAColor::Red().AsPixel(); + ++count; + return AsVariant(color); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u * 100u, count); + + AssertCorrectPipelineFinalState(aFilter, + IntRect(0, 0, 100, 100), + IntRect(0, 0, 20, 20)); + + // Check that the generated image is correct. Note that we skip rows near + // the transitions between colors, since the downscaler does not produce a + // sharp boundary at these points. Even some of the rows we test need a + // small amount of fuzz; this is just the nature of Lanczos downscaling. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSourceSurface(); + EXPECT_TRUE(RowsAreSolidColor(surface, 0, 4, BGRAColor::Green(), /* aFuzz = */ 2)); + EXPECT_TRUE(RowsAreSolidColor(surface, 6, 3, BGRAColor::Red(), /* aFuzz = */ 3)); + EXPECT_TRUE(RowsAreSolidColor(surface, 11, 3, BGRAColor::Green(), /* aFuzz = */ 3)); + EXPECT_TRUE(RowsAreSolidColor(surface, 16, 4, BGRAColor::Red(), /* aFuzz = */ 3)); + }); +} + +TEST(ImageDownscalingFilter, WritePixelsOutput100_100to10_20) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(10, 20), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Fill the image. It consists of 25 lines of green, followed by 25 lines of + // red, followed by 25 lines of green, followed by 25 more lines of red. + uint32_t count = 0; + auto result = aFilter->WritePixels([&]() -> NextPixel { + uint32_t color = (count <= 25 * 100) || (count > 50 * 100 && count <= 75 * 100) + ? BGRAColor::Green().AsPixel() + : BGRAColor::Red().AsPixel(); + ++count; + return AsVariant(color); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u * 100u, count); + + AssertCorrectPipelineFinalState(aFilter, + IntRect(0, 0, 100, 100), + IntRect(0, 0, 10, 20)); + + // Check that the generated image is correct. Note that we skip rows near + // the transitions between colors, since the downscaler does not produce a + // sharp boundary at these points. Even some of the rows we test need a + // small amount of fuzz; this is just the nature of Lanczos downscaling. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSourceSurface(); + EXPECT_TRUE(RowsAreSolidColor(surface, 0, 4, BGRAColor::Green(), /* aFuzz = */ 2)); + EXPECT_TRUE(RowsAreSolidColor(surface, 6, 3, BGRAColor::Red(), /* aFuzz = */ 3)); + EXPECT_TRUE(RowsAreSolidColor(surface, 11, 3, BGRAColor::Green(), /* aFuzz = */ 3)); + EXPECT_TRUE(RowsAreSolidColor(surface, 16, 4, BGRAColor::Red(), /* aFuzz = */ 3)); + }); +} + +TEST(ImageDownscalingFilter, ConfiguringPalettedDownscaleFails) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + // DownscalingFilter does not support paletted images, so configuration should + // fail. + AssertConfiguringPipelineFails(decoder, + DownscalingConfig { IntSize(100, 100), + SurfaceFormat::B8G8R8A8 }, + PalettedSurfaceConfig { decoder, 0, IntSize(20, 20), + IntRect(0, 0, 20, 20), + SurfaceFormat::B8G8R8A8, 8, + false }); +} diff --git a/image/test/gtest/TestDownscalingFilterNoSkia.cpp b/image/test/gtest/TestDownscalingFilterNoSkia.cpp new file mode 100644 index 000000000..c62ca018d --- /dev/null +++ b/image/test/gtest/TestDownscalingFilterNoSkia.cpp @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" + +#include "mozilla/gfx/2D.h" +#include "Decoder.h" +#include "DecoderFactory.h" +#include "SourceBuffer.h" +#include "SurfacePipe.h" + +// We want to ensure that we're testing the non-Skia fallback version of +// DownscalingFilter, but there are two issues: +// (1) We don't know whether Skia is currently enabled. +// (2) If we force disable it, the disabled version will get linked into the +// binary and will cause the tests in TestDownscalingFilter to fail. +// To avoid these problems, we ensure that MOZ_ENABLE_SKIA is defined when +// including DownscalingFilter.h, and we use the preprocessor to redefine the +// DownscalingFilter class to DownscalingFilterNoSkia. + +#define DownscalingFilter DownscalingFilterNoSkia + +#ifdef MOZ_ENABLE_SKIA + +#undef MOZ_ENABLE_SKIA +#include "Common.h" +#include "DownscalingFilter.h" +#define MOZ_ENABLE_SKIA + +#else + +#include "Common.h" +#include "DownscalingFilter.h" + +#endif + +#undef DownscalingFilter + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; + +TEST(ImageDownscalingFilter, NoSkia) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(bool(decoder)); + + // Configuring a DownscalingFilter should fail without Skia. + AssertConfiguringPipelineFails(decoder, + DownscalingConfig { IntSize(100, 100), + SurfaceFormat::B8G8R8A8 }, + SurfaceConfig { decoder, 0, IntSize(50, 50), + SurfaceFormat::B8G8R8A8, false }); +} diff --git a/image/test/gtest/TestMetadata.cpp b/image/test/gtest/TestMetadata.cpp new file mode 100644 index 000000000..9f3a64898 --- /dev/null +++ b/image/test/gtest/TestMetadata.cpp @@ -0,0 +1,255 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" + +#include "Common.h" +#include "Decoder.h" +#include "DecoderFactory.h" +#include "decoders/nsBMPDecoder.h" +#include "IDecodingTask.h" +#include "imgIContainer.h" +#include "imgITools.h" +#include "ImageFactory.h" +#include "mozilla/gfx/2D.h" +#include "nsComponentManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsIInputStream.h" +#include "nsIRunnable.h" +#include "nsIThread.h" +#include "mozilla/RefPtr.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsThreadUtils.h" +#include "ProgressTracker.h" +#include "SourceBuffer.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; + +enum class BMPWithinICO +{ + NO, + YES +}; + +static void +CheckMetadata(const ImageTestCase& aTestCase, + BMPWithinICO aBMPWithinICO = BMPWithinICO::NO) +{ + nsCOMPtr inputStream = LoadFile(aTestCase.mPath); + ASSERT_TRUE(inputStream != nullptr); + + // Figure out how much data we have. + uint64_t length; + nsresult rv = inputStream->Available(&length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + // Write the data into a SourceBuffer. + NotNull> sourceBuffer = WrapNotNull(new SourceBuffer()); + sourceBuffer->ExpectLength(length); + rv = sourceBuffer->AppendFromInputStream(inputStream, length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + sourceBuffer->Complete(NS_OK); + + // Create a metadata decoder. + DecoderType decoderType = + DecoderFactory::GetDecoderType(aTestCase.mMimeType); + RefPtr decoder = + DecoderFactory::CreateAnonymousMetadataDecoder(decoderType, sourceBuffer); + ASSERT_TRUE(decoder != nullptr); + RefPtr task = new AnonymousDecodingTask(WrapNotNull(decoder)); + + if (aBMPWithinICO == BMPWithinICO::YES) { + static_cast(decoder.get())->SetIsWithinICO(); + } + + // Run the metadata decoder synchronously. + task->Run(); + + // Ensure that the metadata decoder didn't make progress it shouldn't have + // (which would indicate that it decoded past the header of the image). + Progress metadataProgress = decoder->TakeProgress(); + EXPECT_TRUE(0 == (metadataProgress & ~(FLAG_SIZE_AVAILABLE | + FLAG_HAS_TRANSPARENCY | + FLAG_IS_ANIMATED))); + + // If the test case is corrupt, assert what we can and return early. + if (aTestCase.mFlags & TEST_CASE_HAS_ERROR) { + EXPECT_TRUE(decoder->GetDecodeDone()); + EXPECT_TRUE(decoder->HasError()); + return; + } + + EXPECT_TRUE(decoder->GetDecodeDone() && !decoder->HasError()); + + // Check that we got the expected metadata. + EXPECT_TRUE(metadataProgress & FLAG_SIZE_AVAILABLE); + + IntSize metadataSize = decoder->Size(); + EXPECT_EQ(aTestCase.mSize.width, metadataSize.width); + EXPECT_EQ(aTestCase.mSize.height, metadataSize.height); + + bool expectTransparency = aBMPWithinICO == BMPWithinICO::YES + ? true + : bool(aTestCase.mFlags & TEST_CASE_IS_TRANSPARENT); + EXPECT_EQ(expectTransparency, bool(metadataProgress & FLAG_HAS_TRANSPARENCY)); + + EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_ANIMATED), + bool(metadataProgress & FLAG_IS_ANIMATED)); + + // Create a full decoder, so we can compare the result. + decoder = + DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, Nothing(), + DefaultSurfaceFlags()); + ASSERT_TRUE(decoder != nullptr); + task = new AnonymousDecodingTask(WrapNotNull(decoder)); + + if (aBMPWithinICO == BMPWithinICO::YES) { + static_cast(decoder.get())->SetIsWithinICO(); + } + + // Run the full decoder synchronously. + task->Run(); + + EXPECT_TRUE(decoder->GetDecodeDone() && !decoder->HasError()); + Progress fullProgress = decoder->TakeProgress(); + + // If the metadata decoder set a progress bit, the full decoder should also + // have set the same bit. + EXPECT_EQ(fullProgress, metadataProgress | fullProgress); + + // The full decoder and the metadata decoder should agree on the image's size. + IntSize fullSize = decoder->Size(); + EXPECT_EQ(metadataSize.width, fullSize.width); + EXPECT_EQ(metadataSize.height, fullSize.height); + + // We should not discover transparency during the full decode that we didn't + // discover during the metadata decode, unless the image is animated. + EXPECT_TRUE(!(fullProgress & FLAG_HAS_TRANSPARENCY) || + (metadataProgress & FLAG_HAS_TRANSPARENCY) || + (fullProgress & FLAG_IS_ANIMATED)); +} + +class ImageDecoderMetadata : public ::testing::Test +{ +protected: + AutoInitializeImageLib mInit; +}; + +TEST_F(ImageDecoderMetadata, PNG) { CheckMetadata(GreenPNGTestCase()); } +TEST_F(ImageDecoderMetadata, TransparentPNG) { CheckMetadata(TransparentPNGTestCase()); } +TEST_F(ImageDecoderMetadata, GIF) { CheckMetadata(GreenGIFTestCase()); } +TEST_F(ImageDecoderMetadata, TransparentGIF) { CheckMetadata(TransparentGIFTestCase()); } +TEST_F(ImageDecoderMetadata, JPG) { CheckMetadata(GreenJPGTestCase()); } +TEST_F(ImageDecoderMetadata, BMP) { CheckMetadata(GreenBMPTestCase()); } +TEST_F(ImageDecoderMetadata, ICO) { CheckMetadata(GreenICOTestCase()); } +TEST_F(ImageDecoderMetadata, Icon) { CheckMetadata(GreenIconTestCase()); } + +TEST_F(ImageDecoderMetadata, AnimatedGIF) +{ + CheckMetadata(GreenFirstFrameAnimatedGIFTestCase()); +} + +TEST_F(ImageDecoderMetadata, AnimatedPNG) +{ + CheckMetadata(GreenFirstFrameAnimatedPNGTestCase()); +} + +TEST_F(ImageDecoderMetadata, FirstFramePaddingGIF) +{ + CheckMetadata(FirstFramePaddingGIFTestCase()); +} + +TEST_F(ImageDecoderMetadata, TransparentIfWithinICOBMPNotWithinICO) +{ + CheckMetadata(TransparentIfWithinICOBMPTestCase(TEST_CASE_DEFAULT_FLAGS), + BMPWithinICO::NO); +} + +TEST_F(ImageDecoderMetadata, TransparentIfWithinICOBMPWithinICO) +{ + CheckMetadata(TransparentIfWithinICOBMPTestCase(TEST_CASE_IS_TRANSPARENT), + BMPWithinICO::YES); +} + +TEST_F(ImageDecoderMetadata, RLE4BMP) { CheckMetadata(RLE4BMPTestCase()); } +TEST_F(ImageDecoderMetadata, RLE8BMP) { CheckMetadata(RLE8BMPTestCase()); } + +TEST_F(ImageDecoderMetadata, Corrupt) { CheckMetadata(CorruptTestCase()); } + +TEST_F(ImageDecoderMetadata, NoFrameDelayGIF) +{ + CheckMetadata(NoFrameDelayGIFTestCase()); +} + +TEST_F(ImageDecoderMetadata, NoFrameDelayGIFFullDecode) +{ + ImageTestCase testCase = NoFrameDelayGIFTestCase(); + + // The previous test (NoFrameDelayGIF) verifies that we *don't* detect that + // this test case is animated, because it has a zero frame delay for the first + // frame. This test verifies that when we do a full decode, we detect the + // animation at that point and successfully decode all the frames. + + // Create an image. + RefPtr image = + ImageFactory::CreateAnonymousImage(nsDependentCString(testCase.mMimeType)); + ASSERT_TRUE(!image->HasError()); + + nsCOMPtr inputStream = LoadFile(testCase.mPath); + ASSERT_TRUE(inputStream != nullptr); + + // Figure out how much data we have. + uint64_t length; + nsresult rv = inputStream->Available(&length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + // Write the data into the image. + rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0, + static_cast(length)); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + // Let the image know we've sent all the data. + rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + RefPtr tracker = image->GetProgressTracker(); + tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE); + + // Use GetFrame() to force a sync decode of the image. + RefPtr surface = + image->GetFrame(imgIContainer::FRAME_CURRENT, + imgIContainer::FLAG_SYNC_DECODE); + + // Ensure that the image's metadata meets our expectations. + IntSize imageSize(0, 0); + rv = image->GetWidth(&imageSize.width); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + rv = image->GetHeight(&imageSize.height); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + + EXPECT_EQ(testCase.mSize.width, imageSize.width); + EXPECT_EQ(testCase.mSize.height, imageSize.height); + + Progress imageProgress = tracker->GetProgress(); + + EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false); + EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true); + + // Ensure that we decoded both frames of the image. + LookupResult result = + SurfaceCache::Lookup(ImageKey(image.get()), + RasterSurfaceKey(imageSize, + DefaultSurfaceFlags(), + PlaybackType::eAnimated)); + ASSERT_EQ(MatchType::EXACT, result.Type()); + + EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0))); + EXPECT_TRUE(bool(result.Surface())); + + EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(1))); + EXPECT_TRUE(bool(result.Surface())); +} diff --git a/image/test/gtest/TestRemoveFrameRectFilter.cpp b/image/test/gtest/TestRemoveFrameRectFilter.cpp new file mode 100644 index 000000000..e1def590e --- /dev/null +++ b/image/test/gtest/TestRemoveFrameRectFilter.cpp @@ -0,0 +1,327 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" + +#include "mozilla/gfx/2D.h" +#include "Common.h" +#include "Decoder.h" +#include "DecoderFactory.h" +#include "SourceBuffer.h" +#include "SurfaceFilters.h" +#include "SurfacePipe.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; + +template void +WithRemoveFrameRectFilter(const IntSize& aSize, + const IntRect& aFrameRect, + Func aFunc) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + WithFilterPipeline(decoder, Forward(aFunc), + RemoveFrameRectConfig { aFrameRect }, + SurfaceConfig { decoder, 0, aSize, + SurfaceFormat::B8G8R8A8, false }); +} + +void +AssertConfiguringRemoveFrameRectFilterFails(const IntSize& aSize, + const IntRect& aFrameRect) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + AssertConfiguringPipelineFails(decoder, + RemoveFrameRectConfig { aFrameRect }, + SurfaceConfig { decoder, 0, aSize, + SurfaceFormat::B8G8R8A8, false }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_0_0_100_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(0, 0, 100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 100, 100))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_0_0_0_0) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(0, 0, 0, 0), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_Minus50_50_0_0) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(-50, 50, 0, 0), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_50_Minus50_0_0) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(50, -50, 0, 0), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_150_50_0_0) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(150, 50, 0, 0), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_50_150_0_0) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(50, 150, 0, 0), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_200_200_100_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(200, 200, 100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Note that aInputRect is zero-size because RemoveFrameRectFilter ignores + // trailing rows that don't show up in the output. (Leading rows + // unfortunately can't be ignored.) + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_Minus200_25_100_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(-200, 25, 100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Note that aInputRect is zero-size because RemoveFrameRectFilter ignores + // trailing rows that don't show up in the output. (Leading rows + // unfortunately can't be ignored.) + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_25_Minus200_100_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(25, -200, 100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Note that aInputRect is zero-size because RemoveFrameRectFilter ignores + // trailing rows that don't show up in the output. (Leading rows + // unfortunately can't be ignored.) + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_200_25_100_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(200, 25, 100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Note that aInputRect is zero-size because RemoveFrameRectFilter ignores + // trailing rows that don't show up in the output. (Leading rows + // unfortunately can't be ignored.) + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_25_200_100_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(25, 200, 100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Note that aInputRect is zero-size because RemoveFrameRectFilter ignores + // trailing rows that don't show up in the output. (Leading rows + // unfortunately can't be ignored.) + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_Minus200_Minus200_100_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(-200, -200, 100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_Minus50_Minus50_100_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(-50, -50, 100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 100, 100)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 50, 50))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_Minus50_25_100_50) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(-50, 25, 100, 50), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 100, 50)), + /* aOutputWriteRect = */ Some(IntRect(0, 25, 50, 50))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_25_Minus50_50_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(25, -50, 50, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 50, 100)), + /* aOutputWriteRect = */ Some(IntRect(25, 0, 50, 50))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_50_25_100_50) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(50, 25, 100, 50), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 100, 50)), + /* aOutputWriteRect = */ Some(IntRect(50, 25, 50, 50))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_25_50_50_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(25, 50, 50, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Note that aInputRect is 50x50 because RemoveFrameRectFilter ignores + // trailing rows that don't show up in the output. (Leading rows + // unfortunately can't be ignored.) + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 50, 50)), + /* aOutputWriteRect = */ Some(IntRect(25, 50, 50, 100))); + }); +} + +TEST(ImageRemoveFrameRectFilter, RemoveFrameRectFailsFor0_0_to_0_0_100_100) +{ + // A zero-size image is disallowed. + AssertConfiguringRemoveFrameRectFilterFails(IntSize(0, 0), + IntRect(0, 0, 100, 100)); +} + +TEST(ImageRemoveFrameRectFilter, RemoveFrameRectFailsForMinus1_Minus1_to_0_0_100_100) +{ + // A negative-size image is disallowed. + AssertConfiguringRemoveFrameRectFilterFails(IntSize(-1, -1), + IntRect(0, 0, 100, 100)); +} + +TEST(ImageRemoveFrameRectFilter, RemoveFrameRectFailsFor100_100_to_0_0_0_0) +{ + // A zero size frame rect is disallowed. + AssertConfiguringRemoveFrameRectFilterFails(IntSize(100, 100), + IntRect(0, 0, -1, -1)); +} + +TEST(ImageRemoveFrameRectFilter, RemoveFrameRectFailsFor100_100_to_0_0_Minus1_Minus1) +{ + // A negative size frame rect is disallowed. + AssertConfiguringRemoveFrameRectFilterFails(IntSize(100, 100), + IntRect(0, 0, -1, -1)); +} + +TEST(ImageRemoveFrameRectFilter, ConfiguringPalettedRemoveFrameRectFails) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + // RemoveFrameRectFilter does not support paletted images, so configuration + // should fail. + AssertConfiguringPipelineFails(decoder, + RemoveFrameRectConfig { IntRect(0, 0, 50, 50) }, + PalettedSurfaceConfig { decoder, 0, IntSize(100, 100), + IntRect(0, 0, 50, 50), + SurfaceFormat::B8G8R8A8, 8, + false }); +} diff --git a/image/test/gtest/TestSourceBuffer.cpp b/image/test/gtest/TestSourceBuffer.cpp new file mode 100644 index 000000000..05a88093f --- /dev/null +++ b/image/test/gtest/TestSourceBuffer.cpp @@ -0,0 +1,810 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" + +#include +#include + +#include "mozilla/Move.h" +#include "SourceBuffer.h" +#include "SurfaceCache.h" + +using namespace mozilla; +using namespace mozilla::image; + +using std::min; + +void +ExpectChunkAndByteCount(const SourceBufferIterator& aIterator, + uint32_t aChunks, + size_t aBytes) +{ + EXPECT_EQ(aChunks, aIterator.ChunkCount()); + EXPECT_EQ(aBytes, aIterator.ByteCount()); +} + +void +ExpectRemainingBytes(const SourceBufferIterator& aIterator, size_t aBytes) +{ + EXPECT_TRUE(aIterator.RemainingBytesIsNoMoreThan(aBytes)); + EXPECT_TRUE(aIterator.RemainingBytesIsNoMoreThan(aBytes + 1)); + + if (aBytes > 0) { + EXPECT_FALSE(aIterator.RemainingBytesIsNoMoreThan(0)); + EXPECT_FALSE(aIterator.RemainingBytesIsNoMoreThan(aBytes - 1)); + } +} + +char +GenerateByte(size_t aIndex) +{ + uint8_t byte = aIndex % 256; + return *reinterpret_cast(&byte); +} + +void +GenerateData(char* aOutput, size_t aOffset, size_t aLength) +{ + for (size_t i = 0; i < aLength; ++i) { + aOutput[i] = GenerateByte(aOffset + i); + } +} + +void +GenerateData(char* aOutput, size_t aLength) +{ + GenerateData(aOutput, 0, aLength); +} + +void +CheckData(const char* aData, size_t aOffset, size_t aLength) +{ + for (size_t i = 0; i < aLength; ++i) { + ASSERT_EQ(GenerateByte(aOffset + i), aData[i]); + } +} + +enum class AdvanceMode +{ + eAdvanceAsMuchAsPossible, + eAdvanceByLengthExactly +}; + +class ImageSourceBuffer : public ::testing::Test +{ +public: + ImageSourceBuffer() + : mSourceBuffer(new SourceBuffer) + , mExpectNoResume(new ExpectNoResume) + , mCountResumes(new CountResumes) + { + GenerateData(mData, sizeof(mData)); + EXPECT_FALSE(mSourceBuffer->IsComplete()); + } + +protected: + void CheckedAppendToBuffer(const char* aData, size_t aLength) + { + EXPECT_TRUE(NS_SUCCEEDED(mSourceBuffer->Append(aData, aLength))); + } + + void CheckedAppendToBufferLastByteForLength(size_t aLength) + { + const char lastByte = GenerateByte(aLength); + CheckedAppendToBuffer(&lastByte, 1); + } + + void CheckedAppendToBufferInChunks(size_t aChunkLength, size_t aTotalLength) + { + char* data = new char[aChunkLength]; + + size_t bytesWritten = 0; + while (bytesWritten < aTotalLength) { + GenerateData(data, bytesWritten, aChunkLength); + size_t toWrite = min(aChunkLength, aTotalLength - bytesWritten); + CheckedAppendToBuffer(data, toWrite); + bytesWritten += toWrite; + } + + delete[] data; + } + + void CheckedCompleteBuffer(nsresult aCompletionStatus = NS_OK) + { + mSourceBuffer->Complete(aCompletionStatus); + EXPECT_TRUE(mSourceBuffer->IsComplete()); + } + + void CheckedCompleteBuffer(SourceBufferIterator& aIterator, + size_t aLength, + nsresult aCompletionStatus = NS_OK) + { + CheckedCompleteBuffer(aCompletionStatus); + ExpectRemainingBytes(aIterator, aLength); + } + + void CheckedAdvanceIteratorStateOnly(SourceBufferIterator& aIterator, + size_t aLength, + uint32_t aChunks, + size_t aTotalLength, + AdvanceMode aAdvanceMode + = AdvanceMode::eAdvanceAsMuchAsPossible) + { + const size_t advanceBy = aAdvanceMode == AdvanceMode::eAdvanceAsMuchAsPossible + ? SIZE_MAX + : aLength; + + auto state = aIterator.AdvanceOrScheduleResume(advanceBy, mExpectNoResume); + ASSERT_EQ(SourceBufferIterator::READY, state); + EXPECT_TRUE(aIterator.Data()); + EXPECT_EQ(aLength, aIterator.Length()); + + ExpectChunkAndByteCount(aIterator, aChunks, aTotalLength); + } + + void CheckedAdvanceIteratorStateOnly(SourceBufferIterator& aIterator, + size_t aLength) + { + CheckedAdvanceIteratorStateOnly(aIterator, aLength, 1, aLength); + } + + void CheckedAdvanceIterator(SourceBufferIterator& aIterator, + size_t aLength, + uint32_t aChunks, + size_t aTotalLength, + AdvanceMode aAdvanceMode + = AdvanceMode::eAdvanceAsMuchAsPossible) + { + // Check that the iterator is in the expected state. + CheckedAdvanceIteratorStateOnly(aIterator, aLength, aChunks, + aTotalLength, aAdvanceMode); + + // Check that we read the expected data. To do this, we need to compute our + // offset in the SourceBuffer, but fortunately that's pretty easy: it's the + // total number of bytes the iterator has advanced through, minus the length + // of the current chunk. + const size_t offset = aIterator.ByteCount() - aIterator.Length(); + CheckData(aIterator.Data(), offset, aIterator.Length()); + } + + void CheckedAdvanceIterator(SourceBufferIterator& aIterator, size_t aLength) + { + CheckedAdvanceIterator(aIterator, aLength, 1, aLength); + } + + void CheckIteratorMustWait(SourceBufferIterator& aIterator, + IResumable* aOnResume) + { + auto state = aIterator.AdvanceOrScheduleResume(1, aOnResume); + EXPECT_EQ(SourceBufferIterator::WAITING, state); + } + + void CheckIteratorIsComplete(SourceBufferIterator& aIterator, + uint32_t aChunks, + size_t aTotalLength, + nsresult aCompletionStatus = NS_OK) + { + ASSERT_TRUE(mSourceBuffer->IsComplete()); + auto state = aIterator.AdvanceOrScheduleResume(1, mExpectNoResume); + ASSERT_EQ(SourceBufferIterator::COMPLETE, state); + EXPECT_EQ(aCompletionStatus, aIterator.CompletionStatus()); + ExpectRemainingBytes(aIterator, 0); + ExpectChunkAndByteCount(aIterator, aChunks, aTotalLength); + } + + void CheckIteratorIsComplete(SourceBufferIterator& aIterator, + size_t aTotalLength) + { + CheckIteratorIsComplete(aIterator, 1, aTotalLength); + } + + AutoInitializeImageLib mInit; + char mData[9]; + RefPtr mSourceBuffer; + RefPtr mExpectNoResume; + RefPtr mCountResumes; +}; + +TEST_F(ImageSourceBuffer, InitialState) +{ + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + + // RemainingBytesIsNoMoreThan() should always return false in the initial + // state, since we can't know the answer until Complete() has been called. + EXPECT_FALSE(iterator.RemainingBytesIsNoMoreThan(0)); + EXPECT_FALSE(iterator.RemainingBytesIsNoMoreThan(SIZE_MAX)); + + // We haven't advanced our iterator at all, so its counters should be zero. + ExpectChunkAndByteCount(iterator, 0, 0); + + // Attempt to advance; we should fail, and end up in the WAITING state. We + // expect no resumes because we don't actually append anything to the + // SourceBuffer in this test. + CheckIteratorMustWait(iterator, mExpectNoResume); +} + +TEST_F(ImageSourceBuffer, ZeroLengthBufferAlwaysFails) +{ + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + + // Complete the buffer without writing to it, providing a successful + // completion status. + CheckedCompleteBuffer(iterator, 0); + + // Completing a buffer without writing to it results in an automatic failure; + // make sure that the actual completion status we get from the iterator + // reflects this. + CheckIteratorIsComplete(iterator, 0, 0, NS_ERROR_FAILURE); +} + +TEST_F(ImageSourceBuffer, CompleteSuccess) +{ + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + + // Write a single byte to the buffer and complete the buffer. (We have to + // write at least one byte because completing a zero length buffer always + // fails; see the ZeroLengthBufferAlwaysFails test.) + CheckedAppendToBuffer(mData, 1); + CheckedCompleteBuffer(iterator, 1); + + // We should be able to advance once (to read the single byte) and then should + // reach the COMPLETE state with a successful status. + CheckedAdvanceIterator(iterator, 1); + CheckIteratorIsComplete(iterator, 1); +} + +TEST_F(ImageSourceBuffer, CompleteFailure) +{ + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + + // Write a single byte to the buffer and complete the buffer. (We have to + // write at least one byte because completing a zero length buffer always + // fails; see the ZeroLengthBufferAlwaysFails test.) + CheckedAppendToBuffer(mData, 1); + CheckedCompleteBuffer(iterator, 1, NS_ERROR_FAILURE); + + // Advance the iterator. Because a failing status is propagated to the + // iterator as soon as it advances, we won't be able to read the single byte + // that we wrote above; we go directly into the COMPLETE state. + CheckIteratorIsComplete(iterator, 0, 0, NS_ERROR_FAILURE); +} + +TEST_F(ImageSourceBuffer, Append) +{ + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + + // Write test data to the buffer. + EXPECT_TRUE(NS_SUCCEEDED(mSourceBuffer->ExpectLength(sizeof(mData)))); + CheckedAppendToBuffer(mData, sizeof(mData)); + CheckedCompleteBuffer(iterator, sizeof(mData)); + + // Verify that we can read it back via the iterator, and that the final state + // is what we expect. + CheckedAdvanceIterator(iterator, sizeof(mData)); + CheckIteratorIsComplete(iterator, sizeof(mData)); +} + +TEST_F(ImageSourceBuffer, HugeAppendFails) +{ + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + + // We should fail to append anything bigger than what the SurfaceCache can + // hold, so use the SurfaceCache's maximum capacity to calculate what a + // "massive amount of data" (see below) consists of on this platform. + ASSERT_LT(SurfaceCache::MaximumCapacity(), SIZE_MAX); + const size_t hugeSize = SurfaceCache::MaximumCapacity() + 1; + + // Attempt to write a massive amount of data and verify that it fails. (We'd + // get a buffer overrun during the test if it succeeds, but if it succeeds + // that's the least of our problems.) + EXPECT_TRUE(NS_FAILED(mSourceBuffer->Append(mData, hugeSize))); + EXPECT_TRUE(mSourceBuffer->IsComplete()); + CheckIteratorIsComplete(iterator, 0, 0, NS_ERROR_OUT_OF_MEMORY); +} + +TEST_F(ImageSourceBuffer, AppendFromInputStream) +{ + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + + // Construct an input stream with some arbitrary data. (We use test data from + // one of the decoder tests.) + nsCOMPtr inputStream = LoadFile(GreenPNGTestCase().mPath); + ASSERT_TRUE(inputStream != nullptr); + + // Figure out how much data we have. + uint64_t length; + ASSERT_TRUE(NS_SUCCEEDED(inputStream->Available(&length))); + + // Write test data to the buffer. + EXPECT_TRUE(NS_SUCCEEDED(mSourceBuffer->AppendFromInputStream(inputStream, + length))); + CheckedCompleteBuffer(iterator, length); + + // Verify that the iterator sees the appropriate amount of data. + CheckedAdvanceIteratorStateOnly(iterator, length); + CheckIteratorIsComplete(iterator, length); +} + +TEST_F(ImageSourceBuffer, AppendAfterComplete) +{ + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + + // Write test data to the buffer. + EXPECT_TRUE(NS_SUCCEEDED(mSourceBuffer->ExpectLength(sizeof(mData)))); + CheckedAppendToBuffer(mData, sizeof(mData)); + CheckedCompleteBuffer(iterator, sizeof(mData)); + + // Verify that we can read it back via the iterator, and that the final state + // is what we expect. + CheckedAdvanceIterator(iterator, sizeof(mData)); + CheckIteratorIsComplete(iterator, sizeof(mData)); + + // Write more data to the completed buffer. + EXPECT_TRUE(NS_FAILED(mSourceBuffer->Append(mData, sizeof(mData)))); + + // Try to read with a new iterator and verify that the new data got ignored. + SourceBufferIterator iterator2 = mSourceBuffer->Iterator(); + CheckedAdvanceIterator(iterator2, sizeof(mData)); + CheckIteratorIsComplete(iterator2, sizeof(mData)); +} + +TEST_F(ImageSourceBuffer, MinChunkCapacity) +{ + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + + // Write test data to the buffer using many small appends. Since + // ExpectLength() isn't being called, we should be able to write up to + // SourceBuffer::MIN_CHUNK_CAPACITY bytes without a second chunk being + // allocated. + CheckedAppendToBufferInChunks(10, SourceBuffer::MIN_CHUNK_CAPACITY); + + // Verify that the iterator sees the appropriate amount of data. + CheckedAdvanceIterator(iterator, SourceBuffer::MIN_CHUNK_CAPACITY); + + // Write one more byte; we expect to see that it triggers an allocation. + CheckedAppendToBufferLastByteForLength(SourceBuffer::MIN_CHUNK_CAPACITY); + CheckedCompleteBuffer(iterator, 1); + + // Verify that the iterator sees the new byte and a new chunk has been + // allocated. + CheckedAdvanceIterator(iterator, 1, 2, SourceBuffer::MIN_CHUNK_CAPACITY + 1); + CheckIteratorIsComplete(iterator, 2, SourceBuffer::MIN_CHUNK_CAPACITY + 1); +} + +TEST_F(ImageSourceBuffer, ExpectLengthDoesNotShrinkBelowMinCapacity) +{ + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + + // Write SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the buffer, + // but call ExpectLength() first to make SourceBuffer expect only a single + // byte. We expect this to still result in only one chunk, because + // regardless of ExpectLength() we won't allocate a chunk smaller than + // MIN_CHUNK_CAPACITY bytes. + EXPECT_TRUE(NS_SUCCEEDED(mSourceBuffer->ExpectLength(1))); + CheckedAppendToBufferInChunks(10, SourceBuffer::MIN_CHUNK_CAPACITY); + CheckedCompleteBuffer(iterator, SourceBuffer::MIN_CHUNK_CAPACITY); + + // Verify that the iterator sees a single chunk. + CheckedAdvanceIterator(iterator, SourceBuffer::MIN_CHUNK_CAPACITY); + CheckIteratorIsComplete(iterator, 1, SourceBuffer::MIN_CHUNK_CAPACITY); +} + +TEST_F(ImageSourceBuffer, ExpectLengthGrowsAboveMinCapacity) +{ + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + + // Write two times SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the + // buffer, calling ExpectLength() with the correct length first. We expect + // this to result in only one chunk, because ExpectLength() allows us to + // allocate a larger first chunk than MIN_CHUNK_CAPACITY bytes. + const size_t length = 2 * SourceBuffer::MIN_CHUNK_CAPACITY; + EXPECT_TRUE(NS_SUCCEEDED(mSourceBuffer->ExpectLength(length))); + CheckedAppendToBufferInChunks(10, length); + + // Verify that the iterator sees a single chunk. + CheckedAdvanceIterator(iterator, length); + + // Write one more byte; we expect to see that it triggers an allocation. + CheckedAppendToBufferLastByteForLength(length); + CheckedCompleteBuffer(iterator, 1); + + // Verify that the iterator sees the new byte and a new chunk has been + // allocated. + CheckedAdvanceIterator(iterator, 1, 2, length + 1); + CheckIteratorIsComplete(iterator, 2, length + 1); +} + +TEST_F(ImageSourceBuffer, HugeExpectLengthFails) +{ + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + + // ExpectLength() should fail if the length is bigger than what the + // SurfaceCache can hold, so use the SurfaceCache's maximum capacity to + // calculate what a "massive amount of data" (see below) consists of on this + // platform. + ASSERT_LT(SurfaceCache::MaximumCapacity(), SIZE_MAX); + const size_t hugeSize = SurfaceCache::MaximumCapacity() + 1; + + // Attempt to write a massive amount of data and verify that it fails. (We'd + // get a buffer overrun during the test if it succeeds, but if it succeeds + // that's the least of our problems.) + EXPECT_TRUE(NS_FAILED(mSourceBuffer->ExpectLength(hugeSize))); + EXPECT_TRUE(mSourceBuffer->IsComplete()); + CheckIteratorIsComplete(iterator, 0, 0, NS_ERROR_OUT_OF_MEMORY); +} + +TEST_F(ImageSourceBuffer, LargeAppendsAllocateOnlyOneChunk) +{ + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + + // Write two times SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the + // buffer in a single Append() call. We expect this to result in only one + // chunk even though ExpectLength() wasn't called, because we should always + // allocate a new chunk large enough to store the data we have at hand. + constexpr size_t length = 2 * SourceBuffer::MIN_CHUNK_CAPACITY; + char data[length]; + GenerateData(data, sizeof(data)); + CheckedAppendToBuffer(data, length); + + // Verify that the iterator sees a single chunk. + CheckedAdvanceIterator(iterator, length); + + // Write one more byte; we expect to see that it triggers an allocation. + CheckedAppendToBufferLastByteForLength(length); + CheckedCompleteBuffer(iterator, 1); + + // Verify that the iterator sees the new byte and a new chunk has been + // allocated. + CheckedAdvanceIterator(iterator, 1, 2, length + 1); + CheckIteratorIsComplete(iterator, 2, length + 1); +} + +TEST_F(ImageSourceBuffer, LargeAppendsAllocateAtMostOneChunk) +{ + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + + // Allocate some data we'll use below. + constexpr size_t firstWriteLength = SourceBuffer::MIN_CHUNK_CAPACITY / 2; + constexpr size_t secondWriteLength = 3 * SourceBuffer::MIN_CHUNK_CAPACITY; + constexpr size_t totalLength = firstWriteLength + secondWriteLength; + char data[totalLength]; + GenerateData(data, sizeof(data)); + + // Write half of SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the + // buffer in a single Append() call. This should fill half of the first chunk. + CheckedAppendToBuffer(data, firstWriteLength); + + // Write three times SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the + // buffer in a single Append() call. We expect this to result in the first of + // the first chunk being filled and a new chunk being allocated for the + // remainder. + CheckedAppendToBuffer(data + firstWriteLength, secondWriteLength); + + // Verify that the iterator sees a MIN_CHUNK_CAPACITY-length chunk. + CheckedAdvanceIterator(iterator, SourceBuffer::MIN_CHUNK_CAPACITY); + + // Verify that the iterator sees a second chunk of the length we expect. + const size_t expectedSecondChunkLength = + totalLength - SourceBuffer::MIN_CHUNK_CAPACITY; + CheckedAdvanceIterator(iterator, expectedSecondChunkLength, 2, totalLength); + + // Write one more byte; we expect to see that it triggers an allocation. + CheckedAppendToBufferLastByteForLength(totalLength); + CheckedCompleteBuffer(iterator, 1); + + // Verify that the iterator sees the new byte and a new chunk has been + // allocated. + CheckedAdvanceIterator(iterator, 1, 3, totalLength + 1); + CheckIteratorIsComplete(iterator, 3, totalLength + 1); +} + +TEST_F(ImageSourceBuffer, CompactionHappensWhenBufferIsComplete) +{ + constexpr size_t chunkLength = SourceBuffer::MIN_CHUNK_CAPACITY; + constexpr size_t totalLength = 2 * chunkLength; + + // Write enough data to create two chunks. + CheckedAppendToBufferInChunks(chunkLength, totalLength); + + { + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + + // Verify that the iterator sees two chunks. + CheckedAdvanceIterator(iterator, chunkLength); + CheckedAdvanceIterator(iterator, chunkLength, 2, totalLength); + } + + // Complete the buffer, which should trigger compaction implicitly. + CheckedCompleteBuffer(); + + { + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + + // Verify that compaction happened and there's now only one chunk. + CheckedAdvanceIterator(iterator, totalLength); + CheckIteratorIsComplete(iterator, 1, totalLength); + } +} + +TEST_F(ImageSourceBuffer, CompactionIsDelayedWhileIteratorsExist) +{ + constexpr size_t chunkLength = SourceBuffer::MIN_CHUNK_CAPACITY; + constexpr size_t totalLength = 2 * chunkLength; + + { + SourceBufferIterator outerIterator = mSourceBuffer->Iterator(); + + { + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + + // Write enough data to create two chunks. + CheckedAppendToBufferInChunks(chunkLength, totalLength); + CheckedCompleteBuffer(iterator, totalLength); + + // Verify that the iterator sees two chunks. Since there are live + // iterators, compaction shouldn't have happened when we completed the + // buffer. + CheckedAdvanceIterator(iterator, chunkLength); + CheckedAdvanceIterator(iterator, chunkLength, 2, totalLength); + CheckIteratorIsComplete(iterator, 2, totalLength); + } + + // Now |iterator| has been destroyed, but |outerIterator| still exists, so + // we expect no compaction to have occurred at this point. + CheckedAdvanceIterator(outerIterator, chunkLength); + CheckedAdvanceIterator(outerIterator, chunkLength, 2, totalLength); + CheckIteratorIsComplete(outerIterator, 2, totalLength); + } + + // Now all iterators have been destroyed. Since the buffer was already + // complete, we expect compaction to happen implicitly here. + + { + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + + // Verify that compaction happened and there's now only one chunk. + CheckedAdvanceIterator(iterator, totalLength); + CheckIteratorIsComplete(iterator, 1, totalLength); + } +} + +TEST_F(ImageSourceBuffer, SourceBufferIteratorsCanBeMoved) +{ + constexpr size_t chunkLength = SourceBuffer::MIN_CHUNK_CAPACITY; + constexpr size_t totalLength = 2 * chunkLength; + + // Write enough data to create two chunks. We create an iterator here to make + // sure that compaction doesn't happen during the test. + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + CheckedAppendToBufferInChunks(chunkLength, totalLength); + CheckedCompleteBuffer(iterator, totalLength); + + auto GetIterator = [&]{ + SourceBufferIterator lambdaIterator = mSourceBuffer->Iterator(); + CheckedAdvanceIterator(lambdaIterator, chunkLength); + return lambdaIterator; + }; + + // Move-construct |movedIterator| from the iterator returned from + // GetIterator() and check that its state is as we expect. + SourceBufferIterator movedIterator = Move(GetIterator()); + EXPECT_TRUE(movedIterator.Data()); + EXPECT_EQ(chunkLength, movedIterator.Length()); + ExpectChunkAndByteCount(movedIterator, 1, chunkLength); + + // Make sure that we can advance the iterator. + CheckedAdvanceIterator(movedIterator, chunkLength, 2, totalLength); + + // Make sure that the iterator handles completion properly. + CheckIteratorIsComplete(movedIterator, 2, totalLength); + + // Move-assign |movedIterator| from the iterator returned from + // GetIterator() and check that its state is as we expect. + movedIterator = Move(GetIterator()); + EXPECT_TRUE(movedIterator.Data()); + EXPECT_EQ(chunkLength, movedIterator.Length()); + ExpectChunkAndByteCount(movedIterator, 1, chunkLength); + + // Make sure that we can advance the iterator. + CheckedAdvanceIterator(movedIterator, chunkLength, 2, totalLength); + + // Make sure that the iterator handles completion properly. + CheckIteratorIsComplete(movedIterator, 2, totalLength); +} + +TEST_F(ImageSourceBuffer, SubchunkAdvance) +{ + constexpr size_t chunkLength = SourceBuffer::MIN_CHUNK_CAPACITY; + constexpr size_t totalLength = 2 * chunkLength; + + // Write enough data to create two chunks. We create our iterator here to make + // sure that compaction doesn't happen during the test. + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + CheckedAppendToBufferInChunks(chunkLength, totalLength); + CheckedCompleteBuffer(iterator, totalLength); + + // Advance through the first chunk. The chunk count should not increase. + // We check that by always passing 1 for the |aChunks| parameter of + // CheckedAdvanceIteratorStateOnly(). We have to call CheckData() manually + // because the offset calculation in CheckedAdvanceIterator() assumes that + // we're advancing a chunk at a time. + size_t offset = 0; + while (offset < chunkLength) { + CheckedAdvanceIteratorStateOnly(iterator, 1, 1, chunkLength, + AdvanceMode::eAdvanceByLengthExactly); + CheckData(iterator.Data(), offset++, iterator.Length()); + } + + // Read the first byte of the second chunk. This is the point at which we + // can't advance within the same chunk, so the chunk count should increase. We + // check that by passing 2 for the |aChunks| parameter of + // CheckedAdvanceIteratorStateOnly(). + CheckedAdvanceIteratorStateOnly(iterator, 1, 2, totalLength, + AdvanceMode::eAdvanceByLengthExactly); + CheckData(iterator.Data(), offset++, iterator.Length()); + + // Read the rest of the second chunk. The chunk count should not increase. + while (offset < totalLength) { + CheckedAdvanceIteratorStateOnly(iterator, 1, 2, totalLength, + AdvanceMode::eAdvanceByLengthExactly); + CheckData(iterator.Data(), offset++, iterator.Length()); + } + + // Make sure we reached the end. + CheckIteratorIsComplete(iterator, 2, totalLength); +} + +TEST_F(ImageSourceBuffer, SubchunkZeroByteAdvance) +{ + constexpr size_t chunkLength = SourceBuffer::MIN_CHUNK_CAPACITY; + constexpr size_t totalLength = 2 * chunkLength; + + // Write enough data to create two chunks. We create our iterator here to make + // sure that compaction doesn't happen during the test. + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + CheckedAppendToBufferInChunks(chunkLength, totalLength); + CheckedCompleteBuffer(iterator, totalLength); + + // Make an initial zero-length advance. Although a zero-length advance + // normally won't cause us to read a chunk from the SourceBuffer, we'll do so + // if the iterator is in the initial state to keep the invariant that + // SourceBufferIterator in the READY state always returns a non-null pointer + // from Data(). + CheckedAdvanceIteratorStateOnly(iterator, 0, 1, chunkLength, + AdvanceMode::eAdvanceByLengthExactly); + + // Advance through the first chunk. As in the |SubchunkAdvance| test, the + // chunk count should not increase. We do a zero-length advance after each + // normal advance to ensure that zero-length advances do not change the + // iterator's position or cause a new chunk to be read. + size_t offset = 0; + while (offset < chunkLength) { + CheckedAdvanceIteratorStateOnly(iterator, 1, 1, chunkLength, + AdvanceMode::eAdvanceByLengthExactly); + CheckData(iterator.Data(), offset++, iterator.Length()); + CheckedAdvanceIteratorStateOnly(iterator, 0, 1, chunkLength, + AdvanceMode::eAdvanceByLengthExactly); + } + + // Read the first byte of the second chunk. This is the point at which we + // can't advance within the same chunk, so the chunk count should increase. As + // before, we do a zero-length advance afterward. + CheckedAdvanceIteratorStateOnly(iterator, 1, 2, totalLength, + AdvanceMode::eAdvanceByLengthExactly); + CheckData(iterator.Data(), offset++, iterator.Length()); + CheckedAdvanceIteratorStateOnly(iterator, 0, 2, totalLength, + AdvanceMode::eAdvanceByLengthExactly); + + // Read the rest of the second chunk. The chunk count should not increase. As + // before, we do a zero-length advance after each normal advance. + while (offset < totalLength) { + CheckedAdvanceIteratorStateOnly(iterator, 1, 2, totalLength, + AdvanceMode::eAdvanceByLengthExactly); + CheckData(iterator.Data(), offset++, iterator.Length()); + CheckedAdvanceIteratorStateOnly(iterator, 0, 2, totalLength, + AdvanceMode::eAdvanceByLengthExactly); + } + + // Make sure we reached the end. + CheckIteratorIsComplete(iterator, 2, totalLength); +} + +TEST_F(ImageSourceBuffer, SubchunkZeroByteAdvanceWithNoData) +{ + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + + // Check that advancing by zero bytes still makes us enter the WAITING state. + // This is because if we entered the READY state before reading any data at + // all, we'd break the invariant that SourceBufferIterator::Data() always + // returns a non-null pointer in the READY state. + auto state = iterator.AdvanceOrScheduleResume(0, mCountResumes); + EXPECT_EQ(SourceBufferIterator::WAITING, state); + + // Call Complete(). This should trigger a resume. + CheckedCompleteBuffer(); + EXPECT_EQ(1u, mCountResumes->Count()); +} + +TEST_F(ImageSourceBuffer, NullIResumable) +{ + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + + // Check that we can't advance. + CheckIteratorMustWait(iterator, nullptr); + + // Append to the buffer, which would cause a resume if we had passed a + // non-null IResumable. + CheckedAppendToBuffer(mData, sizeof(mData)); + CheckedCompleteBuffer(iterator, sizeof(mData)); +} + +TEST_F(ImageSourceBuffer, AppendTriggersResume) +{ + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + + // Check that we can't advance. + CheckIteratorMustWait(iterator, mCountResumes); + + // Call Append(). This should trigger a resume. + mSourceBuffer->Append(mData, sizeof(mData)); + EXPECT_EQ(1u, mCountResumes->Count()); +} + +TEST_F(ImageSourceBuffer, OnlyOneResumeTriggeredPerAppend) +{ + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + + // Check that we can't advance. + CheckIteratorMustWait(iterator, mCountResumes); + + // Allocate some data we'll use below. + constexpr size_t firstWriteLength = SourceBuffer::MIN_CHUNK_CAPACITY / 2; + constexpr size_t secondWriteLength = 3 * SourceBuffer::MIN_CHUNK_CAPACITY; + constexpr size_t totalLength = firstWriteLength + secondWriteLength; + char data[totalLength]; + GenerateData(data, sizeof(data)); + + // Write half of SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the + // buffer in a single Append() call. This should fill half of the first chunk. + // This should trigger a resume. + CheckedAppendToBuffer(data, firstWriteLength); + EXPECT_EQ(1u, mCountResumes->Count()); + + // Advance past the new data and wait again. + CheckedAdvanceIterator(iterator, firstWriteLength); + CheckIteratorMustWait(iterator, mCountResumes); + + // Write three times SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the + // buffer in a single Append() call. We expect this to result in the first of + // the first chunk being filled and a new chunk being allocated for the + // remainder. Even though two chunks are getting written to here, only *one* + // resume should get triggered, for a total of two in this test. + CheckedAppendToBuffer(data + firstWriteLength, secondWriteLength); + EXPECT_EQ(2u, mCountResumes->Count()); +} + +TEST_F(ImageSourceBuffer, CompleteTriggersResume) +{ + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + + // Check that we can't advance. + CheckIteratorMustWait(iterator, mCountResumes); + + // Call Complete(). This should trigger a resume. + CheckedCompleteBuffer(); + EXPECT_EQ(1u, mCountResumes->Count()); +} + +TEST_F(ImageSourceBuffer, ExpectLengthDoesNotTriggerResume) +{ + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + + // Check that we can't advance. + CheckIteratorMustWait(iterator, mExpectNoResume); + + // Call ExpectLength(). If this triggers a resume, |mExpectNoResume| will + // ensure that the test fails. + mSourceBuffer->ExpectLength(1000); +} diff --git a/image/test/gtest/TestStreamingLexer.cpp b/image/test/gtest/TestStreamingLexer.cpp new file mode 100644 index 000000000..590b10e81 --- /dev/null +++ b/image/test/gtest/TestStreamingLexer.cpp @@ -0,0 +1,973 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" + +#include "mozilla/Vector.h" +#include "StreamingLexer.h" + +using namespace mozilla; +using namespace mozilla::image; + +enum class TestState +{ + ONE, + TWO, + THREE, + UNBUFFERED, + TRUNCATED_SUCCESS, + TRUNCATED_FAILURE +}; + +void +CheckLexedData(const char* aData, + size_t aLength, + size_t aOffset, + size_t aExpectedLength) +{ + EXPECT_TRUE(aLength == aExpectedLength); + + for (size_t i = 0; i < aLength; ++i) { + EXPECT_EQ(aData[i], char(aOffset + i + 1)); + } +} + +LexerTransition +DoLex(TestState aState, const char* aData, size_t aLength) +{ + switch (aState) { + case TestState::ONE: + CheckLexedData(aData, aLength, 0, 3); + return Transition::To(TestState::TWO, 3); + case TestState::TWO: + CheckLexedData(aData, aLength, 3, 3); + return Transition::To(TestState::THREE, 3); + case TestState::THREE: + CheckLexedData(aData, aLength, 6, 3); + return Transition::TerminateSuccess(); + case TestState::TRUNCATED_SUCCESS: + return Transition::TerminateSuccess(); + case TestState::TRUNCATED_FAILURE: + return Transition::TerminateFailure(); + default: + MOZ_CRASH("Unexpected or unhandled TestState"); + } +} + +LexerTransition +DoLexWithUnbuffered(TestState aState, const char* aData, size_t aLength, + Vector& aUnbufferedVector) +{ + switch (aState) { + case TestState::ONE: + CheckLexedData(aData, aLength, 0, 3); + return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 3); + case TestState::TWO: + CheckLexedData(aUnbufferedVector.begin(), aUnbufferedVector.length(), 3, 3); + return Transition::To(TestState::THREE, 3); + case TestState::THREE: + CheckLexedData(aData, aLength, 6, 3); + return Transition::TerminateSuccess(); + case TestState::UNBUFFERED: + EXPECT_TRUE(aLength <= 3); + EXPECT_TRUE(aUnbufferedVector.append(aData, aLength)); + return Transition::ContinueUnbuffered(TestState::UNBUFFERED); + default: + MOZ_CRASH("Unexpected or unhandled TestState"); + } +} + +LexerTransition +DoLexWithUnbufferedTerminate(TestState aState, const char* aData, size_t aLength) +{ + switch (aState) { + case TestState::ONE: + CheckLexedData(aData, aLength, 0, 3); + return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 3); + case TestState::UNBUFFERED: + return Transition::TerminateSuccess(); + default: + MOZ_CRASH("Unexpected or unhandled TestState"); + } +} + +LexerTransition +DoLexWithYield(TestState aState, const char* aData, size_t aLength) +{ + switch (aState) { + case TestState::ONE: + CheckLexedData(aData, aLength, 0, 3); + return Transition::ToAfterYield(TestState::TWO); + case TestState::TWO: + CheckLexedData(aData, aLength, 0, 3); + return Transition::To(TestState::THREE, 6); + case TestState::THREE: + CheckLexedData(aData, aLength, 3, 6); + return Transition::TerminateSuccess(); + default: + MOZ_CRASH("Unexpected or unhandled TestState"); + } +} + +LexerTransition +DoLexWithTerminateAfterYield(TestState aState, const char* aData, size_t aLength) +{ + switch (aState) { + case TestState::ONE: + CheckLexedData(aData, aLength, 0, 3); + return Transition::ToAfterYield(TestState::TWO); + case TestState::TWO: + return Transition::TerminateSuccess(); + default: + MOZ_CRASH("Unexpected or unhandled TestState"); + } +} + +LexerTransition +DoLexWithZeroLengthStates(TestState aState, const char* aData, size_t aLength) +{ + switch (aState) { + case TestState::ONE: + EXPECT_TRUE(aLength == 0); + return Transition::To(TestState::TWO, 0); + case TestState::TWO: + EXPECT_TRUE(aLength == 0); + return Transition::To(TestState::THREE, 9); + case TestState::THREE: + CheckLexedData(aData, aLength, 0, 9); + return Transition::TerminateSuccess(); + default: + MOZ_CRASH("Unexpected or unhandled TestState"); + } +} + +LexerTransition +DoLexWithZeroLengthStatesAtEnd(TestState aState, const char* aData, size_t aLength) +{ + switch (aState) { + case TestState::ONE: + CheckLexedData(aData, aLength, 0, 9); + return Transition::To(TestState::TWO, 0); + case TestState::TWO: + EXPECT_TRUE(aLength == 0); + return Transition::To(TestState::THREE, 0); + case TestState::THREE: + EXPECT_TRUE(aLength == 0); + return Transition::TerminateSuccess(); + default: + MOZ_CRASH("Unexpected or unhandled TestState"); + } +} + +LexerTransition +DoLexWithZeroLengthYield(TestState aState, const char* aData, size_t aLength) +{ + switch (aState) { + case TestState::ONE: + EXPECT_EQ(0u, aLength); + return Transition::ToAfterYield(TestState::TWO); + case TestState::TWO: + EXPECT_EQ(0u, aLength); + return Transition::To(TestState::THREE, 9); + case TestState::THREE: + CheckLexedData(aData, aLength, 0, 9); + return Transition::TerminateSuccess(); + default: + MOZ_CRASH("Unexpected or unhandled TestState"); + } +} + +LexerTransition +DoLexWithZeroLengthStatesUnbuffered(TestState aState, + const char* aData, + size_t aLength) +{ + switch (aState) { + case TestState::ONE: + EXPECT_TRUE(aLength == 0); + return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 0); + case TestState::TWO: + EXPECT_TRUE(aLength == 0); + return Transition::To(TestState::THREE, 9); + case TestState::THREE: + CheckLexedData(aData, aLength, 0, 9); + return Transition::TerminateSuccess(); + case TestState::UNBUFFERED: + ADD_FAILURE() << "Should not enter zero-length unbuffered state"; + return Transition::TerminateFailure(); + default: + MOZ_CRASH("Unexpected or unhandled TestState"); + } +} + +LexerTransition +DoLexWithZeroLengthStatesAfterUnbuffered(TestState aState, + const char* aData, + size_t aLength) +{ + switch (aState) { + case TestState::ONE: + EXPECT_TRUE(aLength == 0); + return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 9); + case TestState::TWO: + EXPECT_TRUE(aLength == 0); + return Transition::To(TestState::THREE, 0); + case TestState::THREE: + EXPECT_TRUE(aLength == 0); + return Transition::TerminateSuccess(); + case TestState::UNBUFFERED: + CheckLexedData(aData, aLength, 0, 9); + return Transition::ContinueUnbuffered(TestState::UNBUFFERED); + default: + MOZ_CRASH("Unexpected or unhandled TestState"); + } +} + +class ImageStreamingLexer : public ::testing::Test +{ +public: + // Note that mLexer is configured to enter TerminalState::FAILURE immediately + // if the input data is truncated. We don't expect that to happen in most + // tests, so we want to detect that issue. If a test needs a different + // behavior, we create a special StreamingLexer just for that test. + ImageStreamingLexer() + : mLexer(Transition::To(TestState::ONE, 3), Transition::TerminateFailure()) + , mSourceBuffer(new SourceBuffer) + , mIterator(mSourceBuffer->Iterator()) + , mExpectNoResume(new ExpectNoResume) + , mCountResumes(new CountResumes) + { } + +protected: + void CheckTruncatedState(StreamingLexer& aLexer, + TerminalState aExpectedTerminalState, + nsresult aCompletionStatus = NS_OK) + { + for (unsigned i = 0; i < 9; ++i) { + if (i < 2) { + mSourceBuffer->Append(mData + i, 1); + } else if (i == 2) { + mSourceBuffer->Complete(aCompletionStatus); + } + + LexerResult result = aLexer.Lex(mIterator, mCountResumes, DoLex); + + if (i >= 2) { + EXPECT_TRUE(result.is()); + EXPECT_EQ(aExpectedTerminalState, result.as()); + } else { + EXPECT_TRUE(result.is()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as()); + } + } + + EXPECT_EQ(2u, mCountResumes->Count()); + } + + AutoInitializeImageLib mInit; + const char mData[9] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + StreamingLexer mLexer; + RefPtr mSourceBuffer; + SourceBufferIterator mIterator; + RefPtr mExpectNoResume; + RefPtr mCountResumes; +}; + +TEST_F(ImageStreamingLexer, ZeroLengthData) +{ + // Test a zero-length input. + mSourceBuffer->Complete(NS_OK); + + LexerResult result = mLexer.Lex(mIterator, mExpectNoResume, DoLex); + + EXPECT_TRUE(result.is()); + EXPECT_EQ(TerminalState::FAILURE, result.as()); +} + +TEST_F(ImageStreamingLexer, ZeroLengthDataUnbuffered) +{ + // Test a zero-length input. + mSourceBuffer->Complete(NS_OK); + + // Create a special StreamingLexer for this test because we want the first + // state to be unbuffered. + StreamingLexer lexer(Transition::ToUnbuffered(TestState::ONE, + TestState::UNBUFFERED, + sizeof(mData)), + Transition::TerminateFailure()); + + LexerResult result = lexer.Lex(mIterator, mExpectNoResume, DoLex); + EXPECT_TRUE(result.is()); + EXPECT_EQ(TerminalState::FAILURE, result.as()); +} + +TEST_F(ImageStreamingLexer, StartWithTerminal) +{ + // Create a special StreamingLexer for this test because we want the first + // state to be a terminal state. This doesn't really make sense, but we should + // handle it. + StreamingLexer lexer(Transition::TerminateSuccess(), + Transition::TerminateFailure()); + LexerResult result = lexer.Lex(mIterator, mExpectNoResume, DoLex); + EXPECT_TRUE(result.is()); + EXPECT_EQ(TerminalState::SUCCESS, result.as()); + + mSourceBuffer->Complete(NS_OK); +} + +TEST_F(ImageStreamingLexer, SingleChunk) +{ + // Test delivering all the data at once. + mSourceBuffer->Append(mData, sizeof(mData)); + mSourceBuffer->Complete(NS_OK); + + LexerResult result = mLexer.Lex(mIterator, mExpectNoResume, DoLex); + + EXPECT_TRUE(result.is()); + EXPECT_EQ(TerminalState::SUCCESS, result.as()); +} + +TEST_F(ImageStreamingLexer, SingleChunkWithUnbuffered) +{ + Vector unbufferedVector; + + // Test delivering all the data at once. + mSourceBuffer->Append(mData, sizeof(mData)); + mSourceBuffer->Complete(NS_OK); + + LexerResult result = + mLexer.Lex(mIterator, mExpectNoResume, + [&](TestState aState, const char* aData, size_t aLength) { + return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector); + }); + + EXPECT_TRUE(result.is()); + EXPECT_EQ(TerminalState::SUCCESS, result.as()); +} + +TEST_F(ImageStreamingLexer, SingleChunkWithYield) +{ + // Test delivering all the data at once. + mSourceBuffer->Append(mData, sizeof(mData)); + mSourceBuffer->Complete(NS_OK); + + LexerResult result = mLexer.Lex(mIterator, mExpectNoResume, DoLexWithYield); + ASSERT_TRUE(result.is()); + EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as()); + + result = mLexer.Lex(mIterator, mExpectNoResume, DoLexWithYield); + ASSERT_TRUE(result.is()); + EXPECT_EQ(TerminalState::SUCCESS, result.as()); +} + +TEST_F(ImageStreamingLexer, ChunkPerState) +{ + // Test delivering in perfectly-sized chunks, one per state. + for (unsigned i = 0; i < 3; ++i) { + mSourceBuffer->Append(mData + 3 * i, 3); + LexerResult result = mLexer.Lex(mIterator, mCountResumes, DoLex); + + if (i == 2) { + EXPECT_TRUE(result.is()); + EXPECT_EQ(TerminalState::SUCCESS, result.as()); + } else { + EXPECT_TRUE(result.is()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as()); + } + } + + EXPECT_EQ(2u, mCountResumes->Count()); + mSourceBuffer->Complete(NS_OK); +} + +TEST_F(ImageStreamingLexer, ChunkPerStateWithUnbuffered) +{ + Vector unbufferedVector; + + // Test delivering in perfectly-sized chunks, one per state. + for (unsigned i = 0; i < 3; ++i) { + mSourceBuffer->Append(mData + 3 * i, 3); + LexerResult result = + mLexer.Lex(mIterator, mCountResumes, + [&](TestState aState, const char* aData, size_t aLength) { + return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector); + }); + + if (i == 2) { + EXPECT_TRUE(result.is()); + EXPECT_EQ(TerminalState::SUCCESS, result.as()); + } else { + EXPECT_TRUE(result.is()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as()); + } + } + + EXPECT_EQ(2u, mCountResumes->Count()); + mSourceBuffer->Complete(NS_OK); +} + +TEST_F(ImageStreamingLexer, ChunkPerStateWithYield) +{ + // Test delivering in perfectly-sized chunks, one per state. + mSourceBuffer->Append(mData, 3); + LexerResult result = mLexer.Lex(mIterator, mCountResumes, DoLexWithYield); + EXPECT_TRUE(result.is()); + EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as()); + + result = mLexer.Lex(mIterator, mCountResumes, DoLexWithYield); + EXPECT_TRUE(result.is()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as()); + + mSourceBuffer->Append(mData + 3, 6); + result = mLexer.Lex(mIterator, mCountResumes, DoLexWithYield); + EXPECT_TRUE(result.is()); + EXPECT_EQ(TerminalState::SUCCESS, result.as()); + + EXPECT_EQ(1u, mCountResumes->Count()); + mSourceBuffer->Complete(NS_OK); +} + +TEST_F(ImageStreamingLexer, ChunkPerStateWithUnbufferedYield) +{ + size_t unbufferedCallCount = 0; + Vector unbufferedVector; + auto lexerFunc = [&](TestState aState, const char* aData, size_t aLength) + -> LexerTransition { + switch (aState) { + case TestState::ONE: + CheckLexedData(aData, aLength, 0, 3); + return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 3); + case TestState::TWO: + CheckLexedData(unbufferedVector.begin(), unbufferedVector.length(), 3, 3); + return Transition::To(TestState::THREE, 3); + case TestState::THREE: + CheckLexedData(aData, aLength, 6, 3); + return Transition::TerminateSuccess(); + case TestState::UNBUFFERED: + switch (unbufferedCallCount) { + case 0: + CheckLexedData(aData, aLength, 3, 3); + EXPECT_TRUE(unbufferedVector.append(aData, 2)); + unbufferedCallCount++; + + // Continue after yield, telling StreamingLexer we consumed 2 bytes. + return Transition::ContinueUnbufferedAfterYield(TestState::UNBUFFERED, 2); + + case 1: + CheckLexedData(aData, aLength, 5, 1); + EXPECT_TRUE(unbufferedVector.append(aData, 1)); + unbufferedCallCount++; + + // Continue after yield, telling StreamingLexer we consumed 1 byte. + // We should end up in the TWO state. + return Transition::ContinueUnbuffered(TestState::UNBUFFERED); + } + ADD_FAILURE() << "Too many invocations of TestState::UNBUFFERED"; + return Transition::TerminateFailure(); + default: + MOZ_CRASH("Unexpected or unhandled TestState"); + } + }; + + // Test delivering in perfectly-sized chunks, one per state. + for (unsigned i = 0; i < 3; ++i) { + mSourceBuffer->Append(mData + 3 * i, 3); + LexerResult result = mLexer.Lex(mIterator, mCountResumes, lexerFunc); + + switch (i) { + case 0: + EXPECT_TRUE(result.is()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as()); + EXPECT_EQ(0u, unbufferedCallCount); + break; + + case 1: + EXPECT_TRUE(result.is()); + EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as()); + EXPECT_EQ(1u, unbufferedCallCount); + + result = mLexer.Lex(mIterator, mCountResumes, lexerFunc); + EXPECT_TRUE(result.is()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as()); + EXPECT_EQ(2u, unbufferedCallCount); + break; + + case 2: + EXPECT_TRUE(result.is()); + EXPECT_EQ(TerminalState::SUCCESS, result.as()); + break; + } + } + + EXPECT_EQ(2u, mCountResumes->Count()); + mSourceBuffer->Complete(NS_OK); + + LexerResult result = mLexer.Lex(mIterator, mCountResumes, lexerFunc); + EXPECT_TRUE(result.is()); + EXPECT_EQ(TerminalState::SUCCESS, result.as()); +} + +TEST_F(ImageStreamingLexer, OneByteChunks) +{ + // Test delivering in one byte chunks. + for (unsigned i = 0; i < 9; ++i) { + mSourceBuffer->Append(mData + i, 1); + LexerResult result = mLexer.Lex(mIterator, mCountResumes, DoLex); + + if (i == 8) { + EXPECT_TRUE(result.is()); + EXPECT_EQ(TerminalState::SUCCESS, result.as()); + } else { + EXPECT_TRUE(result.is()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as()); + } + } + + EXPECT_EQ(8u, mCountResumes->Count()); + mSourceBuffer->Complete(NS_OK); +} + +TEST_F(ImageStreamingLexer, OneByteChunksWithUnbuffered) +{ + Vector unbufferedVector; + + // Test delivering in one byte chunks. + for (unsigned i = 0; i < 9; ++i) { + mSourceBuffer->Append(mData + i, 1); + LexerResult result = + mLexer.Lex(mIterator, mCountResumes, + [&](TestState aState, const char* aData, size_t aLength) { + return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector); + }); + + if (i == 8) { + EXPECT_TRUE(result.is()); + EXPECT_EQ(TerminalState::SUCCESS, result.as()); + } else { + EXPECT_TRUE(result.is()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as()); + } + } + + EXPECT_EQ(8u, mCountResumes->Count()); + mSourceBuffer->Complete(NS_OK); +} + +TEST_F(ImageStreamingLexer, OneByteChunksWithYield) +{ + // Test delivering in one byte chunks. + for (unsigned i = 0; i < 9; ++i) { + mSourceBuffer->Append(mData + i, 1); + LexerResult result = mLexer.Lex(mIterator, mCountResumes, DoLexWithYield); + + switch (i) { + case 2: + EXPECT_TRUE(result.is()); + EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as()); + + result = mLexer.Lex(mIterator, mCountResumes, DoLexWithYield); + EXPECT_TRUE(result.is()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as()); + break; + + case 8: + EXPECT_TRUE(result.is()); + EXPECT_EQ(TerminalState::SUCCESS, result.as()); + break; + + default: + EXPECT_TRUE(i < 9); + EXPECT_TRUE(result.is()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as()); + } + } + + EXPECT_EQ(8u, mCountResumes->Count()); + mSourceBuffer->Complete(NS_OK); +} + +TEST_F(ImageStreamingLexer, ZeroLengthState) +{ + mSourceBuffer->Append(mData, sizeof(mData)); + mSourceBuffer->Complete(NS_OK); + + // Create a special StreamingLexer for this test because we want the first + // state to be zero length. + StreamingLexer lexer(Transition::To(TestState::ONE, 0), + Transition::TerminateFailure()); + + LexerResult result = + lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthStates); + + EXPECT_TRUE(result.is()); + EXPECT_EQ(TerminalState::SUCCESS, result.as()); +} + +TEST_F(ImageStreamingLexer, ZeroLengthStatesAtEnd) +{ + mSourceBuffer->Append(mData, sizeof(mData)); + mSourceBuffer->Complete(NS_OK); + + // Create a special StreamingLexer for this test because we want the first + // state to consume the full input. + StreamingLexer lexer(Transition::To(TestState::ONE, 9), + Transition::TerminateFailure()); + + LexerResult result = + lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthStatesAtEnd); + + EXPECT_TRUE(result.is()); + EXPECT_EQ(TerminalState::SUCCESS, result.as()); +} + +TEST_F(ImageStreamingLexer, ZeroLengthStateWithYield) +{ + // Create a special StreamingLexer for this test because we want the first + // state to be zero length. + StreamingLexer lexer(Transition::To(TestState::ONE, 0), + Transition::TerminateFailure()); + + mSourceBuffer->Append(mData, 3); + LexerResult result = + lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthYield); + ASSERT_TRUE(result.is()); + EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as()); + + result = lexer.Lex(mIterator, mCountResumes, DoLexWithZeroLengthYield); + ASSERT_TRUE(result.is()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as()); + + mSourceBuffer->Append(mData + 3, sizeof(mData) - 3); + mSourceBuffer->Complete(NS_OK); + result = lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthYield); + ASSERT_TRUE(result.is()); + EXPECT_EQ(TerminalState::SUCCESS, result.as()); + EXPECT_EQ(1u, mCountResumes->Count()); +} + +TEST_F(ImageStreamingLexer, ZeroLengthStateWithUnbuffered) +{ + mSourceBuffer->Append(mData, sizeof(mData)); + mSourceBuffer->Complete(NS_OK); + + // Create a special StreamingLexer for this test because we want the first + // state to be both zero length and unbuffered. + StreamingLexer lexer(Transition::ToUnbuffered(TestState::ONE, + TestState::UNBUFFERED, + 0), + Transition::TerminateFailure()); + + LexerResult result = + lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthStatesUnbuffered); + + EXPECT_TRUE(result.is()); + EXPECT_EQ(TerminalState::SUCCESS, result.as()); +} + +TEST_F(ImageStreamingLexer, ZeroLengthStateAfterUnbuffered) +{ + mSourceBuffer->Append(mData, sizeof(mData)); + mSourceBuffer->Complete(NS_OK); + + // Create a special StreamingLexer for this test because we want the first + // state to be zero length. + StreamingLexer lexer(Transition::To(TestState::ONE, 0), + Transition::TerminateFailure()); + + LexerResult result = + lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthStatesAfterUnbuffered); + + EXPECT_TRUE(result.is()); + EXPECT_EQ(TerminalState::SUCCESS, result.as()); +} + +TEST_F(ImageStreamingLexer, ZeroLengthStateWithUnbufferedYield) +{ + size_t unbufferedCallCount = 0; + auto lexerFunc = [&](TestState aState, const char* aData, size_t aLength) + -> LexerTransition { + switch (aState) { + case TestState::ONE: + EXPECT_EQ(0u, aLength); + return Transition::TerminateSuccess(); + + case TestState::UNBUFFERED: + switch (unbufferedCallCount) { + case 0: + CheckLexedData(aData, aLength, 0, 3); + unbufferedCallCount++; + + // Continue after yield, telling StreamingLexer we consumed 0 bytes. + return Transition::ContinueUnbufferedAfterYield(TestState::UNBUFFERED, 0); + + case 1: + CheckLexedData(aData, aLength, 0, 3); + unbufferedCallCount++; + + // Continue after yield, telling StreamingLexer we consumed 2 bytes. + return Transition::ContinueUnbufferedAfterYield(TestState::UNBUFFERED, 2); + + case 2: + EXPECT_EQ(1u, aLength); + CheckLexedData(aData, aLength, 2, 1); + unbufferedCallCount++; + + // Continue after yield, telling StreamingLexer we consumed 1 bytes. + return Transition::ContinueUnbufferedAfterYield(TestState::UNBUFFERED, 1); + + case 3: + CheckLexedData(aData, aLength, 3, 6); + unbufferedCallCount++; + + // Continue after yield, telling StreamingLexer we consumed 6 bytes. + // We should transition to TestState::ONE when we return from the + // yield. + return Transition::ContinueUnbufferedAfterYield(TestState::UNBUFFERED, 6); + } + + ADD_FAILURE() << "Too many invocations of TestState::UNBUFFERED"; + return Transition::TerminateFailure(); + + default: + MOZ_CRASH("Unexpected or unhandled TestState"); + } + }; + + // Create a special StreamingLexer for this test because we want the first + // state to be unbuffered. + StreamingLexer lexer(Transition::ToUnbuffered(TestState::ONE, + TestState::UNBUFFERED, + sizeof(mData)), + Transition::TerminateFailure()); + + mSourceBuffer->Append(mData, 3); + LexerResult result = lexer.Lex(mIterator, mExpectNoResume, lexerFunc); + ASSERT_TRUE(result.is()); + EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as()); + EXPECT_EQ(1u, unbufferedCallCount); + + result = lexer.Lex(mIterator, mExpectNoResume, lexerFunc); + ASSERT_TRUE(result.is()); + EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as()); + EXPECT_EQ(2u, unbufferedCallCount); + + result = lexer.Lex(mIterator, mExpectNoResume, lexerFunc); + ASSERT_TRUE(result.is()); + EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as()); + EXPECT_EQ(3u, unbufferedCallCount); + + result = lexer.Lex(mIterator, mCountResumes, lexerFunc); + ASSERT_TRUE(result.is()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as()); + EXPECT_EQ(3u, unbufferedCallCount); + + mSourceBuffer->Append(mData + 3, 6); + mSourceBuffer->Complete(NS_OK); + EXPECT_EQ(1u, mCountResumes->Count()); + result = lexer.Lex(mIterator, mExpectNoResume, lexerFunc); + ASSERT_TRUE(result.is()); + EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as()); + EXPECT_EQ(4u, unbufferedCallCount); + + result = lexer.Lex(mIterator, mExpectNoResume, lexerFunc); + ASSERT_TRUE(result.is()); + EXPECT_EQ(TerminalState::SUCCESS, result.as()); +} + +TEST_F(ImageStreamingLexer, TerminateSuccess) +{ + mSourceBuffer->Append(mData, sizeof(mData)); + mSourceBuffer->Complete(NS_OK); + + // Test that Terminate is "sticky". + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + LexerResult result = + mLexer.Lex(iterator, mExpectNoResume, + [&](TestState aState, const char* aData, size_t aLength) { + EXPECT_TRUE(aState == TestState::ONE); + return Transition::TerminateSuccess(); + }); + EXPECT_TRUE(result.is()); + EXPECT_EQ(TerminalState::SUCCESS, result.as()); + + SourceBufferIterator iterator2 = mSourceBuffer->Iterator(); + result = + mLexer.Lex(iterator2, mExpectNoResume, + [&](TestState aState, const char* aData, size_t aLength) { + EXPECT_TRUE(false); // Shouldn't get here. + return Transition::TerminateFailure(); + }); + EXPECT_TRUE(result.is()); + EXPECT_EQ(TerminalState::SUCCESS, result.as()); +} + +TEST_F(ImageStreamingLexer, TerminateFailure) +{ + mSourceBuffer->Append(mData, sizeof(mData)); + mSourceBuffer->Complete(NS_OK); + + // Test that Terminate is "sticky". + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + LexerResult result = + mLexer.Lex(iterator, mExpectNoResume, + [&](TestState aState, const char* aData, size_t aLength) { + EXPECT_TRUE(aState == TestState::ONE); + return Transition::TerminateFailure(); + }); + EXPECT_TRUE(result.is()); + EXPECT_EQ(TerminalState::FAILURE, result.as()); + + SourceBufferIterator iterator2 = mSourceBuffer->Iterator(); + result = + mLexer.Lex(iterator2, mExpectNoResume, + [&](TestState aState, const char* aData, size_t aLength) { + EXPECT_TRUE(false); // Shouldn't get here. + return Transition::TerminateFailure(); + }); + EXPECT_TRUE(result.is()); + EXPECT_EQ(TerminalState::FAILURE, result.as()); +} + +TEST_F(ImageStreamingLexer, TerminateUnbuffered) +{ + // Test that Terminate works during an unbuffered read. + for (unsigned i = 0; i < 9; ++i) { + mSourceBuffer->Append(mData + i, 1); + LexerResult result = + mLexer.Lex(mIterator, mCountResumes, DoLexWithUnbufferedTerminate); + + if (i > 2) { + EXPECT_TRUE(result.is()); + EXPECT_EQ(TerminalState::SUCCESS, result.as()); + } else { + EXPECT_TRUE(result.is()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as()); + } + } + + // We expect 3 resumes because TestState::ONE consumes 3 bytes and then + // transitions to TestState::UNBUFFERED, which calls TerminateSuccess() as + // soon as it receives a single byte. That's four bytes total, which are + // delivered one at a time, requiring 3 resumes. + EXPECT_EQ(3u, mCountResumes->Count()); + + mSourceBuffer->Complete(NS_OK); +} + +TEST_F(ImageStreamingLexer, TerminateAfterYield) +{ + // Test that Terminate works after yielding. + for (unsigned i = 0; i < 9; ++i) { + mSourceBuffer->Append(mData + i, 1); + LexerResult result = + mLexer.Lex(mIterator, mCountResumes, DoLexWithTerminateAfterYield); + + if (i > 2) { + EXPECT_TRUE(result.is()); + EXPECT_EQ(TerminalState::SUCCESS, result.as()); + } else if (i == 2) { + EXPECT_TRUE(result.is()); + EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as()); + } else { + EXPECT_TRUE(result.is()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as()); + } + } + + // We expect 2 resumes because TestState::ONE consumes 3 bytes and then + // yields. When the lexer resumes at TestState::TWO, which receives the same 3 + // bytes, TerminateSuccess() gets called immediately. That's three bytes + // total, which are delivered one at a time, requiring 2 resumes. + EXPECT_EQ(2u, mCountResumes->Count()); + + mSourceBuffer->Complete(NS_OK); +} + +TEST_F(ImageStreamingLexer, SourceBufferImmediateComplete) +{ + // Test calling SourceBuffer::Complete() without appending any data. This + // causes the SourceBuffer to automatically have a failing completion status, + // no matter what you pass, so we expect TerminalState::FAILURE below. + mSourceBuffer->Complete(NS_OK); + + LexerResult result = mLexer.Lex(mIterator, mExpectNoResume, DoLex); + + EXPECT_TRUE(result.is()); + EXPECT_EQ(TerminalState::FAILURE, result.as()); +} + +TEST_F(ImageStreamingLexer, SourceBufferTruncatedTerminalStateSuccess) +{ + // Test that using a terminal state (in this case TerminalState::SUCCESS) as a + // truncated state works. + StreamingLexer lexer(Transition::To(TestState::ONE, 3), + Transition::TerminateSuccess()); + + CheckTruncatedState(lexer, TerminalState::SUCCESS); +} + +TEST_F(ImageStreamingLexer, SourceBufferTruncatedTerminalStateFailure) +{ + // Test that using a terminal state (in this case TerminalState::FAILURE) as a + // truncated state works. + StreamingLexer lexer(Transition::To(TestState::ONE, 3), + Transition::TerminateFailure()); + + CheckTruncatedState(lexer, TerminalState::FAILURE); +} + +TEST_F(ImageStreamingLexer, SourceBufferTruncatedStateReturningSuccess) +{ + // Test that a truncated state that returns TerminalState::SUCCESS works. When + // |lexer| discovers that the data is truncated, it invokes the + // TRUNCATED_SUCCESS state, which returns TerminalState::SUCCESS. + // CheckTruncatedState() verifies that this happens. + StreamingLexer lexer(Transition::To(TestState::ONE, 3), + Transition::To(TestState::TRUNCATED_SUCCESS, 0)); + + CheckTruncatedState(lexer, TerminalState::SUCCESS); +} + +TEST_F(ImageStreamingLexer, SourceBufferTruncatedStateReturningFailure) +{ + // Test that a truncated state that returns TerminalState::FAILURE works. When + // |lexer| discovers that the data is truncated, it invokes the + // TRUNCATED_FAILURE state, which returns TerminalState::FAILURE. + // CheckTruncatedState() verifies that this happens. + StreamingLexer lexer(Transition::To(TestState::ONE, 3), + Transition::To(TestState::TRUNCATED_FAILURE, 0)); + + CheckTruncatedState(lexer, TerminalState::FAILURE); +} + +TEST_F(ImageStreamingLexer, SourceBufferTruncatedFailingCompleteStatus) +{ + // Test that calling SourceBuffer::Complete() with a failing status results in + // an immediate TerminalState::FAILURE result. (Note that |lexer|'s truncated + // state is TerminalState::SUCCESS, so if we ignore the failing status, the + // test will fail.) + StreamingLexer lexer(Transition::To(TestState::ONE, 3), + Transition::TerminateSuccess()); + + CheckTruncatedState(lexer, TerminalState::FAILURE, NS_ERROR_FAILURE); +} + +TEST_F(ImageStreamingLexer, NoSourceBufferResumable) +{ + // Test delivering in one byte chunks with no IResumable. + for (unsigned i = 0; i < 9; ++i) { + mSourceBuffer->Append(mData + i, 1); + LexerResult result = mLexer.Lex(mIterator, nullptr, DoLex); + + if (i == 8) { + EXPECT_TRUE(result.is()); + EXPECT_EQ(TerminalState::SUCCESS, result.as()); + } else { + EXPECT_TRUE(result.is()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as()); + } + } + + mSourceBuffer->Complete(NS_OK); +} diff --git a/image/test/gtest/TestSurfacePipeIntegration.cpp b/image/test/gtest/TestSurfacePipeIntegration.cpp new file mode 100644 index 000000000..5e8c19fc2 --- /dev/null +++ b/image/test/gtest/TestSurfacePipeIntegration.cpp @@ -0,0 +1,508 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" + +#include "mozilla/gfx/2D.h" +#include "Common.h" +#include "Decoder.h" +#include "DecoderFactory.h" +#include "SourceBuffer.h" +#include "SurfacePipe.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; + +namespace mozilla { +namespace image { + +class TestSurfacePipeFactory +{ +public: + static SurfacePipe SimpleSurfacePipe() + { + SurfacePipe pipe; + return Move(pipe); + } + + template + static SurfacePipe SurfacePipeFromPipeline(T&& aPipeline) + { + return SurfacePipe { Move(aPipeline) }; + } + +private: + TestSurfacePipeFactory() { } +}; + +} // namespace image +} // namespace mozilla + +void +CheckSurfacePipeMethodResults(SurfacePipe* aPipe, + Decoder* aDecoder, + const IntRect& aRect = IntRect(0, 0, 100, 100)) +{ + // Check that the pipeline ended up in the state we expect. Note that we're + // explicitly testing the SurfacePipe versions of these methods, so we don't + // want to use AssertCorrectPipelineFinalState() here. + EXPECT_TRUE(aPipe->IsSurfaceFinished()); + Maybe invalidRect = aPipe->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isSome()); + EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mInputSpaceRect); + EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mOutputSpaceRect); + + // Check the generated image. + CheckGeneratedImage(aDecoder, aRect); + + // Reset and clear the image before the next test. + aPipe->ResetToFirstRow(); + EXPECT_FALSE(aPipe->IsSurfaceFinished()); + invalidRect = aPipe->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + uint32_t count = 0; + auto result = aPipe->WritePixels([&]() { + ++count; + return AsVariant(BGRAColor::Transparent().AsPixel()); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u * 100u, count); + + EXPECT_TRUE(aPipe->IsSurfaceFinished()); + invalidRect = aPipe->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isSome()); + EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mInputSpaceRect); + EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mOutputSpaceRect); + + aPipe->ResetToFirstRow(); + EXPECT_FALSE(aPipe->IsSurfaceFinished()); + invalidRect = aPipe->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); +} + +void +CheckPalettedSurfacePipeMethodResults(SurfacePipe* aPipe, + Decoder* aDecoder, + const IntRect& aRect + = IntRect(0, 0, 100, 100)) +{ + // Check that the pipeline ended up in the state we expect. Note that we're + // explicitly testing the SurfacePipe versions of these methods, so we don't + // want to use AssertCorrectPipelineFinalState() here. + EXPECT_TRUE(aPipe->IsSurfaceFinished()); + Maybe invalidRect = aPipe->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isSome()); + EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mInputSpaceRect); + EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mOutputSpaceRect); + + // Check the generated image. + CheckGeneratedPalettedImage(aDecoder, aRect); + + // Reset and clear the image before the next test. + aPipe->ResetToFirstRow(); + EXPECT_FALSE(aPipe->IsSurfaceFinished()); + invalidRect = aPipe->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + uint32_t count = 0; + auto result = aPipe->WritePixels([&]() { + ++count; + return AsVariant(uint8_t(0)); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u * 100u, count); + + EXPECT_TRUE(aPipe->IsSurfaceFinished()); + invalidRect = aPipe->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isSome()); + EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mInputSpaceRect); + EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mOutputSpaceRect); + + aPipe->ResetToFirstRow(); + EXPECT_FALSE(aPipe->IsSurfaceFinished()); + invalidRect = aPipe->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); +} + +class ImageSurfacePipeIntegration : public ::testing::Test +{ +protected: + AutoInitializeImageLib mInit; +}; + +TEST_F(ImageSurfacePipeIntegration, SurfacePipe) +{ + // Test that SurfacePipe objects can be initialized and move constructed. + SurfacePipe pipe = TestSurfacePipeFactory::SimpleSurfacePipe(); + + // Test that SurfacePipe objects can be move assigned. + pipe = TestSurfacePipeFactory::SimpleSurfacePipe(); + + // Test that SurfacePipe objects can be initialized with a pipeline. + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + auto sink = MakeUnique(); + nsresult rv = + sink->Configure(SurfaceConfig { decoder, 0, IntSize(100, 100), + SurfaceFormat::B8G8R8A8, false }); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + pipe = TestSurfacePipeFactory::SurfacePipeFromPipeline(sink); + + // Test that WritePixels() gets passed through to the underlying pipeline. + { + uint32_t count = 0; + auto result = pipe.WritePixels([&]() { + ++count; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u * 100u, count); + CheckSurfacePipeMethodResults(&pipe, decoder); + } + + // Create a buffer the same size as one row of the surface, containing all + // green pixels. We'll use this for the WriteBuffer() tests. + uint32_t buffer[100]; + for (int i = 0; i < 100; ++i) { + buffer[i] = BGRAColor::Green().AsPixel(); + } + + // Test that WriteBuffer() gets passed through to the underlying pipeline. + { + uint32_t count = 0; + WriteState result = WriteState::NEED_MORE_DATA; + while (result == WriteState::NEED_MORE_DATA) { + result = pipe.WriteBuffer(buffer); + ++count; + } + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u, count); + CheckSurfacePipeMethodResults(&pipe, decoder); + } + + // Test that the 3 argument version of WriteBuffer() gets passed through to + // the underlying pipeline. + { + uint32_t count = 0; + WriteState result = WriteState::NEED_MORE_DATA; + while (result == WriteState::NEED_MORE_DATA) { + result = pipe.WriteBuffer(buffer, 0, 100); + ++count; + } + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u, count); + CheckSurfacePipeMethodResults(&pipe, decoder); + } + + // Test that WriteEmptyRow() gets passed through to the underlying pipeline. + { + uint32_t count = 0; + WriteState result = WriteState::NEED_MORE_DATA; + while (result == WriteState::NEED_MORE_DATA) { + result = pipe.WriteEmptyRow(); + ++count; + } + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u, count); + CheckSurfacePipeMethodResults(&pipe, decoder, IntRect(0, 0, 0, 0)); + } + + // Mark the frame as finished so we don't get an assertion. + RawAccessFrameRef currentFrame = decoder->GetCurrentFrameRef(); + currentFrame->Finish(); +} + +TEST_F(ImageSurfacePipeIntegration, PalettedSurfacePipe) +{ + // Create a SurfacePipe containing a PalettedSurfaceSink. + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + auto sink = MakeUnique(); + nsresult rv = + sink->Configure(PalettedSurfaceConfig { decoder, 0, IntSize(100, 100), + IntRect(0, 0, 100, 100), + SurfaceFormat::B8G8R8A8, + 8, false }); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + SurfacePipe pipe = TestSurfacePipeFactory::SurfacePipeFromPipeline(sink); + + // Test that WritePixels() gets passed through to the underlying pipeline. + { + uint32_t count = 0; + auto result = pipe.WritePixels([&]() { + ++count; + return AsVariant(uint8_t(255)); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u * 100u, count); + CheckPalettedSurfacePipeMethodResults(&pipe, decoder); + } + + // Create a buffer the same size as one row of the surface, containing all + // 255 pixels. We'll use this for the WriteBuffer() tests. + uint8_t buffer[100]; + for (int i = 0; i < 100; ++i) { + buffer[i] = 255; + } + + // Test that WriteBuffer() gets passed through to the underlying pipeline. + { + uint32_t count = 0; + WriteState result = WriteState::NEED_MORE_DATA; + while (result == WriteState::NEED_MORE_DATA) { + result = pipe.WriteBuffer(buffer); + ++count; + } + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u, count); + CheckPalettedSurfacePipeMethodResults(&pipe, decoder); + } + + // Test that the 3 argument version of WriteBuffer() gets passed through to + // the underlying pipeline. + { + uint32_t count = 0; + WriteState result = WriteState::NEED_MORE_DATA; + while (result == WriteState::NEED_MORE_DATA) { + result = pipe.WriteBuffer(buffer, 0, 100); + ++count; + } + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u, count); + CheckPalettedSurfacePipeMethodResults(&pipe, decoder); + } + + // Test that WriteEmptyRow() gets passed through to the underlying pipeline. + { + uint32_t count = 0; + WriteState result = WriteState::NEED_MORE_DATA; + while (result == WriteState::NEED_MORE_DATA) { + result = pipe.WriteEmptyRow(); + ++count; + } + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u, count); + CheckPalettedSurfacePipeMethodResults(&pipe, decoder, IntRect(0, 0, 0, 0)); + } + + // Mark the frame as finished so we don't get an assertion. + RawAccessFrameRef currentFrame = decoder->GetCurrentFrameRef(); + currentFrame->Finish(); +} + +TEST_F(ImageSurfacePipeIntegration, DeinterlaceDownscaleWritePixels) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + auto test = [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 25, 25))); + }; + + WithFilterPipeline(decoder, test, + DeinterlacingConfig { /* mProgressiveDisplay = */ true }, + DownscalingConfig { IntSize(100, 100), + SurfaceFormat::B8G8R8A8 }, + SurfaceConfig { decoder, 0, IntSize(25, 25), + SurfaceFormat::B8G8R8A8, false }); +} + +TEST_F(ImageSurfacePipeIntegration, RemoveFrameRectBottomRightDownscaleWritePixels) +{ + // This test case uses a frame rect that extends beyond the borders of the + // image to the bottom and to the right. It looks roughly like this (with the + // box made of '#'s representing the frame rect): + // + // +------------+ + // + + + // + +------------+ + // + +############+ + // +------+############+ + // +############+ + // +------------+ + + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + // Note that aInputWriteRect is 100x50 because RemoveFrameRectFilter ignores + // trailing rows that don't show up in the output. (Leading rows unfortunately + // can't be ignored.) So the action of the pipeline is as follows: + // + // (1) RemoveFrameRectFilter reads a 100x50 region of the input. + // (aInputWriteRect captures this fact.) The remaining 50 rows are ignored + // because they extend off the bottom of the image due to the frame rect's + // (50, 50) offset. The 50 columns on the right also don't end up in the + // output, so ultimately only a 50x50 region in the output contains data + // from the input. The filter's output is not 50x50, though, but 100x100, + // because what RemoveFrameRectFilter does is introduce blank rows or + // columns as necessary to transform an image that needs a frame rect into + // an image that doesn't. + // + // (2) DownscalingFilter reads the output of RemoveFrameRectFilter (100x100) + // and downscales it to 20x20. + // + // (3) The surface owned by SurfaceSink logically has only a 10x10 region + // region in it that's non-blank; this is the downscaled version of the + // 50x50 region discussed in (1). (aOutputWriteRect captures this fact.) + // Some fuzz, as usual, is necessary when dealing with Lanczos downscaling. + + auto test = [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 20, 20)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(50, 50, 100, 50)), + /* aOutputWriteRect = */ Some(IntRect(10, 10, 10, 10)), + /* aFuzz = */ 0x33); + }; + + WithFilterPipeline(decoder, test, + RemoveFrameRectConfig { IntRect(50, 50, 100, 100) }, + DownscalingConfig { IntSize(100, 100), + SurfaceFormat::B8G8R8A8 }, + SurfaceConfig { decoder, 0, IntSize(20, 20), + SurfaceFormat::B8G8R8A8, false }); +} + +TEST_F(ImageSurfacePipeIntegration, RemoveFrameRectTopLeftDownscaleWritePixels) +{ + // This test case uses a frame rect that extends beyond the borders of the + // image to the top and to the left. It looks roughly like this (with the + // box made of '#'s representing the frame rect): + // + // +------------+ + // +############+ + // +############+------+ + // +############+ + + // +------------+ + + // + + + // +------------+ + + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + auto test = [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 20, 20)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 100, 100)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 10, 10)), + /* aFuzz = */ 0x21); + }; + + WithFilterPipeline(decoder, test, + RemoveFrameRectConfig { IntRect(-50, -50, 100, 100) }, + DownscalingConfig { IntSize(100, 100), + SurfaceFormat::B8G8R8A8 }, + SurfaceConfig { decoder, 0, IntSize(20, 20), + SurfaceFormat::B8G8R8A8, false }); +} + +TEST_F(ImageSurfacePipeIntegration, DeinterlaceRemoveFrameRectWritePixels) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + // Note that aInputRect is the full 100x100 size even though + // RemoveFrameRectFilter is part of this pipeline, because deinterlacing + // requires reading every row. + + auto test = [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(50, 50, 100, 100)), + /* aOutputWriteRect = */ Some(IntRect(50, 50, 50, 50))); + }; + + WithFilterPipeline(decoder, test, + DeinterlacingConfig { /* mProgressiveDisplay = */ true }, + RemoveFrameRectConfig { IntRect(50, 50, 100, 100) }, + SurfaceConfig { decoder, 0, IntSize(100, 100), + SurfaceFormat::B8G8R8A8, false }); +} + +TEST_F(ImageSurfacePipeIntegration, DeinterlaceRemoveFrameRectDownscaleWritePixels) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + auto test = [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 20, 20)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(50, 50, 100, 100)), + /* aOutputWriteRect = */ Some(IntRect(10, 10, 10, 10)), + /* aFuzz = */ 33); + }; + + WithFilterPipeline(decoder, test, + DeinterlacingConfig { /* mProgressiveDisplay = */ true }, + RemoveFrameRectConfig { IntRect(50, 50, 100, 100) }, + DownscalingConfig { IntSize(100, 100), + SurfaceFormat::B8G8R8A8 }, + SurfaceConfig { decoder, 0, IntSize(20, 20), + SurfaceFormat::B8G8R8A8, false }); +} + +TEST_F(ImageSurfacePipeIntegration, ConfiguringPalettedRemoveFrameRectDownscaleFails) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + // This is an invalid pipeline for paletted images, so configuration should + // fail. + AssertConfiguringPipelineFails(decoder, + RemoveFrameRectConfig { IntRect(0, 0, 50, 50) }, + DownscalingConfig { IntSize(100, 100), + SurfaceFormat::B8G8R8A8 }, + PalettedSurfaceConfig { decoder, 0, IntSize(100, 100), + IntRect(0, 0, 50, 50), + SurfaceFormat::B8G8R8A8, 8, + false }); +} + +TEST_F(ImageSurfacePipeIntegration, ConfiguringPalettedDeinterlaceDownscaleFails) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + // This is an invalid pipeline for paletted images, so configuration should + // fail. + AssertConfiguringPipelineFails(decoder, + DeinterlacingConfig { /* mProgressiveDisplay = */ true}, + DownscalingConfig { IntSize(100, 100), + SurfaceFormat::B8G8R8A8 }, + PalettedSurfaceConfig { decoder, 0, IntSize(100, 100), + IntRect(0, 0, 20, 20), + SurfaceFormat::B8G8R8A8, 8, + false }); +} + +TEST_F(ImageSurfacePipeIntegration, ConfiguringHugeDeinterlacingBufferFails) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + // When DownscalingFilter is used, we may succeed in allocating an output + // surface for huge images, because we only need to store the scaled-down + // version of the image. However, regardless of downscaling, + // DeinterlacingFilter needs to allocate a buffer as large as the size of the + // input. This can cause OOMs on operating systems that allow overcommit. This + // test makes sure that we reject such allocations. + AssertConfiguringPipelineFails(decoder, + DeinterlacingConfig { /* mProgressiveDisplay = */ true}, + DownscalingConfig { IntSize(60000, 60000), + SurfaceFormat::B8G8R8A8 }, + SurfaceConfig { decoder, 0, IntSize(600, 600), + SurfaceFormat::B8G8R8A8, false }); +} diff --git a/image/test/gtest/TestSurfaceSink.cpp b/image/test/gtest/TestSurfaceSink.cpp new file mode 100644 index 000000000..ccf9be3ec --- /dev/null +++ b/image/test/gtest/TestSurfaceSink.cpp @@ -0,0 +1,1491 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" + +#include "mozilla/gfx/2D.h" +#include "Common.h" +#include "Decoder.h" +#include "DecoderFactory.h" +#include "SourceBuffer.h" +#include "SurfacePipe.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; + +enum class Orient +{ + NORMAL, + FLIP_VERTICALLY +}; + +template void +WithSurfaceSink(Func aFunc) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + const bool flipVertically = Orientation == Orient::FLIP_VERTICALLY; + + WithFilterPipeline(decoder, Forward(aFunc), + SurfaceConfig { decoder, 0, IntSize(100, 100), + SurfaceFormat::B8G8R8A8, flipVertically }); +} + +template void +WithPalettedSurfaceSink(const IntRect& aFrameRect, Func aFunc) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + WithFilterPipeline(decoder, Forward(aFunc), + PalettedSurfaceConfig { decoder, 0, IntSize(100, 100), + aFrameRect, SurfaceFormat::B8G8R8A8, + 8, false }); +} + +void +ResetForNextPass(SurfaceFilter* aSink) +{ + aSink->ResetToFirstRow(); + EXPECT_FALSE(aSink->IsSurfaceFinished()); + Maybe invalidRect = aSink->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); +} + +template void +DoCheckIterativeWrite(SurfaceFilter* aSink, + WriteFunc aWriteFunc, + CheckFunc aCheckFunc) +{ + // Write the buffer to successive rows until every row of the surface + // has been written. + uint32_t row = 0; + WriteState result = WriteState::NEED_MORE_DATA; + while (result == WriteState::NEED_MORE_DATA) { + result = aWriteFunc(row); + ++row; + } + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u, row); + + AssertCorrectPipelineFinalState(aSink, + IntRect(0, 0, 100, 100), + IntRect(0, 0, 100, 100)); + + // Check that the generated image is correct. + aCheckFunc(); +} + +template void +CheckIterativeWrite(Decoder* aDecoder, + SurfaceSink* aSink, + const IntRect& aOutputRect, + WriteFunc aWriteFunc) +{ + // Ignore the row passed to WriteFunc, since no callers use it. + auto writeFunc = [&](uint32_t) { + return aWriteFunc(); + }; + + DoCheckIterativeWrite(aSink, writeFunc, [&]{ + CheckGeneratedImage(aDecoder, aOutputRect); + }); +} + +template void +CheckPalettedIterativeWrite(Decoder* aDecoder, + PalettedSurfaceSink* aSink, + const IntRect& aOutputRect, + WriteFunc aWriteFunc) +{ + // Ignore the row passed to WriteFunc, since no callers use it. + auto writeFunc = [&](uint32_t) { + return aWriteFunc(); + }; + + DoCheckIterativeWrite(aSink, writeFunc, [&]{ + CheckGeneratedPalettedImage(aDecoder, aOutputRect); + }); +} + +TEST(ImageSurfaceSink, NullSurfaceSink) +{ + // Create the NullSurfaceSink. + NullSurfaceSink sink; + nsresult rv = sink.Configure(NullSurfaceConfig { }); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + EXPECT_TRUE(!sink.IsValidPalettedPipe()); + + // Ensure that we can't write anything. + bool gotCalled = false; + auto result = sink.WritePixels([&]() { + gotCalled = true; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + EXPECT_FALSE(gotCalled); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_TRUE(sink.IsSurfaceFinished()); + Maybe invalidRect = sink.TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + uint32_t source = BGRAColor::Red().AsPixel(); + result = sink.WriteBuffer(&source); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_TRUE(sink.IsSurfaceFinished()); + invalidRect = sink.TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + result = sink.WriteBuffer(&source, 0, 1); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_TRUE(sink.IsSurfaceFinished()); + invalidRect = sink.TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + result = sink.WriteEmptyRow(); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_TRUE(sink.IsSurfaceFinished()); + invalidRect = sink.TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + result = sink.WriteUnsafeComputedRow([&](uint32_t* aRow, + uint32_t aLength) { + gotCalled = true; + for (uint32_t col = 0; col < aLength; ++col, ++aRow) { + *aRow = BGRAColor::Red().AsPixel(); + } + }); + EXPECT_FALSE(gotCalled); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_TRUE(sink.IsSurfaceFinished()); + invalidRect = sink.TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + // Attempt to advance to the next row and make sure nothing changes. + sink.AdvanceRow(); + EXPECT_TRUE(sink.IsSurfaceFinished()); + invalidRect = sink.TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + // Attempt to advance to the next pass and make sure nothing changes. + sink.ResetToFirstRow(); + EXPECT_TRUE(sink.IsSurfaceFinished()); + invalidRect = sink.TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); +} + +TEST(ImageSurfaceSink, SurfaceSinkInitialization) +{ + WithSurfaceSink([](Decoder* aDecoder, SurfaceSink* aSink) { + // Check initial state. + EXPECT_FALSE(aSink->IsSurfaceFinished()); + Maybe invalidRect = aSink->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + // Check that the surface is zero-initialized. We verify this by calling + // CheckGeneratedImage() and telling it that we didn't write to the surface + // anyway (i.e., we wrote to the empty rect); it will then expect the entire + // surface to be transparent, which is what it should be if it was + // zero-initialied. + CheckGeneratedImage(aDecoder, IntRect(0, 0, 0, 0)); + }); +} + +TEST(ImageSurfaceSink, SurfaceSinkWritePixels) +{ + WithSurfaceSink([](Decoder* aDecoder, SurfaceSink* aSink) { + CheckWritePixels(aDecoder, aSink); + }); +} + +TEST(ImageSurfaceSink, SurfaceSinkWritePixelsFinish) +{ + WithSurfaceSink([](Decoder* aDecoder, SurfaceSink* aSink) { + // Write nothing into the surface; just finish immediately. + uint32_t count = 0; + auto result = aSink->WritePixels([&]() { + count++; + return AsVariant(WriteState::FINISHED); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(1u, count); + + AssertCorrectPipelineFinalState(aSink, + IntRect(0, 0, 100, 100), + IntRect(0, 0, 100, 100)); + + // Attempt to write more and make sure that nothing gets written. + count = 0; + result = aSink->WritePixels([&]() { + count++; + return AsVariant(BGRAColor::Red().AsPixel()); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(0u, count); + EXPECT_TRUE(aSink->IsSurfaceFinished()); + + // Check that the generated image is correct. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSourceSurface(); + EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Transparent())); + }); +} + +TEST(ImageSurfaceSink, SurfaceSinkWritePixelsEarlyExit) +{ + auto checkEarlyExit = + [](Decoder* aDecoder, SurfaceSink* aSink, WriteState aState) { + // Write half a row of green pixels and then exit early with |aState|. If + // the lambda keeps getting called, we'll write red pixels, which will cause + // the test to fail. + uint32_t count = 0; + auto result = aSink->WritePixels([&]() -> NextPixel { + if (count == 50) { + return AsVariant(aState); + } + return count++ < 50 ? AsVariant(BGRAColor::Green().AsPixel()) + : AsVariant(BGRAColor::Red().AsPixel()); + }); + + EXPECT_EQ(aState, result); + EXPECT_EQ(50u, count); + CheckGeneratedImage(aDecoder, IntRect(0, 0, 50, 1)); + + if (aState != WriteState::FINISHED) { + // We should still be able to write more at this point. + EXPECT_FALSE(aSink->IsSurfaceFinished()); + + // Verify that we can resume writing. We'll finish up the same row. + count = 0; + result = aSink->WritePixels([&]() -> NextPixel { + if (count == 50) { + return AsVariant(WriteState::NEED_MORE_DATA); + } + ++count; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + + EXPECT_EQ(WriteState::NEED_MORE_DATA, result); + EXPECT_EQ(50u, count); + EXPECT_FALSE(aSink->IsSurfaceFinished()); + CheckGeneratedImage(aDecoder, IntRect(0, 0, 100, 1)); + + return; + } + + // We should've finished the surface at this point. + AssertCorrectPipelineFinalState(aSink, + IntRect(0, 0, 100, 100), + IntRect(0, 0, 100, 100)); + + // Attempt to write more and make sure that nothing gets written. + count = 0; + result = aSink->WritePixels([&]{ + count++; + return AsVariant(BGRAColor::Red().AsPixel()); + }); + + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(0u, count); + EXPECT_TRUE(aSink->IsSurfaceFinished()); + + // Check that the generated image is still correct. + CheckGeneratedImage(aDecoder, IntRect(0, 0, 50, 1)); + }; + + WithSurfaceSink([&](Decoder* aDecoder, SurfaceSink* aSink) { + checkEarlyExit(aDecoder, aSink, WriteState::NEED_MORE_DATA); + }); + + WithSurfaceSink([&](Decoder* aDecoder, SurfaceSink* aSink) { + checkEarlyExit(aDecoder, aSink, WriteState::FAILURE); + }); + + WithSurfaceSink([&](Decoder* aDecoder, SurfaceSink* aSink) { + checkEarlyExit(aDecoder, aSink, WriteState::FINISHED); + }); +} + +TEST(ImageSurfaceSink, SurfaceSinkWritePixelsToRow) +{ + WithSurfaceSink([](Decoder* aDecoder, SurfaceSink* aSink) { + // Write the first 99 rows of our 100x100 surface and verify that even + // though our lambda will yield pixels forever, only one row is written per + // call to WritePixelsToRow(). + for (int row = 0; row < 99; ++row) { + uint32_t count = 0; + WriteState result = aSink->WritePixelsToRow([&]{ + ++count; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + + EXPECT_EQ(WriteState::NEED_MORE_DATA, result); + EXPECT_EQ(100u, count); + EXPECT_FALSE(aSink->IsSurfaceFinished()); + + Maybe invalidRect = aSink->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isSome()); + EXPECT_EQ(IntRect(0, row, 100, 1), invalidRect->mInputSpaceRect); + EXPECT_EQ(IntRect(0, row, 100, 1), invalidRect->mOutputSpaceRect); + + CheckGeneratedImage(aDecoder, IntRect(0, 0, 100, row + 1)); + } + + // Write the final line, which should finish the surface. + uint32_t count = 0; + WriteState result = aSink->WritePixelsToRow([&]{ + ++count; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u, count); + + // Note that the final invalid rect we expect here is only the last row; + // that's because we called TakeInvalidRect() repeatedly in the loop above. + AssertCorrectPipelineFinalState(aSink, + IntRect(0, 99, 100, 1), + IntRect(0, 99, 100, 1)); + + // Check that the generated image is correct. + CheckGeneratedImage(aDecoder, IntRect(0, 0, 100, 100)); + + // Attempt to write more and make sure that nothing gets written. + count = 0; + result = aSink->WritePixelsToRow([&]{ + count++; + return AsVariant(BGRAColor::Red().AsPixel()); + }); + + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(0u, count); + EXPECT_TRUE(aSink->IsSurfaceFinished()); + + // Check that the generated image is still correct. + CheckGeneratedImage(aDecoder, IntRect(0, 0, 100, 100)); + }); +} + +TEST(ImageSurfaceSink, SurfaceSinkWritePixelsToRowEarlyExit) +{ + auto checkEarlyExit = + [](Decoder* aDecoder, SurfaceSink* aSink, WriteState aState) { + // Write half a row of green pixels and then exit early with |aState|. If + // the lambda keeps getting called, we'll write red pixels, which will cause + // the test to fail. + uint32_t count = 0; + auto result = aSink->WritePixelsToRow([&]() -> NextPixel { + if (count == 50) { + return AsVariant(aState); + } + return count++ < 50 ? AsVariant(BGRAColor::Green().AsPixel()) + : AsVariant(BGRAColor::Red().AsPixel()); + }); + + EXPECT_EQ(aState, result); + EXPECT_EQ(50u, count); + CheckGeneratedImage(aDecoder, IntRect(0, 0, 50, 1)); + + if (aState != WriteState::FINISHED) { + // We should still be able to write more at this point. + EXPECT_FALSE(aSink->IsSurfaceFinished()); + + // Verify that we can resume the same row and still stop at the end. + count = 0; + WriteState result = aSink->WritePixelsToRow([&]{ + ++count; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + + EXPECT_EQ(WriteState::NEED_MORE_DATA, result); + EXPECT_EQ(50u, count); + EXPECT_FALSE(aSink->IsSurfaceFinished()); + CheckGeneratedImage(aDecoder, IntRect(0, 0, 100, 1)); + + return; + } + + // We should've finished the surface at this point. + AssertCorrectPipelineFinalState(aSink, + IntRect(0, 0, 100, 100), + IntRect(0, 0, 100, 100)); + + // Attempt to write more and make sure that nothing gets written. + count = 0; + result = aSink->WritePixelsToRow([&]{ + count++; + return AsVariant(BGRAColor::Red().AsPixel()); + }); + + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(0u, count); + EXPECT_TRUE(aSink->IsSurfaceFinished()); + + // Check that the generated image is still correct. + CheckGeneratedImage(aDecoder, IntRect(0, 0, 50, 1)); + }; + + WithSurfaceSink([&](Decoder* aDecoder, SurfaceSink* aSink) { + checkEarlyExit(aDecoder, aSink, WriteState::NEED_MORE_DATA); + }); + + WithSurfaceSink([&](Decoder* aDecoder, SurfaceSink* aSink) { + checkEarlyExit(aDecoder, aSink, WriteState::FAILURE); + }); + + WithSurfaceSink([&](Decoder* aDecoder, SurfaceSink* aSink) { + checkEarlyExit(aDecoder, aSink, WriteState::FINISHED); + }); +} + +TEST(ImageSurfaceSink, SurfaceSinkWriteBuffer) +{ + WithSurfaceSink([](Decoder* aDecoder, SurfaceSink* aSink) { + // Create a green buffer the same size as one row of the surface (which is 100x100), + // containing 60 pixels of green in the middle and 20 transparent pixels on + // either side. + uint32_t buffer[100]; + for (int i = 0; i < 100; ++i) { + buffer[i] = 20 <= i && i < 80 ? BGRAColor::Green().AsPixel() + : BGRAColor::Transparent().AsPixel(); + } + + // Write the buffer to every row of the surface and check that the generated + // image is correct. + CheckIterativeWrite(aDecoder, aSink, IntRect(20, 0, 60, 100), [&]{ + return aSink->WriteBuffer(buffer); + }); + }); +} + +TEST(ImageSurfaceSink, SurfaceSinkWriteBufferPartialRow) +{ + WithSurfaceSink([](Decoder* aDecoder, SurfaceSink* aSink) { + // Create a buffer the same size as one row of the surface, containing all + // green pixels. + uint32_t buffer[100]; + for (int i = 0; i < 100; ++i) { + buffer[i] = BGRAColor::Green().AsPixel(); + } + + // Write the buffer to the middle 60 pixels of every row of the surface and + // check that the generated image is correct. + CheckIterativeWrite(aDecoder, aSink, IntRect(20, 0, 60, 100), [&]{ + return aSink->WriteBuffer(buffer, 20, 60); + }); + }); +} + +TEST(ImageSurfaceSink, SurfaceSinkWriteBufferPartialRowStartColOverflow) +{ + WithSurfaceSink([](Decoder* aDecoder, SurfaceSink* aSink) { + // Create a buffer the same size as one row of the surface, containing all + // green pixels. + uint32_t buffer[100]; + for (int i = 0; i < 100; ++i) { + buffer[i] = BGRAColor::Green().AsPixel(); + } + + { + // Write the buffer to successive rows until every row of the surface + // has been written. We place the start column beyond the end of the row, + // which will prevent us from writing anything, so we check that the + // generated image is entirely transparent. + CheckIterativeWrite(aDecoder, aSink, IntRect(0, 0, 0, 0), [&]{ + return aSink->WriteBuffer(buffer, 100, 100); + }); + } + + ResetForNextPass(aSink); + + { + // Write the buffer to successive rows until every row of the surface + // has been written. We use column 50 as the start column, but we still + // write the buffer, which means we overflow the right edge of the surface + // by 50 pixels. We check that the left half of the generated image is + // transparent and the right half is green. + CheckIterativeWrite(aDecoder, aSink, IntRect(50, 0, 50, 100), [&]{ + return aSink->WriteBuffer(buffer, 50, 100); + }); + } + }); +} + +TEST(ImageSurfaceSink, SurfaceSinkWriteBufferPartialRowBufferOverflow) +{ + WithSurfaceSink([](Decoder* aDecoder, SurfaceSink* aSink) { + // Create a buffer twice as large as a row of the surface. The first half + // (which is as large as a row of the image) will contain green pixels, + // while the second half will contain red pixels. + uint32_t buffer[200]; + for (int i = 0; i < 200; ++i) { + buffer[i] = i < 100 ? BGRAColor::Green().AsPixel() + : BGRAColor::Red().AsPixel(); + } + + { + // Write the buffer to successive rows until every row of the surface has + // been written. The buffer extends 100 pixels to the right of a row of + // the surface, but bounds checking will prevent us from overflowing the + // buffer. We check that the generated image is entirely green since the + // pixels on the right side of the buffer shouldn't have been written to + // the surface. + CheckIterativeWrite(aDecoder, aSink, IntRect(0, 0, 100, 100), [&]{ + return aSink->WriteBuffer(buffer, 0, 200); + }); + } + + ResetForNextPass(aSink); + + { + // Write from the buffer to the middle of each row of the surface. That + // means that the left side of each row should be transparent, since we + // didn't write anything there. A buffer overflow would cause us to write + // buffer contents into the left side of each row. We check that the + // generated image is transparent on the left side and green on the right. + CheckIterativeWrite(aDecoder, aSink, IntRect(50, 0, 50, 100), [&]{ + return aSink->WriteBuffer(buffer, 50, 200); + }); + } + }); +} + +TEST(ImageSurfaceSink, SurfaceSinkWriteBufferFromNullSource) +{ + WithSurfaceSink([](Decoder* aDecoder, SurfaceSink* aSink) { + // Calling WriteBuffer() with a null pointer should fail without making any + // changes to the surface. + uint32_t* nullBuffer = nullptr; + WriteState result = aSink->WriteBuffer(nullBuffer); + + EXPECT_EQ(WriteState::FAILURE, result); + EXPECT_FALSE(aSink->IsSurfaceFinished()); + Maybe invalidRect = aSink->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + // Check that nothing got written to the surface. + CheckGeneratedImage(aDecoder, IntRect(0, 0, 0, 0)); + }); +} + +TEST(ImageSurfaceSink, SurfaceSinkWriteEmptyRow) +{ + WithSurfaceSink([](Decoder* aDecoder, SurfaceSink* aSink) { + { + // Write an empty row to each row of the surface. We check that the + // generated image is entirely transparent. + CheckIterativeWrite(aDecoder, aSink, IntRect(0, 0, 0, 0), [&]{ + return aSink->WriteEmptyRow(); + }); + } + + ResetForNextPass(aSink); + + { + // Write a partial row before we begin calling WriteEmptyRow(). We check + // that the generated image is entirely transparent, which is to be + // expected since WriteEmptyRow() overwrites the current row even if some + // data has already been written to it. + uint32_t count = 0; + auto result = aSink->WritePixels([&]() -> NextPixel { + if (count == 50) { + return AsVariant(WriteState::NEED_MORE_DATA); + } + ++count; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + + EXPECT_EQ(WriteState::NEED_MORE_DATA, result); + EXPECT_EQ(50u, count); + EXPECT_FALSE(aSink->IsSurfaceFinished()); + + CheckIterativeWrite(aDecoder, aSink, IntRect(0, 0, 0, 0), [&]{ + return aSink->WriteEmptyRow(); + }); + } + + ResetForNextPass(aSink); + + { + // Create a buffer the same size as one row of the surface, containing all + // green pixels. + uint32_t buffer[100]; + for (int i = 0; i < 100; ++i) { + buffer[i] = BGRAColor::Green().AsPixel(); + } + + // Write an empty row to the middle 60 rows of the surface. The first 20 + // and last 20 rows will be green. (We need to use DoCheckIterativeWrite() + // here because we need a custom function to check the output, since it + // can't be described by a simple rect.) + auto writeFunc = [&](uint32_t aRow) { + if (aRow < 20 || aRow >= 80) { + return aSink->WriteBuffer(buffer); + } else { + return aSink->WriteEmptyRow(); + } + }; + + auto checkFunc = [&]{ + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSourceSurface(); + + EXPECT_TRUE(RowsAreSolidColor(surface, 0, 20, BGRAColor::Green())); + EXPECT_TRUE(RowsAreSolidColor(surface, 20, 60, BGRAColor::Transparent())); + EXPECT_TRUE(RowsAreSolidColor(surface, 80, 20, BGRAColor::Green())); + }; + + DoCheckIterativeWrite(aSink, writeFunc, checkFunc); + } + }); +} + +TEST(ImageSurfaceSink, SurfaceSinkWriteUnsafeComputedRow) +{ + WithSurfaceSink([](Decoder* aDecoder, SurfaceSink* aSink) { + // Create a green buffer the same size as one row of the surface. + uint32_t buffer[100]; + for (int i = 0; i < 100; ++i) { + buffer[i] = BGRAColor::Green().AsPixel(); + } + + // Write the buffer to successive rows until every row of the surface + // has been written. We only write to the right half of each row, so we + // check that the left side of the generated image is transparent and the + // right side is green. + CheckIterativeWrite(aDecoder, aSink, IntRect(50, 0, 50, 100), [&]{ + return aSink->WriteUnsafeComputedRow([&](uint32_t* aRow, + uint32_t aLength) { + EXPECT_EQ(100u, aLength ); + memcpy(aRow + 50, buffer, 50 * sizeof(uint32_t)); + }); + }); + }); +} + +TEST(ImageSurfaceSink, SurfaceSinkProgressivePasses) +{ + WithSurfaceSink([](Decoder* aDecoder, SurfaceSink* aSink) { + { + // Fill the image with a first pass of red. + uint32_t count = 0; + auto result = aSink->WritePixels([&]() { + ++count; + return AsVariant(BGRAColor::Red().AsPixel()); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u * 100u, count); + + AssertCorrectPipelineFinalState(aSink, + IntRect(0, 0, 100, 100), + IntRect(0, 0, 100, 100)); + + // Check that the generated image is correct. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSourceSurface(); + EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Red())); + } + + { + ResetForNextPass(aSink); + + // Check that the generated image is still the first pass image. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSourceSurface(); + EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Red())); + } + + { + // Fill the image with a second pass of green. + uint32_t count = 0; + auto result = aSink->WritePixels([&]() { + ++count; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u * 100u, count); + + AssertCorrectPipelineFinalState(aSink, + IntRect(0, 0, 100, 100), + IntRect(0, 0, 100, 100)); + + // Check that the generated image is correct. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSourceSurface(); + EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green())); + } + }); +} + +TEST(ImageSurfaceSink, SurfaceSinkInvalidRect) +{ + WithSurfaceSink([](Decoder* aDecoder, SurfaceSink* aSink) { + { + // Write one row. + uint32_t count = 0; + auto result = aSink->WritePixels([&]() -> NextPixel { + if (count == 100) { + return AsVariant(WriteState::NEED_MORE_DATA); + } + count++; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + EXPECT_EQ(WriteState::NEED_MORE_DATA, result); + EXPECT_EQ(100u, count); + EXPECT_FALSE(aSink->IsSurfaceFinished()); + + // Assert that we have the right invalid rect. + Maybe invalidRect = aSink->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isSome()); + EXPECT_EQ(IntRect(0, 0, 100, 1), invalidRect->mInputSpaceRect); + EXPECT_EQ(IntRect(0, 0, 100, 1), invalidRect->mOutputSpaceRect); + } + + { + // Write eight rows. + uint32_t count = 0; + auto result = aSink->WritePixels([&]() -> NextPixel { + if (count == 100 * 8) { + return AsVariant(WriteState::NEED_MORE_DATA); + } + count++; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + EXPECT_EQ(WriteState::NEED_MORE_DATA, result); + EXPECT_EQ(100u * 8u, count); + EXPECT_FALSE(aSink->IsSurfaceFinished()); + + // Assert that we have the right invalid rect. + Maybe invalidRect = aSink->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isSome()); + EXPECT_EQ(IntRect(0, 1, 100, 8), invalidRect->mInputSpaceRect); + EXPECT_EQ(IntRect(0, 1, 100, 8), invalidRect->mOutputSpaceRect); + } + + { + // Write the left half of one row. + uint32_t count = 0; + auto result = aSink->WritePixels([&]() -> NextPixel { + if (count == 50) { + return AsVariant(WriteState::NEED_MORE_DATA); + } + count++; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + EXPECT_EQ(WriteState::NEED_MORE_DATA, result); + EXPECT_EQ(50u, count); + EXPECT_FALSE(aSink->IsSurfaceFinished()); + + // Assert that we don't have an invalid rect, since the invalid rect only + // gets updated when a row gets completed. + Maybe invalidRect = aSink->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + } + + { + // Write the right half of the same row. + uint32_t count = 0; + auto result = aSink->WritePixels([&]() -> NextPixel { + if (count == 50) { + return AsVariant(WriteState::NEED_MORE_DATA); + } + count++; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + EXPECT_EQ(WriteState::NEED_MORE_DATA, result); + EXPECT_EQ(50u, count); + EXPECT_FALSE(aSink->IsSurfaceFinished()); + + // Assert that we have the right invalid rect, which will include both the + // left and right halves of this row now that we've completed it. + Maybe invalidRect = aSink->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isSome()); + EXPECT_EQ(IntRect(0, 9, 100, 1), invalidRect->mInputSpaceRect); + EXPECT_EQ(IntRect(0, 9, 100, 1), invalidRect->mOutputSpaceRect); + } + + { + // Write no rows. + auto result = aSink->WritePixels([&]() { + return AsVariant(WriteState::NEED_MORE_DATA); + }); + EXPECT_EQ(WriteState::NEED_MORE_DATA, result); + EXPECT_FALSE(aSink->IsSurfaceFinished()); + + // Assert that we don't have an invalid rect. + Maybe invalidRect = aSink->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + } + + { + // Fill the rest of the image. + uint32_t count = 0; + auto result = aSink->WritePixels([&]() { + count++; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u * 90u, count); + EXPECT_TRUE(aSink->IsSurfaceFinished()); + + // Assert that we have the right invalid rect. + Maybe invalidRect = aSink->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isSome()); + EXPECT_EQ(IntRect(0, 10, 100, 90), invalidRect->mInputSpaceRect); + EXPECT_EQ(IntRect(0, 10, 100, 90), invalidRect->mOutputSpaceRect); + + // Check that the generated image is correct. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSourceSurface(); + EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green())); + } + }); +} + +TEST(ImageSurfaceSink, SurfaceSinkFlipVertically) +{ + WithSurfaceSink([](Decoder* aDecoder, + SurfaceSink* aSink) { + { + // Fill the image with a first pass of red. + uint32_t count = 0; + auto result = aSink->WritePixels([&]() { + ++count; + return AsVariant(BGRAColor::Red().AsPixel()); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u * 100u, count); + + AssertCorrectPipelineFinalState(aSink, + IntRect(0, 0, 100, 100), + IntRect(0, 0, 100, 100)); + + // Check that the generated image is correct. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSourceSurface(); + EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Red())); + } + + { + ResetForNextPass(aSink); + + // Check that the generated image is still the first pass image. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSourceSurface(); + EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Red())); + } + + { + // Fill 25 rows of the image with green and make sure everything is OK. + uint32_t count = 0; + auto result = aSink->WritePixels([&]() -> NextPixel { + if (count == 25 * 100) { + return AsVariant(WriteState::NEED_MORE_DATA); + } + count++; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + EXPECT_EQ(WriteState::NEED_MORE_DATA, result); + EXPECT_EQ(25u * 100u, count); + EXPECT_FALSE(aSink->IsSurfaceFinished()); + + // Assert that we have the right invalid rect, which should include the + // *bottom* (since we're flipping vertically) 25 rows of the image. + Maybe invalidRect = aSink->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isSome()); + EXPECT_EQ(IntRect(0, 75, 100, 25), invalidRect->mInputSpaceRect); + EXPECT_EQ(IntRect(0, 75, 100, 25), invalidRect->mOutputSpaceRect); + + // Check that the generated image is correct. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSourceSurface(); + EXPECT_TRUE(RowsAreSolidColor(surface, 0, 75, BGRAColor::Red())); + EXPECT_TRUE(RowsAreSolidColor(surface, 75, 25, BGRAColor::Green())); + } + + { + // Fill the rest of the image with a second pass of green. + uint32_t count = 0; + auto result = aSink->WritePixels([&]() { + ++count; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(75u * 100u, count); + + AssertCorrectPipelineFinalState(aSink, + IntRect(0, 0, 100, 75), + IntRect(0, 0, 100, 75)); + + // Check that the generated image is correct. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSourceSurface(); + EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green())); + } + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkInitialization) +{ + WithPalettedSurfaceSink(IntRect(0, 0, 100, 100), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + // Check initial state. + EXPECT_FALSE(aSink->IsSurfaceFinished()); + Maybe invalidRect = aSink->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + // Check that the paletted image data is zero-initialized. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + uint8_t* imageData = nullptr; + uint32_t imageLength = 0; + currentFrame->GetImageData(&imageData, &imageLength); + ASSERT_TRUE(imageData != nullptr); + ASSERT_EQ(100u * 100u, imageLength); + for (uint32_t i = 0; i < imageLength; ++i) { + ASSERT_EQ(uint8_t(0), imageData[i]); + } + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsFor0_0_100_100) +{ + WithPalettedSurfaceSink(IntRect(0, 0, 100, 100), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + CheckPalettedWritePixels(aDecoder, aSink); + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsFor25_25_50_50) +{ + WithPalettedSurfaceSink(IntRect(25, 25, 50, 50), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + CheckPalettedWritePixels(aDecoder, aSink, + /* aOutputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputWriteRect = */ Some(IntRect(25, 25, 50, 50)), + /* aOutputWriteRect = */ Some(IntRect(25, 25, 50, 50))); + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsForMinus25_Minus25_50_50) +{ + WithPalettedSurfaceSink(IntRect(-25, -25, 50, 50), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + CheckPalettedWritePixels(aDecoder, aSink, + /* aOutputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputWriteRect = */ Some(IntRect(-25, -25, 50, 50)), + /* aOutputWriteRect = */ Some(IntRect(-25, -25, 50, 50))); + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsFor75_Minus25_50_50) +{ + WithPalettedSurfaceSink(IntRect(75, -25, 50, 50), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + CheckPalettedWritePixels(aDecoder, aSink, + /* aOutputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputWriteRect = */ Some(IntRect(75, -25, 50, 50)), + /* aOutputWriteRect = */ Some(IntRect(75, -25, 50, 50))); + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsForMinus25_75_50_50) +{ + WithPalettedSurfaceSink(IntRect(-25, 75, 50, 50), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + CheckPalettedWritePixels(aDecoder, aSink, + /* aOutputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputWriteRect = */ Some(IntRect(-25, 75, 50, 50)), + /* aOutputWriteRect = */ Some(IntRect(-25, 75, 50, 50))); + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsFor75_75_50_50) +{ + WithPalettedSurfaceSink(IntRect(75, 75, 50, 50), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + CheckPalettedWritePixels(aDecoder, aSink, + /* aOutputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputWriteRect = */ Some(IntRect(75, 75, 50, 50)), + /* aOutputWriteRect = */ Some(IntRect(75, 75, 50, 50))); + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsFinish) +{ + WithPalettedSurfaceSink(IntRect(0, 0, 100, 100), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + // Write nothing into the surface; just finish immediately. + uint32_t count = 0; + auto result = aSink->WritePixels([&]{ + count++; + return AsVariant(WriteState::FINISHED); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(1u, count); + + AssertCorrectPipelineFinalState(aSink, + IntRect(0, 0, 100, 100), + IntRect(0, 0, 100, 100)); + + // Attempt to write more and make sure that nothing gets written. + count = 0; + result = aSink->WritePixels([&]() { + count++; + return AsVariant(uint8_t(128)); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(0u, count); + EXPECT_TRUE(aSink->IsSurfaceFinished()); + + // Check that the generated image is correct. + EXPECT_TRUE(IsSolidPalettedColor(aDecoder, 0)); + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsEarlyExit) +{ + auto checkEarlyExit = + [](Decoder* aDecoder, PalettedSurfaceSink* aSink, WriteState aState) { + // Write half a row of green pixels and then exit early with |aState|. If + // the lambda keeps getting called, we'll write red pixels, which will cause + // the test to fail. + uint32_t count = 0; + auto result = aSink->WritePixels([&]() -> NextPixel { + if (count == 50) { + return AsVariant(aState); + } + return count++ < 50 ? AsVariant(uint8_t(255)) : AsVariant(uint8_t(128)); + }); + + EXPECT_EQ(aState, result); + EXPECT_EQ(50u, count); + CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 50, 1)); + + if (aState != WriteState::FINISHED) { + // We should still be able to write more at this point. + EXPECT_FALSE(aSink->IsSurfaceFinished()); + + // Verify that we can resume writing. We'll finish up the same row. + count = 0; + result = aSink->WritePixels([&]() -> NextPixel { + if (count == 50) { + return AsVariant(WriteState::NEED_MORE_DATA); + } + ++count; + return AsVariant(uint8_t(255)); + }); + + EXPECT_EQ(WriteState::NEED_MORE_DATA, result); + EXPECT_EQ(50u, count); + EXPECT_FALSE(aSink->IsSurfaceFinished()); + CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 100, 1)); + + return; + } + + // We should've finished the surface at this point. + AssertCorrectPipelineFinalState(aSink, + IntRect(0, 0, 100, 100), + IntRect(0, 0, 100, 100)); + + // Attempt to write more and make sure that nothing gets written. + count = 0; + result = aSink->WritePixels([&]{ + count++; + return AsVariant(uint8_t(128)); + }); + + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(0u, count); + EXPECT_TRUE(aSink->IsSurfaceFinished()); + + // Check that the generated image is still correct. + CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 50, 1)); + }; + + WithPalettedSurfaceSink(IntRect(0, 0, 100, 100), + [&](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + checkEarlyExit(aDecoder, aSink, WriteState::NEED_MORE_DATA); + }); + + WithPalettedSurfaceSink(IntRect(0, 0, 100, 100), + [&](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + checkEarlyExit(aDecoder, aSink, WriteState::FAILURE); + }); + + WithPalettedSurfaceSink(IntRect(0, 0, 100, 100), + [&](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + checkEarlyExit(aDecoder, aSink, WriteState::FINISHED); + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsToRow) +{ + WithPalettedSurfaceSink(IntRect(0, 0, 100, 100), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + // Write the first 99 rows of our 100x100 surface and verify that even + // though our lambda will yield pixels forever, only one row is written per + // call to WritePixelsToRow(). + for (int row = 0; row < 99; ++row) { + uint32_t count = 0; + WriteState result = aSink->WritePixelsToRow([&]{ + ++count; + return AsVariant(uint8_t(255)); + }); + + EXPECT_EQ(WriteState::NEED_MORE_DATA, result); + EXPECT_EQ(100u, count); + EXPECT_FALSE(aSink->IsSurfaceFinished()); + + Maybe invalidRect = aSink->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isSome()); + EXPECT_EQ(IntRect(0, row, 100, 1), invalidRect->mInputSpaceRect); + EXPECT_EQ(IntRect(0, row, 100, 1), invalidRect->mOutputSpaceRect); + + CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 100, row + 1)); + } + + // Write the final line, which should finish the surface. + uint32_t count = 0; + WriteState result = aSink->WritePixelsToRow([&]{ + ++count; + return AsVariant(uint8_t(255)); + }); + + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u, count); + + // Note that the final invalid rect we expect here is only the last row; + // that's because we called TakeInvalidRect() repeatedly in the loop above. + AssertCorrectPipelineFinalState(aSink, + IntRect(0, 99, 100, 1), + IntRect(0, 99, 100, 1)); + + // Check that the generated image is correct. + CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 100, 100)); + + // Attempt to write more and make sure that nothing gets written. + count = 0; + result = aSink->WritePixelsToRow([&]{ + count++; + return AsVariant(uint8_t(128)); + }); + + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(0u, count); + EXPECT_TRUE(aSink->IsSurfaceFinished()); + + // Check that the generated image is still correct. + CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 100, 100)); + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsToRowEarlyExit) +{ + auto checkEarlyExit = + [](Decoder* aDecoder, PalettedSurfaceSink* aSink, WriteState aState) { + // Write half a row of 255s and then exit early with |aState|. If the lambda + // keeps getting called, we'll write 128s, which will cause the test to + // fail. + uint32_t count = 0; + auto result = aSink->WritePixelsToRow([&]() -> NextPixel { + if (count == 50) { + return AsVariant(aState); + } + return count++ < 50 ? AsVariant(uint8_t(255)) + : AsVariant(uint8_t(128)); + }); + + EXPECT_EQ(aState, result); + EXPECT_EQ(50u, count); + CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 50, 1)); + + if (aState != WriteState::FINISHED) { + // We should still be able to write more at this point. + EXPECT_FALSE(aSink->IsSurfaceFinished()); + + // Verify that we can resume the same row and still stop at the end. + count = 0; + WriteState result = aSink->WritePixelsToRow([&]{ + ++count; + return AsVariant(uint8_t(255)); + }); + + EXPECT_EQ(WriteState::NEED_MORE_DATA, result); + EXPECT_EQ(50u, count); + EXPECT_FALSE(aSink->IsSurfaceFinished()); + CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 100, 1)); + + return; + } + + // We should've finished the surface at this point. + AssertCorrectPipelineFinalState(aSink, + IntRect(0, 0, 100, 100), + IntRect(0, 0, 100, 100)); + + // Attempt to write more and make sure that nothing gets written. + count = 0; + result = aSink->WritePixelsToRow([&]{ + count++; + return AsVariant(uint8_t(128)); + }); + + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(0u, count); + EXPECT_TRUE(aSink->IsSurfaceFinished()); + + // Check that the generated image is still correct. + CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 50, 1)); + }; + + WithPalettedSurfaceSink(IntRect(0, 0, 100, 100), + [&](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + checkEarlyExit(aDecoder, aSink, WriteState::NEED_MORE_DATA); + }); + + WithPalettedSurfaceSink(IntRect(0, 0, 100, 100), + [&](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + checkEarlyExit(aDecoder, aSink, WriteState::FAILURE); + }); + + WithPalettedSurfaceSink(IntRect(0, 0, 100, 100), + [&](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + checkEarlyExit(aDecoder, aSink, WriteState::FINISHED); + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteBuffer) +{ + WithPalettedSurfaceSink(IntRect(0, 0, 100, 100), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + // Create a buffer the same size as one row of the surface (which is 100x100), + // containing 60 pixels of 255 in the middle and 20 transparent pixels of 0 on + // either side. + uint8_t buffer[100]; + for (int i = 0; i < 100; ++i) { + buffer[i] = 20 <= i && i < 80 ? 255 : 0; + } + + // Write the buffer to every row of the surface and check that the generated + // image is correct. + CheckPalettedIterativeWrite(aDecoder, aSink, IntRect(20, 0, 60, 100), [&]{ + return aSink->WriteBuffer(buffer); + }); + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteBufferPartialRow) +{ + WithPalettedSurfaceSink(IntRect(0, 0, 100, 100), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + // Create a buffer the same size as one row of the surface, containing all + // 255 pixels. + uint8_t buffer[100]; + for (int i = 0; i < 100; ++i) { + buffer[i] = 255; + } + + // Write the buffer to the middle 60 pixels of every row of the surface and + // check that the generated image is correct. + CheckPalettedIterativeWrite(aDecoder, aSink, IntRect(20, 0, 60, 100), [&]{ + return aSink->WriteBuffer(buffer, 20, 60); + }); + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteBufferPartialRowStartColOverflow) +{ + WithPalettedSurfaceSink(IntRect(0, 0, 100, 100), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + // Create a buffer the same size as one row of the surface, containing all + // 255 pixels. + uint8_t buffer[100]; + for (int i = 0; i < 100; ++i) { + buffer[i] = 255; + } + + { + // Write the buffer to successive rows until every row of the surface + // has been written. We place the start column beyond the end of the row, + // which will prevent us from writing anything, so we check that the + // generated image is entirely 0. + CheckPalettedIterativeWrite(aDecoder, aSink, IntRect(0, 0, 0, 0), [&]{ + return aSink->WriteBuffer(buffer, 100, 100); + }); + } + + ResetForNextPass(aSink); + + { + // Write the buffer to successive rows until every row of the surface + // has been written. We use column 50 as the start column, but we still + // write the buffer, which means we overflow the right edge of the surface + // by 50 pixels. We check that the left half of the generated image is + // 0 and the right half is 255. + CheckPalettedIterativeWrite(aDecoder, aSink, IntRect(50, 0, 50, 100), [&]{ + return aSink->WriteBuffer(buffer, 50, 100); + }); + } + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteBufferPartialRowBufferOverflow) +{ + WithPalettedSurfaceSink(IntRect(0, 0, 100, 100), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + // Create a buffer twice as large as a row of the surface. The first half + // (which is as large as a row of the image) will contain 255 pixels, + // while the second half will contain 128 pixels. + uint8_t buffer[200]; + for (int i = 0; i < 200; ++i) { + buffer[i] = i < 100 ? 255 : 128; + } + + { + // Write the buffer to successive rows until every row of the surface has + // been written. The buffer extends 100 pixels to the right of a row of + // the surface, but bounds checking will prevent us from overflowing the + // buffer. We check that the generated image is entirely 255 since the + // pixels on the right side of the buffer shouldn't have been written to + // the surface. + CheckPalettedIterativeWrite(aDecoder, aSink, IntRect(0, 0, 100, 100), [&]{ + return aSink->WriteBuffer(buffer, 0, 200); + }); + } + + ResetForNextPass(aSink); + + { + // Write from the buffer to the middle of each row of the surface. That + // means that the left side of each row should be 0, since we didn't write + // anything there. A buffer overflow would cause us to write buffer + // contents into the left side of each row. We check that the generated + // image is 0 on the left side and 255 on the right. + CheckPalettedIterativeWrite(aDecoder, aSink, IntRect(50, 0, 50, 100), [&]{ + return aSink->WriteBuffer(buffer, 50, 200); + }); + } + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteBufferFromNullSource) +{ + WithPalettedSurfaceSink(IntRect(0, 0, 100, 100), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + // Calling WriteBuffer() with a null pointer should fail without making any + // changes to the surface. + uint8_t* nullBuffer = nullptr; + WriteState result = aSink->WriteBuffer(nullBuffer); + + EXPECT_EQ(WriteState::FAILURE, result); + EXPECT_FALSE(aSink->IsSurfaceFinished()); + Maybe invalidRect = aSink->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + // Check that nothing got written to the surface. + CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 0, 0)); + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteEmptyRow) +{ + WithPalettedSurfaceSink(IntRect(0, 0, 100, 100), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + { + // Write an empty row to each row of the surface. We check that the + // generated image is entirely 0. + CheckPalettedIterativeWrite(aDecoder, aSink, IntRect(0, 0, 0, 0), [&]{ + return aSink->WriteEmptyRow(); + }); + } + + ResetForNextPass(aSink); + + { + // Write a partial row before we begin calling WriteEmptyRow(). We check + // that the generated image is entirely 0, which is to be expected since + // WriteEmptyRow() overwrites the current row even if some data has + // already been written to it. + uint32_t count = 0; + auto result = aSink->WritePixels([&]() -> NextPixel { + if (count == 50) { + return AsVariant(WriteState::NEED_MORE_DATA); + } + ++count; + return AsVariant(uint8_t(255)); + }); + + EXPECT_EQ(WriteState::NEED_MORE_DATA, result); + EXPECT_EQ(50u, count); + EXPECT_FALSE(aSink->IsSurfaceFinished()); + + CheckPalettedIterativeWrite(aDecoder, aSink, IntRect(0, 0, 0, 0), [&]{ + return aSink->WriteEmptyRow(); + }); + } + + ResetForNextPass(aSink); + + { + // Create a buffer the same size as one row of the surface, containing all + // 255 pixels. + uint8_t buffer[100]; + for (int i = 0; i < 100; ++i) { + buffer[i] = 255; + } + + // Write an empty row to the middle 60 rows of the surface. The first 20 + // and last 20 rows will be 255. (We need to use DoCheckIterativeWrite() + // here because we need a custom function to check the output, since it + // can't be described by a simple rect.) + auto writeFunc = [&](uint32_t aRow) { + if (aRow < 20 || aRow >= 80) { + return aSink->WriteBuffer(buffer); + } else { + return aSink->WriteEmptyRow(); + } + }; + + auto checkFunc = [&]{ + EXPECT_TRUE(PalettedRowsAreSolidColor(aDecoder, 0, 20, 255)); + EXPECT_TRUE(PalettedRowsAreSolidColor(aDecoder, 20, 60, 0)); + EXPECT_TRUE(PalettedRowsAreSolidColor(aDecoder, 80, 20, 255)); + }; + + DoCheckIterativeWrite(aSink, writeFunc, checkFunc); + } + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteUnsafeComputedRow) +{ + WithPalettedSurfaceSink(IntRect(0, 0, 100, 100), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + // Create an all-255 buffer the same size as one row of the surface. + uint8_t buffer[100]; + for (int i = 0; i < 100; ++i) { + buffer[i] = 255; + } + + // Write the buffer to successive rows until every row of the surface has + // been written. We only write to the right half of each row, so we check + // that the left side of the generated image is 0 and the right side is 255. + CheckPalettedIterativeWrite(aDecoder, aSink, IntRect(50, 0, 50, 100), [&]{ + return aSink->WriteUnsafeComputedRow([&](uint8_t* aRow, + uint32_t aLength) { + EXPECT_EQ(100u, aLength ); + memcpy(aRow + 50, buffer, 50 * sizeof(uint8_t)); + }); + }); + }); +} diff --git a/image/test/gtest/animated-with-extra-image-sub-blocks.gif b/image/test/gtest/animated-with-extra-image-sub-blocks.gif new file mode 100644 index 000000000..a145c814a Binary files /dev/null and b/image/test/gtest/animated-with-extra-image-sub-blocks.gif differ diff --git a/image/test/gtest/corrupt-with-bad-bmp-height.ico b/image/test/gtest/corrupt-with-bad-bmp-height.ico new file mode 100644 index 000000000..ee4a90fcd Binary files /dev/null and b/image/test/gtest/corrupt-with-bad-bmp-height.ico differ diff --git a/image/test/gtest/corrupt-with-bad-bmp-width.ico b/image/test/gtest/corrupt-with-bad-bmp-width.ico new file mode 100644 index 000000000..aa4051cd0 Binary files /dev/null and b/image/test/gtest/corrupt-with-bad-bmp-width.ico differ diff --git a/image/test/gtest/corrupt.jpg b/image/test/gtest/corrupt.jpg new file mode 100644 index 000000000..555a416d7 Binary files /dev/null and b/image/test/gtest/corrupt.jpg differ diff --git a/image/test/gtest/downscaled.bmp b/image/test/gtest/downscaled.bmp new file mode 100644 index 000000000..9e6a29e62 Binary files /dev/null and b/image/test/gtest/downscaled.bmp differ diff --git a/image/test/gtest/downscaled.gif b/image/test/gtest/downscaled.gif new file mode 100644 index 000000000..ff9a20bcd Binary files /dev/null and b/image/test/gtest/downscaled.gif differ diff --git a/image/test/gtest/downscaled.ico b/image/test/gtest/downscaled.ico new file mode 100644 index 000000000..ee112af0a Binary files /dev/null and b/image/test/gtest/downscaled.ico differ diff --git a/image/test/gtest/downscaled.icon b/image/test/gtest/downscaled.icon new file mode 100644 index 000000000..19785f5dc Binary files /dev/null and b/image/test/gtest/downscaled.icon differ diff --git a/image/test/gtest/downscaled.jpg b/image/test/gtest/downscaled.jpg new file mode 100644 index 000000000..5a4b3cd03 Binary files /dev/null and b/image/test/gtest/downscaled.jpg differ diff --git a/image/test/gtest/downscaled.png b/image/test/gtest/downscaled.png new file mode 100644 index 000000000..b71b4652d Binary files /dev/null and b/image/test/gtest/downscaled.png differ diff --git a/image/test/gtest/first-frame-green.gif b/image/test/gtest/first-frame-green.gif new file mode 100644 index 000000000..cd3c7d3db Binary files /dev/null and b/image/test/gtest/first-frame-green.gif differ diff --git a/image/test/gtest/first-frame-green.png b/image/test/gtest/first-frame-green.png new file mode 100644 index 000000000..115f035d8 Binary files /dev/null and b/image/test/gtest/first-frame-green.png differ diff --git a/image/test/gtest/first-frame-padding.gif b/image/test/gtest/first-frame-padding.gif new file mode 100644 index 000000000..e6d7c4932 Binary files /dev/null and b/image/test/gtest/first-frame-padding.gif differ diff --git a/image/test/gtest/green-1x1-truncated.gif b/image/test/gtest/green-1x1-truncated.gif new file mode 100644 index 000000000..0829f9694 Binary files /dev/null and b/image/test/gtest/green-1x1-truncated.gif differ diff --git a/image/test/gtest/green.bmp b/image/test/gtest/green.bmp new file mode 100644 index 000000000..f79dd672a Binary files /dev/null and b/image/test/gtest/green.bmp differ diff --git a/image/test/gtest/green.gif b/image/test/gtest/green.gif new file mode 100644 index 000000000..ef215dfc9 Binary files /dev/null and b/image/test/gtest/green.gif differ diff --git a/image/test/gtest/green.ico b/image/test/gtest/green.ico new file mode 100644 index 000000000..c5dfa8b53 Binary files /dev/null and b/image/test/gtest/green.ico differ diff --git a/image/test/gtest/green.icon b/image/test/gtest/green.icon new file mode 100644 index 000000000..c74e62fee Binary files /dev/null and b/image/test/gtest/green.icon differ diff --git a/image/test/gtest/green.jpg b/image/test/gtest/green.jpg new file mode 100644 index 000000000..48c454d27 Binary files /dev/null and b/image/test/gtest/green.jpg differ diff --git a/image/test/gtest/green.png b/image/test/gtest/green.png new file mode 100644 index 000000000..7df25f33b Binary files /dev/null and b/image/test/gtest/green.png differ diff --git a/image/test/gtest/invalid-truncated-metadata.bmp b/image/test/gtest/invalid-truncated-metadata.bmp new file mode 100644 index 000000000..228c5c999 Binary files /dev/null and b/image/test/gtest/invalid-truncated-metadata.bmp differ diff --git a/image/test/gtest/moz.build b/image/test/gtest/moz.build new file mode 100644 index 000000000..5cf6d5116 --- /dev/null +++ b/image/test/gtest/moz.build @@ -0,0 +1,78 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Library('imagetest') + +UNIFIED_SOURCES = [ + 'Common.cpp', + 'TestADAM7InterpolatingFilter.cpp', + 'TestCopyOnWrite.cpp', + 'TestDecoders.cpp', + 'TestDecodeToSurface.cpp', + 'TestDeinterlacingFilter.cpp', + 'TestMetadata.cpp', + 'TestRemoveFrameRectFilter.cpp', + 'TestSourceBuffer.cpp', + 'TestStreamingLexer.cpp', + 'TestSurfaceSink.cpp', +] + +if CONFIG['MOZ_ENABLE_SKIA']: + UNIFIED_SOURCES += [ + 'TestDownscalingFilter.cpp', + 'TestSurfacePipeIntegration.cpp', + ] + +SOURCES += [ + # Can't be unified because it manipulates the preprocessor environment. + 'TestDownscalingFilterNoSkia.cpp', +] + +TEST_HARNESS_FILES.gtest += [ + 'animated-with-extra-image-sub-blocks.gif', + 'corrupt-with-bad-bmp-height.ico', + 'corrupt-with-bad-bmp-width.ico', + 'corrupt.jpg', + 'downscaled.bmp', + 'downscaled.gif', + 'downscaled.ico', + 'downscaled.icon', + 'downscaled.jpg', + 'downscaled.png', + 'first-frame-green.gif', + 'first-frame-green.png', + 'first-frame-padding.gif', + 'green-1x1-truncated.gif', + 'green.bmp', + 'green.gif', + 'green.ico', + 'green.icon', + 'green.jpg', + 'green.png', + 'invalid-truncated-metadata.bmp', + 'no-frame-delay.gif', + 'rle4.bmp', + 'rle8.bmp', + 'transparent-ico-with-and-mask.ico', + 'transparent-if-within-ico.bmp', + 'transparent.gif', + 'transparent.png', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +LOCAL_INCLUDES += [ + '/dom/base', + '/gfx/2d', + '/image', +] + +LOCAL_INCLUDES += CONFIG['SKIA_INCLUDES'] + +FINAL_LIBRARY = 'xul-gtest' + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/image/test/gtest/no-frame-delay.gif b/image/test/gtest/no-frame-delay.gif new file mode 100644 index 000000000..1c50b6743 Binary files /dev/null and b/image/test/gtest/no-frame-delay.gif differ diff --git a/image/test/gtest/rle4.bmp b/image/test/gtest/rle4.bmp new file mode 100644 index 000000000..78a092787 Binary files /dev/null and b/image/test/gtest/rle4.bmp differ diff --git a/image/test/gtest/rle8.bmp b/image/test/gtest/rle8.bmp new file mode 100644 index 000000000..bd793b6b6 Binary files /dev/null and b/image/test/gtest/rle8.bmp differ diff --git a/image/test/gtest/transparent-ico-with-and-mask.ico b/image/test/gtest/transparent-ico-with-and-mask.ico new file mode 100644 index 000000000..ab0dc4bce Binary files /dev/null and b/image/test/gtest/transparent-ico-with-and-mask.ico differ diff --git a/image/test/gtest/transparent-if-within-ico.bmp b/image/test/gtest/transparent-if-within-ico.bmp new file mode 100644 index 000000000..4dc04c181 Binary files /dev/null and b/image/test/gtest/transparent-if-within-ico.bmp differ diff --git a/image/test/gtest/transparent.gif b/image/test/gtest/transparent.gif new file mode 100644 index 000000000..48f5c7caf Binary files /dev/null and b/image/test/gtest/transparent.gif differ diff --git a/image/test/gtest/transparent.png b/image/test/gtest/transparent.png new file mode 100644 index 000000000..fc8002053 Binary files /dev/null and b/image/test/gtest/transparent.png differ diff --git a/image/test/mochitest/12M-pixels-1.png b/image/test/mochitest/12M-pixels-1.png new file mode 100644 index 000000000..f802dd539 Binary files /dev/null and b/image/test/mochitest/12M-pixels-1.png differ diff --git a/image/test/mochitest/12M-pixels-2.png b/image/test/mochitest/12M-pixels-2.png new file mode 100644 index 000000000..a6d430442 Binary files /dev/null and b/image/test/mochitest/12M-pixels-2.png differ diff --git a/image/test/mochitest/6M-pixels.png b/image/test/mochitest/6M-pixels.png new file mode 100644 index 000000000..c813d8b56 Binary files /dev/null and b/image/test/mochitest/6M-pixels.png differ diff --git a/image/test/mochitest/INT32_MIN.bmp b/image/test/mochitest/INT32_MIN.bmp new file mode 100644 index 000000000..d9a001610 Binary files /dev/null and b/image/test/mochitest/INT32_MIN.bmp differ diff --git a/image/test/mochitest/animated-gif-finalframe.gif b/image/test/mochitest/animated-gif-finalframe.gif new file mode 100644 index 000000000..4e80d31a7 Binary files /dev/null and b/image/test/mochitest/animated-gif-finalframe.gif differ diff --git a/image/test/mochitest/animated-gif.gif b/image/test/mochitest/animated-gif.gif new file mode 100644 index 000000000..001cbfb87 Binary files /dev/null and b/image/test/mochitest/animated-gif.gif differ diff --git a/image/test/mochitest/animated-gif2.gif b/image/test/mochitest/animated-gif2.gif new file mode 100644 index 000000000..c66cc4b73 Binary files /dev/null and b/image/test/mochitest/animated-gif2.gif differ diff --git a/image/test/mochitest/animated-gif_trailing-garbage.gif b/image/test/mochitest/animated-gif_trailing-garbage.gif new file mode 100644 index 000000000..02f4de2e3 Binary files /dev/null and b/image/test/mochitest/animated-gif_trailing-garbage.gif differ diff --git a/image/test/mochitest/animated1.gif b/image/test/mochitest/animated1.gif new file mode 100644 index 000000000..2f9d8a512 Binary files /dev/null and b/image/test/mochitest/animated1.gif differ diff --git a/image/test/mochitest/animated2.gif b/image/test/mochitest/animated2.gif new file mode 100644 index 000000000..2f9d8a512 Binary files /dev/null and b/image/test/mochitest/animated2.gif differ diff --git a/image/test/mochitest/animation.svg b/image/test/mochitest/animation.svg new file mode 100644 index 000000000..2141d8679 --- /dev/null +++ b/image/test/mochitest/animation.svg @@ -0,0 +1,5 @@ + + + + diff --git a/image/test/mochitest/animationPolling.js b/image/test/mochitest/animationPolling.js new file mode 100644 index 000000000..6f5e7c889 --- /dev/null +++ b/image/test/mochitest/animationPolling.js @@ -0,0 +1,414 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +var currentTest; +var gIsRefImageLoaded = false; +const gShouldOutputDebugInfo = false; + +function pollForSuccess() +{ + if (!currentTest.isTestFinished) { + if (!currentTest.reusingReferenceImage || (currentTest.reusingReferenceImage + && gRefImageLoaded)) { + currentTest.checkImage(); + } + + setTimeout(pollForSuccess, currentTest.pollFreq); + } +}; + +function referencePoller() +{ + currentTest.takeReferenceSnapshot(); +} + +function reuseImageCallback() +{ + gIsRefImageLoaded = true; +} + +function failTest() +{ + if (currentTest.isTestFinished || currentTest.closeFunc) { + return; + } + + ok(false, "timing out after " + currentTest.timeout + "ms. " + + "Animated image still doesn't look correct, after poll #" + + currentTest.pollCounter); + currentTest.wereFailures = true; + + if (currentTest.currentSnapshotDataURI) { + currentTest.outputDebugInfo("Snapshot #" + currentTest.pollCounter, + "snapNum" + currentTest.pollCounter, + currentTest.currentSnapshotDataURI); + } + + currentTest.enableDisplay(document.getElementById(currentTest.debugElementId)); + + currentTest.cleanUpAndFinish(); +}; + +/** + * Create a new AnimationTest object. + * + * @param pollFreq The amount of time (in ms) to wait between consecutive + * snapshots if the reference image and the test image don't match. + * @param timeout The total amount of time (in ms) to wait before declaring the + * test as failed. + * @param referenceElementId The id attribute of the reference image element, or + * the source of the image to change to, once the reference snapshot has + * been successfully taken. This latter option could be used if you don't + * want the image to become invisible at any time during the test. + * @param imageElementId The id attribute of the test image element. + * @param debugElementId The id attribute of the div where links should be + * appended if the test fails. + * @param cleanId The id attribute of the div or element to use as the 'clean' + * test. This element is only enabled when we are testing to verify that + * the reference image has been loaded. It can be undefined. + * @param srcAttr The location of the source of the image, for preloading. This + * is usually not required, but it useful for preloading reference + * images. + * @param xulTest A boolean value indicating whether or not this is a XUL test + * (uses hidden=true/false rather than display: none to hide/show + * elements). + * @param closeFunc A function that should be called when this test is finished. + * If null, then cleanUpAndFinish() will be called. This can be used to + * chain tests together, so they are all finished exactly once. + * @returns {AnimationTest} + */ +function AnimationTest(pollFreq, timeout, referenceElementId, imageElementId, + debugElementId, cleanId, srcAttr, xulTest, closeFunc) +{ + // We want to test the cold loading behavior, so clear cache in case an + // earlier test got our image in there already. + clearAllImageCaches(); + + this.wereFailures = false; + this.pollFreq = pollFreq; + this.timeout = timeout; + this.imageElementId = imageElementId; + this.referenceElementId = referenceElementId; + + if (!document.getElementById(referenceElementId)) { + // In this case, we're assuming the user passed in a string that + // indicates the source of the image they want to change to, + // after the reference image has been taken. + this.reusingImageAsReference = true; + } + + this.srcAttr = srcAttr; + this.debugElementId = debugElementId; + this.referenceSnapshot = ""; // value will be set in takeReferenceSnapshot() + this.pollCounter = 0; + this.isTestFinished = false; + this.numRefsTaken = 0; + this.blankWaitTime = 0; + + this.cleanId = cleanId ? cleanId : ''; + this.xulTest = xulTest ? xulTest : ''; + this.closeFunc = closeFunc ? closeFunc : ''; +}; + +AnimationTest.prototype.preloadImage = function() +{ + if (this.srcAttr) { + this.myImage = new Image(); + this.myImage.onload = function() { currentTest.continueTest(); }; + this.myImage.src = this.srcAttr; + } else { + this.continueTest(); + } +}; + +AnimationTest.prototype.outputDebugInfo = function(message, id, dataUri) +{ + if (!gShouldOutputDebugInfo) { + return; + } + var debugElement = document.getElementById(this.debugElementId); + var newDataUriElement = document.createElement("a"); + newDataUriElement.setAttribute("id", id); + newDataUriElement.setAttribute("href", dataUri); + newDataUriElement.appendChild(document.createTextNode(message)); + debugElement.appendChild(newDataUriElement); + var brElement = document.createElement("br"); + debugElement.appendChild(brElement); + todo(false, "Debug (" + id + "): " + message + " " + dataUri); +}; + +AnimationTest.prototype.isFinished = function() +{ + return this.isTestFinished; +}; + +AnimationTest.prototype.takeCleanSnapshot = function() +{ + var cleanElement; + if (this.cleanId) { + cleanElement = document.getElementById(this.cleanId); + } + + // Enable clean page comparison element + if (cleanElement) { + this.enableDisplay(cleanElement); + } + + // Take a snapshot of the initial (clean) page + this.cleanSnapshot = snapshotWindow(window, false); + + // Disable the clean page comparison element + if (cleanElement) { + this.disableDisplay(cleanElement); + } + + var dataString1 = "Clean Snapshot"; + this.outputDebugInfo(dataString1, 'cleanSnap', + this.cleanSnapshot.toDataURL()); +}; + +AnimationTest.prototype.takeBlankSnapshot = function() +{ + // Take a snapshot of the initial (essentially blank) page + this.blankSnapshot = snapshotWindow(window, false); + + var dataString1 = "Initial Blank Snapshot"; + this.outputDebugInfo(dataString1, 'blank1Snap', + this.blankSnapshot.toDataURL()); +}; + +/** + * Begin the AnimationTest. This will utilize the information provided in the + * constructor to invoke a mochitest on animated images. It will automatically + * fail if allowed to run past the timeout. This will attempt to preload an + * image, if applicable, and then asynchronously call continueTest(), or if not + * applicable, synchronously trigger a call to continueTest(). + */ +AnimationTest.prototype.beginTest = function() +{ + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestFlakyTimeout("untriaged"); + + currentTest = this; + this.preloadImage(); +}; + +/** + * This is the second part of the test. It is triggered (eventually) from + * beginTest() either synchronously or asynchronously, as an image load + * callback. + */ +AnimationTest.prototype.continueTest = function() +{ + // In case something goes wrong, fail earlier than mochitest timeout, + // and with more information. + setTimeout(failTest, this.timeout); + + if (!this.reusingImageAsReference) { + this.disableDisplay(document.getElementById(this.imageElementId)); + } + + this.takeReferenceSnapshot(); + this.setupPolledImage(); + SimpleTest.executeSoon(pollForSuccess); +}; + +AnimationTest.prototype.setupPolledImage = function () +{ + // Make sure the image is visible + if (!this.reusingImageAsReference) { + this.enableDisplay(document.getElementById(this.imageElementId)); + var currentSnapshot = snapshotWindow(window, false); + var result = compareSnapshots(currentSnapshot, + this.referenceSnapshot, true); + + this.currentSnapshotDataURI = currentSnapshot.toDataURL(); + + if (result[0]) { + // SUCCESS! + ok(true, "Animated image looks correct, at poll #" + + this.pollCounter); + + this.cleanUpAndFinish(); + } + } else { + if (!gIsRefImageLoaded) { + this.myImage = new Image(); + this.myImage.onload = reuseImageCallback; + document.getElementById(this.imageElementId).setAttribute('src', + this.referenceElementId); + } + } +} + +AnimationTest.prototype.checkImage = function () +{ + if (this.isTestFinished) { + return; + } + + this.pollCounter++; + + // We need this for some tests, because we need to force the + // test image to be visible. + if (!this.reusingImageAsReference) { + this.enableDisplay(document.getElementById(this.imageElementId)); + } + + var currentSnapshot = snapshotWindow(window, false); + var result = compareSnapshots(currentSnapshot, this.referenceSnapshot, true); + + this.currentSnapshotDataURI = currentSnapshot.toDataURL(); + + if (result[0]) { + // SUCCESS! + ok(true, "Animated image looks correct, at poll #" + + this.pollCounter); + + this.cleanUpAndFinish(); + } +}; + +AnimationTest.prototype.takeReferenceSnapshot = function () +{ + this.numRefsTaken++; + + // Test to make sure the reference image doesn't match a clean snapshot + if (!this.cleanSnapshot) { + this.takeCleanSnapshot(); + } + + // Used later to verify that the reference div disappeared + if (!this.blankSnapshot) { + this.takeBlankSnapshot(); + } + + if (this.reusingImageAsReference) { + // Show reference elem (which is actually our image), & take a snapshot + var referenceElem = document.getElementById(this.imageElementId); + this.enableDisplay(referenceElem); + + this.referenceSnapshot = snapshotWindow(window, false); + + var snapResult = compareSnapshots(this.cleanSnapshot, + this.referenceSnapshot, false); + if (!snapResult[0]) { + if (this.blankWaitTime > 2000) { + // if it took longer than two seconds to load the image, we probably + // have a problem. + this.wereFailures = true; + ok(snapResult[0], + "Reference snapshot shouldn't match clean (non-image) snapshot"); + } else { + this.blankWaitTime += currentTest.pollFreq; + // let's wait a bit and see if it clears up + setTimeout(referencePoller, currentTest.pollFreq); + return; + } + } + + ok(snapResult[0], + "Reference snapshot shouldn't match clean (non-image) snapshot"); + + var dataString = "Reference Snapshot #" + this.numRefsTaken; + this.outputDebugInfo(dataString, 'refSnapId', + this.referenceSnapshot.toDataURL()); + } else { + // Make sure the animation section is hidden + this.disableDisplay(document.getElementById(this.imageElementId)); + + // Show reference div, & take a snapshot + var referenceDiv = document.getElementById(this.referenceElementId); + this.enableDisplay(referenceDiv); + + this.referenceSnapshot = snapshotWindow(window, false); + var snapResult = compareSnapshots(this.cleanSnapshot, + this.referenceSnapshot, false); + if (!snapResult[0]) { + if (this.blankWaitTime > 2000) { + // if it took longer than two seconds to load the image, we probably + // have a problem. + this.wereFailures = true; + ok(snapResult[0], + "Reference snapshot shouldn't match clean (non-image) snapshot"); + } else { + this.blankWaitTime += 20; + // let's wait a bit and see if it clears up + setTimeout(referencePoller, 20); + return; + } + } + + ok(snapResult[0], + "Reference snapshot shouldn't match clean (non-image) snapshot"); + + var dataString = "Reference Snapshot #" + this.numRefsTaken; + this.outputDebugInfo(dataString, 'refSnapId', + this.referenceSnapshot.toDataURL()); + + // Re-hide reference div, and take another snapshot to be sure it's gone + this.disableDisplay(referenceDiv); + this.testBlankCameBack(); + } +}; + +AnimationTest.prototype.enableDisplay = function(element) +{ + if (!element) { + return; + } + + if (!this.xulTest) { + element.style.display = ''; + } else { + element.setAttribute('hidden', 'false'); + } +}; + +AnimationTest.prototype.disableDisplay = function(element) +{ + if (!element) { + return; + } + + if (!this.xulTest) { + element.style.display = 'none'; + } else { + element.setAttribute('hidden', 'true'); + } +}; + +AnimationTest.prototype.testBlankCameBack = function() +{ + var blankSnapshot2 = snapshotWindow(window, false); + var result = compareSnapshots(this.blankSnapshot, blankSnapshot2, true); + ok(result[0], "Reference image should disappear when it becomes display:none"); + + if (!result[0]) { + this.wereFailures = true; + var dataString = "Second Blank Snapshot"; + this.outputDebugInfo(dataString, 'blank2SnapId', result[2]); + } +}; + +AnimationTest.prototype.cleanUpAndFinish = function () +{ + // On the off chance that failTest and checkImage are triggered + // back-to-back, use a flag to prevent multiple calls to SimpleTest.finish. + if (this.isTestFinished) { + return; + } + + this.isTestFinished = true; + + // Call our closing function, if one exists + if (this.closeFunc) { + this.closeFunc(); + return; + } + + if (this.wereFailures) { + document.getElementById(this.debugElementId).style.display = 'block'; + } + + SimpleTest.finish(); + document.getElementById(this.debugElementId).style.display = ""; +}; diff --git a/image/test/mochitest/bad.jpg b/image/test/mochitest/bad.jpg new file mode 100644 index 000000000..555a416d7 Binary files /dev/null and b/image/test/mochitest/bad.jpg differ diff --git a/image/test/mochitest/big.png b/image/test/mochitest/big.png new file mode 100644 index 000000000..94e7eb6db Binary files /dev/null and b/image/test/mochitest/big.png differ diff --git a/image/test/mochitest/blue.gif b/image/test/mochitest/blue.gif new file mode 100644 index 000000000..339f3702f Binary files /dev/null and b/image/test/mochitest/blue.gif differ diff --git a/image/test/mochitest/blue.png b/image/test/mochitest/blue.png new file mode 100644 index 000000000..8df58f3a5 Binary files /dev/null and b/image/test/mochitest/blue.png differ diff --git a/image/test/mochitest/bug1132427.gif b/image/test/mochitest/bug1132427.gif new file mode 100644 index 000000000..39f49689a Binary files /dev/null and b/image/test/mochitest/bug1132427.gif differ diff --git a/image/test/mochitest/bug1132427.html b/image/test/mochitest/bug1132427.html new file mode 100644 index 000000000..c765ce14c --- /dev/null +++ b/image/test/mochitest/bug1132427.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/image/test/mochitest/bug1180105-waiter.sjs b/image/test/mochitest/bug1180105-waiter.sjs new file mode 100644 index 000000000..4e86ae287 --- /dev/null +++ b/image/test/mochitest/bug1180105-waiter.sjs @@ -0,0 +1,24 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +var timer = Components.classes["@mozilla.org/timer;1"]; +var waitTimer = timer.createInstance(Components.interfaces.nsITimer); + +function handleRequest(request, response) { + response.setHeader("Content-Type", "text/html", false); + response.setHeader("Cache-Control", "no-cache", false); + response.setStatusLine(request.httpVersion, 200, "OK"); + response.processAsync(); + waitForFinish(response); +} + +function waitForFinish(response) { + if (getSharedState("all-parts-done") === "1") { + response.write("done"); + response.finish(); + } else { + waitTimer.initWithCallback(function() {waitForFinish(response);}, 10, + Components.interfaces.nsITimer.TYPE_ONE_SHOT); + } +} diff --git a/image/test/mochitest/bug1180105.sjs b/image/test/mochitest/bug1180105.sjs new file mode 100644 index 000000000..e138e548e --- /dev/null +++ b/image/test/mochitest/bug1180105.sjs @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +var counter = 100; +var timer = Components.classes["@mozilla.org/timer;1"]; +var partTimer = timer.createInstance(Components.interfaces.nsITimer); + +function getFileAsInputStream(aFilename) { + var file = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Components.interfaces.nsIProperties) + .get("CurWorkD", Components.interfaces.nsIFile); + + file.append("tests"); + file.append("image"); + file.append("test"); + file.append("mochitest"); + file.append(aFilename); + + var fileStream = Components.classes['@mozilla.org/network/file-input-stream;1'] + .createInstance(Components.interfaces.nsIFileInputStream); + fileStream.init(file, 1, 0, false); + return fileStream; +} + +function handleRequest(request, response) +{ + response.setHeader("Content-Type", + "multipart/x-mixed-replace;boundary=BOUNDARYOMG", false); + response.setHeader("Cache-Control", "no-cache", false); + response.setStatusLine(request.httpVersion, 200, "OK"); + // We're sending parts off in a delayed fashion, to let the tests occur. + response.processAsync(); + response.write("--BOUNDARYOMG\r\n"); + sendParts(response); +} + +function sendParts(response) { + if (counter-- == 0) { + sendClose(response); + setSharedState("all-parts-done", "1"); + return; + } + sendNextPart(response); + partTimer.initWithCallback(function() {sendParts(response);}, 1, + Components.interfaces.nsITimer.TYPE_ONE_SHOT); +} + +function sendClose(response) { + response.write("--BOUNDARYOMG--\r\n"); + response.finish(); +} + +function sendNextPart(response) { + var nextPartHead = "Content-Type: image/jpeg\r\n\r\n"; + var inputStream = getFileAsInputStream("damon.jpg"); + response.bodyOutputStream.write(nextPartHead, nextPartHead.length); + response.bodyOutputStream.writeFrom(inputStream, inputStream.available()); + inputStream.close(); + // Toss in the boundary, so the browser can know this part is complete + response.write("--BOUNDARYOMG\r\n"); +} + diff --git a/image/test/mochitest/bug1217571-iframe.html b/image/test/mochitest/bug1217571-iframe.html new file mode 100644 index 000000000..d67bb9ed7 --- /dev/null +++ b/image/test/mochitest/bug1217571-iframe.html @@ -0,0 +1,17 @@ + + + + + iframe for Bug 1217571 + + + + +

    + + + diff --git a/image/test/mochitest/bug1319025-ref.png b/image/test/mochitest/bug1319025-ref.png new file mode 100644 index 000000000..482d027a0 Binary files /dev/null and b/image/test/mochitest/bug1319025-ref.png differ diff --git a/image/test/mochitest/bug1319025.png b/image/test/mochitest/bug1319025.png new file mode 100644 index 000000000..8023e7787 Binary files /dev/null and b/image/test/mochitest/bug1319025.png differ diff --git a/image/test/mochitest/bug399925.gif b/image/test/mochitest/bug399925.gif new file mode 100644 index 000000000..fc1c8f3af Binary files /dev/null and b/image/test/mochitest/bug399925.gif differ diff --git a/image/test/mochitest/bug415761.ico b/image/test/mochitest/bug415761.ico new file mode 100644 index 000000000..d3f65abc2 Binary files /dev/null and b/image/test/mochitest/bug415761.ico differ diff --git a/image/test/mochitest/bug468160.sjs b/image/test/mochitest/bug468160.sjs new file mode 100644 index 000000000..4a654216a --- /dev/null +++ b/image/test/mochitest/bug468160.sjs @@ -0,0 +1,6 @@ +function handleRequest(request, response) +{ + response.setStatusLine("1.1", 302, "Found"); + response.setHeader("Location", "red.png", false); + response.setHeader("Cache-Control", "no-cache", false); +} diff --git a/image/test/mochitest/bug478398_ONLY.png b/image/test/mochitest/bug478398_ONLY.png new file mode 100644 index 000000000..e094ae2cf Binary files /dev/null and b/image/test/mochitest/bug478398_ONLY.png differ diff --git a/image/test/mochitest/bug490949-iframe.html b/image/test/mochitest/bug490949-iframe.html new file mode 100644 index 000000000..68f74b587 --- /dev/null +++ b/image/test/mochitest/bug490949-iframe.html @@ -0,0 +1,7 @@ + + +Bug 490949 iframe + + + + diff --git a/image/test/mochitest/bug490949.sjs b/image/test/mochitest/bug490949.sjs new file mode 100644 index 000000000..f306b8337 --- /dev/null +++ b/image/test/mochitest/bug490949.sjs @@ -0,0 +1,33 @@ +function handleRequest(request, response) +{ + var file = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Components.interfaces.nsIProperties) + .get("CurWorkD", Components.interfaces.nsIFile); + + file.append("tests"); + file.append("image"); + file.append("test"); + file.append("mochitest"); + + var redirectstate = "/image/test/mochitest/bug490949.sjs"; + if (getState(redirectstate) == "") { + file.append('blue.png'); + setState(redirectstate, "red"); + } else { + file.append('red.png'); + setState(redirectstate, ""); + } + response.setHeader("Cache-Control", "no-cache", false); + + var fileStream = Components.classes['@mozilla.org/network/file-input-stream;1'] + .createInstance(Components.interfaces.nsIFileInputStream); + fileStream.init(file, 1, 0, false); + var binaryStream = Components.classes['@mozilla.org/binaryinputstream;1'] + .createInstance(Components.interfaces.nsIBinaryInputStream); + binaryStream.setInputStream(fileStream); + + response.bodyOutputStream.writeFrom(binaryStream, binaryStream.available()); + + binaryStream.close(); + fileStream.close(); +} diff --git a/image/test/mochitest/bug496292-1.sjs b/image/test/mochitest/bug496292-1.sjs new file mode 100644 index 000000000..0326b75d6 --- /dev/null +++ b/image/test/mochitest/bug496292-1.sjs @@ -0,0 +1,32 @@ +function handleRequest(request, response) +{ + var file = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Components.interfaces.nsIProperties) + .get("CurWorkD", Components.interfaces.nsIFile); + + file.append("tests"); + file.append("image"); + file.append("test"); + file.append("mochitest"); + + if (request.getHeader("Accept") == "*/*") { + file.append('blue.png'); + } else { + file.append('red.png'); + } + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "image/png", false); + response.setHeader("Cache-Control", "no-cache", false); + + var fileStream = Components.classes['@mozilla.org/network/file-input-stream;1'] + .createInstance(Components.interfaces.nsIFileInputStream); + fileStream.init(file, 1, 0, false); + var binaryStream = Components.classes['@mozilla.org/binaryinputstream;1'] + .createInstance(Components.interfaces.nsIBinaryInputStream); + binaryStream.setInputStream(fileStream); + + response.bodyOutputStream.writeFrom(binaryStream, binaryStream.available()); + + binaryStream.close(); + fileStream.close(); +} diff --git a/image/test/mochitest/bug496292-2.sjs b/image/test/mochitest/bug496292-2.sjs new file mode 100644 index 000000000..756ea9db2 --- /dev/null +++ b/image/test/mochitest/bug496292-2.sjs @@ -0,0 +1,32 @@ +function handleRequest(request, response) +{ + var file = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Components.interfaces.nsIProperties) + .get("CurWorkD", Components.interfaces.nsIFile); + + file.append("tests"); + file.append("image"); + file.append("test"); + file.append("mochitest"); + + if (request.getHeader("Accept") == "image/png") { + file.append('blue.png'); + } else { + file.append('red.png'); + } + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "image/png", false); + response.setHeader("Cache-Control", "no-cache", false); + + var fileStream = Components.classes['@mozilla.org/network/file-input-stream;1'] + .createInstance(Components.interfaces.nsIFileInputStream); + fileStream.init(file, 1, 0, false); + var binaryStream = Components.classes['@mozilla.org/binaryinputstream;1'] + .createInstance(Components.interfaces.nsIBinaryInputStream); + binaryStream.setInputStream(fileStream); + + response.bodyOutputStream.writeFrom(binaryStream, binaryStream.available()); + + binaryStream.close(); + fileStream.close(); +} diff --git a/image/test/mochitest/bug496292-iframe-1.html b/image/test/mochitest/bug496292-iframe-1.html new file mode 100644 index 000000000..00f0fbcfc --- /dev/null +++ b/image/test/mochitest/bug496292-iframe-1.html @@ -0,0 +1,7 @@ + + +Bug 496292 iframe 1 + + + + diff --git a/image/test/mochitest/bug496292-iframe-2.html b/image/test/mochitest/bug496292-iframe-2.html new file mode 100644 index 000000000..67c1ecea1 --- /dev/null +++ b/image/test/mochitest/bug496292-iframe-2.html @@ -0,0 +1,7 @@ + + +Bug 496292 iframe 2 + + + + diff --git a/image/test/mochitest/bug496292-iframe-ref.html b/image/test/mochitest/bug496292-iframe-ref.html new file mode 100644 index 000000000..2e804502e --- /dev/null +++ b/image/test/mochitest/bug496292-iframe-ref.html @@ -0,0 +1,7 @@ + + +Bug 496292 reference iframe + + + + diff --git a/image/test/mochitest/bug497665-iframe.html b/image/test/mochitest/bug497665-iframe.html new file mode 100644 index 000000000..a2b098e31 --- /dev/null +++ b/image/test/mochitest/bug497665-iframe.html @@ -0,0 +1,8 @@ + + +Bug 497665 iframe + + + + + diff --git a/image/test/mochitest/bug497665.sjs b/image/test/mochitest/bug497665.sjs new file mode 100644 index 000000000..67151e25b --- /dev/null +++ b/image/test/mochitest/bug497665.sjs @@ -0,0 +1,34 @@ +function handleRequest(request, response) +{ + var file = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Components.interfaces.nsIProperties) + .get("CurWorkD", Components.interfaces.nsIFile); + + file.append("tests"); + file.append("image"); + file.append("test"); + file.append("mochitest"); + + var redirectstate = "/image/test/mochitest/bug497665.sjs"; + if (getState(redirectstate) == "") { + file.append('blue.png'); + setState(redirectstate, "red"); + } else { + file.append('red.png'); + setState(redirectstate, ""); + } + + response.setHeader("Cache-Control", "max-age=3600", false); + + var fileStream = Components.classes['@mozilla.org/network/file-input-stream;1'] + .createInstance(Components.interfaces.nsIFileInputStream); + fileStream.init(file, 1, 0, false); + var binaryStream = Components.classes['@mozilla.org/binaryinputstream;1'] + .createInstance(Components.interfaces.nsIBinaryInputStream); + binaryStream.setInputStream(fileStream); + + response.bodyOutputStream.writeFrom(binaryStream, binaryStream.available()); + + binaryStream.close(); + fileStream.close(); +} diff --git a/image/test/mochitest/bug552605.sjs b/image/test/mochitest/bug552605.sjs new file mode 100644 index 000000000..806a065b4 --- /dev/null +++ b/image/test/mochitest/bug552605.sjs @@ -0,0 +1,13 @@ +function handleRequest(request, response) +{ + var redirectstate = "/image/test/mochitest/bug89419.sjs"; + response.setStatusLine("1.1", 302, "Found"); + if (getState(redirectstate) == "") { + response.setHeader("Location", "red.png", false); + setState(redirectstate, "red"); + } else { + response.setHeader("Location", "blue.png", false); + setState(redirectstate, ""); + } + response.setHeader("Cache-Control", "no-cache", false); +} diff --git a/image/test/mochitest/bug657191.sjs b/image/test/mochitest/bug657191.sjs new file mode 100644 index 000000000..7451d98ee --- /dev/null +++ b/image/test/mochitest/bug657191.sjs @@ -0,0 +1,27 @@ +function handleRequest(request, response) +{ + var file = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Components.interfaces.nsIProperties) + .get("CurWorkD", Components.interfaces.nsIFile); + + file.append("tests"); + file.append("image"); + file.append("test"); + file.append("mochitest"); + file.append('lime100x100.svg'); + + response.setStatusLine("1.1", 500, "Internal Server Error"); + response.setHeader("Content-Type", "image/svg+xml", false); + + var fileStream = Components.classes['@mozilla.org/network/file-input-stream;1'] + .createInstance(Components.interfaces.nsIFileInputStream); + fileStream.init(file, 1, 0, false); + var binaryStream = Components.classes['@mozilla.org/binaryinputstream;1'] + .createInstance(Components.interfaces.nsIBinaryInputStream); + binaryStream.setInputStream(fileStream); + + response.bodyOutputStream.writeFrom(binaryStream, binaryStream.available()); + + binaryStream.close(); + fileStream.close(); +} diff --git a/image/test/mochitest/bug671906-iframe.html b/image/test/mochitest/bug671906-iframe.html new file mode 100644 index 000000000..87f8183a4 --- /dev/null +++ b/image/test/mochitest/bug671906-iframe.html @@ -0,0 +1,7 @@ + + +Bug 671906 iframe + + + + diff --git a/image/test/mochitest/bug671906.sjs b/image/test/mochitest/bug671906.sjs new file mode 100644 index 000000000..f1fbc1b73 --- /dev/null +++ b/image/test/mochitest/bug671906.sjs @@ -0,0 +1,36 @@ +function handleRequest(request, response) +{ + var file = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Components.interfaces.nsIProperties) + .get("CurWorkD", Components.interfaces.nsIFile); + + file.append("tests"); + file.append("image"); + file.append("test"); + file.append("mochitest"); + + var filestate = "/image/test/mochitest/bug671906.sjs"; + if (getState(filestate) == "") { + file.append('blue.png'); + setState(filestate, "red"); + } else { + file.append('red.png'); + setState(filestate, ""); + } + + // Set the expires date to some silly time in the future so we're sure to + // *want* to cache this image. + var date = new Date(); + date.setFullYear(date.getFullYear() + 1); + response.setHeader("Expires", date.toUTCString(), false); + + var fileStream = Components.classes['@mozilla.org/network/file-input-stream;1'] + .createInstance(Components.interfaces.nsIFileInputStream); + fileStream.init(file, 1, 0, false); + + response.bodyOutputStream.writeFrom(fileStream, fileStream.available()); + + fileStream.close(); + + response.setHeader("Access-Control-Allow-Origin", "*", false); +} diff --git a/image/test/mochitest/bug733553-informant.sjs b/image/test/mochitest/bug733553-informant.sjs new file mode 100644 index 000000000..6f15a1195 --- /dev/null +++ b/image/test/mochitest/bug733553-informant.sjs @@ -0,0 +1,15 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +function handleRequest(request, response) +{ + response.setHeader("Content-Type", "text/plain", false); + response.setHeader("Cache-Control", "no-cache", false); + response.setStatusLine(request.httpVersion, 200, "OK"); + // Tells bug733553.sjs that the consumer is ready for the next part + let partName = request.queryString; + setSharedState("next-part", partName); + response.write("OK!"); +} + diff --git a/image/test/mochitest/bug733553.sjs b/image/test/mochitest/bug733553.sjs new file mode 100644 index 000000000..c5279f3d2 --- /dev/null +++ b/image/test/mochitest/bug733553.sjs @@ -0,0 +1,104 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +var bodyPartIndex = -1; +var bodyParts = [ + ["red.png", "image/png"], + ["animated-gif2.gif", "image/gif"], + ["red.png", "image/png"], + ["lime100x100.svg", "image/svg+xml"], + ["lime100x100.svg", "image/svg+xml"], + ["animated-gif2.gif", "image/gif"], + ["red.png", "image/png"], + // Mime type intentionally wrong (test for bug 907575) + ["shaver.png", "image/gif"], + ["red.png", "image/png"], + ["damon.jpg", "image/jpeg"], + ["damon.jpg", "application/octet-stream"], + ["damon.jpg", "image/jpeg"], + ["rillybad.jpg", "application/x-unknown-content-type"], + ["damon.jpg", "image/jpeg"], + ["bad.jpg", "image/jpeg"], + ["red.png", "image/png"], + ["invalid.jpg", "image/jpeg"], + ["animated-gif2.gif", "image/gif"] +]; +var timer = Components.classes["@mozilla.org/timer;1"]; +var partTimer = timer.createInstance(Components.interfaces.nsITimer); + +function getFileAsInputStream(aFilename) { + var file = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Components.interfaces.nsIProperties) + .get("CurWorkD", Components.interfaces.nsIFile); + + file.append("tests"); + file.append("image"); + file.append("test"); + file.append("mochitest"); + file.append(aFilename); + + var fileStream = Components.classes['@mozilla.org/network/file-input-stream;1'] + .createInstance(Components.interfaces.nsIFileInputStream); + fileStream.init(file, 1, 0, false); + return fileStream; +} + +function handleRequest(request, response) +{ + if (!getSharedState("next-part")) { + setSharedState("next-part", "-1"); + } + response.setHeader("Content-Type", + "multipart/x-mixed-replace;boundary=BOUNDARYOMG", false); + response.setHeader("Cache-Control", "no-cache", false); + response.setStatusLine(request.httpVersion, 200, "OK"); + // We're sending parts off in a delayed fashion, to let the tests occur. + response.processAsync(); + response.write("--BOUNDARYOMG\r\n"); + sendParts(response); +} + +function sendParts(response) { + let wait = false; + let nextPart = parseInt(getSharedState("next-part"), 10); + if (nextPart == bodyPartIndex) { + // Haven't been signaled yet, remain in holding pattern + wait = true; + } else { + bodyPartIndex = nextPart; + } + if (bodyParts.length > bodyPartIndex) { + let callback; + if (!wait) { + callback = getSendNextPart(response); + } else { + callback = function () { sendParts(response); }; + } + partTimer.initWithCallback(callback, 1000, + Components.interfaces.nsITimer.TYPE_ONE_SHOT); + } + else { + sendClose(response); + } +} + +function sendClose(response) { + response.write("--BOUNDARYOMG--\r\n"); + response.finish(); +} + +function getSendNextPart(response) { + var part = bodyParts[bodyPartIndex]; + var nextPartHead = "Content-Type: " + part[1] + "\r\n\r\n"; + var inputStream = getFileAsInputStream(part[0]); + return function () { + response.bodyOutputStream.write(nextPartHead, nextPartHead.length); + response.bodyOutputStream.writeFrom(inputStream, inputStream.available()); + inputStream.close(); + // Toss in the boundary, so the browser can know this part is complete + response.write("--BOUNDARYOMG\r\n"); + sendParts(response); + } +} + diff --git a/image/test/mochitest/bug767779.sjs b/image/test/mochitest/bug767779.sjs new file mode 100644 index 000000000..9a7948d85 --- /dev/null +++ b/image/test/mochitest/bug767779.sjs @@ -0,0 +1,56 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +var timer = Components.classes["@mozilla.org/timer;1"]; +var partTimer = timer.createInstance(Components.interfaces.nsITimer); + +function getFileAsInputStream(aFilename) { + var file = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Components.interfaces.nsIProperties) + .get("CurWorkD", Components.interfaces.nsIFile); + + file.append("tests"); + file.append("image"); + file.append("test"); + file.append("mochitest"); + file.append(aFilename); + + var fileStream = Components.classes['@mozilla.org/network/file-input-stream;1'] + .createInstance(Components.interfaces.nsIFileInputStream); + fileStream.init(file, 1, 0, false); + return fileStream; +} + +function handleRequest(request, response) +{ + response.setHeader("Content-Type", "image/gif", false); + response.setHeader("Cache-Control", "no-cache", false); + response.setStatusLine(request.httpVersion, 200, "OK"); + // We're sending data off in a delayed fashion + response.processAsync(); + var inputStream = getFileAsInputStream("animated-gif_trailing-garbage.gif"); + var available = inputStream.available(); // = 4029 bytes + // Send the good data at once + response.bodyOutputStream.writeFrom(inputStream, 285); + sendParts(inputStream, response); +} + +function sendParts(inputStream, response) { + // 3744 left, send in 8 chunks of 468 each + partTimer.initWithCallback(getSendNextPart(inputStream, response), 500, + Components.interfaces.nsITimer.TYPE_ONE_SHOT); +} + +function getSendNextPart(inputStream, response) { + return function () { + response.bodyOutputStream.writeFrom(inputStream, 468); + if (!inputStream.available()) { + inputStream.close(); + response.finish(); + } else { + sendParts(inputStream, response); + } + }; +} + diff --git a/image/test/mochitest/bug89419-iframe.html b/image/test/mochitest/bug89419-iframe.html new file mode 100644 index 000000000..191531563 --- /dev/null +++ b/image/test/mochitest/bug89419-iframe.html @@ -0,0 +1,7 @@ + + +Bug 89419 iframe + + + + diff --git a/image/test/mochitest/bug89419.sjs b/image/test/mochitest/bug89419.sjs new file mode 100644 index 000000000..806a065b4 --- /dev/null +++ b/image/test/mochitest/bug89419.sjs @@ -0,0 +1,13 @@ +function handleRequest(request, response) +{ + var redirectstate = "/image/test/mochitest/bug89419.sjs"; + response.setStatusLine("1.1", 302, "Found"); + if (getState(redirectstate) == "") { + response.setHeader("Location", "red.png", false); + setState(redirectstate, "red"); + } else { + response.setHeader("Location", "blue.png", false); + setState(redirectstate, ""); + } + response.setHeader("Cache-Control", "no-cache", false); +} diff --git a/image/test/mochitest/bug900200-ref.png b/image/test/mochitest/bug900200-ref.png new file mode 100644 index 000000000..636013132 Binary files /dev/null and b/image/test/mochitest/bug900200-ref.png differ diff --git a/image/test/mochitest/bug900200.png b/image/test/mochitest/bug900200.png new file mode 100644 index 000000000..d7d87adce Binary files /dev/null and b/image/test/mochitest/bug900200.png differ diff --git a/image/test/mochitest/chrome.ini b/image/test/mochitest/chrome.ini new file mode 100644 index 000000000..b84d0af89 --- /dev/null +++ b/image/test/mochitest/chrome.ini @@ -0,0 +1,7 @@ +[DEFAULT] +skip-if = os == 'android' + +[test_bug415761.html] +skip-if = os != "win" || os_version == "6.2" +support-files = + bug415761.ico diff --git a/image/test/mochitest/clear.gif b/image/test/mochitest/clear.gif new file mode 100644 index 000000000..7ae79ba86 Binary files /dev/null and b/image/test/mochitest/clear.gif differ diff --git a/image/test/mochitest/clear.png b/image/test/mochitest/clear.png new file mode 100644 index 000000000..b09aecaaa Binary files /dev/null and b/image/test/mochitest/clear.png differ diff --git a/image/test/mochitest/clear2-results.gif b/image/test/mochitest/clear2-results.gif new file mode 100644 index 000000000..965b65025 Binary files /dev/null and b/image/test/mochitest/clear2-results.gif differ diff --git a/image/test/mochitest/clear2.gif b/image/test/mochitest/clear2.gif new file mode 100644 index 000000000..00ad873c6 Binary files /dev/null and b/image/test/mochitest/clear2.gif differ diff --git a/image/test/mochitest/damon.jpg b/image/test/mochitest/damon.jpg new file mode 100644 index 000000000..917b33660 Binary files /dev/null and b/image/test/mochitest/damon.jpg differ diff --git a/image/test/mochitest/error-early.png b/image/test/mochitest/error-early.png new file mode 100644 index 000000000..5df7507e2 --- /dev/null +++ b/image/test/mochitest/error-early.png @@ -0,0 +1 @@ +ERROR diff --git a/image/test/mochitest/filter-final.svg b/image/test/mochitest/filter-final.svg new file mode 100644 index 000000000..b2b3dca00 --- /dev/null +++ b/image/test/mochitest/filter-final.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/image/test/mochitest/filter.svg b/image/test/mochitest/filter.svg new file mode 100644 index 000000000..e185f15b6 --- /dev/null +++ b/image/test/mochitest/filter.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/image/test/mochitest/first-frame-padding.gif b/image/test/mochitest/first-frame-padding.gif new file mode 100644 index 000000000..e6d7c4932 Binary files /dev/null and b/image/test/mochitest/first-frame-padding.gif differ diff --git a/image/test/mochitest/green-background.html b/image/test/mochitest/green-background.html new file mode 100644 index 000000000..afc3c206e --- /dev/null +++ b/image/test/mochitest/green-background.html @@ -0,0 +1,28 @@ + + + +Background color wrapper for clear image tests + + + + + + + diff --git a/image/test/mochitest/green.png b/image/test/mochitest/green.png new file mode 100644 index 000000000..7df25f33b Binary files /dev/null and b/image/test/mochitest/green.png differ diff --git a/image/test/mochitest/grey.png b/image/test/mochitest/grey.png new file mode 100644 index 000000000..5c82cdeb1 Binary files /dev/null and b/image/test/mochitest/grey.png differ diff --git a/image/test/mochitest/ico-bmp-opaque.ico b/image/test/mochitest/ico-bmp-opaque.ico new file mode 100644 index 000000000..3cf3320ea Binary files /dev/null and b/image/test/mochitest/ico-bmp-opaque.ico differ diff --git a/image/test/mochitest/ico-bmp-transparent.ico b/image/test/mochitest/ico-bmp-transparent.ico new file mode 100644 index 000000000..151b7cb36 Binary files /dev/null and b/image/test/mochitest/ico-bmp-transparent.ico differ diff --git a/image/test/mochitest/iframe.html b/image/test/mochitest/iframe.html new file mode 100644 index 000000000..6d66557ef --- /dev/null +++ b/image/test/mochitest/iframe.html @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/image/test/mochitest/imgutils.js b/image/test/mochitest/imgutils.js new file mode 100644 index 000000000..ab9c478df --- /dev/null +++ b/image/test/mochitest/imgutils.js @@ -0,0 +1,138 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +// Helper file for shared image functionality +// +// Note that this is use by tests elsewhere in the source tree. When in doubt, +// check mxr before removing or changing functionality. + +// Helper function to clear both the content and chrome image caches +function clearAllImageCaches() +{ + var tools = SpecialPowers.Cc["@mozilla.org/image/tools;1"] + .getService(SpecialPowers.Ci.imgITools); + var imageCache = tools.getImgCacheForDocument(window.document); + imageCache.clearCache(true); // true=chrome + imageCache.clearCache(false); // false=content +} + +// Helper function to clear the image cache of content images +function clearImageCache() +{ + var tools = SpecialPowers.Cc["@mozilla.org/image/tools;1"] + .getService(SpecialPowers.Ci.imgITools); + var imageCache = tools.getImgCacheForDocument(window.document); + imageCache.clearCache(false); // true=chrome, false=content +} + +// Helper function to determine if the frame is decoded for a given image id +function isFrameDecoded(id) +{ + return (getImageStatus(id) & + SpecialPowers.Ci.imgIRequest.STATUS_FRAME_COMPLETE) + ? true : false; +} + +// Helper function to determine if the image is loaded for a given image id +function isImageLoaded(id) +{ + return (getImageStatus(id) & + SpecialPowers.Ci.imgIRequest.STATUS_LOAD_COMPLETE) + ? true : false; +} + +// Helper function to get the status flags of an image +function getImageStatus(id) +{ + // Get the image + var img = SpecialPowers.wrap(document.getElementById(id)); + + // QI the image to nsImageLoadingContent + img.QueryInterface(SpecialPowers.Ci.nsIImageLoadingContent); + + // Get the request + var request = img.getRequest(SpecialPowers.Ci + .nsIImageLoadingContent + .CURRENT_REQUEST); + + // Return the status + return request.imageStatus; +} + +// Forces a synchronous decode of an image by drawing it to a canvas. Only +// really meaningful if the image is fully loaded first +function forceDecode(id) +{ + // Get the image + var img = document.getElementById(id); + + // Make a new canvas + var canvas = document.createElement("canvas"); + + // Draw the image to the canvas. This forces a synchronous decode + var ctx = canvas.getContext("2d"); + ctx.drawImage(img, 0, 0); +} + + +// Functions to facilitate getting/setting various image-related prefs +// +// If you change a pref in a mochitest, Don't forget to reset it to its +// original value! +// +// Null indicates no pref set + +const DISCARD_ENABLED_PREF = {name: "discardable", branch: "image.mem.", type: "bool"}; +const DECODEONDRAW_ENABLED_PREF = {name: "decodeondraw", branch: "image.mem.", type: "bool"}; +const DISCARD_TIMEOUT_PREF = {name: "min_discard_timeout_ms", branch: "image.mem.", type: "int"}; + +function setImagePref(pref, val) +{ + var prefService = SpecialPowers.Cc["@mozilla.org/preferences-service;1"] + .getService(SpecialPowers.Ci.nsIPrefService); + var branch = prefService.getBranch(pref.branch); + if (val != null) { + switch(pref.type) { + case "bool": + branch.setBoolPref(pref.name, val); + break; + case "int": + branch.setIntPref(pref.name, val); + break; + default: + throw new Error("Unknown pref type"); + } + } + else if (branch.prefHasUserValue(pref.name)) + branch.clearUserPref(pref.name); +} + +function getImagePref(pref) +{ + var prefService = SpecialPowers.Cc["@mozilla.org/preferences-service;1"] + .getService(SpecialPowers.Ci.nsIPrefService); + var branch = prefService.getBranch(pref.branch); + if (branch.prefHasUserValue(pref.name)) { + switch (pref.type) { + case "bool": + return branch.getBoolPref(pref.name); + case "int": + return branch.getIntPref(pref.name); + default: + throw new Error("Unknown pref type"); + } + } + else + return null; +} + +// JS implementation of imgIScriptedNotificationObserver with stubs for all of its methods. +function ImageDecoderObserverStub() +{ + this.sizeAvailable = function sizeAvailable(aRequest) {} + this.frameComplete = function frameComplete(aRequest) {} + this.decodeComplete = function decodeComplete(aRequest) {} + this.loadComplete = function loadComplete(aRequest) {} + this.frameUpdate = function frameUpdate(aRequest) {} + this.discard = function discard(aRequest) {} + this.isAnimated = function isAnimated(aRequest) {} + this.hasTransparency = function hasTransparency(aRequest) {} +} diff --git a/image/test/mochitest/invalid.jpg b/image/test/mochitest/invalid.jpg new file mode 100644 index 000000000..c677a81e2 --- /dev/null +++ b/image/test/mochitest/invalid.jpg @@ -0,0 +1 @@ +notajpg diff --git a/image/test/mochitest/keep.gif b/image/test/mochitest/keep.gif new file mode 100644 index 000000000..e967d6a6d Binary files /dev/null and b/image/test/mochitest/keep.gif differ diff --git a/image/test/mochitest/keep.png b/image/test/mochitest/keep.png new file mode 100644 index 000000000..aa3ff7445 Binary files /dev/null and b/image/test/mochitest/keep.png differ diff --git a/image/test/mochitest/lime-anim-100x100-2.svg b/image/test/mochitest/lime-anim-100x100-2.svg new file mode 100644 index 000000000..d19d3b0e7 --- /dev/null +++ b/image/test/mochitest/lime-anim-100x100-2.svg @@ -0,0 +1,6 @@ + + + + + diff --git a/image/test/mochitest/lime-anim-100x100.svg b/image/test/mochitest/lime-anim-100x100.svg new file mode 100644 index 000000000..c6584047d --- /dev/null +++ b/image/test/mochitest/lime-anim-100x100.svg @@ -0,0 +1,7 @@ + + + + + + diff --git a/image/test/mochitest/lime-css-anim-100x100.svg b/image/test/mochitest/lime-css-anim-100x100.svg new file mode 100644 index 000000000..3edbd3eaa --- /dev/null +++ b/image/test/mochitest/lime-css-anim-100x100.svg @@ -0,0 +1,19 @@ + + + + + + + diff --git a/image/test/mochitest/lime100x100.svg b/image/test/mochitest/lime100x100.svg new file mode 100644 index 000000000..8bdec62c1 --- /dev/null +++ b/image/test/mochitest/lime100x100.svg @@ -0,0 +1,4 @@ + + + diff --git a/image/test/mochitest/mochitest.ini b/image/test/mochitest/mochitest.ini new file mode 100644 index 000000000..e65de9d9e --- /dev/null +++ b/image/test/mochitest/mochitest.ini @@ -0,0 +1,158 @@ +[DEFAULT] +support-files = + INT32_MIN.bmp + animated1.gif + animated2.gif + animated-gif.gif + animated-gif2.gif + animated-gif_trailing-garbage.gif + animated-gif-finalframe.gif + animation.svg + animationPolling.js + bad.jpg + big.png + blue.gif + blue.png + bug399925.gif + bug468160.sjs + bug478398_ONLY.png + bug490949-iframe.html + bug490949.sjs + bug496292-1.sjs + bug496292-2.sjs + bug496292-iframe-1.html + bug496292-iframe-2.html + bug496292-iframe-ref.html + bug497665-iframe.html + bug497665.sjs + bug552605.sjs + bug657191.sjs + bug671906-iframe.html + bug671906.sjs + bug733553-informant.sjs + bug733553.sjs + bug767779.sjs + bug89419-iframe.html + bug89419.sjs + bug900200.png + bug900200-ref.png + bug1132427.html + bug1132427.gif + bug1180105.sjs + bug1180105-waiter.sjs + bug1217571-iframe.html + bug1319025.png + bug1319025-ref.png + clear.gif + clear.png + clear2.gif + clear2-results.gif + damon.jpg + error-early.png + filter-final.svg + filter.svg + first-frame-padding.gif + green.png + green-background.html + grey.png + ico-bmp-opaque.ico + ico-bmp-transparent.ico + iframe.html + imgutils.js + invalid.jpg + keep.gif + keep.png + lime100x100.svg + lime-anim-100x100.svg + lime-anim-100x100-2.svg + lime-css-anim-100x100.svg + opaque.bmp + purple.gif + red.gif + red.png + ref-iframe.html + restore-previous.gif + restore-previous.png + rillybad.jpg + schrep.png + shaver.png + short_header.gif + source.png + transparent.gif + transparent.png + over.png + webcam-simulacrum.sjs + 6M-pixels.png + 12M-pixels-1.png + 12M-pixels-2.png + +[test_animation.html] +skip-if = os == 'android' +[test_animation_operators.html] +[test_animation2.html] +skip-if = os == 'android' +[test_animSVGImage.html] +skip-if = os == 'android' +[test_animSVGImage2.html] +skip-if = os == 'android' +[test_background_image_anim.html] +skip-if = os == 'android' +[test_bug399925.html] +[test_bug435296.html] +skip-if = true # disabled - See bug 578591 +[test_bug466586.html] +[test_bug468160.html] +[test_bug478398.html] +skip-if = true # disabled - See bug 579139 +[test_bug490949.html] +[test_bug496292.html] +[test_bug497665.html] +[test_bug552605-1.html] +[test_bug552605-2.html] +[test_bug553982.html] +[test_bug601470.html] +[test_bug614392.html] +[test_bug657191.html] +[test_bug671906.html] +[test_bug733553.html] +[test_bug767779.html] +[test_bug865919.html] +[test_bug89419-1.html] +[test_bug89419-2.html] +[test_bug1132427.html] +skip-if = os == 'android' +[test_bug1180105.html] +[test_bug1217571.html] +[test_bullet_animation.html] +skip-if = os == 'android' +[test_changeOfSource.html] +skip-if = os == 'android' +[test_changeOfSource2.html] +skip-if = os == 'android' +[test_drawDiscardedImage.html] +[test_error_events.html] +[test_image_crossorigin_data_url.html] +[test_ImageContentLoaded.html] +[test_has_transparency.html] +skip-if = os == 'android' +[test_net_failedtoprocess.html] +skip-if = os == 'android' +[test_removal_ondecode.html] +skip-if = os == 'android' +[test_removal_onload.html] +skip-if = os == 'android' +[test_short_gif_header.html] +[test_staticClone.html] +skip-if = os == 'android' +[test_svg_animatedGIF.html] +skip-if = os == 'android' +[test_svg_filter_animation.html] +skip-if = os == 'android' +[test_synchronized_animation.html] +#skip-if = os == 'android' +disabled = bug 1295501 +[test_undisplayed_iframe.html] +skip-if = os == 'android' +[test_webcam.html] +[test_xultree_animation.xhtml] +skip-if = os == 'android' diff --git a/image/test/mochitest/opaque.bmp b/image/test/mochitest/opaque.bmp new file mode 100644 index 000000000..63d3f1c05 Binary files /dev/null and b/image/test/mochitest/opaque.bmp differ diff --git a/image/test/mochitest/over.png b/image/test/mochitest/over.png new file mode 100644 index 000000000..9e957182f Binary files /dev/null and b/image/test/mochitest/over.png differ diff --git a/image/test/mochitest/purple.gif b/image/test/mochitest/purple.gif new file mode 100644 index 000000000..79826af20 Binary files /dev/null and b/image/test/mochitest/purple.gif differ diff --git a/image/test/mochitest/red.gif b/image/test/mochitest/red.gif new file mode 100644 index 000000000..d3c32bae2 Binary files /dev/null and b/image/test/mochitest/red.gif differ diff --git a/image/test/mochitest/red.png b/image/test/mochitest/red.png new file mode 100644 index 000000000..aa9ce2526 Binary files /dev/null and b/image/test/mochitest/red.png differ diff --git a/image/test/mochitest/ref-iframe.html b/image/test/mochitest/ref-iframe.html new file mode 100644 index 000000000..585772c8a --- /dev/null +++ b/image/test/mochitest/ref-iframe.html @@ -0,0 +1,6 @@ + + +
    + + diff --git a/image/test/mochitest/restore-previous.gif b/image/test/mochitest/restore-previous.gif new file mode 100644 index 000000000..15ba9ddc4 Binary files /dev/null and b/image/test/mochitest/restore-previous.gif differ diff --git a/image/test/mochitest/restore-previous.png b/image/test/mochitest/restore-previous.png new file mode 100644 index 000000000..09dee6382 Binary files /dev/null and b/image/test/mochitest/restore-previous.png differ diff --git a/image/test/mochitest/rillybad.jpg b/image/test/mochitest/rillybad.jpg new file mode 100644 index 000000000..e2fb1d303 Binary files /dev/null and b/image/test/mochitest/rillybad.jpg differ diff --git a/image/test/mochitest/schrep.png b/image/test/mochitest/schrep.png new file mode 100644 index 000000000..bcb406387 Binary files /dev/null and b/image/test/mochitest/schrep.png differ diff --git a/image/test/mochitest/shaver.png b/image/test/mochitest/shaver.png new file mode 100644 index 000000000..ab0b6c7b4 Binary files /dev/null and b/image/test/mochitest/shaver.png differ diff --git a/image/test/mochitest/short_header.gif b/image/test/mochitest/short_header.gif new file mode 100644 index 000000000..70af95ac6 Binary files /dev/null and b/image/test/mochitest/short_header.gif differ diff --git a/image/test/mochitest/source.png b/image/test/mochitest/source.png new file mode 100644 index 000000000..df1c76dae Binary files /dev/null and b/image/test/mochitest/source.png differ diff --git a/image/test/mochitest/test_ImageContentLoaded.html b/image/test/mochitest/test_ImageContentLoaded.html new file mode 100644 index 000000000..38d3238a1 --- /dev/null +++ b/image/test/mochitest/test_ImageContentLoaded.html @@ -0,0 +1,28 @@ + + + + +Test for Bug 691610 + + + + + + + diff --git a/image/test/mochitest/test_animSVGImage.html b/image/test/mochitest/test_animSVGImage.html new file mode 100644 index 000000000..9515472d5 --- /dev/null +++ b/image/test/mochitest/test_animSVGImage.html @@ -0,0 +1,122 @@ + + + + + Test for Bug 610419 + + + + + + +Mozilla Bug 610419 +

    +
    +
    + +
    +
    +
    +
    + + diff --git a/image/test/mochitest/test_animSVGImage2.html b/image/test/mochitest/test_animSVGImage2.html new file mode 100644 index 000000000..5f3bbfde8 --- /dev/null +++ b/image/test/mochitest/test_animSVGImage2.html @@ -0,0 +1,124 @@ + + + + + Test for Bug 907503 + + + + + + +Mozilla Bug 907503 +

    +
    +
    + +
    +
    +
    +
    + + diff --git a/image/test/mochitest/test_animation.html b/image/test/mochitest/test_animation.html new file mode 100644 index 000000000..e144323fd --- /dev/null +++ b/image/test/mochitest/test_animation.html @@ -0,0 +1,45 @@ + + + + + Test for Bug 666446 - General Animated GIF Test + + + + + + + + +Mozilla Bug 666446: lots of animated gifs swamp us with paint events + +

    + +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + + diff --git a/image/test/mochitest/test_animation2.html b/image/test/mochitest/test_animation2.html new file mode 100644 index 000000000..c53817a0a --- /dev/null +++ b/image/test/mochitest/test_animation2.html @@ -0,0 +1,49 @@ + + + + + Test for Bug 705580 - General Animated GIF Test 2 + + + + + + + + +Mozilla Bug 705580: Test animated GIFs that are converted to ImageLayers + +

    + +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + + diff --git a/image/test/mochitest/test_animation_operators.html b/image/test/mochitest/test_animation_operators.html new file mode 100644 index 000000000..a03088b12 --- /dev/null +++ b/image/test/mochitest/test_animation_operators.html @@ -0,0 +1,159 @@ + + + + + Test for Bug 936720 + + + + + +Mozilla Bug 936720 +
    +
    +
    + + diff --git a/image/test/mochitest/test_background_image_anim.html b/image/test/mochitest/test_background_image_anim.html new file mode 100644 index 000000000..957166e82 --- /dev/null +++ b/image/test/mochitest/test_background_image_anim.html @@ -0,0 +1,44 @@ + + + + + Test for Bug 666446 - Animated Background Images + + + + + + + + +Mozilla Bug 666446: lots of animated gifs swamp us with paint events + +

    +
    +
    + +
    + +
    +
    +
    + + diff --git a/image/test/mochitest/test_bug1132427.html b/image/test/mochitest/test_bug1132427.html new file mode 100644 index 000000000..ceb01179e --- /dev/null +++ b/image/test/mochitest/test_bug1132427.html @@ -0,0 +1,96 @@ + + + + Test for scrolling selection into view + + + + + + +
    +
    +
    + + + diff --git a/image/test/mochitest/test_bug1180105.html b/image/test/mochitest/test_bug1180105.html new file mode 100644 index 000000000..1691b621c --- /dev/null +++ b/image/test/mochitest/test_bug1180105.html @@ -0,0 +1,46 @@ + + + + + Test for Bug 1180105 + + + + + +Mozilla Bug 1180105 +

    +
    +
    +
    +
    > + +
    + + diff --git a/image/test/mochitest/test_bug1217571.html b/image/test/mochitest/test_bug1217571.html new file mode 100644 index 000000000..db24096e1 --- /dev/null +++ b/image/test/mochitest/test_bug1217571.html @@ -0,0 +1,44 @@ + + + + + Test for Bug 1217571 + + + + +Mozilla Bug 1217571 +

    + + +
    +
    +
    + + diff --git a/image/test/mochitest/test_bug399925.html b/image/test/mochitest/test_bug399925.html new file mode 100644 index 000000000..1d9bdb14c --- /dev/null +++ b/image/test/mochitest/test_bug399925.html @@ -0,0 +1,105 @@ + + + + + Test for Bug 399925 + + + + + +Mozilla Bug 399925 +

    + +
    +
    +
    + + + diff --git a/image/test/mochitest/test_bug415761.html b/image/test/mochitest/test_bug415761.html new file mode 100644 index 000000000..3799cd80c --- /dev/null +++ b/image/test/mochitest/test_bug415761.html @@ -0,0 +1,98 @@ + + + + Test for icon filenames + + + + + + +
    +
    +
    + + + + diff --git a/image/test/mochitest/test_bug435296.html b/image/test/mochitest/test_bug435296.html new file mode 100644 index 000000000..de24538dc --- /dev/null +++ b/image/test/mochitest/test_bug435296.html @@ -0,0 +1,81 @@ + + + + + Test for Bug 435296 + + + + + + +Mozilla Bug 435296 + +
    +
    +
    + + diff --git a/image/test/mochitest/test_bug466586.html b/image/test/mochitest/test_bug466586.html new file mode 100644 index 000000000..cf34bb783 --- /dev/null +++ b/image/test/mochitest/test_bug466586.html @@ -0,0 +1,58 @@ + + + + + Test for Bug 466586 + + + + + +Mozilla Bug 466586 +

    + +
    +
    +
    + + diff --git a/image/test/mochitest/test_bug468160.html b/image/test/mochitest/test_bug468160.html new file mode 100644 index 000000000..322216cb2 --- /dev/null +++ b/image/test/mochitest/test_bug468160.html @@ -0,0 +1,29 @@ + + + + + Test for Bug 468160 + + + + +Mozilla Bug 468160 +

    + +
    +
    +
    + + diff --git a/image/test/mochitest/test_bug478398.html b/image/test/mochitest/test_bug478398.html new file mode 100644 index 000000000..a583f93a8 --- /dev/null +++ b/image/test/mochitest/test_bug478398.html @@ -0,0 +1,85 @@ + + + + + Test for Bug 478398 + + + + + +Mozilla Bug 478398 +
    +
    +
    + + + diff --git a/image/test/mochitest/test_bug490949.html b/image/test/mochitest/test_bug490949.html new file mode 100644 index 000000000..8bf3ba0c0 --- /dev/null +++ b/image/test/mochitest/test_bug490949.html @@ -0,0 +1,112 @@ + + + + + Test for Bug 490949 + + + + + +Mozilla Bug 490949 +

    + +
    +
    +
    + + diff --git a/image/test/mochitest/test_bug496292.html b/image/test/mochitest/test_bug496292.html new file mode 100644 index 000000000..299480106 --- /dev/null +++ b/image/test/mochitest/test_bug496292.html @@ -0,0 +1,130 @@ + + + + + Test for Bug 496292 + + + + + +Mozilla Bug 496292 +

    + +
    +
    +
    + + diff --git a/image/test/mochitest/test_bug497665.html b/image/test/mochitest/test_bug497665.html new file mode 100644 index 000000000..3a72c0912 --- /dev/null +++ b/image/test/mochitest/test_bug497665.html @@ -0,0 +1,92 @@ + + + + + Test for Bug 497665 + + + + + +Mozilla Bug 497665 +

    +
    +
    +
    +
    + + +
    + + diff --git a/image/test/mochitest/test_bug552605-1.html b/image/test/mochitest/test_bug552605-1.html new file mode 100644 index 000000000..19783711c --- /dev/null +++ b/image/test/mochitest/test_bug552605-1.html @@ -0,0 +1,56 @@ + + + + + Test for Bug 552605 + + + + + + +Mozilla Bug 552605 +

    +
    +
    +
    +
    + +
    + + diff --git a/image/test/mochitest/test_bug552605-2.html b/image/test/mochitest/test_bug552605-2.html new file mode 100644 index 000000000..4ad1c61b2 --- /dev/null +++ b/image/test/mochitest/test_bug552605-2.html @@ -0,0 +1,53 @@ + + + + + Test for Bug 552605 + + + + + + +Mozilla Bug 552605 +

    +
    +
    +
    +
    + + +
    + + diff --git a/image/test/mochitest/test_bug553982.html b/image/test/mochitest/test_bug553982.html new file mode 100644 index 000000000..01314ba00 --- /dev/null +++ b/image/test/mochitest/test_bug553982.html @@ -0,0 +1,39 @@ + + + + + Test for Bug 553982 + + + + + +Mozilla Bug 553982 +
    +
    +
    + + + diff --git a/image/test/mochitest/test_bug601470.html b/image/test/mochitest/test_bug601470.html new file mode 100644 index 000000000..a9e1cc788 --- /dev/null +++ b/image/test/mochitest/test_bug601470.html @@ -0,0 +1,45 @@ + + + + + Test for Bug 601470 + + + + +Mozilla Bug 601470 +

    + +
    +
    +
    + + diff --git a/image/test/mochitest/test_bug614392.html b/image/test/mochitest/test_bug614392.html new file mode 100644 index 000000000..4cec54a05 --- /dev/null +++ b/image/test/mochitest/test_bug614392.html @@ -0,0 +1,43 @@ + + + + + Test for Bug 614392 + + + + +Mozilla Bug 614392 +

    + +
    +
    +
    + + diff --git a/image/test/mochitest/test_bug657191.html b/image/test/mochitest/test_bug657191.html new file mode 100644 index 000000000..ccc4fd7bc --- /dev/null +++ b/image/test/mochitest/test_bug657191.html @@ -0,0 +1,34 @@ + + + + + Test for Bug 657191 + + + + + +Mozilla Bug 657191 +

    + +
    +
    +
    + + diff --git a/image/test/mochitest/test_bug671906.html b/image/test/mochitest/test_bug671906.html new file mode 100644 index 000000000..e3cd8d39f --- /dev/null +++ b/image/test/mochitest/test_bug671906.html @@ -0,0 +1,71 @@ + + + + + Test for Bug 671906 + + + + + + +Mozilla Bug 671906 +

    +
    +
    +
    +
    + +
    + + diff --git a/image/test/mochitest/test_bug733553.html b/image/test/mochitest/test_bug733553.html new file mode 100644 index 000000000..cd2982593 --- /dev/null +++ b/image/test/mochitest/test_bug733553.html @@ -0,0 +1,92 @@ + + + + + Test for Bug 733553 + + + + + +Mozilla Bug 733553 +

    +
    +
    +
    +
    + +
    + + diff --git a/image/test/mochitest/test_bug767779.html b/image/test/mochitest/test_bug767779.html new file mode 100644 index 000000000..ba9c93d56 --- /dev/null +++ b/image/test/mochitest/test_bug767779.html @@ -0,0 +1,44 @@ + + + + + Test for Bug 767779 + + + + + + + + +Mozilla Bug 767779 +

    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    + + diff --git a/image/test/mochitest/test_bug865919.html b/image/test/mochitest/test_bug865919.html new file mode 100644 index 000000000..6854c609a --- /dev/null +++ b/image/test/mochitest/test_bug865919.html @@ -0,0 +1,53 @@ + + + + + + Test for Bug 865919 + + + + + + + +
    + +
    + + + + diff --git a/image/test/mochitest/test_bug89419-1.html b/image/test/mochitest/test_bug89419-1.html new file mode 100644 index 000000000..96140e66d --- /dev/null +++ b/image/test/mochitest/test_bug89419-1.html @@ -0,0 +1,68 @@ + + + + + Test for Bug 89419 + + + + + + +Mozilla Bug 89419 +

    +
    +
    +
    +
    + +
    + + diff --git a/image/test/mochitest/test_bug89419-2.html b/image/test/mochitest/test_bug89419-2.html new file mode 100644 index 000000000..9251c0253 --- /dev/null +++ b/image/test/mochitest/test_bug89419-2.html @@ -0,0 +1,68 @@ + + + + + Test for Bug 89419 + + + + + + +Mozilla Bug 89419 +

    +
    +
    +
    +
    + +
    + + diff --git a/image/test/mochitest/test_bullet_animation.html b/image/test/mochitest/test_bullet_animation.html new file mode 100644 index 000000000..bbc97cc85 --- /dev/null +++ b/image/test/mochitest/test_bullet_animation.html @@ -0,0 +1,56 @@ + + + + + Test for Bug 666446 - Animated Bullets + + + + + + + + +Mozilla Bug 666446: lots of animated gifs swamp us with paint events + +

    + +
    + + + +
    + +
    +
    +
    +
    + + diff --git a/image/test/mochitest/test_changeOfSource.html b/image/test/mochitest/test_changeOfSource.html new file mode 100644 index 000000000..be0993e56 --- /dev/null +++ b/image/test/mochitest/test_changeOfSource.html @@ -0,0 +1,62 @@ + + + + + Test for Bug 666446 - Change of Source (1st Version) + + + + + + + + +Mozilla Bug 666446: lots of animated gifs swamp us with paint events + +

    + +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + + diff --git a/image/test/mochitest/test_changeOfSource2.html b/image/test/mochitest/test_changeOfSource2.html new file mode 100644 index 000000000..7f61b0f57 --- /dev/null +++ b/image/test/mochitest/test_changeOfSource2.html @@ -0,0 +1,47 @@ + + + + + Test for Bug 691792 - Change of Source (2nd Version) + + + + + + + + +Mozilla Bug 691792: Change of src attribute for animated gifs no longer works as expected + +

    + +
    +
    + +
    +
    + +
    +
    +
    +
    + + diff --git a/image/test/mochitest/test_drawDiscardedImage.html b/image/test/mochitest/test_drawDiscardedImage.html new file mode 100644 index 000000000..7c99ba504 --- /dev/null +++ b/image/test/mochitest/test_drawDiscardedImage.html @@ -0,0 +1,85 @@ + + + + + Test for Bug 731419 - Draw an ostensibly discarded image to a canvas + + + + + + + + + + + + + + + + + diff --git a/image/test/mochitest/test_error_events.html b/image/test/mochitest/test_error_events.html new file mode 100644 index 000000000..ac1e0353b --- /dev/null +++ b/image/test/mochitest/test_error_events.html @@ -0,0 +1,67 @@ + + + + + Test for Bug 715308 comment 93 + + + + + + + + + +
    + + +
    + + + + + diff --git a/image/test/mochitest/test_has_transparency.html b/image/test/mochitest/test_has_transparency.html new file mode 100644 index 000000000..ee42e655f --- /dev/null +++ b/image/test/mochitest/test_has_transparency.html @@ -0,0 +1,168 @@ + + + + + Test for Bug 1089880 + + + + + + +Mozilla Bug 1089880 +

    +
    +
    +
    +
    +
    + + diff --git a/image/test/mochitest/test_image_crossorigin_data_url.html b/image/test/mochitest/test_image_crossorigin_data_url.html new file mode 100644 index 000000000..9facc00d4 --- /dev/null +++ b/image/test/mochitest/test_image_crossorigin_data_url.html @@ -0,0 +1,27 @@ + + +Test for handling of 'crossorigin' attribute on CSS link with data: URL + + +
    +
    + + diff --git a/image/test/mochitest/test_net_failedtoprocess.html b/image/test/mochitest/test_net_failedtoprocess.html new file mode 100644 index 000000000..37d179c78 --- /dev/null +++ b/image/test/mochitest/test_net_failedtoprocess.html @@ -0,0 +1,51 @@ + + + + + Test for image net:failed-to-process-uri-content + + + + +

    +
    +
    + + + diff --git a/image/test/mochitest/test_removal_ondecode.html b/image/test/mochitest/test_removal_ondecode.html new file mode 100644 index 000000000..fd594acf6 --- /dev/null +++ b/image/test/mochitest/test_removal_ondecode.html @@ -0,0 +1,128 @@ + + + + + Test for Bug 841579 + + + + + + +Mozilla Bug 841579 +

    +
    +
    +
    +
    +
    + + diff --git a/image/test/mochitest/test_removal_onload.html b/image/test/mochitest/test_removal_onload.html new file mode 100644 index 000000000..fbfe62905 --- /dev/null +++ b/image/test/mochitest/test_removal_onload.html @@ -0,0 +1,128 @@ + + + + + Test for Bug 841579 + + + + + + +Mozilla Bug 841579 +

    +
    +
    +
    +
    +
    + + diff --git a/image/test/mochitest/test_short_gif_header.html b/image/test/mochitest/test_short_gif_header.html new file mode 100644 index 000000000..13276fb54 --- /dev/null +++ b/image/test/mochitest/test_short_gif_header.html @@ -0,0 +1,35 @@ + + + + + Test for Bug 844684 + + + + + +Mozilla Bug 844684 +
    + +
    +
    +
    +
    + + diff --git a/image/test/mochitest/test_staticClone.html b/image/test/mochitest/test_staticClone.html new file mode 100644 index 000000000..0ceb0c0af --- /dev/null +++ b/image/test/mochitest/test_staticClone.html @@ -0,0 +1,41 @@ + + + + + Test for Bug 878037 + + + + +Mozilla Bug 878037 +

    +
    + + +
    +
    +
    +
    + + diff --git a/image/test/mochitest/test_svg_animatedGIF.html b/image/test/mochitest/test_svg_animatedGIF.html new file mode 100644 index 000000000..aa9445e00 --- /dev/null +++ b/image/test/mochitest/test_svg_animatedGIF.html @@ -0,0 +1,53 @@ + + + + + Test for Bug 666446 - Animated Raster Images inside of SVG Frames + + + + + + + + + + +

    +
    +
    + + +
    + +
    +
    +
    + + diff --git a/image/test/mochitest/test_svg_filter_animation.html b/image/test/mochitest/test_svg_filter_animation.html new file mode 100644 index 000000000..62e579068 --- /dev/null +++ b/image/test/mochitest/test_svg_filter_animation.html @@ -0,0 +1,42 @@ + + + + + Test for Bug 666446 - Animated Images within SVG Filters + + + + + + + + +Mozilla Bug 666446: lots of animated gifs swamp us with paint events + +

    +
    + + +
    + +
    +
    +
    + + diff --git a/image/test/mochitest/test_synchronized_animation.html b/image/test/mochitest/test_synchronized_animation.html new file mode 100644 index 000000000..01ddd481e --- /dev/null +++ b/image/test/mochitest/test_synchronized_animation.html @@ -0,0 +1,128 @@ + + + + + Test for Bug 867758 + + + + + + +Mozilla Bug 867758 +

    +
    +
    +
    +
    +
    + + diff --git a/image/test/mochitest/test_undisplayed_iframe.html b/image/test/mochitest/test_undisplayed_iframe.html new file mode 100644 index 000000000..adcdbb92d --- /dev/null +++ b/image/test/mochitest/test_undisplayed_iframe.html @@ -0,0 +1,47 @@ + + + + +Test for Bug 666446 - Test for Animated Gif within IFRAME + + + + + + + + + Mozilla Bug 666446: lots of animated gifs swamp us with paint events +

    + +
    + +
    + +
    + +
    +
    +
    +
    + + diff --git a/image/test/mochitest/test_webcam.html b/image/test/mochitest/test_webcam.html new file mode 100644 index 000000000..9f9764c24 --- /dev/null +++ b/image/test/mochitest/test_webcam.html @@ -0,0 +1,68 @@ + + + + + Test for Bug 641748 - WebCam Simulacrum + + + + + + + + +Mozilla Bug 641748: GIF decoder doesn't support multipart/x-mixed-replace + +

    + +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + + diff --git a/image/test/mochitest/test_xultree_animation.xhtml b/image/test/mochitest/test_xultree_animation.xhtml new file mode 100644 index 000000000..ad8ee3f4f --- /dev/null +++ b/image/test/mochitest/test_xultree_animation.xhtml @@ -0,0 +1,67 @@ + + + + + Test for Bug 666446 - Animated Images within SVG Filters + + + + + + + + +Mozilla Bug 666446: lots of animated gifs swamp us with paint events + +

    +
    + + +
    + + + + + + + + + + + + +
    + +
    +
    +
    + + diff --git a/image/test/mochitest/transparent.gif b/image/test/mochitest/transparent.gif new file mode 100644 index 000000000..48f5c7caf Binary files /dev/null and b/image/test/mochitest/transparent.gif differ diff --git a/image/test/mochitest/transparent.png b/image/test/mochitest/transparent.png new file mode 100644 index 000000000..fc8002053 Binary files /dev/null and b/image/test/mochitest/transparent.png differ diff --git a/image/test/mochitest/webcam-simulacrum.sjs b/image/test/mochitest/webcam-simulacrum.sjs new file mode 100644 index 000000000..23872e675 --- /dev/null +++ b/image/test/mochitest/webcam-simulacrum.sjs @@ -0,0 +1,51 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +var counter = 2; +var frames = ['red.gif', 'blue.gif']; +var timer = Components.classes["@mozilla.org/timer;1"]; +var partTimer = timer.createInstance(Components.interfaces.nsITimer); + +function getFileAsInputStream(aFilename) { + var file = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Components.interfaces.nsIProperties) + .get("CurWorkD", Components.interfaces.nsIFile); + + file.append("tests"); + file.append("image"); + file.append("test"); + file.append("mochitest"); + file.append(aFilename); + + var fileStream = Components.classes['@mozilla.org/network/file-input-stream;1'] + .createInstance(Components.interfaces.nsIFileInputStream); + fileStream.init(file, 1, 0, false); + return fileStream; +} + +function handleRequest(request, response) +{ + response.setHeader("Content-Type", + "multipart/x-mixed-replace;boundary=BOUNDARYOMG", false); + response.setHeader("Cache-Control", "no-cache", false); + response.setStatusLine(request.httpVersion, 200, "OK"); + response.processAsync(); + response.write("--BOUNDARYOMG\r\n"); + while (frames.length > 0) { + sendNextPart(response); + } + response.write("--BOUNDARYOMG--\r\n"); + response.finish(); +} + +function sendNextPart(response) { + var nextPartHead = "Content-Type: image/gif\r\n\r\n"; + var inputStream = getFileAsInputStream(frames.shift()); + response.bodyOutputStream.write(nextPartHead, nextPartHead.length); + response.bodyOutputStream.writeFrom(inputStream, inputStream.available()); + inputStream.close(); + // Toss in the boundary, so the browser can know this part is complete + response.write("--BOUNDARYOMG\r\n"); +} + diff --git a/image/test/reftest/ImageDocument.css b/image/test/reftest/ImageDocument.css new file mode 100644 index 000000000..b44981098 --- /dev/null +++ b/image/test/reftest/ImageDocument.css @@ -0,0 +1,16 @@ +body { + background-image: url("chrome://global/skin/media/imagedoc-darknoise.png"); + margin: 0; +} + +body > :first-child { + display: block; + position: absolute; + margin: auto; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: hsl(0,0%,90%) url("chrome://global/skin/media/imagedoc-lightnoise.png"); + color: #222; +} diff --git a/image/test/reftest/apng/bug411852-1-ref.png b/image/test/reftest/apng/bug411852-1-ref.png new file mode 100644 index 000000000..04b862a1d Binary files /dev/null and b/image/test/reftest/apng/bug411852-1-ref.png differ diff --git a/image/test/reftest/apng/bug411852-1.png b/image/test/reftest/apng/bug411852-1.png new file mode 100644 index 000000000..643f87e17 Binary files /dev/null and b/image/test/reftest/apng/bug411852-1.png differ diff --git a/image/test/reftest/apng/bug546272-ref.png b/image/test/reftest/apng/bug546272-ref.png new file mode 100644 index 000000000..85dfd8ccf Binary files /dev/null and b/image/test/reftest/apng/bug546272-ref.png differ diff --git a/image/test/reftest/apng/bug546272.png b/image/test/reftest/apng/bug546272.png new file mode 100644 index 000000000..5232d7f8f Binary files /dev/null and b/image/test/reftest/apng/bug546272.png differ diff --git a/image/test/reftest/apng/delaytest.html b/image/test/reftest/apng/delaytest.html new file mode 100644 index 000000000..af3937a57 --- /dev/null +++ b/image/test/reftest/apng/delaytest.html @@ -0,0 +1,41 @@ + + + +Delayed image reftest wrapper + + + + + + diff --git a/image/test/reftest/apng/reftest-stylo.list b/image/test/reftest/apng/reftest-stylo.list new file mode 100644 index 000000000..229de2161 --- /dev/null +++ b/image/test/reftest/apng/reftest-stylo.list @@ -0,0 +1,7 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# APNG tests +# +# delaytest.html delays the reftest snapshot to allow time for the +# animation to complete. +random == delaytest.html?bug411852-1.png delaytest.html?bug411852-1.png +random == delaytest.html?bug546272.png delaytest.html?bug546272.png diff --git a/image/test/reftest/apng/reftest.list b/image/test/reftest/apng/reftest.list new file mode 100644 index 000000000..eb1c02462 --- /dev/null +++ b/image/test/reftest/apng/reftest.list @@ -0,0 +1,6 @@ +# APNG tests +# +# delaytest.html delays the reftest snapshot to allow time for the +# animation to complete. +random == delaytest.html?bug411852-1.png bug411852-1-ref.png +random == delaytest.html?bug546272.png bug546272-ref.png diff --git a/image/test/reftest/blob/blob-uri-with-ref-param-notref.html b/image/test/reftest/blob/blob-uri-with-ref-param-notref.html new file mode 100644 index 000000000..3b62a3c13 --- /dev/null +++ b/image/test/reftest/blob/blob-uri-with-ref-param-notref.html @@ -0,0 +1,41 @@ + + + + + + + + + + + diff --git a/image/test/reftest/blob/blob-uri-with-ref-param.html b/image/test/reftest/blob/blob-uri-with-ref-param.html new file mode 100644 index 000000000..8f2e4e7cf --- /dev/null +++ b/image/test/reftest/blob/blob-uri-with-ref-param.html @@ -0,0 +1,40 @@ + + + + + + + + + + + diff --git a/image/test/reftest/blob/image.png b/image/test/reftest/blob/image.png new file mode 100644 index 000000000..d7d87adce Binary files /dev/null and b/image/test/reftest/blob/image.png differ diff --git a/image/test/reftest/blob/reftest-stylo.list b/image/test/reftest/blob/reftest-stylo.list new file mode 100644 index 000000000..06f01ef7f --- /dev/null +++ b/image/test/reftest/blob/reftest-stylo.list @@ -0,0 +1,8 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# Blob URI tests + +# Test that blob URIs don't get merged if they have different ref params. +# (We run the test twice to check both cached and non-cached cases.) +default-preferences pref(image.mozsamplesize.enabled,true) +== blob-uri-with-ref-param.html blob-uri-with-ref-param.html +== blob-uri-with-ref-param.html blob-uri-with-ref-param.html diff --git a/image/test/reftest/blob/reftest.list b/image/test/reftest/blob/reftest.list new file mode 100644 index 000000000..e795ba9d3 --- /dev/null +++ b/image/test/reftest/blob/reftest.list @@ -0,0 +1,7 @@ +# Blob URI tests + +# Test that blob URIs don't get merged if they have different ref params. +# (We run the test twice to check both cached and non-cached cases.) +default-preferences pref(image.mozsamplesize.enabled,true) +!= blob-uri-with-ref-param.html blob-uri-with-ref-param-notref.html +!= blob-uri-with-ref-param.html blob-uri-with-ref-param-notref.html diff --git a/image/test/reftest/bmp/1240629-1.bmp b/image/test/reftest/bmp/1240629-1.bmp new file mode 100644 index 000000000..604d248e7 Binary files /dev/null and b/image/test/reftest/bmp/1240629-1.bmp differ diff --git a/image/test/reftest/bmp/1240629-2.bmp b/image/test/reftest/bmp/1240629-2.bmp new file mode 100644 index 000000000..e4fe80d59 Binary files /dev/null and b/image/test/reftest/bmp/1240629-2.bmp differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-not-square-1bpp.bmp b/image/test/reftest/bmp/bmp-1bpp/bmp-not-square-1bpp.bmp new file mode 100644 index 000000000..302e0c712 Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-not-square-1bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-not-square-1bpp.png b/image/test/reftest/bmp/bmp-1bpp/bmp-not-square-1bpp.png new file mode 100644 index 000000000..f9318693d Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-not-square-1bpp.png differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-15x15-1bpp.bmp b/image/test/reftest/bmp/bmp-1bpp/bmp-size-15x15-1bpp.bmp new file mode 100644 index 000000000..e769ff864 Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-15x15-1bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-15x15-1bpp.png b/image/test/reftest/bmp/bmp-1bpp/bmp-size-15x15-1bpp.png new file mode 100644 index 000000000..956c78ece Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-15x15-1bpp.png differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-16x16-1bpp.bmp b/image/test/reftest/bmp/bmp-1bpp/bmp-size-16x16-1bpp.bmp new file mode 100644 index 000000000..ff012d98c Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-16x16-1bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-16x16-1bpp.png b/image/test/reftest/bmp/bmp-1bpp/bmp-size-16x16-1bpp.png new file mode 100644 index 000000000..90088351f Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-16x16-1bpp.png differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-17x17-1bpp.bmp b/image/test/reftest/bmp/bmp-1bpp/bmp-size-17x17-1bpp.bmp new file mode 100644 index 000000000..86f56476e Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-17x17-1bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-17x17-1bpp.png b/image/test/reftest/bmp/bmp-1bpp/bmp-size-17x17-1bpp.png new file mode 100644 index 000000000..9a294696c Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-17x17-1bpp.png differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-1x1-1bpp.bmp b/image/test/reftest/bmp/bmp-1bpp/bmp-size-1x1-1bpp.bmp new file mode 100644 index 000000000..0f98654d8 Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-1x1-1bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-1x1-1bpp.ico b/image/test/reftest/bmp/bmp-1bpp/bmp-size-1x1-1bpp.ico new file mode 100644 index 000000000..5af8bef61 Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-1x1-1bpp.ico differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-1x1-1bpp.png b/image/test/reftest/bmp/bmp-1bpp/bmp-size-1x1-1bpp.png new file mode 100644 index 000000000..7a07a124e Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-1x1-1bpp.png differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-2x2-1bpp.bmp b/image/test/reftest/bmp/bmp-1bpp/bmp-size-2x2-1bpp.bmp new file mode 100644 index 000000000..5544c6437 Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-2x2-1bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-2x2-1bpp.png b/image/test/reftest/bmp/bmp-1bpp/bmp-size-2x2-1bpp.png new file mode 100644 index 000000000..3b09f8076 Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-2x2-1bpp.png differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-31x31-1bpp.bmp b/image/test/reftest/bmp/bmp-1bpp/bmp-size-31x31-1bpp.bmp new file mode 100644 index 000000000..8afcc56cc Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-31x31-1bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-31x31-1bpp.png b/image/test/reftest/bmp/bmp-1bpp/bmp-size-31x31-1bpp.png new file mode 100644 index 000000000..d1fe6ddee Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-31x31-1bpp.png differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-32x32-1bpp.bmp b/image/test/reftest/bmp/bmp-1bpp/bmp-size-32x32-1bpp.bmp new file mode 100644 index 000000000..255e5526c Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-32x32-1bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-32x32-1bpp.png b/image/test/reftest/bmp/bmp-1bpp/bmp-size-32x32-1bpp.png new file mode 100644 index 000000000..078d3dc5d Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-32x32-1bpp.png differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-33x33-1bpp.bmp b/image/test/reftest/bmp/bmp-1bpp/bmp-size-33x33-1bpp.bmp new file mode 100644 index 000000000..6d752a2e1 Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-33x33-1bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-33x33-1bpp.png b/image/test/reftest/bmp/bmp-1bpp/bmp-size-33x33-1bpp.png new file mode 100644 index 000000000..e64e12b2a Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-33x33-1bpp.png differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-3x3-1bpp.bmp b/image/test/reftest/bmp/bmp-1bpp/bmp-size-3x3-1bpp.bmp new file mode 100644 index 000000000..d4f885687 Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-3x3-1bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-3x3-1bpp.png b/image/test/reftest/bmp/bmp-1bpp/bmp-size-3x3-1bpp.png new file mode 100644 index 000000000..b8519a874 Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-3x3-1bpp.png differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-4x4-1bpp.bmp b/image/test/reftest/bmp/bmp-1bpp/bmp-size-4x4-1bpp.bmp new file mode 100644 index 000000000..c0b9128fe Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-4x4-1bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-4x4-1bpp.png b/image/test/reftest/bmp/bmp-1bpp/bmp-size-4x4-1bpp.png new file mode 100644 index 000000000..3977b5454 Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-4x4-1bpp.png differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-5x5-1bpp.bmp b/image/test/reftest/bmp/bmp-1bpp/bmp-size-5x5-1bpp.bmp new file mode 100644 index 000000000..c02b2288d Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-5x5-1bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-5x5-1bpp.png b/image/test/reftest/bmp/bmp-1bpp/bmp-size-5x5-1bpp.png new file mode 100644 index 000000000..caa9246b6 Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-5x5-1bpp.png differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-6x6-1bpp.bmp b/image/test/reftest/bmp/bmp-1bpp/bmp-size-6x6-1bpp.bmp new file mode 100644 index 000000000..64415c6ec Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-6x6-1bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-6x6-1bpp.png b/image/test/reftest/bmp/bmp-1bpp/bmp-size-6x6-1bpp.png new file mode 100644 index 000000000..30e1b0249 Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-6x6-1bpp.png differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-7x7-1bpp.bmp b/image/test/reftest/bmp/bmp-1bpp/bmp-size-7x7-1bpp.bmp new file mode 100644 index 000000000..d8e867a0d Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-7x7-1bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-7x7-1bpp.png b/image/test/reftest/bmp/bmp-1bpp/bmp-size-7x7-1bpp.png new file mode 100644 index 000000000..9dbaae84c Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-7x7-1bpp.png differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-8x8-1bpp.bmp b/image/test/reftest/bmp/bmp-1bpp/bmp-size-8x8-1bpp.bmp new file mode 100644 index 000000000..207e84f80 Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-8x8-1bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-8x8-1bpp.png b/image/test/reftest/bmp/bmp-1bpp/bmp-size-8x8-1bpp.png new file mode 100644 index 000000000..220138840 Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-8x8-1bpp.png differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-9x9-1bpp.bmp b/image/test/reftest/bmp/bmp-1bpp/bmp-size-9x9-1bpp.bmp new file mode 100644 index 000000000..871eb7c0f Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-9x9-1bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-1bpp/bmp-size-9x9-1bpp.png b/image/test/reftest/bmp/bmp-1bpp/bmp-size-9x9-1bpp.png new file mode 100644 index 000000000..7fe1b548b Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/bmp-size-9x9-1bpp.png differ diff --git a/image/test/reftest/bmp/bmp-1bpp/os2bmp-size-32x32-1bpp.bmp b/image/test/reftest/bmp/bmp-1bpp/os2bmp-size-32x32-1bpp.bmp new file mode 100644 index 000000000..32bfc5e8f Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/os2bmp-size-32x32-1bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-1bpp/reftest-stylo.list b/image/test/reftest/bmp/bmp-1bpp/reftest-stylo.list new file mode 100644 index 000000000..ff10dd811 --- /dev/null +++ b/image/test/reftest/bmp/bmp-1bpp/reftest-stylo.list @@ -0,0 +1,22 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# BMP 1BPP tests + +# Images of various sizes +fails == bmp-size-1x1-1bpp.bmp bmp-size-1x1-1bpp.bmp +fails == bmp-size-2x2-1bpp.bmp bmp-size-2x2-1bpp.bmp +fails == bmp-size-3x3-1bpp.bmp bmp-size-3x3-1bpp.bmp +fails == bmp-size-4x4-1bpp.bmp bmp-size-4x4-1bpp.bmp +fails == bmp-size-5x5-1bpp.bmp bmp-size-5x5-1bpp.bmp +fails == bmp-size-6x6-1bpp.bmp bmp-size-6x6-1bpp.bmp +fails == bmp-size-7x7-1bpp.bmp bmp-size-7x7-1bpp.bmp +fails == bmp-size-8x8-1bpp.bmp bmp-size-8x8-1bpp.bmp +fails == bmp-size-9x9-1bpp.bmp bmp-size-9x9-1bpp.bmp +fails == bmp-size-15x15-1bpp.bmp bmp-size-15x15-1bpp.bmp +fails == bmp-size-16x16-1bpp.bmp bmp-size-16x16-1bpp.bmp +fails == bmp-size-17x17-1bpp.bmp bmp-size-17x17-1bpp.bmp +fails == bmp-size-31x31-1bpp.bmp bmp-size-31x31-1bpp.bmp +fails == bmp-size-32x32-1bpp.bmp bmp-size-32x32-1bpp.bmp +fails == bmp-size-33x33-1bpp.bmp bmp-size-33x33-1bpp.bmp +fails == bmp-not-square-1bpp.bmp bmp-not-square-1bpp.bmp +fails == os2bmp-size-32x32-1bpp.bmp os2bmp-size-32x32-1bpp.bmp +fails == top-to-bottom-16x16-1bpp.bmp top-to-bottom-16x16-1bpp.bmp diff --git a/image/test/reftest/bmp/bmp-1bpp/reftest.list b/image/test/reftest/bmp/bmp-1bpp/reftest.list new file mode 100644 index 000000000..15274c2bc --- /dev/null +++ b/image/test/reftest/bmp/bmp-1bpp/reftest.list @@ -0,0 +1,21 @@ +# BMP 1BPP tests + +# Images of various sizes +== bmp-size-1x1-1bpp.bmp bmp-size-1x1-1bpp.png +== bmp-size-2x2-1bpp.bmp bmp-size-2x2-1bpp.png +== bmp-size-3x3-1bpp.bmp bmp-size-3x3-1bpp.png +== bmp-size-4x4-1bpp.bmp bmp-size-4x4-1bpp.png +== bmp-size-5x5-1bpp.bmp bmp-size-5x5-1bpp.png +== bmp-size-6x6-1bpp.bmp bmp-size-6x6-1bpp.png +== bmp-size-7x7-1bpp.bmp bmp-size-7x7-1bpp.png +== bmp-size-8x8-1bpp.bmp bmp-size-8x8-1bpp.png +== bmp-size-9x9-1bpp.bmp bmp-size-9x9-1bpp.png +== bmp-size-15x15-1bpp.bmp bmp-size-15x15-1bpp.png +== bmp-size-16x16-1bpp.bmp bmp-size-16x16-1bpp.png +== bmp-size-17x17-1bpp.bmp bmp-size-17x17-1bpp.png +== bmp-size-31x31-1bpp.bmp bmp-size-31x31-1bpp.png +== bmp-size-32x32-1bpp.bmp bmp-size-32x32-1bpp.png +== bmp-size-33x33-1bpp.bmp bmp-size-33x33-1bpp.png +== bmp-not-square-1bpp.bmp bmp-not-square-1bpp.png +== os2bmp-size-32x32-1bpp.bmp bmp-size-32x32-1bpp.png +== top-to-bottom-16x16-1bpp.bmp bmp-size-16x16-1bpp.png diff --git a/image/test/reftest/bmp/bmp-1bpp/top-to-bottom-16x16-1bpp.bmp b/image/test/reftest/bmp/bmp-1bpp/top-to-bottom-16x16-1bpp.bmp new file mode 100644 index 000000000..8633ef2aa Binary files /dev/null and b/image/test/reftest/bmp/bmp-1bpp/top-to-bottom-16x16-1bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-not-square-24bpp.bmp b/image/test/reftest/bmp/bmp-24bpp/bmp-not-square-24bpp.bmp new file mode 100644 index 000000000..9d1f4de2c Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-not-square-24bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-not-square-24bpp.png b/image/test/reftest/bmp/bmp-24bpp/bmp-not-square-24bpp.png new file mode 100644 index 000000000..9b0d16081 Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-not-square-24bpp.png differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-15x15-24bpp.bmp b/image/test/reftest/bmp/bmp-24bpp/bmp-size-15x15-24bpp.bmp new file mode 100644 index 000000000..ba029510b Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-15x15-24bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-15x15-24bpp.png b/image/test/reftest/bmp/bmp-24bpp/bmp-size-15x15-24bpp.png new file mode 100644 index 000000000..e1287430d Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-15x15-24bpp.png differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-16x16-24bpp.bmp b/image/test/reftest/bmp/bmp-24bpp/bmp-size-16x16-24bpp.bmp new file mode 100644 index 000000000..f35d70669 Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-16x16-24bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-16x16-24bpp.png b/image/test/reftest/bmp/bmp-24bpp/bmp-size-16x16-24bpp.png new file mode 100644 index 000000000..c04869e72 Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-16x16-24bpp.png differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-17x17-24bpp.bmp b/image/test/reftest/bmp/bmp-24bpp/bmp-size-17x17-24bpp.bmp new file mode 100644 index 000000000..fc576c498 Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-17x17-24bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-17x17-24bpp.png b/image/test/reftest/bmp/bmp-24bpp/bmp-size-17x17-24bpp.png new file mode 100644 index 000000000..00fb8e4f3 Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-17x17-24bpp.png differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-1x1-24bpp.bmp b/image/test/reftest/bmp/bmp-24bpp/bmp-size-1x1-24bpp.bmp new file mode 100644 index 000000000..db790d50c Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-1x1-24bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-1x1-24bpp.png b/image/test/reftest/bmp/bmp-24bpp/bmp-size-1x1-24bpp.png new file mode 100644 index 000000000..c05f5fef8 Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-1x1-24bpp.png differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-2x2-24bpp.bmp b/image/test/reftest/bmp/bmp-24bpp/bmp-size-2x2-24bpp.bmp new file mode 100644 index 000000000..19bff3d01 Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-2x2-24bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-2x2-24bpp.png b/image/test/reftest/bmp/bmp-24bpp/bmp-size-2x2-24bpp.png new file mode 100644 index 000000000..e512d3f9b Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-2x2-24bpp.png differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-31x31-24bpp.bmp b/image/test/reftest/bmp/bmp-24bpp/bmp-size-31x31-24bpp.bmp new file mode 100644 index 000000000..da11048cb Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-31x31-24bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-31x31-24bpp.png b/image/test/reftest/bmp/bmp-24bpp/bmp-size-31x31-24bpp.png new file mode 100644 index 000000000..e4a864251 Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-31x31-24bpp.png differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-32x32-24bpp.bmp b/image/test/reftest/bmp/bmp-24bpp/bmp-size-32x32-24bpp.bmp new file mode 100644 index 000000000..e1631e5fd Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-32x32-24bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-32x32-24bpp.png b/image/test/reftest/bmp/bmp-24bpp/bmp-size-32x32-24bpp.png new file mode 100644 index 000000000..3a6fbe8ee Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-32x32-24bpp.png differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-33x33-24bpp.bmp b/image/test/reftest/bmp/bmp-24bpp/bmp-size-33x33-24bpp.bmp new file mode 100644 index 000000000..d228cf063 Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-33x33-24bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-33x33-24bpp.png b/image/test/reftest/bmp/bmp-24bpp/bmp-size-33x33-24bpp.png new file mode 100644 index 000000000..72ef7eb63 Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-33x33-24bpp.png differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-3x3-24bpp.bmp b/image/test/reftest/bmp/bmp-24bpp/bmp-size-3x3-24bpp.bmp new file mode 100644 index 000000000..f353f9b6d Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-3x3-24bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-3x3-24bpp.png b/image/test/reftest/bmp/bmp-24bpp/bmp-size-3x3-24bpp.png new file mode 100644 index 000000000..cb42ec4f8 Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-3x3-24bpp.png differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-4x4-24bpp.bmp b/image/test/reftest/bmp/bmp-24bpp/bmp-size-4x4-24bpp.bmp new file mode 100644 index 000000000..2373435f4 Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-4x4-24bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-4x4-24bpp.png b/image/test/reftest/bmp/bmp-24bpp/bmp-size-4x4-24bpp.png new file mode 100644 index 000000000..e6afafd89 Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-4x4-24bpp.png differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-5x5-24bpp.bmp b/image/test/reftest/bmp/bmp-24bpp/bmp-size-5x5-24bpp.bmp new file mode 100644 index 000000000..a3016fc1a Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-5x5-24bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-5x5-24bpp.png b/image/test/reftest/bmp/bmp-24bpp/bmp-size-5x5-24bpp.png new file mode 100644 index 000000000..a844aff76 Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-5x5-24bpp.png differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-6x6-24bpp.bmp b/image/test/reftest/bmp/bmp-24bpp/bmp-size-6x6-24bpp.bmp new file mode 100644 index 000000000..cba1d62cc Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-6x6-24bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-6x6-24bpp.png b/image/test/reftest/bmp/bmp-24bpp/bmp-size-6x6-24bpp.png new file mode 100644 index 000000000..415c2d9c6 Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-6x6-24bpp.png differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-7x7-24bpp.bmp b/image/test/reftest/bmp/bmp-24bpp/bmp-size-7x7-24bpp.bmp new file mode 100644 index 000000000..87cd419b4 Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-7x7-24bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-7x7-24bpp.png b/image/test/reftest/bmp/bmp-24bpp/bmp-size-7x7-24bpp.png new file mode 100644 index 000000000..ab2f89274 Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-7x7-24bpp.png differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-8x8-24bpp.bmp b/image/test/reftest/bmp/bmp-24bpp/bmp-size-8x8-24bpp.bmp new file mode 100644 index 000000000..b6f108a04 Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-8x8-24bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-8x8-24bpp.png b/image/test/reftest/bmp/bmp-24bpp/bmp-size-8x8-24bpp.png new file mode 100644 index 000000000..fe2ff40a1 Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-8x8-24bpp.png differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-9x9-24bpp.bmp b/image/test/reftest/bmp/bmp-24bpp/bmp-size-9x9-24bpp.bmp new file mode 100644 index 000000000..8140b1905 Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-9x9-24bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-24bpp/bmp-size-9x9-24bpp.png b/image/test/reftest/bmp/bmp-24bpp/bmp-size-9x9-24bpp.png new file mode 100644 index 000000000..18ab4b25d Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/bmp-size-9x9-24bpp.png differ diff --git a/image/test/reftest/bmp/bmp-24bpp/os2bmp-size-32x32-24bpp.bmp b/image/test/reftest/bmp/bmp-24bpp/os2bmp-size-32x32-24bpp.bmp new file mode 100644 index 000000000..b75ae62ca Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/os2bmp-size-32x32-24bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-24bpp/reftest-stylo.list b/image/test/reftest/bmp/bmp-24bpp/reftest-stylo.list new file mode 100644 index 000000000..29040bfc5 --- /dev/null +++ b/image/test/reftest/bmp/bmp-24bpp/reftest-stylo.list @@ -0,0 +1,22 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# BMP 24BPP tests + +# Images of various sizes +fails == bmp-size-1x1-24bpp.bmp bmp-size-1x1-24bpp.bmp +fails == bmp-size-2x2-24bpp.bmp bmp-size-2x2-24bpp.bmp +fails == bmp-size-3x3-24bpp.bmp bmp-size-3x3-24bpp.bmp +fails == bmp-size-4x4-24bpp.bmp bmp-size-4x4-24bpp.bmp +fails == bmp-size-5x5-24bpp.bmp bmp-size-5x5-24bpp.bmp +fails == bmp-size-6x6-24bpp.bmp bmp-size-6x6-24bpp.bmp +fails == bmp-size-7x7-24bpp.bmp bmp-size-7x7-24bpp.bmp +fails == bmp-size-8x8-24bpp.bmp bmp-size-8x8-24bpp.bmp +fails == bmp-size-9x9-24bpp.bmp bmp-size-9x9-24bpp.bmp +fails == bmp-size-15x15-24bpp.bmp bmp-size-15x15-24bpp.bmp +fails == bmp-size-16x16-24bpp.bmp bmp-size-16x16-24bpp.bmp +fails == bmp-size-17x17-24bpp.bmp bmp-size-17x17-24bpp.bmp +fails == bmp-size-31x31-24bpp.bmp bmp-size-31x31-24bpp.bmp +fails == bmp-size-32x32-24bpp.bmp bmp-size-32x32-24bpp.bmp +fails == bmp-size-33x33-24bpp.bmp bmp-size-33x33-24bpp.bmp +fails == bmp-not-square-24bpp.bmp bmp-not-square-24bpp.bmp +fails == os2bmp-size-32x32-24bpp.bmp os2bmp-size-32x32-24bpp.bmp +fails == top-to-bottom-16x16-24bpp.bmp top-to-bottom-16x16-24bpp.bmp diff --git a/image/test/reftest/bmp/bmp-24bpp/reftest.list b/image/test/reftest/bmp/bmp-24bpp/reftest.list new file mode 100644 index 000000000..83ec17563 --- /dev/null +++ b/image/test/reftest/bmp/bmp-24bpp/reftest.list @@ -0,0 +1,21 @@ +# BMP 24BPP tests + +# Images of various sizes +== bmp-size-1x1-24bpp.bmp bmp-size-1x1-24bpp.png +== bmp-size-2x2-24bpp.bmp bmp-size-2x2-24bpp.png +== bmp-size-3x3-24bpp.bmp bmp-size-3x3-24bpp.png +== bmp-size-4x4-24bpp.bmp bmp-size-4x4-24bpp.png +== bmp-size-5x5-24bpp.bmp bmp-size-5x5-24bpp.png +== bmp-size-6x6-24bpp.bmp bmp-size-6x6-24bpp.png +== bmp-size-7x7-24bpp.bmp bmp-size-7x7-24bpp.png +== bmp-size-8x8-24bpp.bmp bmp-size-8x8-24bpp.png +== bmp-size-9x9-24bpp.bmp bmp-size-9x9-24bpp.png +== bmp-size-15x15-24bpp.bmp bmp-size-15x15-24bpp.png +== bmp-size-16x16-24bpp.bmp bmp-size-16x16-24bpp.png +== bmp-size-17x17-24bpp.bmp bmp-size-17x17-24bpp.png +== bmp-size-31x31-24bpp.bmp bmp-size-31x31-24bpp.png +== bmp-size-32x32-24bpp.bmp bmp-size-32x32-24bpp.png +== bmp-size-33x33-24bpp.bmp bmp-size-33x33-24bpp.png +== bmp-not-square-24bpp.bmp bmp-not-square-24bpp.png +== os2bmp-size-32x32-24bpp.bmp bmp-size-32x32-24bpp.png +== top-to-bottom-16x16-24bpp.bmp bmp-size-16x16-24bpp.png diff --git a/image/test/reftest/bmp/bmp-24bpp/top-to-bottom-16x16-24bpp.bmp b/image/test/reftest/bmp/bmp-24bpp/top-to-bottom-16x16-24bpp.bmp new file mode 100644 index 000000000..bd18f85d4 Binary files /dev/null and b/image/test/reftest/bmp/bmp-24bpp/top-to-bottom-16x16-24bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-not-square-4bpp.bmp b/image/test/reftest/bmp/bmp-4bpp/bmp-not-square-4bpp.bmp new file mode 100644 index 000000000..f63dd81bd Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-not-square-4bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-not-square-4bpp.png b/image/test/reftest/bmp/bmp-4bpp/bmp-not-square-4bpp.png new file mode 100644 index 000000000..7c713c557 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-not-square-4bpp.png differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-15x15-4bpp.bmp b/image/test/reftest/bmp/bmp-4bpp/bmp-size-15x15-4bpp.bmp new file mode 100644 index 000000000..8b586dbfd Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-15x15-4bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-15x15-4bpp.png b/image/test/reftest/bmp/bmp-4bpp/bmp-size-15x15-4bpp.png new file mode 100644 index 000000000..5d4a3f953 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-15x15-4bpp.png differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-16x16-4bpp.bmp b/image/test/reftest/bmp/bmp-4bpp/bmp-size-16x16-4bpp.bmp new file mode 100644 index 000000000..eae432e65 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-16x16-4bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-16x16-4bpp.png b/image/test/reftest/bmp/bmp-4bpp/bmp-size-16x16-4bpp.png new file mode 100644 index 000000000..d45d63f53 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-16x16-4bpp.png differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-17x17-4bpp.bmp b/image/test/reftest/bmp/bmp-4bpp/bmp-size-17x17-4bpp.bmp new file mode 100644 index 000000000..5880c6111 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-17x17-4bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-17x17-4bpp.png b/image/test/reftest/bmp/bmp-4bpp/bmp-size-17x17-4bpp.png new file mode 100644 index 000000000..bf4890329 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-17x17-4bpp.png differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-1x1-4bpp.bmp b/image/test/reftest/bmp/bmp-4bpp/bmp-size-1x1-4bpp.bmp new file mode 100644 index 000000000..2ba68a391 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-1x1-4bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-1x1-4bpp.png b/image/test/reftest/bmp/bmp-4bpp/bmp-size-1x1-4bpp.png new file mode 100644 index 000000000..d41dd645b Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-1x1-4bpp.png differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-2x2-4bpp.bmp b/image/test/reftest/bmp/bmp-4bpp/bmp-size-2x2-4bpp.bmp new file mode 100644 index 000000000..6c6383aa8 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-2x2-4bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-2x2-4bpp.png b/image/test/reftest/bmp/bmp-4bpp/bmp-size-2x2-4bpp.png new file mode 100644 index 000000000..b2d605041 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-2x2-4bpp.png differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-31x31-4bpp.bmp b/image/test/reftest/bmp/bmp-4bpp/bmp-size-31x31-4bpp.bmp new file mode 100644 index 000000000..ac440a6d8 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-31x31-4bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-31x31-4bpp.png b/image/test/reftest/bmp/bmp-4bpp/bmp-size-31x31-4bpp.png new file mode 100644 index 000000000..cb12a3448 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-31x31-4bpp.png differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-32x32-4bpp.bmp b/image/test/reftest/bmp/bmp-4bpp/bmp-size-32x32-4bpp.bmp new file mode 100644 index 000000000..e4383c473 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-32x32-4bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-32x32-4bpp.png b/image/test/reftest/bmp/bmp-4bpp/bmp-size-32x32-4bpp.png new file mode 100644 index 000000000..58d867d12 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-32x32-4bpp.png differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-33x33-4bpp.bmp b/image/test/reftest/bmp/bmp-4bpp/bmp-size-33x33-4bpp.bmp new file mode 100644 index 000000000..04b2c1d1f Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-33x33-4bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-33x33-4bpp.png b/image/test/reftest/bmp/bmp-4bpp/bmp-size-33x33-4bpp.png new file mode 100644 index 000000000..064fde198 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-33x33-4bpp.png differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-3x3-4bpp.bmp b/image/test/reftest/bmp/bmp-4bpp/bmp-size-3x3-4bpp.bmp new file mode 100644 index 000000000..179dbcfa5 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-3x3-4bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-3x3-4bpp.png b/image/test/reftest/bmp/bmp-4bpp/bmp-size-3x3-4bpp.png new file mode 100644 index 000000000..e34114d5c Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-3x3-4bpp.png differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-4x4-4bpp.bmp b/image/test/reftest/bmp/bmp-4bpp/bmp-size-4x4-4bpp.bmp new file mode 100644 index 000000000..0f57e102e Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-4x4-4bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-4x4-4bpp.png b/image/test/reftest/bmp/bmp-4bpp/bmp-size-4x4-4bpp.png new file mode 100644 index 000000000..3efa55562 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-4x4-4bpp.png differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-5x5-4bpp.bmp b/image/test/reftest/bmp/bmp-4bpp/bmp-size-5x5-4bpp.bmp new file mode 100644 index 000000000..a4efe6660 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-5x5-4bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-5x5-4bpp.png b/image/test/reftest/bmp/bmp-4bpp/bmp-size-5x5-4bpp.png new file mode 100644 index 000000000..02ebf57a5 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-5x5-4bpp.png differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-6x6-4bpp.bmp b/image/test/reftest/bmp/bmp-4bpp/bmp-size-6x6-4bpp.bmp new file mode 100644 index 000000000..f4e1a2918 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-6x6-4bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-6x6-4bpp.png b/image/test/reftest/bmp/bmp-4bpp/bmp-size-6x6-4bpp.png new file mode 100644 index 000000000..1f5769d09 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-6x6-4bpp.png differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-7x7-4bpp.bmp b/image/test/reftest/bmp/bmp-4bpp/bmp-size-7x7-4bpp.bmp new file mode 100644 index 000000000..e7ee1cf20 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-7x7-4bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-7x7-4bpp.png b/image/test/reftest/bmp/bmp-4bpp/bmp-size-7x7-4bpp.png new file mode 100644 index 000000000..59a1b98b5 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-7x7-4bpp.png differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-8x8-4bpp.bmp b/image/test/reftest/bmp/bmp-4bpp/bmp-size-8x8-4bpp.bmp new file mode 100644 index 000000000..aa6959baf Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-8x8-4bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-8x8-4bpp.png b/image/test/reftest/bmp/bmp-4bpp/bmp-size-8x8-4bpp.png new file mode 100644 index 000000000..cf44f5967 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-8x8-4bpp.png differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-9x9-4bpp.bmp b/image/test/reftest/bmp/bmp-4bpp/bmp-size-9x9-4bpp.bmp new file mode 100644 index 000000000..65ec12a37 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-9x9-4bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-4bpp/bmp-size-9x9-4bpp.png b/image/test/reftest/bmp/bmp-4bpp/bmp-size-9x9-4bpp.png new file mode 100644 index 000000000..2e0736413 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/bmp-size-9x9-4bpp.png differ diff --git a/image/test/reftest/bmp/bmp-4bpp/os2bmp-size-32x32-4bpp.bmp b/image/test/reftest/bmp/bmp-4bpp/os2bmp-size-32x32-4bpp.bmp new file mode 100644 index 000000000..08fc30d5f Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/os2bmp-size-32x32-4bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-4bpp/reftest-stylo.list b/image/test/reftest/bmp/bmp-4bpp/reftest-stylo.list new file mode 100644 index 000000000..229f1c0d6 --- /dev/null +++ b/image/test/reftest/bmp/bmp-4bpp/reftest-stylo.list @@ -0,0 +1,25 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# BMP 4BPP tests + +# Images of various sizes +fails == bmp-size-1x1-4bpp.bmp bmp-size-1x1-4bpp.bmp +fails == bmp-size-2x2-4bpp.bmp bmp-size-2x2-4bpp.bmp +fails == bmp-size-3x3-4bpp.bmp bmp-size-3x3-4bpp.bmp +fails == bmp-size-4x4-4bpp.bmp bmp-size-4x4-4bpp.bmp +fails == bmp-size-5x5-4bpp.bmp bmp-size-5x5-4bpp.bmp +fails == bmp-size-6x6-4bpp.bmp bmp-size-6x6-4bpp.bmp +fails == bmp-size-7x7-4bpp.bmp bmp-size-7x7-4bpp.bmp +fails == bmp-size-8x8-4bpp.bmp bmp-size-8x8-4bpp.bmp +fails == bmp-size-9x9-4bpp.bmp bmp-size-9x9-4bpp.bmp +fails == bmp-size-15x15-4bpp.bmp bmp-size-15x15-4bpp.bmp +skip == bmp-size-16x16-4bpp.bmp bmp-size-16x16-4bpp.bmp +fails == bmp-size-17x17-4bpp.bmp bmp-size-17x17-4bpp.bmp +fails == bmp-size-31x31-4bpp.bmp bmp-size-31x31-4bpp.bmp +fails == bmp-size-32x32-4bpp.bmp bmp-size-32x32-4bpp.bmp +fails == bmp-size-33x33-4bpp.bmp bmp-size-33x33-4bpp.bmp +fails == bmp-not-square-4bpp.bmp bmp-not-square-4bpp.bmp +fails == os2bmp-size-32x32-4bpp.bmp os2bmp-size-32x32-4bpp.bmp +fails == top-to-bottom-16x16-4bpp.bmp top-to-bottom-16x16-4bpp.bmp +# test that delta skips are drawn as transparent +# taken from http://bmptestsuite.sourceforge.net/ +== rle4-delta-320x240.bmp rle4-delta-320x240.bmp diff --git a/image/test/reftest/bmp/bmp-4bpp/reftest.list b/image/test/reftest/bmp/bmp-4bpp/reftest.list new file mode 100644 index 000000000..4a1785e86 --- /dev/null +++ b/image/test/reftest/bmp/bmp-4bpp/reftest.list @@ -0,0 +1,24 @@ +# BMP 4BPP tests + +# Images of various sizes +== bmp-size-1x1-4bpp.bmp bmp-size-1x1-4bpp.png +== bmp-size-2x2-4bpp.bmp bmp-size-2x2-4bpp.png +== bmp-size-3x3-4bpp.bmp bmp-size-3x3-4bpp.png +== bmp-size-4x4-4bpp.bmp bmp-size-4x4-4bpp.png +== bmp-size-5x5-4bpp.bmp bmp-size-5x5-4bpp.png +== bmp-size-6x6-4bpp.bmp bmp-size-6x6-4bpp.png +== bmp-size-7x7-4bpp.bmp bmp-size-7x7-4bpp.png +== bmp-size-8x8-4bpp.bmp bmp-size-8x8-4bpp.png +== bmp-size-9x9-4bpp.bmp bmp-size-9x9-4bpp.png +== bmp-size-15x15-4bpp.bmp bmp-size-15x15-4bpp.png +== bmp-size-16x16-4bpp.bmp bmp-size-16x16-4bpp.png +== bmp-size-17x17-4bpp.bmp bmp-size-17x17-4bpp.png +== bmp-size-31x31-4bpp.bmp bmp-size-31x31-4bpp.png +== bmp-size-32x32-4bpp.bmp bmp-size-32x32-4bpp.png +== bmp-size-33x33-4bpp.bmp bmp-size-33x33-4bpp.png +== bmp-not-square-4bpp.bmp bmp-not-square-4bpp.png +== os2bmp-size-32x32-4bpp.bmp bmp-size-32x32-4bpp.png +== top-to-bottom-16x16-4bpp.bmp bmp-size-16x16-4bpp.png +# test that delta skips are drawn as transparent +# taken from http://bmptestsuite.sourceforge.net/ +== rle4-delta-320x240.bmp rle4-delta-320x240.png diff --git a/image/test/reftest/bmp/bmp-4bpp/rle4-delta-320x240.bmp b/image/test/reftest/bmp/bmp-4bpp/rle4-delta-320x240.bmp new file mode 100644 index 000000000..78a092787 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/rle4-delta-320x240.bmp differ diff --git a/image/test/reftest/bmp/bmp-4bpp/rle4-delta-320x240.png b/image/test/reftest/bmp/bmp-4bpp/rle4-delta-320x240.png new file mode 100644 index 000000000..f9a3ceae2 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/rle4-delta-320x240.png differ diff --git a/image/test/reftest/bmp/bmp-4bpp/top-to-bottom-16x16-4bpp.bmp b/image/test/reftest/bmp/bmp-4bpp/top-to-bottom-16x16-4bpp.bmp new file mode 100644 index 000000000..c77696b32 Binary files /dev/null and b/image/test/reftest/bmp/bmp-4bpp/top-to-bottom-16x16-4bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-not-square-8bpp.bmp b/image/test/reftest/bmp/bmp-8bpp/bmp-not-square-8bpp.bmp new file mode 100644 index 000000000..d7a99164c Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-not-square-8bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-not-square-8bpp.png b/image/test/reftest/bmp/bmp-8bpp/bmp-not-square-8bpp.png new file mode 100644 index 000000000..be45f19d5 Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-not-square-8bpp.png differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-15x15-8bpp.bmp b/image/test/reftest/bmp/bmp-8bpp/bmp-size-15x15-8bpp.bmp new file mode 100644 index 000000000..8dac8ec86 Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-15x15-8bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-15x15-8bpp.png b/image/test/reftest/bmp/bmp-8bpp/bmp-size-15x15-8bpp.png new file mode 100644 index 000000000..ce0055305 Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-15x15-8bpp.png differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-16x16-8bpp.bmp b/image/test/reftest/bmp/bmp-8bpp/bmp-size-16x16-8bpp.bmp new file mode 100644 index 000000000..bb60249ac Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-16x16-8bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-16x16-8bpp.png b/image/test/reftest/bmp/bmp-8bpp/bmp-size-16x16-8bpp.png new file mode 100644 index 000000000..6a2394618 Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-16x16-8bpp.png differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-17x17-8bpp.bmp b/image/test/reftest/bmp/bmp-8bpp/bmp-size-17x17-8bpp.bmp new file mode 100644 index 000000000..b81788851 Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-17x17-8bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-17x17-8bpp.png b/image/test/reftest/bmp/bmp-8bpp/bmp-size-17x17-8bpp.png new file mode 100644 index 000000000..494cd96cd Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-17x17-8bpp.png differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-1x1-8bpp.bmp b/image/test/reftest/bmp/bmp-8bpp/bmp-size-1x1-8bpp.bmp new file mode 100644 index 000000000..9f3ef5136 Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-1x1-8bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-1x1-8bpp.png b/image/test/reftest/bmp/bmp-8bpp/bmp-size-1x1-8bpp.png new file mode 100644 index 000000000..a7553a73f Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-1x1-8bpp.png differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-2x2-8bpp.bmp b/image/test/reftest/bmp/bmp-8bpp/bmp-size-2x2-8bpp.bmp new file mode 100644 index 000000000..63d3f1c05 Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-2x2-8bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-2x2-8bpp.png b/image/test/reftest/bmp/bmp-8bpp/bmp-size-2x2-8bpp.png new file mode 100644 index 000000000..17257e992 Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-2x2-8bpp.png differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-31x31-8bpp.bmp b/image/test/reftest/bmp/bmp-8bpp/bmp-size-31x31-8bpp.bmp new file mode 100644 index 000000000..e4fd01fe0 Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-31x31-8bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-31x31-8bpp.png b/image/test/reftest/bmp/bmp-8bpp/bmp-size-31x31-8bpp.png new file mode 100644 index 000000000..d43ac8390 Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-31x31-8bpp.png differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-32x32-8bpp.bmp b/image/test/reftest/bmp/bmp-8bpp/bmp-size-32x32-8bpp.bmp new file mode 100644 index 000000000..d2f800d67 Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-32x32-8bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-32x32-8bpp.png b/image/test/reftest/bmp/bmp-8bpp/bmp-size-32x32-8bpp.png new file mode 100644 index 000000000..03642849a Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-32x32-8bpp.png differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-33x33-8bpp.bmp b/image/test/reftest/bmp/bmp-8bpp/bmp-size-33x33-8bpp.bmp new file mode 100644 index 000000000..19b0744db Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-33x33-8bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-33x33-8bpp.png b/image/test/reftest/bmp/bmp-8bpp/bmp-size-33x33-8bpp.png new file mode 100644 index 000000000..078b56df0 Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-33x33-8bpp.png differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-3x3-8bpp.bmp b/image/test/reftest/bmp/bmp-8bpp/bmp-size-3x3-8bpp.bmp new file mode 100644 index 000000000..9f15f3583 Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-3x3-8bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-3x3-8bpp.png b/image/test/reftest/bmp/bmp-8bpp/bmp-size-3x3-8bpp.png new file mode 100644 index 000000000..ba34b2601 Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-3x3-8bpp.png differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-4x4-8bpp.bmp b/image/test/reftest/bmp/bmp-8bpp/bmp-size-4x4-8bpp.bmp new file mode 100644 index 000000000..1ad7a8de1 Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-4x4-8bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-4x4-8bpp.png b/image/test/reftest/bmp/bmp-8bpp/bmp-size-4x4-8bpp.png new file mode 100644 index 000000000..ecf9e5e79 Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-4x4-8bpp.png differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-5x5-8bpp.bmp b/image/test/reftest/bmp/bmp-8bpp/bmp-size-5x5-8bpp.bmp new file mode 100644 index 000000000..6eb759d06 Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-5x5-8bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-5x5-8bpp.png b/image/test/reftest/bmp/bmp-8bpp/bmp-size-5x5-8bpp.png new file mode 100644 index 000000000..1a440a16b Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-5x5-8bpp.png differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-6x6-8bpp.bmp b/image/test/reftest/bmp/bmp-8bpp/bmp-size-6x6-8bpp.bmp new file mode 100644 index 000000000..a1e0e2415 Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-6x6-8bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-6x6-8bpp.png b/image/test/reftest/bmp/bmp-8bpp/bmp-size-6x6-8bpp.png new file mode 100644 index 000000000..e0ac1a8f6 Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-6x6-8bpp.png differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-7x7-8bpp.bmp b/image/test/reftest/bmp/bmp-8bpp/bmp-size-7x7-8bpp.bmp new file mode 100644 index 000000000..25c59d735 Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-7x7-8bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-7x7-8bpp.png b/image/test/reftest/bmp/bmp-8bpp/bmp-size-7x7-8bpp.png new file mode 100644 index 000000000..51c764265 Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-7x7-8bpp.png differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-8x8-8bpp.bmp b/image/test/reftest/bmp/bmp-8bpp/bmp-size-8x8-8bpp.bmp new file mode 100644 index 000000000..ff5b7681c Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-8x8-8bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-8x8-8bpp.png b/image/test/reftest/bmp/bmp-8bpp/bmp-size-8x8-8bpp.png new file mode 100644 index 000000000..77dc7782e Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-8x8-8bpp.png differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-9x9-8bpp.bmp b/image/test/reftest/bmp/bmp-8bpp/bmp-size-9x9-8bpp.bmp new file mode 100644 index 000000000..006961628 Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-9x9-8bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-8bpp/bmp-size-9x9-8bpp.png b/image/test/reftest/bmp/bmp-8bpp/bmp-size-9x9-8bpp.png new file mode 100644 index 000000000..93914c3e1 Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/bmp-size-9x9-8bpp.png differ diff --git a/image/test/reftest/bmp/bmp-8bpp/os2-bmp-size-32x32-8bpp.bmp b/image/test/reftest/bmp/bmp-8bpp/os2-bmp-size-32x32-8bpp.bmp new file mode 100644 index 000000000..b6df221e1 Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/os2-bmp-size-32x32-8bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-8bpp/reftest-stylo.list b/image/test/reftest/bmp/bmp-8bpp/reftest-stylo.list new file mode 100644 index 000000000..237517976 --- /dev/null +++ b/image/test/reftest/bmp/bmp-8bpp/reftest-stylo.list @@ -0,0 +1,25 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# BMP 8BPP tests + +# Images of various sizes +fails == bmp-size-1x1-8bpp.bmp bmp-size-1x1-8bpp.bmp +fails == bmp-size-2x2-8bpp.bmp bmp-size-2x2-8bpp.bmp +fails == bmp-size-3x3-8bpp.bmp bmp-size-3x3-8bpp.bmp +fails == bmp-size-4x4-8bpp.bmp bmp-size-4x4-8bpp.bmp +fails == bmp-size-5x5-8bpp.bmp bmp-size-5x5-8bpp.bmp +fails == bmp-size-6x6-8bpp.bmp bmp-size-6x6-8bpp.bmp +fails == bmp-size-7x7-8bpp.bmp bmp-size-7x7-8bpp.bmp +fails == bmp-size-8x8-8bpp.bmp bmp-size-8x8-8bpp.bmp +fails == bmp-size-9x9-8bpp.bmp bmp-size-9x9-8bpp.bmp +fails == bmp-size-15x15-8bpp.bmp bmp-size-15x15-8bpp.bmp +fails == bmp-size-16x16-8bpp.bmp bmp-size-16x16-8bpp.bmp +fails == bmp-size-17x17-8bpp.bmp bmp-size-17x17-8bpp.bmp +fails == bmp-size-31x31-8bpp.bmp bmp-size-31x31-8bpp.bmp +fails == bmp-size-32x32-8bpp.bmp bmp-size-32x32-8bpp.bmp +fails == bmp-size-33x33-8bpp.bmp bmp-size-33x33-8bpp.bmp +fails == bmp-not-square-8bpp.bmp bmp-not-square-8bpp.bmp +random == rle-bmp-not-square-8bpp.bmp rle-bmp-not-square-8bpp.bmp +fails == os2-bmp-size-32x32-8bpp.bmp os2-bmp-size-32x32-8bpp.bmp +random == rle-bmp-size-32x32-8bpp.bmp rle-bmp-size-32x32-8bpp.bmp +== top-to-bottom-rle-bmp-size-32x32-8bpp.bmp top-to-bottom-rle-bmp-size-32x32-8bpp.bmp +fails == top-to-bottom-16x16-8bpp.bmp top-to-bottom-16x16-8bpp.bmp diff --git a/image/test/reftest/bmp/bmp-8bpp/reftest.list b/image/test/reftest/bmp/bmp-8bpp/reftest.list new file mode 100644 index 000000000..c1f264143 --- /dev/null +++ b/image/test/reftest/bmp/bmp-8bpp/reftest.list @@ -0,0 +1,24 @@ +# BMP 8BPP tests + +# Images of various sizes +== bmp-size-1x1-8bpp.bmp bmp-size-1x1-8bpp.png +== bmp-size-2x2-8bpp.bmp bmp-size-2x2-8bpp.png +== bmp-size-3x3-8bpp.bmp bmp-size-3x3-8bpp.png +== bmp-size-4x4-8bpp.bmp bmp-size-4x4-8bpp.png +== bmp-size-5x5-8bpp.bmp bmp-size-5x5-8bpp.png +== bmp-size-6x6-8bpp.bmp bmp-size-6x6-8bpp.png +== bmp-size-7x7-8bpp.bmp bmp-size-7x7-8bpp.png +== bmp-size-8x8-8bpp.bmp bmp-size-8x8-8bpp.png +== bmp-size-9x9-8bpp.bmp bmp-size-9x9-8bpp.png +== bmp-size-15x15-8bpp.bmp bmp-size-15x15-8bpp.png +== bmp-size-16x16-8bpp.bmp bmp-size-16x16-8bpp.png +== bmp-size-17x17-8bpp.bmp bmp-size-17x17-8bpp.png +== bmp-size-31x31-8bpp.bmp bmp-size-31x31-8bpp.png +== bmp-size-32x32-8bpp.bmp bmp-size-32x32-8bpp.png +== bmp-size-33x33-8bpp.bmp bmp-size-33x33-8bpp.png +== bmp-not-square-8bpp.bmp bmp-not-square-8bpp.png +== rle-bmp-not-square-8bpp.bmp bmp-not-square-8bpp.png +== os2-bmp-size-32x32-8bpp.bmp bmp-size-32x32-8bpp.png +== rle-bmp-size-32x32-8bpp.bmp bmp-size-32x32-8bpp.png +== top-to-bottom-rle-bmp-size-32x32-8bpp.bmp bmp-size-32x32-8bpp.png +== top-to-bottom-16x16-8bpp.bmp bmp-size-16x16-8bpp.png diff --git a/image/test/reftest/bmp/bmp-8bpp/rle-bmp-not-square-8bpp.bmp b/image/test/reftest/bmp/bmp-8bpp/rle-bmp-not-square-8bpp.bmp new file mode 100644 index 000000000..8687aab6c Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/rle-bmp-not-square-8bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-8bpp/rle-bmp-size-32x32-8bpp.bmp b/image/test/reftest/bmp/bmp-8bpp/rle-bmp-size-32x32-8bpp.bmp new file mode 100644 index 000000000..bd793b6b6 Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/rle-bmp-size-32x32-8bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-8bpp/top-to-bottom-16x16-8bpp.bmp b/image/test/reftest/bmp/bmp-8bpp/top-to-bottom-16x16-8bpp.bmp new file mode 100644 index 000000000..bb60249ac Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/top-to-bottom-16x16-8bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-8bpp/top-to-bottom-rle-bmp-size-32x32-8bpp.bmp b/image/test/reftest/bmp/bmp-8bpp/top-to-bottom-rle-bmp-size-32x32-8bpp.bmp new file mode 100644 index 000000000..396672ea1 Binary files /dev/null and b/image/test/reftest/bmp/bmp-8bpp/top-to-bottom-rle-bmp-size-32x32-8bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-corrupted/invalid-bpp.bmp b/image/test/reftest/bmp/bmp-corrupted/invalid-bpp.bmp new file mode 100644 index 000000000..c00dd3fa4 Binary files /dev/null and b/image/test/reftest/bmp/bmp-corrupted/invalid-bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-corrupted/invalid-compression-BITFIELDS.bmp b/image/test/reftest/bmp/bmp-corrupted/invalid-compression-BITFIELDS.bmp new file mode 100644 index 000000000..92a722526 Binary files /dev/null and b/image/test/reftest/bmp/bmp-corrupted/invalid-compression-BITFIELDS.bmp differ diff --git a/image/test/reftest/bmp/bmp-corrupted/invalid-compression-RLE4.bmp b/image/test/reftest/bmp/bmp-corrupted/invalid-compression-RLE4.bmp new file mode 100644 index 000000000..d73c89411 Binary files /dev/null and b/image/test/reftest/bmp/bmp-corrupted/invalid-compression-RLE4.bmp differ diff --git a/image/test/reftest/bmp/bmp-corrupted/invalid-compression-RLE8.bmp b/image/test/reftest/bmp/bmp-corrupted/invalid-compression-RLE8.bmp new file mode 100644 index 000000000..5a8806454 Binary files /dev/null and b/image/test/reftest/bmp/bmp-corrupted/invalid-compression-RLE8.bmp differ diff --git a/image/test/reftest/bmp/bmp-corrupted/invalid-compression.bmp b/image/test/reftest/bmp/bmp-corrupted/invalid-compression.bmp new file mode 100644 index 000000000..aa3134e56 Binary files /dev/null and b/image/test/reftest/bmp/bmp-corrupted/invalid-compression.bmp differ diff --git a/image/test/reftest/bmp/bmp-corrupted/invalid-signature.bmp b/image/test/reftest/bmp/bmp-corrupted/invalid-signature.bmp new file mode 100644 index 000000000..6eebb5718 Binary files /dev/null and b/image/test/reftest/bmp/bmp-corrupted/invalid-signature.bmp differ diff --git a/image/test/reftest/bmp/bmp-corrupted/invalid-truncated-metadata.bmp b/image/test/reftest/bmp/bmp-corrupted/invalid-truncated-metadata.bmp new file mode 100644 index 000000000..228c5c999 Binary files /dev/null and b/image/test/reftest/bmp/bmp-corrupted/invalid-truncated-metadata.bmp differ diff --git a/image/test/reftest/bmp/bmp-corrupted/os2-invalid-bpp.bmp b/image/test/reftest/bmp/bmp-corrupted/os2-invalid-bpp.bmp new file mode 100644 index 000000000..af4678a28 Binary files /dev/null and b/image/test/reftest/bmp/bmp-corrupted/os2-invalid-bpp.bmp differ diff --git a/image/test/reftest/bmp/bmp-corrupted/reftest-stylo.list b/image/test/reftest/bmp/bmp-corrupted/reftest-stylo.list new file mode 100644 index 000000000..bb776a7af --- /dev/null +++ b/image/test/reftest/bmp/bmp-corrupted/reftest-stylo.list @@ -0,0 +1,19 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# Corrupted BMP tests + +skip == wrapper.html?invalid-signature.bmp wrapper.html?invalid-signature.bmp +skip == wrapper.html?invalid-bpp.bmp wrapper.html?invalid-bpp.bmp +skip == wrapper.html?os2-invalid-bpp.bmp wrapper.html?os2-invalid-bpp.bmp +# Tests for an unsupported compression value +skip == wrapper.html?invalid-compression.bmp wrapper.html?invalid-compression.bmp +# Tests for RLE4 with an invalid BPP +skip == wrapper.html?invalid-compression-RLE4.bmp wrapper.html?invalid-compression-RLE4.bmp +# Tests for RLE8 with an invalid BPP +skip == wrapper.html?invalid-compression-RLE8.bmp wrapper.html?invalid-compression-RLE8.bmp + +# Test for BITFIELDS with an invalid BIH size. (This is the obscure +# BITMAPV3INFOHEADER variant mentioned in +# https://en.wikipedia.org/wiki/BMP_file_format which we don't accept.) +skip == wrapper.html?invalid-compression-BITFIELDS.bmp wrapper.html?invalid-compression-BITFIELDS.bmp + +skip == wrapper.html?invalid-truncated-metadata.bmp wrapper.html?invalid-truncated-metadata.bmp diff --git a/image/test/reftest/bmp/bmp-corrupted/reftest.list b/image/test/reftest/bmp/bmp-corrupted/reftest.list new file mode 100644 index 000000000..be73e2cf3 --- /dev/null +++ b/image/test/reftest/bmp/bmp-corrupted/reftest.list @@ -0,0 +1,18 @@ +# Corrupted BMP tests + +== wrapper.html?invalid-signature.bmp about:blank +== wrapper.html?invalid-bpp.bmp about:blank +== wrapper.html?os2-invalid-bpp.bmp about:blank +# Tests for an unsupported compression value +== wrapper.html?invalid-compression.bmp about:blank +# Tests for RLE4 with an invalid BPP +== wrapper.html?invalid-compression-RLE4.bmp about:blank +# Tests for RLE8 with an invalid BPP +== wrapper.html?invalid-compression-RLE8.bmp about:blank + +# Test for BITFIELDS with an invalid BIH size. (This is the obscure +# BITMAPV3INFOHEADER variant mentioned in +# https://en.wikipedia.org/wiki/BMP_file_format which we don't accept.) +== wrapper.html?invalid-compression-BITFIELDS.bmp about:blank + +== wrapper.html?invalid-truncated-metadata.bmp about:blank diff --git a/image/test/reftest/bmp/bmp-corrupted/wrapper.html b/image/test/reftest/bmp/bmp-corrupted/wrapper.html new file mode 100644 index 000000000..47e68959f --- /dev/null +++ b/image/test/reftest/bmp/bmp-corrupted/wrapper.html @@ -0,0 +1,28 @@ + + + +Image reftest wrapper + + + + + + + + + diff --git a/image/test/reftest/bmp/bmpsuite/COPYING.txt b/image/test/reftest/bmp/bmpsuite/COPYING.txt new file mode 100644 index 000000000..10926e87f --- /dev/null +++ b/image/test/reftest/bmp/bmpsuite/COPYING.txt @@ -0,0 +1,675 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + diff --git a/image/test/reftest/bmp/bmpsuite/README.mozilla b/image/test/reftest/bmp/bmpsuite/README.mozilla new file mode 100644 index 000000000..87d185e89 --- /dev/null +++ b/image/test/reftest/bmp/bmpsuite/README.mozilla @@ -0,0 +1,39 @@ +bmpsuite, by Jason Summers, is an excellent BMP test suite that covers many +obscure corners of the BMP format. All the test images can be seen here: + + http://entropymine.com/jason/bmpsuite/bmpsuite/html/bmpsuite.html + +The code used to generate the test images is available here: + + https://github.com/jsummers/bmpsuite/ + +The readme.txt file states that the code is GPLv3 and the generated image files +are in the public domain. We have not included the code, but we have included: +(a) some quotes from the documentation and (b) some of the reference PNG +images. However, (a) and (b) are for testing purposes only and are not included +in Firefox releases. + +The BMP files within this directory were generated with bmpsuite v2.3 (git +revision 3adcc9e20c0b6d2d665966b7e047b6f9474cef12). + +There are three sub-directories. +- g/: for "good" images. +- q/: for "questionable" images. +- b/: for "bad" images. + +Each file listed in a reftest.list file is annotated with the following lines. + +- The first line starts with "BMP:" and is the output of the MOZ_LOG call in + nsBMPDecoder.cpp. It has basic image info. + +- Next is a quote from the bmpsuite docs, which describes the particulars of + the file. + +- Some files also have additional notes in square brackets. These explain + anything non-obvious about the file, such as how we handle things that are + ambiguous, any shortcomings in our decoding, and how Chromium handles the + image. + +Some of the reference PNGs need a small amount of fuzziness to match the BMPs. +This might be due to PNG color correction. + diff --git a/image/test/reftest/bmp/bmpsuite/b/badbitcount.bmp b/image/test/reftest/bmp/bmpsuite/b/badbitcount.bmp new file mode 100644 index 000000000..d4fa4e8b8 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/b/badbitcount.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/b/badbitssize.bmp b/image/test/reftest/bmp/bmpsuite/b/badbitssize.bmp new file mode 100644 index 000000000..0a99a605a Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/b/badbitssize.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/b/baddens1.bmp b/image/test/reftest/bmp/bmpsuite/b/baddens1.bmp new file mode 100644 index 000000000..a6150a6fe Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/b/baddens1.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/b/baddens2.bmp b/image/test/reftest/bmp/bmpsuite/b/baddens2.bmp new file mode 100644 index 000000000..f2c1dfb66 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/b/baddens2.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/b/badfilesize.bmp b/image/test/reftest/bmp/bmpsuite/b/badfilesize.bmp new file mode 100644 index 000000000..da52cb51d Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/b/badfilesize.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/b/badheadersize.bmp b/image/test/reftest/bmp/bmpsuite/b/badheadersize.bmp new file mode 100644 index 000000000..2a4083a6f Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/b/badheadersize.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/b/badpalettesize.bmp b/image/test/reftest/bmp/bmpsuite/b/badpalettesize.bmp new file mode 100644 index 000000000..7d9d1b745 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/b/badpalettesize.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/b/badplanes.bmp b/image/test/reftest/bmp/bmpsuite/b/badplanes.bmp new file mode 100644 index 000000000..92d2855b6 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/b/badplanes.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/b/badrle.bmp b/image/test/reftest/bmp/bmpsuite/b/badrle.bmp new file mode 100644 index 000000000..cbf8fdc2e Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/b/badrle.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/b/badrle.png b/image/test/reftest/bmp/bmpsuite/b/badrle.png new file mode 100644 index 000000000..1764ef9f9 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/b/badrle.png differ diff --git a/image/test/reftest/bmp/bmpsuite/b/badwidth.bmp b/image/test/reftest/bmp/bmpsuite/b/badwidth.bmp new file mode 100644 index 000000000..9fca005dc Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/b/badwidth.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/b/pal1.png b/image/test/reftest/bmp/bmpsuite/b/pal1.png new file mode 100644 index 000000000..89a433ed7 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/b/pal1.png differ diff --git a/image/test/reftest/bmp/bmpsuite/b/pal8.png b/image/test/reftest/bmp/bmpsuite/b/pal8.png new file mode 100644 index 000000000..2bfd3e650 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/b/pal8.png differ diff --git a/image/test/reftest/bmp/bmpsuite/b/pal8badindex.bmp b/image/test/reftest/bmp/bmpsuite/b/pal8badindex.bmp new file mode 100644 index 000000000..efe16c05c Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/b/pal8badindex.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/b/pal8badindex.png b/image/test/reftest/bmp/bmpsuite/b/pal8badindex.png new file mode 100644 index 000000000..0efb392b9 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/b/pal8badindex.png differ diff --git a/image/test/reftest/bmp/bmpsuite/b/reallybig.bmp b/image/test/reftest/bmp/bmpsuite/b/reallybig.bmp new file mode 100644 index 000000000..101e0b494 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/b/reallybig.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/b/reftest-stylo.list b/image/test/reftest/bmp/bmpsuite/b/reftest-stylo.list new file mode 100644 index 000000000..244d80cb4 --- /dev/null +++ b/image/test/reftest/bmp/bmpsuite/b/reftest-stylo.list @@ -0,0 +1,85 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# bmpsuite "bad" tests + +# See ../README.mozilla for details. + +# BMP: bihsize=40, 127 x 64, bpp=30000, compression=0, colors=2 +# "Header indicates an absurdly large number of bits/pixel." +# [We reject it. So does Chromium.] +skip == wrapper.html?badbitcount.bmp wrapper.html?badbitcount.bmp + +# BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2 +# "Header incorrectly indicates that the bitmap is several GB in size." +# [We accept it. So does Chromium.] +fails == badbitssize.bmp badbitssize.bmp + +# BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2 +# BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2 +# "Density (pixels per meter) suggests the image is much larger in one +# dimension than the other." +# [We accept them. So does Chromium.] +fails == baddens1.bmp baddens1.bmp +fails == baddens2.bmp baddens2.bmp + +# BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2 +# "Header incorrectly indicates that the file is several GB in size." +# [We accept it. So does Chromium.] +fails == badfilesize.bmp badfilesize.bmp + +# BMP: +# "Header size is 66 bytes, which is not a valid size for any known BMP +# version." +# [We reject it. So does Chromium.] +skip == wrapper.html?badheadersize.bmp wrapper.html?badheadersize.bmp + +# BMP: bihsize=40, 127 x 64, bpp=8, compression=0, colors=305402420 +# "Header incorrectly indicates that the palette contains an absurdly large +# number of colors." +# [We reject it. Chromium accepts it but draws nothing. Rejecting seems +# preferable give that the data is clearly untrustworthy.] +skip == wrapper.html?badpalettesize.bmp wrapper.html?badpalettesize.bmp + +# BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2 +# "The 'planes' setting, which is required to be 1, is not 1." +# [We accept it. So does Chromium.] +fails == badplanes.bmp badplanes.bmp + +# BMP: bihsize=40, 127 x 64, bpp=8, compression=1, colors=253 +# "An invalid RLE-compressed image that tries to cause buffer overruns." +# [We accept it, drawing the valid first part and leaving the rest black. +# Chromium accepts it, drawing the valid first part and leaving the rest +# transparent. Using black for the invalid part is arguably better because it +# makes the image edges more obvious.] +== badrle.bmp badrle.bmp + +# BMP: bihsize=40, -127 x 64, bpp=1, compression=0, colors=2 +# "The image claims to be a negative number of pixels in width." +# [We reject it. So does Chromium.] +skip == wrapper.html?badwidth.bmp wrapper.html?badwidth.bmp + +# BMP: bihsize=40, 127 x 64, bpp=8, compression=0, colors=101 +# "Many of the palette indices used in the image are not present in the +# palette." +# [We accept it and use black for the missing colors. So does Chromium.] +fails == pal8badindex.bmp pal8badindex.bmp + +# BMP: bihsize=40, 3000000 x 2000000, bpp=24, compression=0, colors=0 +# "An image with a very large reported width and height." +# [We reject it. So does Chromium.] +skip == wrapper.html?reallybig.bmp wrapper.html?reallybig.bmp + +# BMP: bihsize=40, 127 x -64, bpp=8, compression=1, colors=252 +# "An RLE-compressed image that tries to use top-down orientation, which isn’t +# allowed." +# [We accept it. Chromium rejects it. Accepting seems better given that we can +# decode it perfectly well.] +== rletopdown.bmp rletopdown.bmp + +# BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2 +# "A file that has been truncated in the middle of the bitmap." +# [We accept it, drawing the part that is present and leaving the rest black. +# Chromium draws the part that is present and leaves the rest transparent. +# Using black for the invalid part is arguably better because it makes the +# image edges more obvious.] +fails == shortfile.bmp shortfile.bmp + diff --git a/image/test/reftest/bmp/bmpsuite/b/reftest.list b/image/test/reftest/bmp/bmpsuite/b/reftest.list new file mode 100644 index 000000000..2d8ef5c75 --- /dev/null +++ b/image/test/reftest/bmp/bmpsuite/b/reftest.list @@ -0,0 +1,84 @@ +# bmpsuite "bad" tests + +# See ../README.mozilla for details. + +# BMP: bihsize=40, 127 x 64, bpp=30000, compression=0, colors=2 +# "Header indicates an absurdly large number of bits/pixel." +# [We reject it. So does Chromium.] +== wrapper.html?badbitcount.bmp about:blank + +# BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2 +# "Header incorrectly indicates that the bitmap is several GB in size." +# [We accept it. So does Chromium.] +== badbitssize.bmp pal1.png + +# BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2 +# BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2 +# "Density (pixels per meter) suggests the image is much larger in one +# dimension than the other." +# [We accept them. So does Chromium.] +== baddens1.bmp pal1.png +== baddens2.bmp pal1.png + +# BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2 +# "Header incorrectly indicates that the file is several GB in size." +# [We accept it. So does Chromium.] +== badfilesize.bmp pal1.png + +# BMP: +# "Header size is 66 bytes, which is not a valid size for any known BMP +# version." +# [We reject it. So does Chromium.] +== wrapper.html?badheadersize.bmp about:blank + +# BMP: bihsize=40, 127 x 64, bpp=8, compression=0, colors=305402420 +# "Header incorrectly indicates that the palette contains an absurdly large +# number of colors." +# [We reject it. Chromium accepts it but draws nothing. Rejecting seems +# preferable give that the data is clearly untrustworthy.] +== wrapper.html?badpalettesize.bmp about:blank + +# BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2 +# "The 'planes' setting, which is required to be 1, is not 1." +# [We accept it. So does Chromium.] +== badplanes.bmp pal1.png + +# BMP: bihsize=40, 127 x 64, bpp=8, compression=1, colors=253 +# "An invalid RLE-compressed image that tries to cause buffer overruns." +# [We accept it, drawing the valid first part and leaving the rest black. +# Chromium accepts it, drawing the valid first part and leaving the rest +# transparent. Using black for the invalid part is arguably better because it +# makes the image edges more obvious.] +== badrle.bmp badrle.png + +# BMP: bihsize=40, -127 x 64, bpp=1, compression=0, colors=2 +# "The image claims to be a negative number of pixels in width." +# [We reject it. So does Chromium.] +== wrapper.html?badwidth.bmp about:blank + +# BMP: bihsize=40, 127 x 64, bpp=8, compression=0, colors=101 +# "Many of the palette indices used in the image are not present in the +# palette." +# [We accept it and use black for the missing colors. So does Chromium.] +== pal8badindex.bmp pal8badindex.png + +# BMP: bihsize=40, 3000000 x 2000000, bpp=24, compression=0, colors=0 +# "An image with a very large reported width and height." +# [We reject it. So does Chromium.] +== wrapper.html?reallybig.bmp about:blank + +# BMP: bihsize=40, 127 x -64, bpp=8, compression=1, colors=252 +# "An RLE-compressed image that tries to use top-down orientation, which isn’t +# allowed." +# [We accept it. Chromium rejects it. Accepting seems better given that we can +# decode it perfectly well.] +fuzzy(1,899) == rletopdown.bmp pal8.png + +# BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2 +# "A file that has been truncated in the middle of the bitmap." +# [We accept it, drawing the part that is present and leaving the rest black. +# Chromium draws the part that is present and leaves the rest transparent. +# Using black for the invalid part is arguably better because it makes the +# image edges more obvious.] +== shortfile.bmp shortfile.png + diff --git a/image/test/reftest/bmp/bmpsuite/b/rletopdown.bmp b/image/test/reftest/bmp/bmpsuite/b/rletopdown.bmp new file mode 100644 index 000000000..21a909fda Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/b/rletopdown.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/b/shortfile.bmp b/image/test/reftest/bmp/bmpsuite/b/shortfile.bmp new file mode 100644 index 000000000..73960797b Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/b/shortfile.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/b/shortfile.png b/image/test/reftest/bmp/bmpsuite/b/shortfile.png new file mode 100644 index 000000000..0ec21d929 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/b/shortfile.png differ diff --git a/image/test/reftest/bmp/bmpsuite/b/wrapper.html b/image/test/reftest/bmp/bmpsuite/b/wrapper.html new file mode 100644 index 000000000..47e68959f --- /dev/null +++ b/image/test/reftest/bmp/bmpsuite/b/wrapper.html @@ -0,0 +1,28 @@ + + + +Image reftest wrapper + + + + + + + + + diff --git a/image/test/reftest/bmp/bmpsuite/g/pal1.bmp b/image/test/reftest/bmp/bmpsuite/g/pal1.bmp new file mode 100644 index 000000000..4776f8277 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/pal1.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/g/pal1.png b/image/test/reftest/bmp/bmpsuite/g/pal1.png new file mode 100644 index 000000000..89a433ed7 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/pal1.png differ diff --git a/image/test/reftest/bmp/bmpsuite/g/pal1bg.bmp b/image/test/reftest/bmp/bmpsuite/g/pal1bg.bmp new file mode 100644 index 000000000..466d0ba72 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/pal1bg.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/g/pal1bg.png b/image/test/reftest/bmp/bmpsuite/g/pal1bg.png new file mode 100644 index 000000000..20c4bb838 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/pal1bg.png differ diff --git a/image/test/reftest/bmp/bmpsuite/g/pal1wb.bmp b/image/test/reftest/bmp/bmpsuite/g/pal1wb.bmp new file mode 100644 index 000000000..56cb93203 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/pal1wb.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/g/pal4.bmp b/image/test/reftest/bmp/bmpsuite/g/pal4.bmp new file mode 100644 index 000000000..7fd36303c Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/pal4.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/g/pal4.png b/image/test/reftest/bmp/bmpsuite/g/pal4.png new file mode 100644 index 000000000..188bb0499 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/pal4.png differ diff --git a/image/test/reftest/bmp/bmpsuite/g/pal4rle.bmp b/image/test/reftest/bmp/bmpsuite/g/pal4rle.bmp new file mode 100644 index 000000000..a5672aebd Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/pal4rle.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/g/pal8-0.bmp b/image/test/reftest/bmp/bmpsuite/g/pal8-0.bmp new file mode 100644 index 000000000..ab8815a36 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/pal8-0.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/g/pal8.bmp b/image/test/reftest/bmp/bmpsuite/g/pal8.bmp new file mode 100644 index 000000000..96b2f8668 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/pal8.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/g/pal8.png b/image/test/reftest/bmp/bmpsuite/g/pal8.png new file mode 100644 index 000000000..2bfd3e650 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/pal8.png differ diff --git a/image/test/reftest/bmp/bmpsuite/g/pal8nonsquare-e.png b/image/test/reftest/bmp/bmpsuite/g/pal8nonsquare-e.png new file mode 100644 index 000000000..646665f2d Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/pal8nonsquare-e.png differ diff --git a/image/test/reftest/bmp/bmpsuite/g/pal8nonsquare.bmp b/image/test/reftest/bmp/bmpsuite/g/pal8nonsquare.bmp new file mode 100644 index 000000000..0aa8de04c Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/pal8nonsquare.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/g/pal8nonsquare.png b/image/test/reftest/bmp/bmpsuite/g/pal8nonsquare.png new file mode 100644 index 000000000..9648cb682 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/pal8nonsquare.png differ diff --git a/image/test/reftest/bmp/bmpsuite/g/pal8os2.bmp b/image/test/reftest/bmp/bmpsuite/g/pal8os2.bmp new file mode 100644 index 000000000..14901b388 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/pal8os2.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/g/pal8rle.bmp b/image/test/reftest/bmp/bmpsuite/g/pal8rle.bmp new file mode 100644 index 000000000..d43101490 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/pal8rle.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/g/pal8topdown.bmp b/image/test/reftest/bmp/bmpsuite/g/pal8topdown.bmp new file mode 100644 index 000000000..4b2f8e019 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/pal8topdown.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/g/pal8v4.bmp b/image/test/reftest/bmp/bmpsuite/g/pal8v4.bmp new file mode 100644 index 000000000..7064be315 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/pal8v4.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/g/pal8v5.bmp b/image/test/reftest/bmp/bmpsuite/g/pal8v5.bmp new file mode 100644 index 000000000..c54647a31 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/pal8v5.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/g/pal8w124.bmp b/image/test/reftest/bmp/bmpsuite/g/pal8w124.bmp new file mode 100644 index 000000000..b7cc2d8bf Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/pal8w124.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/g/pal8w124.png b/image/test/reftest/bmp/bmpsuite/g/pal8w124.png new file mode 100644 index 000000000..f80236df6 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/pal8w124.png differ diff --git a/image/test/reftest/bmp/bmpsuite/g/pal8w125.bmp b/image/test/reftest/bmp/bmpsuite/g/pal8w125.bmp new file mode 100644 index 000000000..06efed744 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/pal8w125.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/g/pal8w125.png b/image/test/reftest/bmp/bmpsuite/g/pal8w125.png new file mode 100644 index 000000000..2a45116b9 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/pal8w125.png differ diff --git a/image/test/reftest/bmp/bmpsuite/g/pal8w126.bmp b/image/test/reftest/bmp/bmpsuite/g/pal8w126.bmp new file mode 100644 index 000000000..112aa9fe6 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/pal8w126.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/g/pal8w126.png b/image/test/reftest/bmp/bmpsuite/g/pal8w126.png new file mode 100644 index 000000000..a41eab93d Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/pal8w126.png differ diff --git a/image/test/reftest/bmp/bmpsuite/g/reftest-stylo.list b/image/test/reftest/bmp/bmpsuite/g/reftest-stylo.list new file mode 100644 index 000000000..ba8a53b4f --- /dev/null +++ b/image/test/reftest/bmp/bmpsuite/g/reftest-stylo.list @@ -0,0 +1,113 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# bmpsuite "good" tests + +# See ../README.mozilla for details. + +# BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2 +# "1 bit/pixel paletted image, in which black is the first color in the +# palette." +fails == pal1.bmp pal1.bmp + +# BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2 +# "1 bit/pixel paletted image, in which white is the first color in the +# palette." +fails == pal1wb.bmp pal1wb.bmp + +# BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2 +# "1 bit/pixel paletted image, with colors other than black and white." +fails == pal1bg.bmp pal1bg.bmp + +# BMP: bihsize=40, 127 x 64, bpp=4, compression=0, colors=12 +# "Paletted image with 12 palette colors, and 4 bits/pixel." +fails == pal4.bmp pal4.bmp + +# BMP: bihsize=40, 127 x 64, bpp=4, compression=2, colors=12 +# "4-bit image that uses RLE compression." +== pal4rle.bmp pal4rle.bmp + +# BMP: bihsize=40, 127 x 64, bpp=8, compression=0, colors=252 +# "Our standard paletted image, with 252 palette colors, and 8 bits/pixel." +fails == pal8.bmp pal8.bmp + +# BMP: bihsize=40, 127 x 64, bpp=8, compression=0, colors=0 +# "Every field that can be set to 0 is set to 0: pixels/meter=0; colors used=0 +# (meaning the default 256); size-of-image=0." +fails == pal8-0.bmp pal8-0.bmp + +# BMP: bihsize=40, 127 x 64, bpp=8, compression=1, colors=252 +# "8-bit image that uses RLE compression." +== pal8rle.bmp pal8rle.bmp + +# BMP: bihsize=40, 126 x 63, bpp=8, compression=0, colors=252 +# BMP: bihsize=40, 125 x 62, bpp=8, compression=0, colors=252 +# BMP: bihsize=40, 124 x 61, bpp=8, compression=0, colors=252 +# "Images with different widths and heights. In BMP format, rows are padded to +# a multiple of four bytes, so we test all four possibilities." +fails == pal8w126.bmp pal8w126.bmp +fails == pal8w125.bmp pal8w125.bmp +fails == pal8w124.bmp pal8w124.bmp + +# BMP: bihsize=40, 127 x -64, bpp=8, compression=0, colors=252 +# "BMP images are normally stored from the bottom up, but there is a way to +# store them from the top down." +fails == pal8topdown.bmp pal8topdown.bmp + +# BMP: bihsize=40, 127 x 32, bpp=8, compression=0, colors=252 +# "An image with non-square pixels: the X pixels/meter is twice the Y +# pixels/meter. Image editors can be expected to leave the image 'squashed'; +# image viewers should consider stretching it to its correct proportions." +# [We leave it squashed, as does Chromium.] +fails == pal8nonsquare.bmp pal8nonsquare.bmp + +# BMP: bihsize=12, 127 x 64, bpp=8, compression=0, colors=0 +# "An OS/2-style bitmap." +fails == pal8os2.bmp pal8os2.bmp + +# BMP: bihsize=108, 127 x 64, bpp=8, compression=0, colors=252 +# "A v4 bitmap. I’m not sure that the gamma and chromaticity values in this +# file are sensible, because I can’t find any detailed documentation of them." +fails == pal8v4.bmp pal8v4.bmp + +# BMP: bihsize=124, 127 x 64, bpp=8, compression=0, colors=252 +# "A v5 bitmap. Version 5 has additional colorspace options over v4, so it is +# easier to create, and ought to be more portable." +fails == pal8v5.bmp pal8v5.bmp + +# BMP: bihsize=40, 127 x 64, bpp=16, compression=0, colors=0 +# "A 16-bit image with the default color format: 5 bits each for red, green, and +# blue, and 1 unused bit. The whitest colors should (I assume) be displayed as +# pure white: (255,255,255), not (248,248,248)." +fails == rgb16.bmp rgb16.bmp + +# BMP: bihsize=40, 127 x 64, bpp=16, compression=3, colors=0 +# "A 16-bit image with a BITFIELDS segment indicating 5 red, 6 green, and 5 blue +# bits. This is a standard 16-bit format, even supported by old versions of +# Windows that don’t support any other non-default 16-bit formats. The whitest +# colors should be displayed as pure white: (255,255,255), not (248,252,248)." +== rgb16.bmp rgb16.bmp + +# BMP: bihsize=40, 127 x 64, bpp=16, compression=3, colors=256 +# "A 16-bit image with both a BITFIELDS segment and a palette." +== rgb16.bmp rgb16.bmp + +# BMP: bihsize=40, 127 x 64, bpp=24, compression=0, colors=0 +# "A perfectly ordinary 24-bit (truecolor) image." +fails == rgb24.bmp rgb24.bmp + +# BMP: bihsize=40, 127 x 64, bpp=24, compression=0, colors=256 +# "A 24-bit image, with a palette containing 256 colors. There is little if any +# reason for a truecolor image to contain a palette, but it is legal." +fails == rgb24pal.bmp rgb24pal.bmp + +# BMP: bihsize=40, 127 x 64, bpp=32, compression=0, colors=0 +# "A 32-bit image using the default color format for 32-bit images (no +# BITFIELDS segment). There are 8 bits per color channel, and 8 unused bits. +# The unused bits are set to 0." +skip == rgb32.bmp rgb32.bmp + +# BMP: bihsize=40, 127 x 64, bpp=32, compression=3, colors=0 +# "A 32-bit image with a BITFIELDS segment. As usual, there are 8 bits per color +# channel, and 8 unused bits. But the color channels are in an unusual order, +# so the viewer must read the BITFIELDS, and not just guess." +fails == rgb32bf.bmp rgb32bf.bmp + diff --git a/image/test/reftest/bmp/bmpsuite/g/reftest.list b/image/test/reftest/bmp/bmpsuite/g/reftest.list new file mode 100644 index 000000000..9715b1ac8 --- /dev/null +++ b/image/test/reftest/bmp/bmpsuite/g/reftest.list @@ -0,0 +1,112 @@ +# bmpsuite "good" tests + +# See ../README.mozilla for details. + +# BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2 +# "1 bit/pixel paletted image, in which black is the first color in the +# palette." +== pal1.bmp pal1.png + +# BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2 +# "1 bit/pixel paletted image, in which white is the first color in the +# palette." +== pal1wb.bmp pal1.png + +# BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2 +# "1 bit/pixel paletted image, with colors other than black and white." +== pal1bg.bmp pal1bg.png + +# BMP: bihsize=40, 127 x 64, bpp=4, compression=0, colors=12 +# "Paletted image with 12 palette colors, and 4 bits/pixel." +== pal4.bmp pal4.png + +# BMP: bihsize=40, 127 x 64, bpp=4, compression=2, colors=12 +# "4-bit image that uses RLE compression." +== pal4rle.bmp pal4.png + +# BMP: bihsize=40, 127 x 64, bpp=8, compression=0, colors=252 +# "Our standard paletted image, with 252 palette colors, and 8 bits/pixel." +fuzzy(1,899) == pal8.bmp pal8.png + +# BMP: bihsize=40, 127 x 64, bpp=8, compression=0, colors=0 +# "Every field that can be set to 0 is set to 0: pixels/meter=0; colors used=0 +# (meaning the default 256); size-of-image=0." +fuzzy(1,899) == pal8-0.bmp pal8.png + +# BMP: bihsize=40, 127 x 64, bpp=8, compression=1, colors=252 +# "8-bit image that uses RLE compression." +fuzzy(1,899) == pal8rle.bmp pal8.png + +# BMP: bihsize=40, 126 x 63, bpp=8, compression=0, colors=252 +# BMP: bihsize=40, 125 x 62, bpp=8, compression=0, colors=252 +# BMP: bihsize=40, 124 x 61, bpp=8, compression=0, colors=252 +# "Images with different widths and heights. In BMP format, rows are padded to +# a multiple of four bytes, so we test all four possibilities." +fuzzy(1,889) == pal8w126.bmp pal8w126.png +fuzzy(1,879) == pal8w125.bmp pal8w125.png +fuzzy(1,869) == pal8w124.bmp pal8w124.png + +# BMP: bihsize=40, 127 x -64, bpp=8, compression=0, colors=252 +# "BMP images are normally stored from the bottom up, but there is a way to +# store them from the top down." +fuzzy(1,899) == pal8topdown.bmp pal8.png + +# BMP: bihsize=40, 127 x 32, bpp=8, compression=0, colors=252 +# "An image with non-square pixels: the X pixels/meter is twice the Y +# pixels/meter. Image editors can be expected to leave the image 'squashed'; +# image viewers should consider stretching it to its correct proportions." +# [We leave it squashed, as does Chromium.] +fuzzy(1,473) == pal8nonsquare.bmp pal8nonsquare-e.png + +# BMP: bihsize=12, 127 x 64, bpp=8, compression=0, colors=0 +# "An OS/2-style bitmap." +fuzzy(1,899) == pal8os2.bmp pal8.png + +# BMP: bihsize=108, 127 x 64, bpp=8, compression=0, colors=252 +# "A v4 bitmap. I’m not sure that the gamma and chromaticity values in this +# file are sensible, because I can’t find any detailed documentation of them." +fuzzy(1,899) == pal8v4.bmp pal8.png + +# BMP: bihsize=124, 127 x 64, bpp=8, compression=0, colors=252 +# "A v5 bitmap. Version 5 has additional colorspace options over v4, so it is +# easier to create, and ought to be more portable." +fuzzy(1,899) == pal8v5.bmp pal8.png + +# BMP: bihsize=40, 127 x 64, bpp=16, compression=0, colors=0 +# "A 16-bit image with the default color format: 5 bits each for red, green, and +# blue, and 1 unused bit. The whitest colors should (I assume) be displayed as +# pure white: (255,255,255), not (248,248,248)." +fuzzy(1,1296) == rgb16.bmp rgb16.png + +# BMP: bihsize=40, 127 x 64, bpp=16, compression=3, colors=0 +# "A 16-bit image with a BITFIELDS segment indicating 5 red, 6 green, and 5 blue +# bits. This is a standard 16-bit format, even supported by old versions of +# Windows that don’t support any other non-default 16-bit formats. The whitest +# colors should be displayed as pure white: (255,255,255), not (248,252,248)." +fuzzy(1,1296) == rgb16.bmp rgb16.png + +# BMP: bihsize=40, 127 x 64, bpp=16, compression=3, colors=256 +# "A 16-bit image with both a BITFIELDS segment and a palette." +fuzzy(1,1516) == rgb16.bmp rgb16.png + +# BMP: bihsize=40, 127 x 64, bpp=24, compression=0, colors=0 +# "A perfectly ordinary 24-bit (truecolor) image." +== rgb24.bmp rgb24.png + +# BMP: bihsize=40, 127 x 64, bpp=24, compression=0, colors=256 +# "A 24-bit image, with a palette containing 256 colors. There is little if any +# reason for a truecolor image to contain a palette, but it is legal." +== rgb24pal.bmp rgb24.png + +# BMP: bihsize=40, 127 x 64, bpp=32, compression=0, colors=0 +# "A 32-bit image using the default color format for 32-bit images (no +# BITFIELDS segment). There are 8 bits per color channel, and 8 unused bits. +# The unused bits are set to 0." +== rgb32.bmp rgb24.png + +# BMP: bihsize=40, 127 x 64, bpp=32, compression=3, colors=0 +# "A 32-bit image with a BITFIELDS segment. As usual, there are 8 bits per color +# channel, and 8 unused bits. But the color channels are in an unusual order, +# so the viewer must read the BITFIELDS, and not just guess." +== rgb32bf.bmp rgb24.png + diff --git a/image/test/reftest/bmp/bmpsuite/g/rgb16-565.bmp b/image/test/reftest/bmp/bmpsuite/g/rgb16-565.bmp new file mode 100644 index 000000000..c03a27975 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/rgb16-565.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/g/rgb16-565.png b/image/test/reftest/bmp/bmpsuite/g/rgb16-565.png new file mode 100644 index 000000000..04a3121d2 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/rgb16-565.png differ diff --git a/image/test/reftest/bmp/bmpsuite/g/rgb16-565pal.bmp b/image/test/reftest/bmp/bmpsuite/g/rgb16-565pal.bmp new file mode 100644 index 000000000..e7632e344 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/rgb16-565pal.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/g/rgb16.bmp b/image/test/reftest/bmp/bmpsuite/g/rgb16.bmp new file mode 100644 index 000000000..6bfe47af4 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/rgb16.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/g/rgb16.png b/image/test/reftest/bmp/bmpsuite/g/rgb16.png new file mode 100644 index 000000000..d9545840a Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/rgb16.png differ diff --git a/image/test/reftest/bmp/bmpsuite/g/rgb24.bmp b/image/test/reftest/bmp/bmpsuite/g/rgb24.bmp new file mode 100644 index 000000000..40f8bb094 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/rgb24.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/g/rgb24.png b/image/test/reftest/bmp/bmpsuite/g/rgb24.png new file mode 100644 index 000000000..86a9c945b Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/rgb24.png differ diff --git a/image/test/reftest/bmp/bmpsuite/g/rgb24pal.bmp b/image/test/reftest/bmp/bmpsuite/g/rgb24pal.bmp new file mode 100644 index 000000000..102e971dd Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/rgb24pal.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/g/rgb32.bmp b/image/test/reftest/bmp/bmpsuite/g/rgb32.bmp new file mode 100644 index 000000000..5d57eaaea Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/rgb32.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/g/rgb32bf.bmp b/image/test/reftest/bmp/bmpsuite/g/rgb32bf.bmp new file mode 100644 index 000000000..20fa9a132 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/g/rgb32bf.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/q/pal1p1.bmp b/image/test/reftest/bmp/bmpsuite/q/pal1p1.bmp new file mode 100644 index 000000000..b68321c4c Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/pal1p1.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/q/pal1p1.png b/image/test/reftest/bmp/bmpsuite/q/pal1p1.png new file mode 100644 index 000000000..92fc0f945 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/pal1p1.png differ diff --git a/image/test/reftest/bmp/bmpsuite/q/pal2.bmp b/image/test/reftest/bmp/bmpsuite/q/pal2.bmp new file mode 100644 index 000000000..983e9fa92 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/pal2.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/q/pal4rletrns.bmp b/image/test/reftest/bmp/bmpsuite/q/pal4rletrns.bmp new file mode 100644 index 000000000..58994e92b Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/pal4rletrns.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/q/pal4rletrns.png b/image/test/reftest/bmp/bmpsuite/q/pal4rletrns.png new file mode 100644 index 000000000..9b0c04436 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/pal4rletrns.png differ diff --git a/image/test/reftest/bmp/bmpsuite/q/pal8.png b/image/test/reftest/bmp/bmpsuite/q/pal8.png new file mode 100644 index 000000000..2bfd3e650 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/pal8.png differ diff --git a/image/test/reftest/bmp/bmpsuite/q/pal8offs.bmp b/image/test/reftest/bmp/bmpsuite/q/pal8offs.bmp new file mode 100644 index 000000000..8673e9740 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/pal8offs.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/q/pal8os2sp.bmp b/image/test/reftest/bmp/bmpsuite/q/pal8os2sp.bmp new file mode 100644 index 000000000..e532c8986 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/pal8os2sp.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/q/pal8os2v2-16.bmp b/image/test/reftest/bmp/bmpsuite/q/pal8os2v2-16.bmp new file mode 100644 index 000000000..95a1d2345 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/pal8os2v2-16.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/q/pal8os2v2.bmp b/image/test/reftest/bmp/bmpsuite/q/pal8os2v2.bmp new file mode 100644 index 000000000..1324a40d0 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/pal8os2v2.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/q/pal8oversizepal.bmp b/image/test/reftest/bmp/bmpsuite/q/pal8oversizepal.bmp new file mode 100644 index 000000000..93b8187ca Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/pal8oversizepal.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/q/pal8rletrns.bmp b/image/test/reftest/bmp/bmpsuite/q/pal8rletrns.bmp new file mode 100644 index 000000000..a2af88d87 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/pal8rletrns.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/q/pal8rletrns.png b/image/test/reftest/bmp/bmpsuite/q/pal8rletrns.png new file mode 100644 index 000000000..2d8e957f1 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/pal8rletrns.png differ diff --git a/image/test/reftest/bmp/bmpsuite/q/reftest-stylo.list b/image/test/reftest/bmp/bmpsuite/q/reftest-stylo.list new file mode 100644 index 000000000..63c55b671 --- /dev/null +++ b/image/test/reftest/bmp/bmpsuite/q/reftest-stylo.list @@ -0,0 +1,131 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# bmpsuite "questionable" tests + +# See ../README.mozilla for details. + +# BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=1 +# "1 bit/pixel paletted image, with only one color in the palette. The +# documentation says that 1-bpp images have a palette size of 2 (not 'up to +# 2'), but it would be silly for a viewer not to support a size of 1." +# [We accept it. So does Chromium.] +fails == pal1p1.bmp pal1p1.bmp + +# BMP: bihsize=40, 127 x 64, bpp=2, compression=0, colors=4 +# "A paletted image with 2 bits/pixel. Usually only 1, 4, and 8 are allowed, +# but 2 is legal on Windows CE." +# [We reject it. So does Chromium.] +skip == wrapper.html?pal2.bmp wrapper.html?pal2.bmp + +# BMP: bihsize=40, 127 x 64, bpp=4, compression=2, colors=13 +# "An RLE-compressed image that used 'delta' codes to skip over some pixels, +# leaving them undefined. Some viewers make undefined pixels transparent, +# others make them black, and others assign them palette color 0 (purple, in +# this case)." +# [We make the undefined pixels transparent. So does Chromium.] +== pal4rletrns.bmp pal4rletrns.bmp + +# BMP: bihsize=40, 127 x 64, bpp=8, compression=1, colors=253 +# "8-bit version of q/pal4rletrns.bmp." +# [Ditto.] +== pal8rletrns.bmp pal8rletrns.bmp + +# BMP: bihsize=40, 127 x 64, bpp=8, compression=0, colors=252 +# "A file with some unused bytes between the palette and the image. This is +# probably valid, but I’m not 100% sure." +# [We accept it. So does Chromium.] +fails == pal8offs.bmp pal8offs.bmp + +# BMP: bihsize=40, 127 x 64, bpp=8, compression=0, colors=300 +# "An 8-bit image with 300 palette colors. This may be invalid, because the +# documentation could be interpreted to imply that 8-bit images aren’t allowed +# to have more than 256 colors." +# [We accept it. So does Chromium.] +fails == pal8oversizepal.bmp pal8oversizepal.bmp + +# BMP: bihsize=12, 127 x 64, bpp=8, compression=0, colors=0 +# "An OS/2v1 with a less-than-full-sized palette. Probably not valid, but such +# files have been seen in the wild." +# [We reject it. Chromium accepts it but draws nothing. Rejecting seems +# preferable given that the color and pixel data must overlap, which can only +# lead to rubbish results.] +skip == wrapper.html?pal8os2sp.bmp wrapper.html?pal8os2sp.bmp + +# BMP: bihsize=64, 127 x 64, bpp=8, compression=0, colors=252 +# "My attempt to make an OS/2v2 bitmap." +# [We accept it. So does Chromium.] +fails == pal8os2v2.bmp pal8os2v2.bmp + +# BMP: bihsize=16, 127 x 64, bpp=8, compression=0, colors=0 +# "An OS/2v2 bitmap whose header has only 16 bytes, instead of the full 64." +# [We accept it. So does Chromium.] +fails == pal8os2v2-16.bmp pal8os2v2-16.bmp + +# BMP: bihsize=40, 127 x 64, bpp=16, compression=3, colors=0 +# "An unusual and silly 16-bit image, with 2 red bits, 3 green bits, and 1 blue +# bit. Most viewers do support this image, but the colors may be darkened with +# a yellow-green shadow. That’s because they’re doing simple bit-shifting +# (possibly including one round of bit replication), instead of proper +# scaling." +fails == rgb16-231.bmp rgb16-231.bmp + +# BMP: bihsize=124, 127 x 64, bpp=16, compression=3, colors=0 +# "A 16-bit image with an alpha channel. There are 4 bits for each color +# channel, and 4 bits for the alpha channel. It’s not clear if this is valid, +# but I can’t find anything that suggests it isn’t." +== rgba16-4444.bmp rgba16-4444.bmp + +# BMP: bihsize=40, 127 x 64, bpp=24, compression=0, colors=300 +# "A 24-bit image, with a palette containing 300 colors. The fact that the +# palette has more than 256 colors may cause some viewers to complain, but the +# documentation does not mention a size limit." +# [We accept it. So does Chromium.] +fails == rgb24largepal.bmp rgb24largepal.bmp + +# BMP: bihsize=124, 127 x 64, bpp=24, compression=0, colors=0 +# "My attempt to make a BMP file with an embedded color profile." +# [We support it, though we don't do anything with the color profile. Chromium +# also handles it.] +fails == rgb24prof.bmp rgb24prof.bmp + +# BMP: bihsize=124, 127 x 64, bpp=24, compression=0, colors=0 +# "My attempt to make a BMP file with a linked color profile." +# [We accept it, though we don't do anything with the color profile. Chromium +# also handles it.] +fails == rgb24lprof.bmp rgb24lprof.bmp + +# BMP: bihsize=124, 127 x 64, bpp=0, compression=4, colors=0 +# BMP: bihsize=124, 127 x 64, bpp=0, compression=5, colors=0 +# "My attempt to make BMP files with embedded JPEG and PNG images. These are +# not likely to be supported by much of anything (they’re intended for +# printers)." +# [We reject them. So does Chromium.] +skip == wrapper.html?rgb24jpeg.bmp wrapper.html?rgb24jpeg.bmp +skip == wrapper.html?rgb24png.bmp wrapper.html?rgb24png.bmp + +# BMP: bihsize=40, 127 x 64, bpp=32, compression=0, colors=0 +# "Same as g/rgb32.bmp, except that the unused bits are set to something other +# than 0. If the image becomes transparent toward the bottom, it probably means +# the viewer uses heuristics to guess whether the undefined data represents +# transparency." +# [We don't apply transparency here. Chromium does the same.] +fails == rgb32fakealpha.bmp rgb32fakealpha.bmp + +# BMP: bihsize=40, 127 x 64, bpp=32, compression=3, colors=0 +# "A 32 bits/pixel image, with all 32 bits used: 11 each for red and green, and +# 10 for blue. As far as I know, this is perfectly valid, but it is unusual." +fails == rgb32-111110.bmp rgb32-111110.bmp + +# BMP: bihsize=124, 127 x 64, bpp=32, compression=3, colors=0 +# "A BMP with an alpha channel. Transparency is barely documented, so it’s +# possible that this file is not correctly formed. The color channels are in an +# unusual order, to prevent viewers from passing this test by making a lucky +# guess." +== rgba32.bmp rgba32.bmp + +# BMP: bihsize=40, 127 x 64, bpp=32, compression=6, colors=0 +# "An image of type BI_ALPHABITFIELDS. Supposedly, this was used on Windows CE. +# I don’t know whether it is constructed correctly." +# [We reject it. So does Chromium.] +skip == wrapper.html?rgba32abf.bmp wrapper.html?rgba32abf.bmp + + diff --git a/image/test/reftest/bmp/bmpsuite/q/reftest.list b/image/test/reftest/bmp/bmpsuite/q/reftest.list new file mode 100644 index 000000000..80da580cc --- /dev/null +++ b/image/test/reftest/bmp/bmpsuite/q/reftest.list @@ -0,0 +1,130 @@ +# bmpsuite "questionable" tests + +# See ../README.mozilla for details. + +# BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=1 +# "1 bit/pixel paletted image, with only one color in the palette. The +# documentation says that 1-bpp images have a palette size of 2 (not 'up to +# 2'), but it would be silly for a viewer not to support a size of 1." +# [We accept it. So does Chromium.] +== pal1p1.bmp pal1p1.png + +# BMP: bihsize=40, 127 x 64, bpp=2, compression=0, colors=4 +# "A paletted image with 2 bits/pixel. Usually only 1, 4, and 8 are allowed, +# but 2 is legal on Windows CE." +# [We reject it. So does Chromium.] +== wrapper.html?pal2.bmp about:blank + +# BMP: bihsize=40, 127 x 64, bpp=4, compression=2, colors=13 +# "An RLE-compressed image that used 'delta' codes to skip over some pixels, +# leaving them undefined. Some viewers make undefined pixels transparent, +# others make them black, and others assign them palette color 0 (purple, in +# this case)." +# [We make the undefined pixels transparent. So does Chromium.] +== pal4rletrns.bmp pal4rletrns.png + +# BMP: bihsize=40, 127 x 64, bpp=8, compression=1, colors=253 +# "8-bit version of q/pal4rletrns.bmp." +# [Ditto.] +== pal8rletrns.bmp pal8rletrns.png + +# BMP: bihsize=40, 127 x 64, bpp=8, compression=0, colors=252 +# "A file with some unused bytes between the palette and the image. This is +# probably valid, but I’m not 100% sure." +# [We accept it. So does Chromium.] +fuzzy(1,899) == pal8offs.bmp pal8.png + +# BMP: bihsize=40, 127 x 64, bpp=8, compression=0, colors=300 +# "An 8-bit image with 300 palette colors. This may be invalid, because the +# documentation could be interpreted to imply that 8-bit images aren’t allowed +# to have more than 256 colors." +# [We accept it. So does Chromium.] +fuzzy(1,899) == pal8oversizepal.bmp pal8.png + +# BMP: bihsize=12, 127 x 64, bpp=8, compression=0, colors=0 +# "An OS/2v1 with a less-than-full-sized palette. Probably not valid, but such +# files have been seen in the wild." +# [We reject it. Chromium accepts it but draws nothing. Rejecting seems +# preferable given that the color and pixel data must overlap, which can only +# lead to rubbish results.] +== wrapper.html?pal8os2sp.bmp about:blank + +# BMP: bihsize=64, 127 x 64, bpp=8, compression=0, colors=252 +# "My attempt to make an OS/2v2 bitmap." +# [We accept it. So does Chromium.] +fuzzy(1,899) == pal8os2v2.bmp pal8.png + +# BMP: bihsize=16, 127 x 64, bpp=8, compression=0, colors=0 +# "An OS/2v2 bitmap whose header has only 16 bytes, instead of the full 64." +# [We accept it. So does Chromium.] +fuzzy(1,899) == pal8os2v2-16.bmp pal8.png + +# BMP: bihsize=40, 127 x 64, bpp=16, compression=3, colors=0 +# "An unusual and silly 16-bit image, with 2 red bits, 3 green bits, and 1 blue +# bit. Most viewers do support this image, but the colors may be darkened with +# a yellow-green shadow. That’s because they’re doing simple bit-shifting +# (possibly including one round of bit replication), instead of proper +# scaling." +== rgb16-231.bmp rgb16-231.png + +# BMP: bihsize=124, 127 x 64, bpp=16, compression=3, colors=0 +# "A 16-bit image with an alpha channel. There are 4 bits for each color +# channel, and 4 bits for the alpha channel. It’s not clear if this is valid, +# but I can’t find anything that suggests it isn’t." +== rgba16-4444.bmp rgba16-4444.png + +# BMP: bihsize=40, 127 x 64, bpp=24, compression=0, colors=300 +# "A 24-bit image, with a palette containing 300 colors. The fact that the +# palette has more than 256 colors may cause some viewers to complain, but the +# documentation does not mention a size limit." +# [We accept it. So does Chromium.] +== rgb24largepal.bmp rgb24.png + +# BMP: bihsize=124, 127 x 64, bpp=24, compression=0, colors=0 +# "My attempt to make a BMP file with an embedded color profile." +# [We support it, though we don't do anything with the color profile. Chromium +# also handles it.] +== rgb24prof.bmp rgb24.png + +# BMP: bihsize=124, 127 x 64, bpp=24, compression=0, colors=0 +# "My attempt to make a BMP file with a linked color profile." +# [We accept it, though we don't do anything with the color profile. Chromium +# also handles it.] +== rgb24lprof.bmp rgb24.png + +# BMP: bihsize=124, 127 x 64, bpp=0, compression=4, colors=0 +# BMP: bihsize=124, 127 x 64, bpp=0, compression=5, colors=0 +# "My attempt to make BMP files with embedded JPEG and PNG images. These are +# not likely to be supported by much of anything (they’re intended for +# printers)." +# [We reject them. So does Chromium.] +== wrapper.html?rgb24jpeg.bmp about:blank +== wrapper.html?rgb24png.bmp about:blank + +# BMP: bihsize=40, 127 x 64, bpp=32, compression=0, colors=0 +# "Same as g/rgb32.bmp, except that the unused bits are set to something other +# than 0. If the image becomes transparent toward the bottom, it probably means +# the viewer uses heuristics to guess whether the undefined data represents +# transparency." +# [We don't apply transparency here. Chromium does the same.] +== rgb32fakealpha.bmp rgb24.png + +# BMP: bihsize=40, 127 x 64, bpp=32, compression=3, colors=0 +# "A 32 bits/pixel image, with all 32 bits used: 11 each for red and green, and +# 10 for blue. As far as I know, this is perfectly valid, but it is unusual." +fuzzy(1,1408) == rgb32-111110.bmp rgb24.png + +# BMP: bihsize=124, 127 x 64, bpp=32, compression=3, colors=0 +# "A BMP with an alpha channel. Transparency is barely documented, so it’s +# possible that this file is not correctly formed. The color channels are in an +# unusual order, to prevent viewers from passing this test by making a lucky +# guess." +== rgba32.bmp rgba32.png + +# BMP: bihsize=40, 127 x 64, bpp=32, compression=6, colors=0 +# "An image of type BI_ALPHABITFIELDS. Supposedly, this was used on Windows CE. +# I don’t know whether it is constructed correctly." +# [We reject it. So does Chromium.] +== wrapper.html?rgba32abf.bmp about:blank + + diff --git a/image/test/reftest/bmp/bmpsuite/q/rgb16-231.bmp b/image/test/reftest/bmp/bmpsuite/q/rgb16-231.bmp new file mode 100644 index 000000000..6300f69f0 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/rgb16-231.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/q/rgb16-231.png b/image/test/reftest/bmp/bmpsuite/q/rgb16-231.png new file mode 100644 index 000000000..76efe526e Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/rgb16-231.png differ diff --git a/image/test/reftest/bmp/bmpsuite/q/rgb24.png b/image/test/reftest/bmp/bmpsuite/q/rgb24.png new file mode 100644 index 000000000..86a9c945b Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/rgb24.png differ diff --git a/image/test/reftest/bmp/bmpsuite/q/rgb24jpeg.bmp b/image/test/reftest/bmp/bmpsuite/q/rgb24jpeg.bmp new file mode 100644 index 000000000..87d73d75b Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/rgb24jpeg.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/q/rgb24largepal.bmp b/image/test/reftest/bmp/bmpsuite/q/rgb24largepal.bmp new file mode 100644 index 000000000..d5e418c2d Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/rgb24largepal.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/q/rgb24lprof.bmp b/image/test/reftest/bmp/bmpsuite/q/rgb24lprof.bmp new file mode 100644 index 000000000..b868b88f2 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/rgb24lprof.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/q/rgb24png.bmp b/image/test/reftest/bmp/bmpsuite/q/rgb24png.bmp new file mode 100644 index 000000000..e87ec7add Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/rgb24png.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/q/rgb24prof.bmp b/image/test/reftest/bmp/bmpsuite/q/rgb24prof.bmp new file mode 100644 index 000000000..627e676ea Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/rgb24prof.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/q/rgb32-111110.bmp b/image/test/reftest/bmp/bmpsuite/q/rgb32-111110.bmp new file mode 100644 index 000000000..ec07d89b5 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/rgb32-111110.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/q/rgb32fakealpha.bmp b/image/test/reftest/bmp/bmpsuite/q/rgb32fakealpha.bmp new file mode 100644 index 000000000..cb544da5b Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/rgb32fakealpha.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/q/rgba16-4444.bmp b/image/test/reftest/bmp/bmpsuite/q/rgba16-4444.bmp new file mode 100644 index 000000000..051ff2358 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/rgba16-4444.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/q/rgba16-4444.png b/image/test/reftest/bmp/bmpsuite/q/rgba16-4444.png new file mode 100644 index 000000000..bfeda6fae Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/rgba16-4444.png differ diff --git a/image/test/reftest/bmp/bmpsuite/q/rgba32.bmp b/image/test/reftest/bmp/bmpsuite/q/rgba32.bmp new file mode 100644 index 000000000..829c7c7e3 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/rgba32.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/q/rgba32.png b/image/test/reftest/bmp/bmpsuite/q/rgba32.png new file mode 100644 index 000000000..25e542a65 Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/rgba32.png differ diff --git a/image/test/reftest/bmp/bmpsuite/q/rgba32abf.bmp b/image/test/reftest/bmp/bmpsuite/q/rgba32abf.bmp new file mode 100644 index 000000000..d9bb0189c Binary files /dev/null and b/image/test/reftest/bmp/bmpsuite/q/rgba32abf.bmp differ diff --git a/image/test/reftest/bmp/bmpsuite/q/wrapper.html b/image/test/reftest/bmp/bmpsuite/q/wrapper.html new file mode 100644 index 000000000..47e68959f --- /dev/null +++ b/image/test/reftest/bmp/bmpsuite/q/wrapper.html @@ -0,0 +1,28 @@ + + + +Image reftest wrapper + + + + + + + + + diff --git a/image/test/reftest/bmp/bmpsuite/reftest-stylo.list b/image/test/reftest/bmp/bmpsuite/reftest-stylo.list new file mode 100644 index 000000000..5ec496272 --- /dev/null +++ b/image/test/reftest/bmp/bmpsuite/reftest-stylo.list @@ -0,0 +1,8 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# bmpsuite tests + +# See README.mozilla for details about these tests. + +include g/reftest-stylo.list +include q/reftest-stylo.list +include b/reftest-stylo.list diff --git a/image/test/reftest/bmp/bmpsuite/reftest.list b/image/test/reftest/bmp/bmpsuite/reftest.list new file mode 100644 index 000000000..8f48430f9 --- /dev/null +++ b/image/test/reftest/bmp/bmpsuite/reftest.list @@ -0,0 +1,7 @@ +# bmpsuite tests + +# See README.mozilla for details about these tests. + +include g/reftest.list +include q/reftest.list +include b/reftest.list diff --git a/image/test/reftest/bmp/reftest-stylo.list b/image/test/reftest/bmp/reftest-stylo.list new file mode 100644 index 000000000..80aa0ab32 --- /dev/null +++ b/image/test/reftest/bmp/reftest-stylo.list @@ -0,0 +1,17 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# BMP tests + +include bmp-1bpp/reftest-stylo.list +include bmp-4bpp/reftest-stylo.list +include bmp-8bpp/reftest-stylo.list +include bmp-24bpp/reftest-stylo.list +include bmp-corrupted/reftest-stylo.list +include bmpsuite/reftest-stylo.list + +# Two bmp files where the offset to the start of the image data in the file +# is past the end of the file. In 1240629-1.bmp the offset us uint32_max, +# so we are testing that we don't try to allocate a buffer that size (and +# fail on 32 bit platforms) and declare the image in error state. If in the +# future we decide that such bmps (offset past the end of the file) are +# invalid the test will still pass, but won't be testing much. +fails == 1240629-1.bmp 1240629-1.bmp diff --git a/image/test/reftest/bmp/reftest.list b/image/test/reftest/bmp/reftest.list new file mode 100644 index 000000000..87183e6b2 --- /dev/null +++ b/image/test/reftest/bmp/reftest.list @@ -0,0 +1,16 @@ +# BMP tests + +include bmp-1bpp/reftest.list +include bmp-4bpp/reftest.list +include bmp-8bpp/reftest.list +include bmp-24bpp/reftest.list +include bmp-corrupted/reftest.list +include bmpsuite/reftest.list + +# Two bmp files where the offset to the start of the image data in the file +# is past the end of the file. In 1240629-1.bmp the offset us uint32_max, +# so we are testing that we don't try to allocate a buffer that size (and +# fail on 32 bit platforms) and declare the image in error state. If in the +# future we decide that such bmps (offset past the end of the file) are +# invalid the test will still pass, but won't be testing much. +== 1240629-1.bmp 1240629-2.bmp diff --git a/image/test/reftest/color-management/color-curv.png b/image/test/reftest/color-management/color-curv.png new file mode 100644 index 000000000..994e3a38a Binary files /dev/null and b/image/test/reftest/color-management/color-curv.png differ diff --git a/image/test/reftest/color-management/color-lin.png b/image/test/reftest/color-management/color-lin.png new file mode 100644 index 000000000..0ee276fca Binary files /dev/null and b/image/test/reftest/color-management/color-lin.png differ diff --git a/image/test/reftest/color-management/color-table.png b/image/test/reftest/color-management/color-table.png new file mode 100644 index 000000000..355b3a2ba Binary files /dev/null and b/image/test/reftest/color-management/color-table.png differ diff --git a/image/test/reftest/color-management/invalid-chrm-ref.png b/image/test/reftest/color-management/invalid-chrm-ref.png new file mode 100644 index 000000000..85f83f783 Binary files /dev/null and b/image/test/reftest/color-management/invalid-chrm-ref.png differ diff --git a/image/test/reftest/color-management/invalid-chrm.png b/image/test/reftest/color-management/invalid-chrm.png new file mode 100644 index 000000000..33dc9e9ce Binary files /dev/null and b/image/test/reftest/color-management/invalid-chrm.png differ diff --git a/image/test/reftest/color-management/invalid-whitepoint.png b/image/test/reftest/color-management/invalid-whitepoint.png new file mode 100644 index 000000000..383a0a035 Binary files /dev/null and b/image/test/reftest/color-management/invalid-whitepoint.png differ diff --git a/image/test/reftest/color-management/reftest-stylo.list b/image/test/reftest/color-management/reftest-stylo.list new file mode 100644 index 000000000..64f503fe6 --- /dev/null +++ b/image/test/reftest/color-management/reftest-stylo.list @@ -0,0 +1,8 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# Colormangement + +# test for bug 489133, test for bug 460520 +fails == invalid-chrm.png invalid-chrm.png +fails == invalid-whitepoint.png invalid-whitepoint.png +# test for bug 488955 +== trc-type.html trc-type.html diff --git a/image/test/reftest/color-management/reftest.list b/image/test/reftest/color-management/reftest.list new file mode 100644 index 000000000..ee6e16adb --- /dev/null +++ b/image/test/reftest/color-management/reftest.list @@ -0,0 +1,7 @@ +# Colormangement + +# test for bug 489133, test for bug 460520 +== invalid-chrm.png invalid-chrm-ref.png +== invalid-whitepoint.png invalid-chrm-ref.png +# test for bug 488955 +== trc-type.html trc-type-ref.html diff --git a/image/test/reftest/color-management/trc-type-ref.html b/image/test/reftest/color-management/trc-type-ref.html new file mode 100644 index 000000000..5140e6e6a --- /dev/null +++ b/image/test/reftest/color-management/trc-type-ref.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/image/test/reftest/color-management/trc-type.html b/image/test/reftest/color-management/trc-type.html new file mode 100644 index 000000000..f13052bbf --- /dev/null +++ b/image/test/reftest/color-management/trc-type.html @@ -0,0 +1,53 @@ + + + + + + + + + diff --git a/image/test/reftest/colordepth.html b/image/test/reftest/colordepth.html new file mode 100644 index 000000000..15d2c9f95 --- /dev/null +++ b/image/test/reftest/colordepth.html @@ -0,0 +1,16 @@ + diff --git a/image/test/reftest/downscaling/black-border-bottom.png b/image/test/reftest/downscaling/black-border-bottom.png new file mode 100644 index 000000000..efa7ce2dc Binary files /dev/null and b/image/test/reftest/downscaling/black-border-bottom.png differ diff --git a/image/test/reftest/downscaling/black-border-left.png b/image/test/reftest/downscaling/black-border-left.png new file mode 100644 index 000000000..11bc67e98 Binary files /dev/null and b/image/test/reftest/downscaling/black-border-left.png differ diff --git a/image/test/reftest/downscaling/black-border-rect.svg b/image/test/reftest/downscaling/black-border-rect.svg new file mode 100644 index 000000000..0fa01a0a6 --- /dev/null +++ b/image/test/reftest/downscaling/black-border-rect.svg @@ -0,0 +1,3 @@ + + + diff --git a/image/test/reftest/downscaling/black-border-right.png b/image/test/reftest/downscaling/black-border-right.png new file mode 100644 index 000000000..081c52d5b Binary files /dev/null and b/image/test/reftest/downscaling/black-border-right.png differ diff --git a/image/test/reftest/downscaling/black-border-top.png b/image/test/reftest/downscaling/black-border-top.png new file mode 100644 index 000000000..fc6e69e02 Binary files /dev/null and b/image/test/reftest/downscaling/black-border-top.png differ diff --git a/image/test/reftest/downscaling/bmp-size-16x16-24bpp.png b/image/test/reftest/downscaling/bmp-size-16x16-24bpp.png new file mode 100644 index 000000000..c04869e72 Binary files /dev/null and b/image/test/reftest/downscaling/bmp-size-16x16-24bpp.png differ diff --git a/image/test/reftest/downscaling/downscale-1-bigimage.png b/image/test/reftest/downscaling/downscale-1-bigimage.png new file mode 100644 index 000000000..5e018590c Binary files /dev/null and b/image/test/reftest/downscaling/downscale-1-bigimage.png differ diff --git a/image/test/reftest/downscaling/downscale-1-ref.html b/image/test/reftest/downscaling/downscale-1-ref.html new file mode 100644 index 000000000..63ec56ef7 --- /dev/null +++ b/image/test/reftest/downscaling/downscale-1-ref.html @@ -0,0 +1,8 @@ + + + + + + + diff --git a/image/test/reftest/downscaling/downscale-1-smallimage.png b/image/test/reftest/downscaling/downscale-1-smallimage.png new file mode 100644 index 000000000..588e6b78d Binary files /dev/null and b/image/test/reftest/downscaling/downscale-1-smallimage.png differ diff --git a/image/test/reftest/downscaling/downscale-1.html b/image/test/reftest/downscaling/downscale-1.html new file mode 100644 index 000000000..a9629ef85 --- /dev/null +++ b/image/test/reftest/downscaling/downscale-1.html @@ -0,0 +1,24 @@ + + + + + + + + + + + diff --git a/image/test/reftest/downscaling/downscale-16px.html b/image/test/reftest/downscaling/downscale-16px.html new file mode 100644 index 000000000..b34adb93d --- /dev/null +++ b/image/test/reftest/downscaling/downscale-16px.html @@ -0,0 +1,28 @@ + + + +Image reftest wrapper + + + + + + + + + diff --git a/image/test/reftest/downscaling/downscale-2a.html b/image/test/reftest/downscaling/downscale-2a.html new file mode 100644 index 000000000..fac11ccee --- /dev/null +++ b/image/test/reftest/downscaling/downscale-2a.html @@ -0,0 +1,31 @@ + + + + + + + + + + + diff --git a/image/test/reftest/downscaling/downscale-2b.html b/image/test/reftest/downscaling/downscale-2b.html new file mode 100644 index 000000000..af7ecbff3 --- /dev/null +++ b/image/test/reftest/downscaling/downscale-2b.html @@ -0,0 +1,31 @@ + + + + + + + + + + + diff --git a/image/test/reftest/downscaling/downscale-2c.html b/image/test/reftest/downscaling/downscale-2c.html new file mode 100644 index 000000000..18f70456b --- /dev/null +++ b/image/test/reftest/downscaling/downscale-2c.html @@ -0,0 +1,31 @@ + + + + + + + + + + + diff --git a/image/test/reftest/downscaling/downscale-2d.html b/image/test/reftest/downscaling/downscale-2d.html new file mode 100644 index 000000000..8d9547b73 --- /dev/null +++ b/image/test/reftest/downscaling/downscale-2d.html @@ -0,0 +1,31 @@ + + + + + + + + + + + diff --git a/image/test/reftest/downscaling/downscale-2e.html b/image/test/reftest/downscaling/downscale-2e.html new file mode 100644 index 000000000..c3d0d771f --- /dev/null +++ b/image/test/reftest/downscaling/downscale-2e.html @@ -0,0 +1,31 @@ + + + + + + + + + + + diff --git a/image/test/reftest/downscaling/downscale-2f.html b/image/test/reftest/downscaling/downscale-2f.html new file mode 100644 index 000000000..42cfad1f5 --- /dev/null +++ b/image/test/reftest/downscaling/downscale-2f.html @@ -0,0 +1,31 @@ + + + + + + + + + + + diff --git a/image/test/reftest/downscaling/downscale-32px-ref.html b/image/test/reftest/downscaling/downscale-32px-ref.html new file mode 100644 index 000000000..1caf3c73b --- /dev/null +++ b/image/test/reftest/downscaling/downscale-32px-ref.html @@ -0,0 +1,8 @@ + + + + + + + diff --git a/image/test/reftest/downscaling/downscale-32px.html b/image/test/reftest/downscaling/downscale-32px.html new file mode 100644 index 000000000..f5fce324d --- /dev/null +++ b/image/test/reftest/downscaling/downscale-32px.html @@ -0,0 +1,31 @@ + + + + + + + + + + + diff --git a/image/test/reftest/downscaling/downscale-8px.html b/image/test/reftest/downscaling/downscale-8px.html new file mode 100644 index 000000000..32cb1b211 --- /dev/null +++ b/image/test/reftest/downscaling/downscale-8px.html @@ -0,0 +1,27 @@ + + + +Image reftest wrapper + + + + + + + + diff --git a/image/test/reftest/downscaling/downscale-moz-icon-1-ref.html b/image/test/reftest/downscaling/downscale-moz-icon-1-ref.html new file mode 100644 index 000000000..ade5e2192 --- /dev/null +++ b/image/test/reftest/downscaling/downscale-moz-icon-1-ref.html @@ -0,0 +1,37 @@ + + + + + + Reference for downscaling moz-icon images (bug 1261964) + + + + + + + diff --git a/image/test/reftest/downscaling/downscale-moz-icon-1.html b/image/test/reftest/downscaling/downscale-moz-icon-1.html new file mode 100644 index 000000000..ba3795127 --- /dev/null +++ b/image/test/reftest/downscaling/downscale-moz-icon-1.html @@ -0,0 +1,19 @@ + + + + + + Testcase for downscaling moz-icon images (bug 1261964) + + + + + + diff --git a/image/test/reftest/downscaling/downscale-png.html b/image/test/reftest/downscaling/downscale-png.html new file mode 100644 index 000000000..4752b2155 --- /dev/null +++ b/image/test/reftest/downscaling/downscale-png.html @@ -0,0 +1,31 @@ + + + + + + + + + + + diff --git a/image/test/reftest/downscaling/downscale-svg-1-ref.html b/image/test/reftest/downscaling/downscale-svg-1-ref.html new file mode 100644 index 000000000..8935619eb --- /dev/null +++ b/image/test/reftest/downscaling/downscale-svg-1-ref.html @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/image/test/reftest/downscaling/downscale-svg-1a.html b/image/test/reftest/downscaling/downscale-svg-1a.html new file mode 100644 index 000000000..2263cc998 --- /dev/null +++ b/image/test/reftest/downscaling/downscale-svg-1a.html @@ -0,0 +1,8 @@ + + + + +
    + + diff --git a/image/test/reftest/downscaling/downscale-svg-1b.html b/image/test/reftest/downscaling/downscale-svg-1b.html new file mode 100644 index 000000000..9db239c7c --- /dev/null +++ b/image/test/reftest/downscaling/downscale-svg-1b.html @@ -0,0 +1,8 @@ + + + + +
    + + diff --git a/image/test/reftest/downscaling/downscale-svg-1c.html b/image/test/reftest/downscaling/downscale-svg-1c.html new file mode 100644 index 000000000..f8babf026 --- /dev/null +++ b/image/test/reftest/downscaling/downscale-svg-1c.html @@ -0,0 +1,8 @@ + + + + +
    + + diff --git a/image/test/reftest/downscaling/downscale-svg-1d.html b/image/test/reftest/downscaling/downscale-svg-1d.html new file mode 100644 index 000000000..9a56a51de --- /dev/null +++ b/image/test/reftest/downscaling/downscale-svg-1d.html @@ -0,0 +1,8 @@ + + + + +
    + + diff --git a/image/test/reftest/downscaling/downscale-svg-1e.html b/image/test/reftest/downscaling/downscale-svg-1e.html new file mode 100644 index 000000000..732ac22c9 --- /dev/null +++ b/image/test/reftest/downscaling/downscale-svg-1e.html @@ -0,0 +1,8 @@ + + + + +
    + + diff --git a/image/test/reftest/downscaling/downscale-svg-1f.html b/image/test/reftest/downscaling/downscale-svg-1f.html new file mode 100644 index 000000000..0124682c7 --- /dev/null +++ b/image/test/reftest/downscaling/downscale-svg-1f.html @@ -0,0 +1,8 @@ + + + + +
    + + diff --git a/image/test/reftest/downscaling/ff-0RGB.ico b/image/test/reftest/downscaling/ff-0RGB.ico new file mode 100644 index 000000000..56116b9f6 Binary files /dev/null and b/image/test/reftest/downscaling/ff-0RGB.ico differ diff --git a/image/test/reftest/downscaling/ff-0RGB.png b/image/test/reftest/downscaling/ff-0RGB.png new file mode 100644 index 000000000..749ffcdfb Binary files /dev/null and b/image/test/reftest/downscaling/ff-0RGB.png differ diff --git a/image/test/reftest/downscaling/ff-ARGB.ico b/image/test/reftest/downscaling/ff-ARGB.ico new file mode 100644 index 000000000..4681dc649 Binary files /dev/null and b/image/test/reftest/downscaling/ff-ARGB.ico differ diff --git a/image/test/reftest/downscaling/ff-ARGB.png b/image/test/reftest/downscaling/ff-ARGB.png new file mode 100644 index 000000000..74ea0e2f3 Binary files /dev/null and b/image/test/reftest/downscaling/ff-ARGB.png differ diff --git a/image/test/reftest/downscaling/lime-red-256px-bmp-in.ico b/image/test/reftest/downscaling/lime-red-256px-bmp-in.ico new file mode 100644 index 000000000..b372cba4a Binary files /dev/null and b/image/test/reftest/downscaling/lime-red-256px-bmp-in.ico differ diff --git a/image/test/reftest/downscaling/lime-red-256px-png-in.ico b/image/test/reftest/downscaling/lime-red-256px-png-in.ico new file mode 100644 index 000000000..e8578d293 Binary files /dev/null and b/image/test/reftest/downscaling/lime-red-256px-png-in.ico differ diff --git a/image/test/reftest/downscaling/lime-red-256px.bmp b/image/test/reftest/downscaling/lime-red-256px.bmp new file mode 100644 index 000000000..3dc808970 Binary files /dev/null and b/image/test/reftest/downscaling/lime-red-256px.bmp differ diff --git a/image/test/reftest/downscaling/lime-red-256px.gif b/image/test/reftest/downscaling/lime-red-256px.gif new file mode 100644 index 000000000..f9f669aa4 Binary files /dev/null and b/image/test/reftest/downscaling/lime-red-256px.gif differ diff --git a/image/test/reftest/downscaling/lime-red-256px.jpg b/image/test/reftest/downscaling/lime-red-256px.jpg new file mode 100644 index 000000000..ac8efdf36 Binary files /dev/null and b/image/test/reftest/downscaling/lime-red-256px.jpg differ diff --git a/image/test/reftest/downscaling/lime-red-256px.png b/image/test/reftest/downscaling/lime-red-256px.png new file mode 100644 index 000000000..2be2e05a5 Binary files /dev/null and b/image/test/reftest/downscaling/lime-red-256px.png differ diff --git a/image/test/reftest/downscaling/lime-red-256px.svg b/image/test/reftest/downscaling/lime-red-256px.svg new file mode 100644 index 000000000..530ae6d6d --- /dev/null +++ b/image/test/reftest/downscaling/lime-red-256px.svg @@ -0,0 +1,5 @@ + + + + diff --git a/image/test/reftest/downscaling/lime-red-32px.png b/image/test/reftest/downscaling/lime-red-32px.png new file mode 100644 index 000000000..bfa2e7b73 Binary files /dev/null and b/image/test/reftest/downscaling/lime-red-32px.png differ diff --git a/image/test/reftest/downscaling/png-interlaced.png b/image/test/reftest/downscaling/png-interlaced.png new file mode 100644 index 000000000..a938d0bd6 Binary files /dev/null and b/image/test/reftest/downscaling/png-interlaced.png differ diff --git a/image/test/reftest/downscaling/png-normal.png b/image/test/reftest/downscaling/png-normal.png new file mode 100644 index 000000000..c2780fdd9 Binary files /dev/null and b/image/test/reftest/downscaling/png-normal.png differ diff --git a/image/test/reftest/downscaling/reftest-stylo.list b/image/test/reftest/downscaling/reftest-stylo.list new file mode 100644 index 000000000..6feb9080b --- /dev/null +++ b/image/test/reftest/downscaling/reftest-stylo.list @@ -0,0 +1,195 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# Reftests for downscaling +# +# Downscaling can be a lossy process, so a bit of mismatch is acceptable here, +# as long as it's barely noticable visually. When necessary, this can be +# explicitly allowed via 'fuzzy'/'fuzzy-if' annotations. +# +# Many of these tests check primarily that we don't lose rows or columns of +# pixels when downscaling by making sure that the result isn't too similar to +# about:blank. A small amount of fuzziness is used to ensure that the tests +# don't pass because of very slight deviations; passing tests should be +# substantially different from about:blank. This fuzziness should *not* be +# removed as doing so would make the tests pass in situations where they +# shouldn't. +# +# IMPORTANT: For robustness, each test should be listed *twice* in this +# manifest -- once with the high quality downscaling pref disabled, and once +# with this pref enabled. The pref is set via "default-preferences", so +# simply appending a new test to the lists below each of those lines should be +# sufficient. +# +# Also note that Mac OS X has its own system-level downscaling algorithm, so +# tests here may need Mac-specific "fuzzy-if(cocoaWidget,...)" annotations. +# Similarly, modern versions of Windows have slightly different downscaling +# behavior than other platforms, and may require "fuzzy-if(winWidget,...)". + + +# RUN TESTS NOT AFFECTED BY DOWNSCALE-DURING-DECODE: +# # +fails fuzzy-if(skiaContent,14,416) == downscale-svg-1a.html downscale-svg-1a.html +== downscale-svg-1b.html downscale-svg-1b.html +fails fuzzy-if(skiaContent,8,292) == downscale-svg-1c.html downscale-svg-1c.html +fuzzy-if(B2G,255,207) == downscale-svg-1d.html downscale-svg-1d.html +# right side is 1 pixel off for B2G, probably regression from 974242 +fails fuzzy-if(skiaContent,110,181) == downscale-svg-1e.html downscale-svg-1e.html +fails fuzzy-if(skiaContent,142,77) == downscale-svg-1f.html downscale-svg-1f.html + +# RUN TESTS WITH DOWNSCALE-DURING-DECODE DISABLED: +# # +default-preferences pref(image.downscale-during-decode.enabled,false) + +fuzzy-if(winWidget,16,20) fuzzy-if(cocoaWidget,106,31) == downscale-1.html downscale-1.html + +== downscale-2a.html?203,52,left downscale-2a.html?203,52,left +== downscale-2b.html?203,52,left downscale-2b.html?203,52,left +skip == downscale-2c.html?203,52,left downscale-2c.html?203,52,left +== downscale-2d.html?203,52,left downscale-2d.html?203,52,left +== downscale-2e.html?203,52,left downscale-2e.html?203,52,left + +== downscale-2a.html?205,53,left downscale-2a.html?205,53,left +== downscale-2b.html?205,53,left downscale-2b.html?205,53,left +== downscale-2c.html?205,53,left downscale-2c.html?205,53,left +skip == downscale-2d.html?205,53,left downscale-2d.html?205,53,left +== downscale-2e.html?205,53,left downscale-2e.html?205,53,left + +== downscale-2a.html?203,52,right downscale-2a.html?203,52,right +== downscale-2b.html?203,52,right downscale-2b.html?203,52,right +== downscale-2c.html?203,52,right downscale-2c.html?203,52,right +== downscale-2d.html?203,52,right downscale-2d.html?203,52,right +== downscale-2e.html?203,52,right downscale-2e.html?203,52,right + +== downscale-2a.html?205,53,right downscale-2a.html?205,53,right +== downscale-2b.html?205,53,right downscale-2b.html?205,53,right +== downscale-2c.html?205,53,right downscale-2c.html?205,53,right +== downscale-2d.html?205,53,right downscale-2d.html?205,53,right +== downscale-2e.html?205,53,right downscale-2e.html?205,53,right + +== downscale-2a.html?203,52,top downscale-2a.html?203,52,top +== downscale-2b.html?203,52,top downscale-2b.html?203,52,top +== downscale-2c.html?203,52,top downscale-2c.html?203,52,top +skip == downscale-2d.html?203,52,top downscale-2d.html?203,52,top +== downscale-2e.html?203,52,top downscale-2e.html?203,52,top + +== downscale-2a.html?205,53,top downscale-2a.html?205,53,top +== downscale-2b.html?205,53,top downscale-2b.html?205,53,top +== downscale-2c.html?205,53,top downscale-2c.html?205,53,top +== downscale-2d.html?205,53,top downscale-2d.html?205,53,top +== downscale-2e.html?205,53,top downscale-2e.html?205,53,top + +== downscale-2a.html?203,52,bottom downscale-2a.html?203,52,bottom +== downscale-2b.html?203,52,bottom downscale-2b.html?203,52,bottom +== downscale-2c.html?203,52,bottom downscale-2c.html?203,52,bottom +== downscale-2d.html?203,52,bottom downscale-2d.html?203,52,bottom +skip == downscale-2e.html?203,52,bottom downscale-2e.html?203,52,bottom + +== downscale-2a.html?205,53,bottom downscale-2a.html?205,53,bottom +== downscale-2b.html?205,53,bottom downscale-2b.html?205,53,bottom +== downscale-2c.html?205,53,bottom downscale-2c.html?205,53,bottom +== downscale-2d.html?205,53,bottom downscale-2d.html?205,53,bottom +fails-if(OSX>=1008&&!skiaContent) == downscale-2e.html?205,53,bottom downscale-2e.html?205,53,bottom + +== downscale-moz-icon-1.html downscale-moz-icon-1.html + +== downscale-png.html?16,16,interlaced downscale-png.html?16,16,interlaced +== downscale-png.html?24,24,interlaced downscale-png.html?24,24,interlaced + +# Non-transparent and transparent ICO images +random == downscale-16px.html?ff-0RGB.ico downscale-16px.html?ff-0RGB.ico +random == downscale-16px.html?ff-ARGB.ico downscale-16px.html?ff-ARGB.ico + +# Upside-down (negative height) BMP +random == downscale-8px.html?top-to-bottom-16x16-24bpp.bmp downscale-8px.html?top-to-bottom-16x16-24bpp.bmp + +# Test downscaling from all supported formats from 256 to 32. +== downscale-32px.html?.bmp downscale-32px.html?.bmp +== downscale-32px.html?.gif downscale-32px.html?.gif +== downscale-32px.html?.jpg downscale-32px.html?.jpg +== downscale-32px.html?.png downscale-32px.html?.png +== downscale-32px.html?.svg downscale-32px.html?.svg +== downscale-32px.html?-bmp-in.ico downscale-32px.html?-bmp-in.ico +== downscale-32px.html?-png-in.ico downscale-32px.html?-png-in.ico + +# RUN TESTS WITH DOWNSCALE-DURING-DECODE ENABLED: +# # +default-preferences pref(image.downscale-during-decode.enabled,true) + +fuzzy-if(d2d,31,147) == downscale-1.html downscale-1.html +# intermittently 147 pixels on win7 accelerated only (not win8) + +== downscale-2a.html?203,52,left downscale-2a.html?203,52,left +== downscale-2b.html?203,52,left downscale-2b.html?203,52,left +skip == downscale-2c.html?203,52,left downscale-2c.html?203,52,left +== downscale-2d.html?203,52,left downscale-2d.html?203,52,left +== downscale-2e.html?203,52,left downscale-2e.html?203,52,left +== downscale-2f.html?203,52,left downscale-2f.html?203,52,left + +== downscale-2a.html?205,53,left downscale-2a.html?205,53,left +== downscale-2b.html?205,53,left downscale-2b.html?205,53,left +== downscale-2c.html?205,53,left downscale-2c.html?205,53,left +skip == downscale-2d.html?205,53,left downscale-2d.html?205,53,left +== downscale-2e.html?205,53,left downscale-2e.html?205,53,left +== downscale-2f.html?205,53,left downscale-2f.html?205,53,left + +== downscale-2a.html?203,52,right downscale-2a.html?203,52,right +== downscale-2b.html?203,52,right downscale-2b.html?203,52,right +== downscale-2c.html?203,52,right downscale-2c.html?203,52,right +== downscale-2d.html?203,52,right downscale-2d.html?203,52,right +== downscale-2e.html?203,52,right downscale-2e.html?203,52,right +== downscale-2f.html?203,52,right downscale-2f.html?203,52,right + +== downscale-2a.html?205,53,right downscale-2a.html?205,53,right +== downscale-2b.html?205,53,right downscale-2b.html?205,53,right +== downscale-2c.html?205,53,right downscale-2c.html?205,53,right +== downscale-2d.html?205,53,right downscale-2d.html?205,53,right +== downscale-2e.html?205,53,right downscale-2e.html?205,53,right +== downscale-2f.html?205,53,right downscale-2f.html?205,53,right + +== downscale-2a.html?203,52,top downscale-2a.html?203,52,top +== downscale-2b.html?203,52,top downscale-2b.html?203,52,top +== downscale-2c.html?203,52,top downscale-2c.html?203,52,top +skip == downscale-2d.html?203,52,top downscale-2d.html?203,52,top +== downscale-2e.html?203,52,top downscale-2e.html?203,52,top +== downscale-2f.html?203,52,top downscale-2f.html?203,52,top + +== downscale-2a.html?205,53,top downscale-2a.html?205,53,top +== downscale-2b.html?205,53,top downscale-2b.html?205,53,top +== downscale-2c.html?205,53,top downscale-2c.html?205,53,top +== downscale-2d.html?205,53,top downscale-2d.html?205,53,top +== downscale-2e.html?205,53,top downscale-2e.html?205,53,top +== downscale-2f.html?205,53,top downscale-2f.html?205,53,top + +== downscale-2a.html?203,52,bottom downscale-2a.html?203,52,bottom +== downscale-2b.html?203,52,bottom downscale-2b.html?203,52,bottom +== downscale-2c.html?203,52,bottom downscale-2c.html?203,52,bottom +== downscale-2d.html?203,52,bottom downscale-2d.html?203,52,bottom +skip == downscale-2e.html?203,52,bottom downscale-2e.html?203,52,bottom +== downscale-2f.html?203,52,bottom downscale-2f.html?203,52,bottom + +== downscale-2a.html?205,53,bottom downscale-2a.html?205,53,bottom +== downscale-2b.html?205,53,bottom downscale-2b.html?205,53,bottom +== downscale-2c.html?205,53,bottom downscale-2c.html?205,53,bottom +== downscale-2d.html?205,53,bottom downscale-2d.html?205,53,bottom +== downscale-2e.html?205,53,bottom downscale-2e.html?205,53,bottom +== downscale-2f.html?205,53,bottom downscale-2f.html?205,53,bottom + +== downscale-moz-icon-1.html downscale-moz-icon-1.html + +== downscale-png.html?16,16,interlaced downscale-png.html?16,16,interlaced +== downscale-png.html?24,24,interlaced downscale-png.html?24,24,interlaced + +# Non-transparent and transparent ICO images +random == downscale-16px.html?ff-0RGB.ico downscale-16px.html?ff-0RGB.ico +random == downscale-16px.html?ff-ARGB.ico downscale-16px.html?ff-ARGB.ico + +# Upside-down (negative height) BMP +random == downscale-8px.html?top-to-bottom-16x16-24bpp.bmp downscale-8px.html?top-to-bottom-16x16-24bpp.bmp + +# Test downscaling from all supported formats from 256 to 32. +== downscale-32px.html?.bmp downscale-32px.html?.bmp +== downscale-32px.html?.gif downscale-32px.html?.gif +== downscale-32px.html?.jpg downscale-32px.html?.jpg +== downscale-32px.html?.png downscale-32px.html?.png +== downscale-32px.html?.svg downscale-32px.html?.svg +== downscale-32px.html?-bmp-in.ico downscale-32px.html?-bmp-in.ico +== downscale-32px.html?-png-in.ico downscale-32px.html?-png-in.ico diff --git a/image/test/reftest/downscaling/reftest.list b/image/test/reftest/downscaling/reftest.list new file mode 100644 index 000000000..300edd81b --- /dev/null +++ b/image/test/reftest/downscaling/reftest.list @@ -0,0 +1,193 @@ +# Reftests for downscaling +# +# Downscaling can be a lossy process, so a bit of mismatch is acceptable here, +# as long as it's barely noticable visually. When necessary, this can be +# explicitly allowed via 'fuzzy'/'fuzzy-if' annotations. +# +# Many of these tests check primarily that we don't lose rows or columns of +# pixels when downscaling by making sure that the result isn't too similar to +# about:blank. A small amount of fuzziness is used to ensure that the tests +# don't pass because of very slight deviations; passing tests should be +# substantially different from about:blank. This fuzziness should *not* be +# removed as doing so would make the tests pass in situations where they +# shouldn't. +# +# IMPORTANT: For robustness, each test should be listed *twice* in this +# manifest -- once with the high quality downscaling pref disabled, and once +# with this pref enabled. The pref is set via "default-preferences", so +# simply appending a new test to the lists below each of those lines should be +# sufficient. +# +# Also note that Mac OS X has its own system-level downscaling algorithm, so +# tests here may need Mac-specific "fuzzy-if(cocoaWidget,...)" annotations. +# Similarly, modern versions of Windows have slightly different downscaling +# behavior than other platforms, and may require "fuzzy-if(winWidget,...)". + + +# RUN TESTS NOT AFFECTED BY DOWNSCALE-DURING-DECODE: +# ================================================== +fuzzy-if(skiaContent,14,416) == downscale-svg-1a.html downscale-svg-1-ref.html?80 +fuzzy(80,468) == downscale-svg-1b.html downscale-svg-1-ref.html?72 +fuzzy-if(skiaContent,8,292) == downscale-svg-1c.html downscale-svg-1-ref.html?64 +fuzzy(17,208) == downscale-svg-1d.html downscale-svg-1-ref.html?53 +fuzzy(78,216) fuzzy-if(skiaContent,110,181) == downscale-svg-1e.html downscale-svg-1-ref.html?40 +fuzzy(51,90) fuzzy-if(skiaContent,142,77) == downscale-svg-1f.html downscale-svg-1-ref.html?24 + +# RUN TESTS WITH DOWNSCALE-DURING-DECODE DISABLED: +# ================================================ +default-preferences pref(image.downscale-during-decode.enabled,false) + +fuzzy-if(winWidget,16,20) fuzzy-if(cocoaWidget,106,31) == downscale-1.html downscale-1-ref.html + +fuzzy(20,999) != downscale-2a.html?203,52,left about:blank +fuzzy(20,999) != downscale-2b.html?203,52,left about:blank +fuzzy(20,999) != downscale-2c.html?203,52,left about:blank +fuzzy(20,999) != downscale-2d.html?203,52,left about:blank +fuzzy(20,999) != downscale-2e.html?203,52,left about:blank + +fuzzy(20,999) != downscale-2a.html?205,53,left about:blank +fuzzy(20,999) != downscale-2b.html?205,53,left about:blank +fuzzy(20,999) != downscale-2c.html?205,53,left about:blank +fuzzy(20,999) != downscale-2d.html?205,53,left about:blank +fuzzy(20,999) != downscale-2e.html?205,53,left about:blank + +fuzzy(20,999) != downscale-2a.html?203,52,right about:blank +fuzzy(20,999) != downscale-2b.html?203,52,right about:blank +fuzzy(20,999) != downscale-2c.html?203,52,right about:blank +fuzzy(20,999) != downscale-2d.html?203,52,right about:blank +fuzzy(20,999) != downscale-2e.html?203,52,right about:blank + +fuzzy(20,999) != downscale-2a.html?205,53,right about:blank +fuzzy(20,999) != downscale-2b.html?205,53,right about:blank +fuzzy(20,999) != downscale-2c.html?205,53,right about:blank +fuzzy(20,999) != downscale-2d.html?205,53,right about:blank +fuzzy(20,999) != downscale-2e.html?205,53,right about:blank + +fuzzy(20,999) != downscale-2a.html?203,52,top about:blank +fuzzy(20,999) != downscale-2b.html?203,52,top about:blank +fuzzy(20,999) != downscale-2c.html?203,52,top about:blank +fuzzy(20,999) != downscale-2d.html?203,52,top about:blank +fuzzy(20,999) != downscale-2e.html?203,52,top about:blank + +fuzzy(20,999) != downscale-2a.html?205,53,top about:blank +fuzzy(20,999) != downscale-2b.html?205,53,top about:blank +fuzzy(20,999) != downscale-2c.html?205,53,top about:blank +fuzzy(20,999) != downscale-2d.html?205,53,top about:blank +fuzzy(20,999) != downscale-2e.html?205,53,top about:blank + +fuzzy(20,999) != downscale-2a.html?203,52,bottom about:blank +fuzzy(20,999) != downscale-2b.html?203,52,bottom about:blank +fuzzy(20,999) != downscale-2c.html?203,52,bottom about:blank +fuzzy(20,999) != downscale-2d.html?203,52,bottom about:blank +fuzzy(20,999) != downscale-2e.html?203,52,bottom about:blank + +fuzzy(20,999) != downscale-2a.html?205,53,bottom about:blank +fuzzy(20,999) != downscale-2b.html?205,53,bottom about:blank +fuzzy(20,999) != downscale-2c.html?205,53,bottom about:blank +fuzzy(20,999) != downscale-2d.html?205,53,bottom about:blank +fuzzy(20,999) fails-if(OSX>=1008&&!skiaContent) != downscale-2e.html?205,53,bottom about:blank + +fuzzy(63,3391) == downscale-moz-icon-1.html downscale-moz-icon-1-ref.html + +== downscale-png.html?16,16,interlaced downscale-png.html?16,16,normal +== downscale-png.html?24,24,interlaced downscale-png.html?24,24,normal + +# Non-transparent and transparent ICO images +== downscale-16px.html?ff-0RGB.ico downscale-16px.html?ff-0RGB.png +fuzzy(1,1) == downscale-16px.html?ff-ARGB.ico downscale-16px.html?ff-ARGB.png + +# Upside-down (negative height) BMP +== downscale-8px.html?top-to-bottom-16x16-24bpp.bmp downscale-8px.html?bmp-size-16x16-24bpp.png + +# Test downscaling from all supported formats from 256 to 32. +== downscale-32px.html?.bmp downscale-32px-ref.html +== downscale-32px.html?.gif downscale-32px-ref.html +fuzzy(1,1024) == downscale-32px.html?.jpg downscale-32px-ref.html +== downscale-32px.html?.png downscale-32px-ref.html +== downscale-32px.html?.svg downscale-32px-ref.html +== downscale-32px.html?-bmp-in.ico downscale-32px-ref.html +== downscale-32px.html?-png-in.ico downscale-32px-ref.html + +# RUN TESTS WITH DOWNSCALE-DURING-DECODE ENABLED: +# =============================================== +default-preferences pref(image.downscale-during-decode.enabled,true) + +fuzzy(31,127) fuzzy-if(d2d,31,147) == downscale-1.html downscale-1-ref.html # intermittently 147 pixels on win7 accelerated only (not win8) + +fuzzy(20,999) != downscale-2a.html?203,52,left about:blank +fuzzy(20,999) != downscale-2b.html?203,52,left about:blank +fuzzy(20,999) != downscale-2c.html?203,52,left about:blank +fuzzy(20,999) != downscale-2d.html?203,52,left about:blank +fuzzy(20,999) != downscale-2e.html?203,52,left about:blank +fuzzy(20,999) != downscale-2f.html?203,52,left about:blank + +fuzzy(20,999) != downscale-2a.html?205,53,left about:blank +fuzzy(20,999) != downscale-2b.html?205,53,left about:blank +fuzzy(20,999) != downscale-2c.html?205,53,left about:blank +fuzzy(20,999) != downscale-2d.html?205,53,left about:blank +fuzzy(20,999) != downscale-2e.html?205,53,left about:blank +fuzzy(20,999) != downscale-2f.html?205,53,left about:blank + +fuzzy(20,999) != downscale-2a.html?203,52,right about:blank +fuzzy(20,999) != downscale-2b.html?203,52,right about:blank +fuzzy(20,999) != downscale-2c.html?203,52,right about:blank +fuzzy(20,999) != downscale-2d.html?203,52,right about:blank +fuzzy(20,999) != downscale-2e.html?203,52,right about:blank +fuzzy(20,999) != downscale-2f.html?203,52,right about:blank + +fuzzy(20,999) != downscale-2a.html?205,53,right about:blank +fuzzy(20,999) != downscale-2b.html?205,53,right about:blank +fuzzy(20,999) != downscale-2c.html?205,53,right about:blank +fuzzy(20,999) != downscale-2d.html?205,53,right about:blank +fuzzy(20,999) != downscale-2e.html?205,53,right about:blank +fuzzy(20,999) != downscale-2f.html?205,53,right about:blank + +fuzzy(20,999) != downscale-2a.html?203,52,top about:blank +fuzzy(20,999) != downscale-2b.html?203,52,top about:blank +fuzzy(20,999) != downscale-2c.html?203,52,top about:blank +fuzzy(20,999) != downscale-2d.html?203,52,top about:blank +fuzzy(20,999) != downscale-2e.html?203,52,top about:blank +fuzzy(20,999) != downscale-2f.html?203,52,top about:blank + +fuzzy(20,999) != downscale-2a.html?205,53,top about:blank +fuzzy(20,999) != downscale-2b.html?205,53,top about:blank +fuzzy(20,999) != downscale-2c.html?205,53,top about:blank +fuzzy(20,999) != downscale-2d.html?205,53,top about:blank +fuzzy(20,999) != downscale-2e.html?205,53,top about:blank +fuzzy(20,999) != downscale-2f.html?205,53,top about:blank + +fuzzy(20,999) != downscale-2a.html?203,52,bottom about:blank +fuzzy(20,999) != downscale-2b.html?203,52,bottom about:blank +fuzzy(20,999) != downscale-2c.html?203,52,bottom about:blank +fuzzy(20,999) != downscale-2d.html?203,52,bottom about:blank +fuzzy(20,999) != downscale-2e.html?203,52,bottom about:blank +fuzzy(20,999) != downscale-2f.html?203,52,bottom about:blank + +fuzzy(20,999) != downscale-2a.html?205,53,bottom about:blank +fuzzy(20,999) != downscale-2b.html?205,53,bottom about:blank +fuzzy(20,999) != downscale-2c.html?205,53,bottom about:blank +fuzzy(20,999) != downscale-2d.html?205,53,bottom about:blank +fuzzy(20,999) != downscale-2e.html?205,53,bottom about:blank +fuzzy(20,999) != downscale-2f.html?205,53,bottom about:blank + +# Skip on WinXP with skia content +fuzzy(71,4439) fails-if(/^Windows\x20NT\x205\.1/.test(http.oscpu)) == downscale-moz-icon-1.html downscale-moz-icon-1-ref.html + +== downscale-png.html?16,16,interlaced downscale-png.html?16,16,normal +== downscale-png.html?24,24,interlaced downscale-png.html?24,24,normal + +# Non-transparent and transparent ICO images +fuzzy(1,3) == downscale-16px.html?ff-0RGB.ico downscale-16px.html?ff-0RGB.png +fuzzy(3,32) == downscale-16px.html?ff-ARGB.ico downscale-16px.html?ff-ARGB.png + +# Upside-down (negative height) BMP +== downscale-8px.html?top-to-bottom-16x16-24bpp.bmp downscale-8px.html?bmp-size-16x16-24bpp.png + +# Test downscaling from all supported formats from 256 to 32. +fuzzy(18,128) == downscale-32px.html?.bmp downscale-32px-ref.html +fuzzy(18,128) == downscale-32px.html?.gif downscale-32px-ref.html +fuzzy(19,992) == downscale-32px.html?.jpg downscale-32px-ref.html +fuzzy(18,128) == downscale-32px.html?.png downscale-32px-ref.html +== downscale-32px.html?.svg downscale-32px-ref.html +fuzzy(18,128) == downscale-32px.html?-bmp-in.ico downscale-32px-ref.html +fuzzy(18,128) == downscale-32px.html?-png-in.ico downscale-32px-ref.html diff --git a/image/test/reftest/downscaling/top-to-bottom-16x16-24bpp.bmp b/image/test/reftest/downscaling/top-to-bottom-16x16-24bpp.bmp new file mode 100644 index 000000000..bd18f85d4 Binary files /dev/null and b/image/test/reftest/downscaling/top-to-bottom-16x16-24bpp.bmp differ diff --git a/image/test/reftest/encoders-lossless/ImageDocument.css b/image/test/reftest/encoders-lossless/ImageDocument.css new file mode 100644 index 000000000..9a41b4c16 --- /dev/null +++ b/image/test/reftest/encoders-lossless/ImageDocument.css @@ -0,0 +1,16 @@ +body { + background: #222 url("chrome://global/skin/media/imagedoc-darknoise.png"); + margin: 0; +} + +img { + display: block; + position: absolute; + margin: auto; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: hsl(0,0%,90%) url("chrome://global/skin/media/imagedoc-lightnoise.png"); + color: #222; +} diff --git a/image/test/reftest/encoders-lossless/encoder.html b/image/test/reftest/encoders-lossless/encoder.html new file mode 100644 index 000000000..6e07995ae --- /dev/null +++ b/image/test/reftest/encoders-lossless/encoder.html @@ -0,0 +1,113 @@ + + + Image reftest wrapper + + + + + + + + + + diff --git a/image/test/reftest/encoders-lossless/reftest-stylo.list b/image/test/reftest/encoders-lossless/reftest-stylo.list new file mode 100644 index 000000000..a2b36bcc4 --- /dev/null +++ b/image/test/reftest/encoders-lossless/reftest-stylo.list @@ -0,0 +1,160 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# Encoder ref tests +# These reftests must be run as HTTP because of canvas' origin-clean security +# file:// URLs are always considered from a different origin unless same URL +# +# The test will copy a PNG image to a canvas, then use canvas.toDataUrl to get +# the data, then set the data to a new image hence invoking the appropriate +# encoder. +# +# The tests should only be used with lossless encoders. +# +# Valid arguments for encoder.html in the query string: +# - img= +# - mime= +# - options= +# Example: +# encoder.html?img=escape(reference_image.png) +# &mime=escape(image/vnd.microsoft.icon) +# &options=escape(-moz-parse-options:bpp=24;format=png) + +# PNG +skip HTTP == size-1x1.png size-1x1.png +HTTP == size-2x2.png size-2x2.png +skip HTTP == size-3x3.png size-3x3.png +skip HTTP == size-4x4.png size-4x4.png +skip HTTP == size-5x5.png size-5x5.png +skip HTTP == size-6x6.png size-6x6.png +HTTP == size-7x7.png size-7x7.png +fails skip HTTP == size-8x8.png size-8x8.png +skip HTTP == size-9x9.png size-9x9.png +skip HTTP == size-15x15.png size-15x15.png +skip HTTP == size-16x16.png size-16x16.png +skip HTTP == size-17x17.png size-17x17.png +skip HTTP == size-31x31.png size-31x31.png +skip HTTP == size-32x32.png size-32x32.png +skip HTTP == size-33x33.png size-33x33.png + +# BMP using default parse options +skip HTTP == size-1x1.png size-1x1.png +HTTP == size-2x2.png size-2x2.png +skip HTTP == size-3x3.png size-3x3.png +skip HTTP == size-4x4.png size-4x4.png +skip HTTP == size-5x5.png size-5x5.png +skip HTTP == size-6x6.png size-6x6.png +HTTP == size-7x7.png size-7x7.png +fails skip HTTP == size-8x8.png size-8x8.png +skip HTTP == size-9x9.png size-9x9.png +skip HTTP == size-15x15.png size-15x15.png +skip HTTP == size-16x16.png size-16x16.png +skip HTTP == size-17x17.png size-17x17.png +skip HTTP == size-31x31.png size-31x31.png +skip HTTP == size-32x32.png size-32x32.png +skip HTTP == size-33x33.png size-33x33.png + +# BMP using image/bmp mime type and 32bpp parse options +skip HTTP == size-1x1.png size-1x1.png +HTTP == size-2x2.png size-2x2.png +skip HTTP == size-3x3.png size-3x3.png +skip HTTP == size-4x4.png size-4x4.png +skip HTTP == size-5x5.png size-5x5.png +skip HTTP == size-6x6.png size-6x6.png +HTTP == size-7x7.png size-7x7.png +fails skip HTTP == size-8x8.png size-8x8.png +skip HTTP == size-9x9.png size-9x9.png +skip HTTP == size-15x15.png size-15x15.png +skip HTTP == size-16x16.png size-16x16.png +skip HTTP == size-17x17.png size-17x17.png +skip HTTP == size-31x31.png size-31x31.png +skip HTTP == size-32x32.png size-32x32.png +skip HTTP == size-33x33.png size-33x33.png + +# BMP using image/bmp mime type and 24bpp parse options +skip HTTP == size-1x1.png size-1x1.png +HTTP == size-2x2.png size-2x2.png +skip HTTP == size-3x3.png size-3x3.png +skip HTTP == size-4x4.png size-4x4.png +skip HTTP == size-5x5.png size-5x5.png +skip HTTP == size-6x6.png size-6x6.png +HTTP == size-7x7.png size-7x7.png +fails skip HTTP == size-8x8.png size-8x8.png +skip HTTP == size-9x9.png size-9x9.png +skip HTTP == size-15x15.png size-15x15.png +skip HTTP == size-16x16.png size-16x16.png +skip HTTP == size-17x17.png size-17x17.png +skip HTTP == size-31x31.png size-31x31.png +skip HTTP == size-32x32.png size-32x32.png +skip HTTP == size-33x33.png size-33x33.png + +# ICO using default parse options +skip HTTP == size-1x1.png size-1x1.png +HTTP == size-2x2.png size-2x2.png +skip HTTP == size-3x3.png size-3x3.png +skip HTTP == size-4x4.png size-4x4.png +skip HTTP == size-5x5.png size-5x5.png +skip HTTP == size-6x6.png size-6x6.png +HTTP == size-7x7.png size-7x7.png +fails skip HTTP == size-8x8.png size-8x8.png +skip HTTP == size-9x9.png size-9x9.png +skip HTTP == size-15x15.png size-15x15.png +skip HTTP == size-16x16.png size-16x16.png +skip HTTP == size-17x17.png size-17x17.png +skip HTTP == size-31x31.png size-31x31.png +skip HTTP == size-32x32.png size-32x32.png +skip HTTP == size-33x33.png size-33x33.png +# skip HTTP == size-256x256.png size-256x256.png + +# ICO using image/vnd.microsoft.icon mime type and 32bpp parse options with bmp +skip HTTP == size-1x1.png size-1x1.png +HTTP == size-2x2.png size-2x2.png +skip HTTP == size-3x3.png size-3x3.png +skip HTTP == size-4x4.png size-4x4.png +skip HTTP == size-5x5.png size-5x5.png +skip HTTP == size-6x6.png size-6x6.png +HTTP == size-7x7.png size-7x7.png +fails skip HTTP == size-8x8.png size-8x8.png +skip HTTP == size-9x9.png size-9x9.png +skip HTTP == size-15x15.png size-15x15.png +skip HTTP == size-16x16.png size-16x16.png +skip HTTP == size-17x17.png size-17x17.png +skip HTTP == size-31x31.png size-31x31.png +skip HTTP == size-32x32.png size-32x32.png +skip HTTP == size-33x33.png size-33x33.png +# skip HTTP == size-256x256.png size-256x256.png + +# ICO using image/vnd.microsoft.icon mime type and 24bpp parse options with bmp +skip HTTP == size-1x1.png size-1x1.png +HTTP == size-2x2.png size-2x2.png +skip HTTP == size-3x3.png size-3x3.png +skip HTTP == size-4x4.png size-4x4.png +skip HTTP == size-5x5.png size-5x5.png +skip HTTP == size-6x6.png size-6x6.png +HTTP == size-7x7.png size-7x7.png +fails skip HTTP == size-8x8.png size-8x8.png +skip HTTP == size-9x9.png size-9x9.png +skip HTTP == size-15x15.png size-15x15.png +skip HTTP == size-16x16.png size-16x16.png +skip HTTP == size-17x17.png size-17x17.png +skip HTTP == size-31x31.png size-31x31.png +skip HTTP == size-32x32.png size-32x32.png +skip HTTP == size-33x33.png size-33x33.png +# skip HTTP == size-256x256.png size-256x256.png + +# ICO using image/vnd.microsoft.icon mime type png +skip HTTP == size-1x1.png size-1x1.png +HTTP == size-2x2.png size-2x2.png +skip HTTP == size-3x3.png size-3x3.png +skip HTTP == size-4x4.png size-4x4.png +skip HTTP == size-5x5.png size-5x5.png +skip HTTP == size-6x6.png size-6x6.png +HTTP == size-7x7.png size-7x7.png +fails skip HTTP == size-8x8.png size-8x8.png +skip HTTP == size-9x9.png size-9x9.png +skip HTTP == size-15x15.png size-15x15.png +skip HTTP == size-16x16.png size-16x16.png +skip HTTP == size-17x17.png size-17x17.png +skip HTTP == size-31x31.png size-31x31.png +skip HTTP == size-32x32.png size-32x32.png +skip HTTP == size-33x33.png size-33x33.png +# skip HTTP == size-256x256.png size-256x256.png + diff --git a/image/test/reftest/encoders-lossless/reftest.list b/image/test/reftest/encoders-lossless/reftest.list new file mode 100644 index 000000000..6cd96d0de --- /dev/null +++ b/image/test/reftest/encoders-lossless/reftest.list @@ -0,0 +1,159 @@ +# Encoder ref tests +# These reftests must be run as HTTP because of canvas' origin-clean security +# file:// URLs are always considered from a different origin unless same URL +# +# The test will copy a PNG image to a canvas, then use canvas.toDataUrl to get +# the data, then set the data to a new image hence invoking the appropriate +# encoder. +# +# The tests should only be used with lossless encoders. +# +# Valid arguments for encoder.html in the query string: +# - img= +# - mime= +# - options= +# Example: +# encoder.html?img=escape(reference_image.png) +# &mime=escape(image/vnd.microsoft.icon) +# &options=escape(-moz-parse-options:bpp=24;format=png) + +# PNG +HTTP == size-1x1.png encoder.html?img=size-1x1.png&mime=image/png +HTTP == size-2x2.png encoder.html?img=size-2x2.png&mime=image/png +HTTP == size-3x3.png encoder.html?img=size-3x3.png&mime=image/png +HTTP == size-4x4.png encoder.html?img=size-4x4.png&mime=image/png +HTTP == size-5x5.png encoder.html?img=size-5x5.png&mime=image/png +HTTP == size-6x6.png encoder.html?img=size-6x6.png&mime=image/png +HTTP == size-7x7.png encoder.html?img=size-7x7.png&mime=image/png +HTTP == size-8x8.png encoder.html?img=size-8x8.png&mime=image/png +HTTP == size-9x9.png encoder.html?img=size-9x9.png&mime=image/png +HTTP == size-15x15.png encoder.html?img=size-15x15.png&mime=image/png +HTTP == size-16x16.png encoder.html?img=size-16x16.png&mime=image/png +HTTP == size-17x17.png encoder.html?img=size-17x17.png&mime=image/png +HTTP == size-31x31.png encoder.html?img=size-31x31.png&mime=image/png +HTTP == size-32x32.png encoder.html?img=size-32x32.png&mime=image/png +HTTP == size-33x33.png encoder.html?img=size-33x33.png&mime=image/png + +# BMP using default parse options +HTTP == size-1x1.png encoder.html?img=size-1x1.png&mime=image/bmp +HTTP == size-2x2.png encoder.html?img=size-2x2.png&mime=image/bmp +HTTP == size-3x3.png encoder.html?img=size-3x3.png&mime=image/bmp +HTTP == size-4x4.png encoder.html?img=size-4x4.png&mime=image/bmp +HTTP == size-5x5.png encoder.html?img=size-5x5.png&mime=image/bmp +HTTP == size-6x6.png encoder.html?img=size-6x6.png&mime=image/bmp +HTTP == size-7x7.png encoder.html?img=size-7x7.png&mime=image/bmp +HTTP == size-8x8.png encoder.html?img=size-8x8.png&mime=image/bmp +HTTP == size-9x9.png encoder.html?img=size-9x9.png&mime=image/bmp +HTTP == size-15x15.png encoder.html?img=size-15x15.png&mime=image/bmp +HTTP == size-16x16.png encoder.html?img=size-16x16.png&mime=image/bmp +HTTP == size-17x17.png encoder.html?img=size-17x17.png&mime=image/bmp +HTTP == size-31x31.png encoder.html?img=size-31x31.png&mime=image/bmp +HTTP == size-32x32.png encoder.html?img=size-32x32.png&mime=image/bmp +HTTP == size-33x33.png encoder.html?img=size-33x33.png&mime=image/bmp + +# BMP using image/bmp mime type and 32bpp parse options +HTTP == size-1x1.png encoder.html?img=size-1x1.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D32 +HTTP == size-2x2.png encoder.html?img=size-2x2.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D32 +HTTP == size-3x3.png encoder.html?img=size-3x3.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D32 +HTTP == size-4x4.png encoder.html?img=size-4x4.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D32 +HTTP == size-5x5.png encoder.html?img=size-5x5.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D32 +HTTP == size-6x6.png encoder.html?img=size-6x6.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D32 +HTTP == size-7x7.png encoder.html?img=size-7x7.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D32 +HTTP == size-8x8.png encoder.html?img=size-8x8.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D32 +HTTP == size-9x9.png encoder.html?img=size-9x9.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D32 +HTTP == size-15x15.png encoder.html?img=size-15x15.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D32 +HTTP == size-16x16.png encoder.html?img=size-16x16.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D32 +HTTP == size-17x17.png encoder.html?img=size-17x17.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D32 +HTTP == size-31x31.png encoder.html?img=size-31x31.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D32 +HTTP == size-32x32.png encoder.html?img=size-32x32.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D32 +HTTP == size-33x33.png encoder.html?img=size-33x33.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D32 + +# BMP using image/bmp mime type and 24bpp parse options +HTTP == size-1x1.png encoder.html?img=size-1x1.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D24 +HTTP == size-2x2.png encoder.html?img=size-2x2.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D24 +HTTP == size-3x3.png encoder.html?img=size-3x3.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D24 +HTTP == size-4x4.png encoder.html?img=size-4x4.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D24 +HTTP == size-5x5.png encoder.html?img=size-5x5.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D24 +HTTP == size-6x6.png encoder.html?img=size-6x6.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D24 +HTTP == size-7x7.png encoder.html?img=size-7x7.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D24 +HTTP == size-8x8.png encoder.html?img=size-8x8.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D24 +HTTP == size-9x9.png encoder.html?img=size-9x9.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D24 +HTTP == size-15x15.png encoder.html?img=size-15x15.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D24 +HTTP == size-16x16.png encoder.html?img=size-16x16.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D24 +HTTP == size-17x17.png encoder.html?img=size-17x17.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D24 +HTTP == size-31x31.png encoder.html?img=size-31x31.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D24 +HTTP == size-32x32.png encoder.html?img=size-32x32.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D24 +HTTP == size-33x33.png encoder.html?img=size-33x33.png&mime=image/bmp&options=-moz-parse-options%3Abpp%3D24 + +# ICO using default parse options +HTTP == size-1x1.png encoder.html?img=size-1x1.png&mime=image/vnd.microsoft.icon +HTTP == size-2x2.png encoder.html?img=size-2x2.png&mime=image/vnd.microsoft.icon +HTTP == size-3x3.png encoder.html?img=size-3x3.png&mime=image/vnd.microsoft.icon +HTTP == size-4x4.png encoder.html?img=size-4x4.png&mime=image/vnd.microsoft.icon +HTTP == size-5x5.png encoder.html?img=size-5x5.png&mime=image/vnd.microsoft.icon +HTTP == size-6x6.png encoder.html?img=size-6x6.png&mime=image/vnd.microsoft.icon +HTTP == size-7x7.png encoder.html?img=size-7x7.png&mime=image/vnd.microsoft.icon +HTTP == size-8x8.png encoder.html?img=size-8x8.png&mime=image/vnd.microsoft.icon +HTTP == size-9x9.png encoder.html?img=size-9x9.png&mime=image/vnd.microsoft.icon +HTTP == size-15x15.png encoder.html?img=size-15x15.png&mime=image/vnd.microsoft.icon +HTTP == size-16x16.png encoder.html?img=size-16x16.png&mime=image/vnd.microsoft.icon +HTTP == size-17x17.png encoder.html?img=size-17x17.png&mime=image/vnd.microsoft.icon +HTTP == size-31x31.png encoder.html?img=size-31x31.png&mime=image/vnd.microsoft.icon +HTTP == size-32x32.png encoder.html?img=size-32x32.png&mime=image/vnd.microsoft.icon +HTTP == size-33x33.png encoder.html?img=size-33x33.png&mime=image/vnd.microsoft.icon +HTTP == size-256x256.png encoder.html?img=size-256x256.png&mime=image/vnd.microsoft.icon + +# ICO using image/vnd.microsoft.icon mime type and 32bpp parse options with bmp +HTTP == size-1x1.png encoder.html?img=size-1x1.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D32%3Bformat%3Dbmp +HTTP == size-2x2.png encoder.html?img=size-2x2.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D32%3Bformat%3Dbmp +HTTP == size-3x3.png encoder.html?img=size-3x3.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D32%3Bformat%3Dbmp +HTTP == size-4x4.png encoder.html?img=size-4x4.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D32%3Bformat%3Dbmp +HTTP == size-5x5.png encoder.html?img=size-5x5.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D32%3Bformat%3Dbmp +HTTP == size-6x6.png encoder.html?img=size-6x6.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D32%3Bformat%3Dbmp +HTTP == size-7x7.png encoder.html?img=size-7x7.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D32%3Bformat%3Dbmp +HTTP == size-8x8.png encoder.html?img=size-8x8.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D32%3Bformat%3Dbmp +HTTP == size-9x9.png encoder.html?img=size-9x9.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D32%3Bformat%3Dbmp +HTTP == size-15x15.png encoder.html?img=size-15x15.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D32%3Bformat%3Dbmp +HTTP == size-16x16.png encoder.html?img=size-16x16.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D32%3Bformat%3Dbmp +HTTP == size-17x17.png encoder.html?img=size-17x17.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D32%3Bformat%3Dbmp +HTTP == size-31x31.png encoder.html?img=size-31x31.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D32%3Bformat%3Dbmp +HTTP == size-32x32.png encoder.html?img=size-32x32.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D32%3Bformat%3Dbmp +HTTP == size-33x33.png encoder.html?img=size-33x33.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D32%3Bformat%3Dbmp +HTTP == size-256x256.png encoder.html?img=size-256x256.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D32%3Bformat%3Dbmp + +# ICO using image/vnd.microsoft.icon mime type and 24bpp parse options with bmp +HTTP == size-1x1.png encoder.html?img=size-1x1.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D24%3Bformat%3Dbmp +HTTP == size-2x2.png encoder.html?img=size-2x2.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D24%3Bformat%3Dbmp +HTTP == size-3x3.png encoder.html?img=size-3x3.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D24%3Bformat%3Dbmp +HTTP == size-4x4.png encoder.html?img=size-4x4.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D24%3Bformat%3Dbmp +HTTP == size-5x5.png encoder.html?img=size-5x5.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D24%3Bformat%3Dbmp +HTTP == size-6x6.png encoder.html?img=size-6x6.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D24%3Bformat%3Dbmp +HTTP == size-7x7.png encoder.html?img=size-7x7.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D24%3Bformat%3Dbmp +HTTP == size-8x8.png encoder.html?img=size-8x8.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D24%3Bformat%3Dbmp +HTTP == size-9x9.png encoder.html?img=size-9x9.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D24%3Bformat%3Dbmp +HTTP == size-15x15.png encoder.html?img=size-15x15.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D24%3Bformat%3Dbmp +HTTP == size-16x16.png encoder.html?img=size-16x16.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D24%3Bformat%3Dbmp +HTTP == size-17x17.png encoder.html?img=size-17x17.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D24%3Bformat%3Dbmp +HTTP == size-31x31.png encoder.html?img=size-31x31.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D24%3Bformat%3Dbmp +HTTP == size-32x32.png encoder.html?img=size-32x32.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D24%3Bformat%3Dbmp +HTTP == size-33x33.png encoder.html?img=size-33x33.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D24%3Bformat%3Dbmp +HTTP == size-256x256.png encoder.html?img=size-256x256.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Abpp%3D24%3Bformat%3Dbmp + +# ICO using image/vnd.microsoft.icon mime type png +HTTP == size-1x1.png encoder.html?img=size-1x1.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Aformat%3Dpng +HTTP == size-2x2.png encoder.html?img=size-2x2.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Aformat%3Dpng +HTTP == size-3x3.png encoder.html?img=size-3x3.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Aformat%3Dpng +HTTP == size-4x4.png encoder.html?img=size-4x4.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Aformat%3Dpng +HTTP == size-5x5.png encoder.html?img=size-5x5.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Aformat%3Dpng +HTTP == size-6x6.png encoder.html?img=size-6x6.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Aformat%3Dpng +HTTP == size-7x7.png encoder.html?img=size-7x7.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Aformat%3Dpng +HTTP == size-8x8.png encoder.html?img=size-8x8.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Aformat%3Dpng +HTTP == size-9x9.png encoder.html?img=size-9x9.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Aformat%3Dpng +HTTP == size-15x15.png encoder.html?img=size-15x15.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Aformat%3Dpng +HTTP == size-16x16.png encoder.html?img=size-16x16.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Aformat%3Dpng +HTTP == size-17x17.png encoder.html?img=size-17x17.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Aformat%3Dpng +HTTP == size-31x31.png encoder.html?img=size-31x31.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Aformat%3Dpng +HTTP == size-32x32.png encoder.html?img=size-32x32.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Aformat%3Dpng +HTTP == size-33x33.png encoder.html?img=size-33x33.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Aformat%3Dpng +HTTP == size-256x256.png encoder.html?img=size-256x256.png&mime=image/vnd.microsoft.icon&options=-moz-parse-options%3Aformat%3Dpng + diff --git a/image/test/reftest/encoders-lossless/size-15x15.png b/image/test/reftest/encoders-lossless/size-15x15.png new file mode 100644 index 000000000..e1287430d Binary files /dev/null and b/image/test/reftest/encoders-lossless/size-15x15.png differ diff --git a/image/test/reftest/encoders-lossless/size-16x16.png b/image/test/reftest/encoders-lossless/size-16x16.png new file mode 100644 index 000000000..c04869e72 Binary files /dev/null and b/image/test/reftest/encoders-lossless/size-16x16.png differ diff --git a/image/test/reftest/encoders-lossless/size-17x17.png b/image/test/reftest/encoders-lossless/size-17x17.png new file mode 100644 index 000000000..00fb8e4f3 Binary files /dev/null and b/image/test/reftest/encoders-lossless/size-17x17.png differ diff --git a/image/test/reftest/encoders-lossless/size-1x1.png b/image/test/reftest/encoders-lossless/size-1x1.png new file mode 100644 index 000000000..c05f5fef8 Binary files /dev/null and b/image/test/reftest/encoders-lossless/size-1x1.png differ diff --git a/image/test/reftest/encoders-lossless/size-256x256.png b/image/test/reftest/encoders-lossless/size-256x256.png new file mode 100644 index 000000000..84bfada76 Binary files /dev/null and b/image/test/reftest/encoders-lossless/size-256x256.png differ diff --git a/image/test/reftest/encoders-lossless/size-2x2.png b/image/test/reftest/encoders-lossless/size-2x2.png new file mode 100644 index 000000000..e512d3f9b Binary files /dev/null and b/image/test/reftest/encoders-lossless/size-2x2.png differ diff --git a/image/test/reftest/encoders-lossless/size-31x31.png b/image/test/reftest/encoders-lossless/size-31x31.png new file mode 100644 index 000000000..e4a864251 Binary files /dev/null and b/image/test/reftest/encoders-lossless/size-31x31.png differ diff --git a/image/test/reftest/encoders-lossless/size-32x32.png b/image/test/reftest/encoders-lossless/size-32x32.png new file mode 100644 index 000000000..3a6fbe8ee Binary files /dev/null and b/image/test/reftest/encoders-lossless/size-32x32.png differ diff --git a/image/test/reftest/encoders-lossless/size-33x33.png b/image/test/reftest/encoders-lossless/size-33x33.png new file mode 100644 index 000000000..72ef7eb63 Binary files /dev/null and b/image/test/reftest/encoders-lossless/size-33x33.png differ diff --git a/image/test/reftest/encoders-lossless/size-3x3.png b/image/test/reftest/encoders-lossless/size-3x3.png new file mode 100644 index 000000000..cb42ec4f8 Binary files /dev/null and b/image/test/reftest/encoders-lossless/size-3x3.png differ diff --git a/image/test/reftest/encoders-lossless/size-4x4.png b/image/test/reftest/encoders-lossless/size-4x4.png new file mode 100644 index 000000000..e6afafd89 Binary files /dev/null and b/image/test/reftest/encoders-lossless/size-4x4.png differ diff --git a/image/test/reftest/encoders-lossless/size-5x5.png b/image/test/reftest/encoders-lossless/size-5x5.png new file mode 100644 index 000000000..a844aff76 Binary files /dev/null and b/image/test/reftest/encoders-lossless/size-5x5.png differ diff --git a/image/test/reftest/encoders-lossless/size-6x6.png b/image/test/reftest/encoders-lossless/size-6x6.png new file mode 100644 index 000000000..415c2d9c6 Binary files /dev/null and b/image/test/reftest/encoders-lossless/size-6x6.png differ diff --git a/image/test/reftest/encoders-lossless/size-7x7.png b/image/test/reftest/encoders-lossless/size-7x7.png new file mode 100644 index 000000000..ab2f89274 Binary files /dev/null and b/image/test/reftest/encoders-lossless/size-7x7.png differ diff --git a/image/test/reftest/encoders-lossless/size-8x8.png b/image/test/reftest/encoders-lossless/size-8x8.png new file mode 100644 index 000000000..fe2ff40a1 Binary files /dev/null and b/image/test/reftest/encoders-lossless/size-8x8.png differ diff --git a/image/test/reftest/encoders-lossless/size-9x9.png b/image/test/reftest/encoders-lossless/size-9x9.png new file mode 100644 index 000000000..18ab4b25d Binary files /dev/null and b/image/test/reftest/encoders-lossless/size-9x9.png differ diff --git a/image/test/reftest/encoders-lossless/test.png b/image/test/reftest/encoders-lossless/test.png new file mode 100644 index 000000000..3a6fbe8ee Binary files /dev/null and b/image/test/reftest/encoders-lossless/test.png differ diff --git a/image/test/reftest/generic/accept-image-catchall-ref.html b/image/test/reftest/generic/accept-image-catchall-ref.html new file mode 100644 index 000000000..32daa9f5d --- /dev/null +++ b/image/test/reftest/generic/accept-image-catchall-ref.html @@ -0,0 +1,12 @@ + + + + + Accept: header should include image/* catchall + + + + + diff --git a/image/test/reftest/generic/accept-image-catchall.html b/image/test/reftest/generic/accept-image-catchall.html new file mode 100644 index 000000000..bcacd9fd8 --- /dev/null +++ b/image/test/reftest/generic/accept-image-catchall.html @@ -0,0 +1,13 @@ + + + + + Accept: header should include */* catchall + + + + + diff --git a/image/test/reftest/generic/check-header.sjs b/image/test/reftest/generic/check-header.sjs new file mode 100644 index 000000000..9e905bc1c --- /dev/null +++ b/image/test/reftest/generic/check-header.sjs @@ -0,0 +1,72 @@ +const BinaryOutputStream = Components.Constructor("@mozilla.org/binaryoutputstream;1", "nsIBinaryOutputStream", "setOutputStream"); + +function isCatchall(v) +{ + // "*/*" exactly + return /^\*\/\*$/.test(v); +} + +/* +# Python used to generate the following byte array +def toHex(n): + if n < 16: return "0x" + hex(n)[2:].upper() + return "0x" + hex(n)[2:].upper() + +def hexFile(name): + f = open(name, "rb") + try: + while True: + print toHex(ord(f.read(1))) + ", ", + except: + pass + +hexFile("image/test/reftest/generic/green.png") +*/ + +const IMAGE_DATA = + [ + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, + 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, + 0x00, 0x64, 0x08, 0x02, 0x00, 0x00, 0x00, 0xFF, 0x80, 0x02, 0x03, + 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, + 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x9E, 0x49, 0x44, 0x41, 0x54, 0x78, + 0xDA, 0xED, 0xD0, 0x31, 0x01, 0x00, 0x00, 0x08, 0x03, 0xA0, 0x69, + 0xFF, 0xCE, 0x5A, 0xC1, 0xCF, 0x07, 0x22, 0x50, 0x99, 0x70, 0xD4, + 0x0A, 0x64, 0xC9, 0x92, 0x25, 0x4B, 0x96, 0x2C, 0x05, 0xB2, 0x64, + 0xC9, 0x92, 0x25, 0x4B, 0x96, 0x02, 0x59, 0xB2, 0x64, 0xC9, 0x92, + 0x25, 0x4B, 0x81, 0x2C, 0x59, 0xB2, 0x64, 0xC9, 0x92, 0xA5, 0x40, + 0x96, 0x2C, 0x59, 0xB2, 0x64, 0xC9, 0x52, 0x20, 0x4B, 0x96, 0x2C, + 0x59, 0xB2, 0x64, 0x29, 0x90, 0x25, 0x4B, 0x96, 0x2C, 0x59, 0xB2, + 0x14, 0xC8, 0x92, 0x25, 0x4B, 0x96, 0x2C, 0x59, 0x0A, 0x64, 0xC9, + 0x92, 0x25, 0x4B, 0x96, 0x2C, 0x05, 0xB2, 0x64, 0xC9, 0x92, 0x25, + 0x4B, 0x96, 0x02, 0x59, 0xB2, 0x64, 0xC9, 0x92, 0x25, 0x4B, 0x81, + 0x2C, 0x59, 0xB2, 0x64, 0xC9, 0x92, 0xA5, 0x40, 0x96, 0x2C, 0x59, + 0xB2, 0x64, 0xC9, 0x52, 0x20, 0x4B, 0x96, 0x2C, 0x59, 0xB2, 0x64, + 0x29, 0x90, 0x25, 0x4B, 0x96, 0x2C, 0x59, 0xB2, 0x14, 0xC8, 0x92, + 0x25, 0x4B, 0x96, 0x2C, 0x59, 0x0A, 0x64, 0xC9, 0xFA, 0xB6, 0x89, + 0x5F, 0x01, 0xC7, 0x24, 0x83, 0xB2, 0x0C, 0x00, 0x00, 0x00, 0x00, + 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, + ]; + +function handleRequest(request, response) +{ + response.setHeader("Content-Type", "text/plain", false); + response.setHeader("Cache-Control", "no-cache", false); + + var accept = request.hasHeader("Accept") + ? request.getHeader("Accept") + : ""; + + if (accept.split(",").some(isCatchall)) + { + response.setHeader("Content-Type", "image/png", false); + + var stream = new BinaryOutputStream(response.bodyOutputStream); + stream.writeByteArray(IMAGE_DATA, IMAGE_DATA.length); + } + else + { + response.setStatusLine(request.httpVersion, 404, "Not found"); + response.write("Accept header contained: " + accept); + } +} diff --git a/image/test/reftest/generic/green.png b/image/test/reftest/generic/green.png new file mode 100644 index 000000000..4718c00e6 Binary files /dev/null and b/image/test/reftest/generic/green.png differ diff --git a/image/test/reftest/generic/reftest-stylo.list b/image/test/reftest/generic/reftest-stylo.list new file mode 100644 index 000000000..1c0cbd6d9 --- /dev/null +++ b/image/test/reftest/generic/reftest-stylo.list @@ -0,0 +1,2 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +HTTP == accept-image-catchall.html accept-image-catchall.html diff --git a/image/test/reftest/generic/reftest.list b/image/test/reftest/generic/reftest.list new file mode 100644 index 000000000..c8d850ccb --- /dev/null +++ b/image/test/reftest/generic/reftest.list @@ -0,0 +1 @@ +HTTP == accept-image-catchall.html accept-image-catchall-ref.html diff --git a/image/test/reftest/gif/1bit-255-trans.gif b/image/test/reftest/gif/1bit-255-trans.gif new file mode 100644 index 000000000..60273ba81 Binary files /dev/null and b/image/test/reftest/gif/1bit-255-trans.gif differ diff --git a/image/test/reftest/gif/1bit-255-trans.png b/image/test/reftest/gif/1bit-255-trans.png new file mode 100644 index 000000000..611480ac4 Binary files /dev/null and b/image/test/reftest/gif/1bit-255-trans.png differ diff --git a/image/test/reftest/gif/ImageDocument.css b/image/test/reftest/gif/ImageDocument.css new file mode 100644 index 000000000..b44981098 --- /dev/null +++ b/image/test/reftest/gif/ImageDocument.css @@ -0,0 +1,16 @@ +body { + background-image: url("chrome://global/skin/media/imagedoc-darknoise.png"); + margin: 0; +} + +body > :first-child { + display: block; + position: absolute; + margin: auto; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: hsl(0,0%,90%) url("chrome://global/skin/media/imagedoc-lightnoise.png"); + color: #222; +} diff --git a/image/test/reftest/gif/animation1a.gif b/image/test/reftest/gif/animation1a.gif new file mode 100644 index 000000000..d32827654 Binary files /dev/null and b/image/test/reftest/gif/animation1a.gif differ diff --git a/image/test/reftest/gif/animation2a-finalframe.gif b/image/test/reftest/gif/animation2a-finalframe.gif new file mode 100644 index 000000000..8d9c4aaf7 Binary files /dev/null and b/image/test/reftest/gif/animation2a-finalframe.gif differ diff --git a/image/test/reftest/gif/animation2a.gif b/image/test/reftest/gif/animation2a.gif new file mode 100644 index 000000000..07abf8d98 Binary files /dev/null and b/image/test/reftest/gif/animation2a.gif differ diff --git a/image/test/reftest/gif/blue.gif b/image/test/reftest/gif/blue.gif new file mode 100644 index 000000000..f9dbeeea8 Binary files /dev/null and b/image/test/reftest/gif/blue.gif differ diff --git a/image/test/reftest/gif/comment.gif b/image/test/reftest/gif/comment.gif new file mode 100644 index 000000000..255cceb41 Binary files /dev/null and b/image/test/reftest/gif/comment.gif differ diff --git a/image/test/reftest/gif/comment.png b/image/test/reftest/gif/comment.png new file mode 100644 index 000000000..89394ba18 Binary files /dev/null and b/image/test/reftest/gif/comment.png differ diff --git a/image/test/reftest/gif/delaytest.html b/image/test/reftest/gif/delaytest.html new file mode 100644 index 000000000..58fb080f9 --- /dev/null +++ b/image/test/reftest/gif/delaytest.html @@ -0,0 +1,41 @@ + + + +Delayed image reftest wrapper + + + + + + diff --git a/image/test/reftest/gif/in-colormap-trans.gif b/image/test/reftest/gif/in-colormap-trans.gif new file mode 100644 index 000000000..48f5c7caf Binary files /dev/null and b/image/test/reftest/gif/in-colormap-trans.gif differ diff --git a/image/test/reftest/gif/in-colormap-trans.png b/image/test/reftest/gif/in-colormap-trans.png new file mode 100644 index 000000000..08761dfe4 Binary files /dev/null and b/image/test/reftest/gif/in-colormap-trans.png differ diff --git a/image/test/reftest/gif/one-color-offset-ref.gif b/image/test/reftest/gif/one-color-offset-ref.gif new file mode 100644 index 000000000..14a59ff47 Binary files /dev/null and b/image/test/reftest/gif/one-color-offset-ref.gif differ diff --git a/image/test/reftest/gif/one-color-offset.gif b/image/test/reftest/gif/one-color-offset.gif new file mode 100644 index 000000000..e6d7c4932 Binary files /dev/null and b/image/test/reftest/gif/one-color-offset.gif differ diff --git a/image/test/reftest/gif/out-of-colormap-trans.gif b/image/test/reftest/gif/out-of-colormap-trans.gif new file mode 100644 index 000000000..17e747c9b Binary files /dev/null and b/image/test/reftest/gif/out-of-colormap-trans.gif differ diff --git a/image/test/reftest/gif/out-of-colormap-trans.png b/image/test/reftest/gif/out-of-colormap-trans.png new file mode 100644 index 000000000..8d3eb581a Binary files /dev/null and b/image/test/reftest/gif/out-of-colormap-trans.png differ diff --git a/image/test/reftest/gif/red.gif b/image/test/reftest/gif/red.gif new file mode 100644 index 000000000..d3c32bae2 Binary files /dev/null and b/image/test/reftest/gif/red.gif differ diff --git a/image/test/reftest/gif/reftest-stylo.list b/image/test/reftest/gif/reftest-stylo.list new file mode 100644 index 000000000..5567ca61d --- /dev/null +++ b/image/test/reftest/gif/reftest-stylo.list @@ -0,0 +1,57 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# GIF tests + +# tests for bug 519589 +== 1bit-255-trans.gif 1bit-255-trans.gif +== in-colormap-trans.gif in-colormap-trans.gif +== out-of-colormap-trans.gif out-of-colormap-trans.gif + +# a GIF file that uses the comment extension +fails == comment.gif comment.gif + +# a GIF file with a background smaller than the size of the canvas +== small-background-size.gif small-background-size.gif +== small-background-size-2.gif small-background-size-2.gif + +# a transparent gif that disposes previous frames with clear; we must properly +# clear each frame to pass. +random == delaytest.html?transparent-animation.gif delaytest.html?transparent-animation.gif +# incorrect timing dependence (bug 558678) + +# test for bug 641198 +skip == test_bug641198.html test_bug641198.html +# Disabled; see bug 1120144. + +# Bug 1062886: a gif with a single color and an offset +== one-color-offset.gif one-color-offset.gif + +# Bug 1068230 +== tile-transform.html tile-transform.html + +# Bug 1234077 +== truncated-framerect.html truncated-framerect.html + +# webcam-simulacrum.mgif is a hand-edited file containing red.gif and blue.gif, +# concatenated together with the relevant headers for +# multipart/x-mixed-replace. Specifically, with the headers in +# webcam-simulacrum.mjpg^headers^, the web browser will get the following: +# +# HTTP 200 OK +# Content-Type: multipart/x-mixed-replace;boundary=BOUNDARYOMG +# +# --BOUNDARYOMG\r\n +# Content-Type: image/gif\r\n +# \r\n +# (no newline) +# --BOUNDARYOMG\r\n +# Content-Type: image/gif\r\n +# \r\n +# (no newline) +# --BOUNDARYOMG--\r\n +# +# (The boundary is arbitrary, and just has to be defined as something that +# won't be in the text of the contents themselves. --$(boundary)\r\n means +# "Here is the beginning of a boundary," and --$(boundary)-- means "All done +# sending you parts.") +skip-if(B2G) HTTP == webcam.html webcam.html +# bug 773482 diff --git a/image/test/reftest/gif/reftest.list b/image/test/reftest/gif/reftest.list new file mode 100644 index 000000000..75a0252b6 --- /dev/null +++ b/image/test/reftest/gif/reftest.list @@ -0,0 +1,29 @@ +# GIF tests + +# tests for bug 519589 +== 1bit-255-trans.gif 1bit-255-trans.png +== in-colormap-trans.gif in-colormap-trans.png +== out-of-colormap-trans.gif out-of-colormap-trans.png + +# a GIF file that uses the comment extension +== comment.gif comment.png + +# a GIF file with a background smaller than the size of the canvas +== small-background-size.gif small-background-size-ref.gif +== small-background-size-2.gif small-background-size-2-ref.gif + +# a transparent gif that disposes previous frames with clear; we must properly +# clear each frame to pass. +random == delaytest.html?transparent-animation.gif transparent-animation-finalframe.gif # incorrect timing dependence (bug 558678) + +# test for bug 641198 +skip == test_bug641198.html animation2a-finalframe.gif # Disabled; see bug 1120144. + +# Bug 1062886: a gif with a single color and an offset +== one-color-offset.gif one-color-offset-ref.gif + +# Bug 1068230 +== tile-transform.html tile-transform-ref.html + +# Bug 1234077 +== truncated-framerect.html truncated-framerect-ref.html diff --git a/image/test/reftest/gif/small-background-size-2-ref.gif b/image/test/reftest/gif/small-background-size-2-ref.gif new file mode 100644 index 000000000..b513c41aa Binary files /dev/null and b/image/test/reftest/gif/small-background-size-2-ref.gif differ diff --git a/image/test/reftest/gif/small-background-size-2.gif b/image/test/reftest/gif/small-background-size-2.gif new file mode 100644 index 000000000..a5e214767 Binary files /dev/null and b/image/test/reftest/gif/small-background-size-2.gif differ diff --git a/image/test/reftest/gif/small-background-size-ref.gif b/image/test/reftest/gif/small-background-size-ref.gif new file mode 100644 index 000000000..1b656ce43 Binary files /dev/null and b/image/test/reftest/gif/small-background-size-ref.gif differ diff --git a/image/test/reftest/gif/small-background-size.gif b/image/test/reftest/gif/small-background-size.gif new file mode 100644 index 000000000..8185eb71e Binary files /dev/null and b/image/test/reftest/gif/small-background-size.gif differ diff --git a/image/test/reftest/gif/test_bug641198.html b/image/test/reftest/gif/test_bug641198.html new file mode 100644 index 000000000..46bdb0d47 --- /dev/null +++ b/image/test/reftest/gif/test_bug641198.html @@ -0,0 +1,53 @@ + + + +Test for bug 641198 + + + + + +Animated + + + + + diff --git a/image/test/reftest/gif/tile-transform-ref.html b/image/test/reftest/gif/tile-transform-ref.html new file mode 100644 index 000000000..5dac1a5bd --- /dev/null +++ b/image/test/reftest/gif/tile-transform-ref.html @@ -0,0 +1,12 @@ + + + + + Intermediate surface should be transformed correctly when tiling an image + + + + + diff --git a/image/test/reftest/gif/tile-transform.html b/image/test/reftest/gif/tile-transform.html new file mode 100644 index 000000000..541ae6bbc --- /dev/null +++ b/image/test/reftest/gif/tile-transform.html @@ -0,0 +1,12 @@ + + + + + Intermediate surface should be transformed correctly when tiling an image + + + + + diff --git a/image/test/reftest/gif/tiletest-ref.png b/image/test/reftest/gif/tiletest-ref.png new file mode 100644 index 000000000..b493899cc Binary files /dev/null and b/image/test/reftest/gif/tiletest-ref.png differ diff --git a/image/test/reftest/gif/tiletest.gif b/image/test/reftest/gif/tiletest.gif new file mode 100644 index 000000000..7a04c9654 Binary files /dev/null and b/image/test/reftest/gif/tiletest.gif differ diff --git a/image/test/reftest/gif/transparent-animation-finalframe.gif b/image/test/reftest/gif/transparent-animation-finalframe.gif new file mode 100644 index 000000000..a55f92a81 Binary files /dev/null and b/image/test/reftest/gif/transparent-animation-finalframe.gif differ diff --git a/image/test/reftest/gif/transparent-animation.gif b/image/test/reftest/gif/transparent-animation.gif new file mode 100644 index 000000000..b2895487b Binary files /dev/null and b/image/test/reftest/gif/transparent-animation.gif differ diff --git a/image/test/reftest/gif/truncated-framerect-interlaced-ref.gif b/image/test/reftest/gif/truncated-framerect-interlaced-ref.gif new file mode 100644 index 000000000..ca9bf2fa7 Binary files /dev/null and b/image/test/reftest/gif/truncated-framerect-interlaced-ref.gif differ diff --git a/image/test/reftest/gif/truncated-framerect-interlaced.gif b/image/test/reftest/gif/truncated-framerect-interlaced.gif new file mode 100644 index 000000000..59709898b Binary files /dev/null and b/image/test/reftest/gif/truncated-framerect-interlaced.gif differ diff --git a/image/test/reftest/gif/truncated-framerect-ref.gif b/image/test/reftest/gif/truncated-framerect-ref.gif new file mode 100644 index 000000000..ab79a455b Binary files /dev/null and b/image/test/reftest/gif/truncated-framerect-ref.gif differ diff --git a/image/test/reftest/gif/truncated-framerect-ref.html b/image/test/reftest/gif/truncated-framerect-ref.html new file mode 100644 index 000000000..ef48b8a19 --- /dev/null +++ b/image/test/reftest/gif/truncated-framerect-ref.html @@ -0,0 +1,33 @@ + + + + + Bug 1234077 - Make sure GIFs still render correctly with a truncated frameRect + + + + + +
    + +
    + +
    + +
    + + diff --git a/image/test/reftest/gif/truncated-framerect.gif b/image/test/reftest/gif/truncated-framerect.gif new file mode 100644 index 000000000..8febb2a74 Binary files /dev/null and b/image/test/reftest/gif/truncated-framerect.gif differ diff --git a/image/test/reftest/gif/truncated-framerect.html b/image/test/reftest/gif/truncated-framerect.html new file mode 100644 index 000000000..c1c5df653 --- /dev/null +++ b/image/test/reftest/gif/truncated-framerect.html @@ -0,0 +1,28 @@ + + + + + Bug 1234077 - Make sure GIFs still render correctly with a truncated frameRect + + + + + +
    + +
    + +
    + +
    + + diff --git a/image/test/reftest/ico/cur/pointer.cur b/image/test/reftest/ico/cur/pointer.cur new file mode 100644 index 000000000..025ebaed1 Binary files /dev/null and b/image/test/reftest/ico/cur/pointer.cur differ diff --git a/image/test/reftest/ico/cur/pointer.png b/image/test/reftest/ico/cur/pointer.png new file mode 100644 index 000000000..84ad8f3fb Binary files /dev/null and b/image/test/reftest/ico/cur/pointer.png differ diff --git a/image/test/reftest/ico/cur/reftest-stylo.list b/image/test/reftest/ico/cur/reftest-stylo.list new file mode 100644 index 000000000..b59c26dc5 --- /dev/null +++ b/image/test/reftest/ico/cur/reftest-stylo.list @@ -0,0 +1,5 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# ICO BMP and PNG mixed tests + +skip == wrapper.html?pointer.cur wrapper.html?pointer.cur + diff --git a/image/test/reftest/ico/cur/reftest.list b/image/test/reftest/ico/cur/reftest.list new file mode 100644 index 000000000..635136506 --- /dev/null +++ b/image/test/reftest/ico/cur/reftest.list @@ -0,0 +1,4 @@ +# ICO BMP and PNG mixed tests + +== wrapper.html?pointer.cur wrapper.html?pointer.png + diff --git a/image/test/reftest/ico/cur/wrapper.html b/image/test/reftest/ico/cur/wrapper.html new file mode 100644 index 000000000..5bbe75e01 --- /dev/null +++ b/image/test/reftest/ico/cur/wrapper.html @@ -0,0 +1,27 @@ + + + +Image reftest wrapper + + + + + + + + + diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-not-square-transparent-1bpp.ico b/image/test/reftest/ico/ico-bmp-1bpp/ico-not-square-transparent-1bpp.ico new file mode 100644 index 000000000..0f51d504a Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-not-square-transparent-1bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-not-square-transparent-1bpp.png b/image/test/reftest/ico/ico-bmp-1bpp/ico-not-square-transparent-1bpp.png new file mode 100644 index 000000000..152b30d71 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-not-square-transparent-1bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-partial-transparent-1bpp.ico b/image/test/reftest/ico/ico-bmp-1bpp/ico-partial-transparent-1bpp.ico new file mode 100644 index 000000000..def2a4ece Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-partial-transparent-1bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-partial-transparent-1bpp.png b/image/test/reftest/ico/ico-bmp-1bpp/ico-partial-transparent-1bpp.png new file mode 100644 index 000000000..064a68bb2 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-partial-transparent-1bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-15x15-1bpp.ico b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-15x15-1bpp.ico new file mode 100644 index 000000000..b68cf0ef0 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-15x15-1bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-15x15-1bpp.png b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-15x15-1bpp.png new file mode 100644 index 000000000..956c78ece Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-15x15-1bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-16x16-1bpp.ico b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-16x16-1bpp.ico new file mode 100644 index 000000000..d96a4a0e1 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-16x16-1bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-16x16-1bpp.png b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-16x16-1bpp.png new file mode 100644 index 000000000..90088351f Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-16x16-1bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-17x17-1bpp.ico b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-17x17-1bpp.ico new file mode 100644 index 000000000..4f10ad13c Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-17x17-1bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-17x17-1bpp.png b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-17x17-1bpp.png new file mode 100644 index 000000000..9a294696c Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-17x17-1bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-1x1-1bpp.ico b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-1x1-1bpp.ico new file mode 100644 index 000000000..5af8bef61 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-1x1-1bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-1x1-1bpp.png b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-1x1-1bpp.png new file mode 100644 index 000000000..7a07a124e Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-1x1-1bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-256x256-1bpp.ico b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-256x256-1bpp.ico new file mode 100644 index 000000000..63d95e3b7 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-256x256-1bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-256x256-1bpp.png b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-256x256-1bpp.png new file mode 100644 index 000000000..0a23d8c8e Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-256x256-1bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-2x2-1bpp.ico b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-2x2-1bpp.ico new file mode 100644 index 000000000..09c140f1b Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-2x2-1bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-2x2-1bpp.png b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-2x2-1bpp.png new file mode 100644 index 000000000..3b09f8076 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-2x2-1bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-31x31-1bpp.ico b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-31x31-1bpp.ico new file mode 100644 index 000000000..bbfc3165a Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-31x31-1bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-31x31-1bpp.png b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-31x31-1bpp.png new file mode 100644 index 000000000..d1fe6ddee Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-31x31-1bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-32x32-1bpp.ico b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-32x32-1bpp.ico new file mode 100644 index 000000000..279ecb835 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-32x32-1bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-32x32-1bpp.png b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-32x32-1bpp.png new file mode 100644 index 000000000..078d3dc5d Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-32x32-1bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-33x33-1bpp.ico b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-33x33-1bpp.ico new file mode 100644 index 000000000..fa1862c1c Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-33x33-1bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-33x33-1bpp.png b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-33x33-1bpp.png new file mode 100644 index 000000000..e64e12b2a Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-33x33-1bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-3x3-1bpp.ico b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-3x3-1bpp.ico new file mode 100644 index 000000000..733b1f12b Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-3x3-1bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-3x3-1bpp.png b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-3x3-1bpp.png new file mode 100644 index 000000000..b8519a874 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-3x3-1bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-4x4-1bpp.ico b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-4x4-1bpp.ico new file mode 100644 index 000000000..ba3097cec Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-4x4-1bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-4x4-1bpp.png b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-4x4-1bpp.png new file mode 100644 index 000000000..3977b5454 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-4x4-1bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-5x5-1bpp.ico b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-5x5-1bpp.ico new file mode 100644 index 000000000..52e32df27 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-5x5-1bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-5x5-1bpp.png b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-5x5-1bpp.png new file mode 100644 index 000000000..caa9246b6 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-5x5-1bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-6x6-1bpp.ico b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-6x6-1bpp.ico new file mode 100644 index 000000000..c29651400 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-6x6-1bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-6x6-1bpp.png b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-6x6-1bpp.png new file mode 100644 index 000000000..30e1b0249 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-6x6-1bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-7x7-1bpp.ico b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-7x7-1bpp.ico new file mode 100644 index 000000000..8ce9915c4 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-7x7-1bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-7x7-1bpp.png b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-7x7-1bpp.png new file mode 100644 index 000000000..9dbaae84c Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-7x7-1bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-8x8-1bpp.ico b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-8x8-1bpp.ico new file mode 100644 index 000000000..485dff028 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-8x8-1bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-8x8-1bpp.png b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-8x8-1bpp.png new file mode 100644 index 000000000..220138840 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-8x8-1bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-9x9-1bpp.ico b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-9x9-1bpp.ico new file mode 100644 index 000000000..38f34ec50 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-9x9-1bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-size-9x9-1bpp.png b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-9x9-1bpp.png new file mode 100644 index 000000000..7fe1b548b Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-size-9x9-1bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-transparent-1bpp.ico b/image/test/reftest/ico/ico-bmp-1bpp/ico-transparent-1bpp.ico new file mode 100644 index 000000000..8e361306c Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-transparent-1bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/ico-transparent-1bpp.png b/image/test/reftest/ico/ico-bmp-1bpp/ico-transparent-1bpp.png new file mode 100644 index 000000000..062152e3b Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-1bpp/ico-transparent-1bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-1bpp/reftest-stylo.list b/image/test/reftest/ico/ico-bmp-1bpp/reftest-stylo.list new file mode 100644 index 000000000..43a597e78 --- /dev/null +++ b/image/test/reftest/ico/ico-bmp-1bpp/reftest-stylo.list @@ -0,0 +1,25 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# ICO BMP 1BPP tests + +# Images of various sizes +== ico-size-1x1-1bpp.ico ico-size-1x1-1bpp.ico +== ico-size-2x2-1bpp.ico ico-size-2x2-1bpp.ico +== ico-size-3x3-1bpp.ico ico-size-3x3-1bpp.ico +== ico-size-4x4-1bpp.ico ico-size-4x4-1bpp.ico +== ico-size-5x5-1bpp.ico ico-size-5x5-1bpp.ico +== ico-size-6x6-1bpp.ico ico-size-6x6-1bpp.ico +== ico-size-7x7-1bpp.ico ico-size-7x7-1bpp.ico +== ico-size-8x8-1bpp.ico ico-size-8x8-1bpp.ico +== ico-size-9x9-1bpp.ico ico-size-9x9-1bpp.ico +== ico-size-15x15-1bpp.ico ico-size-15x15-1bpp.ico +== ico-size-16x16-1bpp.ico ico-size-16x16-1bpp.ico +== ico-size-17x17-1bpp.ico ico-size-17x17-1bpp.ico +== ico-size-31x31-1bpp.ico ico-size-31x31-1bpp.ico +== ico-size-32x32-1bpp.ico ico-size-32x32-1bpp.ico +== ico-size-33x33-1bpp.ico ico-size-33x33-1bpp.ico +skip-if(B2G) == ico-size-256x256-1bpp.ico ico-size-256x256-1bpp.ico +# bug 773482 +== ico-partial-transparent-1bpp.ico ico-partial-transparent-1bpp.ico +== ico-transparent-1bpp.ico ico-transparent-1bpp.ico +== ico-not-square-transparent-1bpp.ico ico-not-square-transparent-1bpp.ico + diff --git a/image/test/reftest/ico/ico-bmp-1bpp/reftest.list b/image/test/reftest/ico/ico-bmp-1bpp/reftest.list new file mode 100644 index 000000000..1b9ca1348 --- /dev/null +++ b/image/test/reftest/ico/ico-bmp-1bpp/reftest.list @@ -0,0 +1,23 @@ +# ICO BMP 1BPP tests + +# Images of various sizes +== ico-size-1x1-1bpp.ico ico-size-1x1-1bpp.png +== ico-size-2x2-1bpp.ico ico-size-2x2-1bpp.png +== ico-size-3x3-1bpp.ico ico-size-3x3-1bpp.png +== ico-size-4x4-1bpp.ico ico-size-4x4-1bpp.png +== ico-size-5x5-1bpp.ico ico-size-5x5-1bpp.png +== ico-size-6x6-1bpp.ico ico-size-6x6-1bpp.png +== ico-size-7x7-1bpp.ico ico-size-7x7-1bpp.png +== ico-size-8x8-1bpp.ico ico-size-8x8-1bpp.png +== ico-size-9x9-1bpp.ico ico-size-9x9-1bpp.png +== ico-size-15x15-1bpp.ico ico-size-15x15-1bpp.png +== ico-size-16x16-1bpp.ico ico-size-16x16-1bpp.png +== ico-size-17x17-1bpp.ico ico-size-17x17-1bpp.png +== ico-size-31x31-1bpp.ico ico-size-31x31-1bpp.png +== ico-size-32x32-1bpp.ico ico-size-32x32-1bpp.png +== ico-size-33x33-1bpp.ico ico-size-33x33-1bpp.png +== ico-size-256x256-1bpp.ico ico-size-256x256-1bpp.png +== ico-partial-transparent-1bpp.ico ico-partial-transparent-1bpp.png +== ico-transparent-1bpp.ico ico-transparent-1bpp.png +== ico-not-square-transparent-1bpp.ico ico-not-square-transparent-1bpp.png + diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-not-square-transparent-24bpp.ico b/image/test/reftest/ico/ico-bmp-24bpp/ico-not-square-transparent-24bpp.ico new file mode 100644 index 000000000..16d6584ef Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-not-square-transparent-24bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-not-square-transparent-24bpp.png b/image/test/reftest/ico/ico-bmp-24bpp/ico-not-square-transparent-24bpp.png new file mode 100644 index 000000000..a881048b9 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-not-square-transparent-24bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-partial-transparent-24bpp.ico b/image/test/reftest/ico/ico-bmp-24bpp/ico-partial-transparent-24bpp.ico new file mode 100644 index 000000000..ab0dc4bce Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-partial-transparent-24bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-partial-transparent-24bpp.png b/image/test/reftest/ico/ico-bmp-24bpp/ico-partial-transparent-24bpp.png new file mode 100644 index 000000000..0363210c7 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-partial-transparent-24bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-15x15-24bpp.ico b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-15x15-24bpp.ico new file mode 100644 index 000000000..8721b0d16 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-15x15-24bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-15x15-24bpp.png b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-15x15-24bpp.png new file mode 100644 index 000000000..e1287430d Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-15x15-24bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-16x16-24bpp.ico b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-16x16-24bpp.ico new file mode 100644 index 000000000..04e473618 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-16x16-24bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-16x16-24bpp.png b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-16x16-24bpp.png new file mode 100644 index 000000000..c04869e72 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-16x16-24bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-17x17-24bpp.ico b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-17x17-24bpp.ico new file mode 100644 index 000000000..308ccb7a6 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-17x17-24bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-17x17-24bpp.png b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-17x17-24bpp.png new file mode 100644 index 000000000..00fb8e4f3 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-17x17-24bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-1x1-24bpp.ico b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-1x1-24bpp.ico new file mode 100644 index 000000000..e2bf90c09 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-1x1-24bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-1x1-24bpp.png b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-1x1-24bpp.png new file mode 100644 index 000000000..c05f5fef8 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-1x1-24bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-256x256-24bpp.ico b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-256x256-24bpp.ico new file mode 100644 index 000000000..c3977400a Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-256x256-24bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-256x256-24bpp.png b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-256x256-24bpp.png new file mode 100644 index 000000000..84bfada76 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-256x256-24bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-2x2-24bpp.ico b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-2x2-24bpp.ico new file mode 100644 index 000000000..dba180a07 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-2x2-24bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-2x2-24bpp.png b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-2x2-24bpp.png new file mode 100644 index 000000000..e512d3f9b Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-2x2-24bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-31x31-24bpp.ico b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-31x31-24bpp.ico new file mode 100644 index 000000000..aa67502f6 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-31x31-24bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-31x31-24bpp.png b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-31x31-24bpp.png new file mode 100644 index 000000000..e4a864251 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-31x31-24bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-32x32-24bpp.ico b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-32x32-24bpp.ico new file mode 100644 index 000000000..a85b871c5 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-32x32-24bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-32x32-24bpp.png b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-32x32-24bpp.png new file mode 100644 index 000000000..3a6fbe8ee Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-32x32-24bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-33x33-24bpp.ico b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-33x33-24bpp.ico new file mode 100644 index 000000000..a5c49374d Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-33x33-24bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-33x33-24bpp.png b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-33x33-24bpp.png new file mode 100644 index 000000000..72ef7eb63 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-33x33-24bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-3x3-24bpp.ico b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-3x3-24bpp.ico new file mode 100644 index 000000000..8a0b9433f Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-3x3-24bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-3x3-24bpp.png b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-3x3-24bpp.png new file mode 100644 index 000000000..cb42ec4f8 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-3x3-24bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-4x4-24bpp.ico b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-4x4-24bpp.ico new file mode 100644 index 000000000..feb3f11e1 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-4x4-24bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-4x4-24bpp.png b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-4x4-24bpp.png new file mode 100644 index 000000000..e6afafd89 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-4x4-24bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-5x5-24bpp.ico b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-5x5-24bpp.ico new file mode 100644 index 000000000..d607ca572 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-5x5-24bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-5x5-24bpp.png b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-5x5-24bpp.png new file mode 100644 index 000000000..a844aff76 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-5x5-24bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-6x6-24bpp.ico b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-6x6-24bpp.ico new file mode 100644 index 000000000..62a231602 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-6x6-24bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-6x6-24bpp.png b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-6x6-24bpp.png new file mode 100644 index 000000000..415c2d9c6 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-6x6-24bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-7x7-24bpp.ico b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-7x7-24bpp.ico new file mode 100644 index 000000000..d884ecfd7 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-7x7-24bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-7x7-24bpp.png b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-7x7-24bpp.png new file mode 100644 index 000000000..ab2f89274 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-7x7-24bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-8x8-24bpp.ico b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-8x8-24bpp.ico new file mode 100644 index 000000000..782ae220d Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-8x8-24bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-8x8-24bpp.png b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-8x8-24bpp.png new file mode 100644 index 000000000..fe2ff40a1 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-8x8-24bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-9x9-24bpp.ico b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-9x9-24bpp.ico new file mode 100644 index 000000000..97992643b Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-9x9-24bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-size-9x9-24bpp.png b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-9x9-24bpp.png new file mode 100644 index 000000000..18ab4b25d Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-size-9x9-24bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-transparent-24bpp.ico b/image/test/reftest/ico/ico-bmp-24bpp/ico-transparent-24bpp.ico new file mode 100644 index 000000000..8e361306c Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-transparent-24bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/ico-transparent-24bpp.png b/image/test/reftest/ico/ico-bmp-24bpp/ico-transparent-24bpp.png new file mode 100644 index 000000000..062152e3b Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-24bpp/ico-transparent-24bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-24bpp/reftest-stylo.list b/image/test/reftest/ico/ico-bmp-24bpp/reftest-stylo.list new file mode 100644 index 000000000..54d8521dc --- /dev/null +++ b/image/test/reftest/ico/ico-bmp-24bpp/reftest-stylo.list @@ -0,0 +1,24 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# ICO BMP 24BPP tests + +# Images of various sizes +== ico-size-1x1-24bpp.ico ico-size-1x1-24bpp.ico +== ico-size-2x2-24bpp.ico ico-size-2x2-24bpp.ico +== ico-size-3x3-24bpp.ico ico-size-3x3-24bpp.ico +== ico-size-4x4-24bpp.ico ico-size-4x4-24bpp.ico +skip == ico-size-5x5-24bpp.ico ico-size-5x5-24bpp.ico +== ico-size-6x6-24bpp.ico ico-size-6x6-24bpp.ico +== ico-size-7x7-24bpp.ico ico-size-7x7-24bpp.ico +== ico-size-8x8-24bpp.ico ico-size-8x8-24bpp.ico +== ico-size-9x9-24bpp.ico ico-size-9x9-24bpp.ico +== ico-size-15x15-24bpp.ico ico-size-15x15-24bpp.ico +== ico-size-16x16-24bpp.ico ico-size-16x16-24bpp.ico +== ico-size-17x17-24bpp.ico ico-size-17x17-24bpp.ico +== ico-size-31x31-24bpp.ico ico-size-31x31-24bpp.ico +fails == ico-size-32x32-24bpp.ico ico-size-32x32-24bpp.ico +== ico-size-33x33-24bpp.ico ico-size-33x33-24bpp.ico +== ico-size-256x256-24bpp.ico ico-size-256x256-24bpp.ico +== ico-partial-transparent-24bpp.ico ico-partial-transparent-24bpp.ico +== ico-transparent-24bpp.ico ico-transparent-24bpp.ico +== ico-not-square-transparent-24bpp.ico ico-not-square-transparent-24bpp.ico + diff --git a/image/test/reftest/ico/ico-bmp-24bpp/reftest.list b/image/test/reftest/ico/ico-bmp-24bpp/reftest.list new file mode 100644 index 000000000..877293660 --- /dev/null +++ b/image/test/reftest/ico/ico-bmp-24bpp/reftest.list @@ -0,0 +1,23 @@ +# ICO BMP 24BPP tests + +# Images of various sizes +== ico-size-1x1-24bpp.ico ico-size-1x1-24bpp.png +== ico-size-2x2-24bpp.ico ico-size-2x2-24bpp.png +== ico-size-3x3-24bpp.ico ico-size-3x3-24bpp.png +== ico-size-4x4-24bpp.ico ico-size-4x4-24bpp.png +== ico-size-5x5-24bpp.ico ico-size-5x5-24bpp.png +== ico-size-6x6-24bpp.ico ico-size-6x6-24bpp.png +== ico-size-7x7-24bpp.ico ico-size-7x7-24bpp.png +== ico-size-8x8-24bpp.ico ico-size-8x8-24bpp.png +== ico-size-9x9-24bpp.ico ico-size-9x9-24bpp.png +== ico-size-15x15-24bpp.ico ico-size-15x15-24bpp.png +== ico-size-16x16-24bpp.ico ico-size-16x16-24bpp.png +== ico-size-17x17-24bpp.ico ico-size-17x17-24bpp.png +== ico-size-31x31-24bpp.ico ico-size-31x31-24bpp.png +== ico-size-32x32-24bpp.ico ico-size-32x32-24bpp.png +== ico-size-33x33-24bpp.ico ico-size-33x33-24bpp.png +== ico-size-256x256-24bpp.ico ico-size-256x256-24bpp.png +== ico-partial-transparent-24bpp.ico ico-partial-transparent-24bpp.png +== ico-transparent-24bpp.ico ico-transparent-24bpp.png +== ico-not-square-transparent-24bpp.ico ico-not-square-transparent-24bpp.png + diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-not-square-transparent-32bpp.ico b/image/test/reftest/ico/ico-bmp-32bpp/ico-not-square-transparent-32bpp.ico new file mode 100644 index 000000000..dd0299c41 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-not-square-transparent-32bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-not-square-transparent-32bpp.png b/image/test/reftest/ico/ico-bmp-32bpp/ico-not-square-transparent-32bpp.png new file mode 100644 index 000000000..befc66555 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-not-square-transparent-32bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-partial-transparent-32bpp.ico b/image/test/reftest/ico/ico-bmp-32bpp/ico-partial-transparent-32bpp.ico new file mode 100644 index 000000000..8ad62f7d0 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-partial-transparent-32bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-partial-transparent-32bpp.png b/image/test/reftest/ico/ico-bmp-32bpp/ico-partial-transparent-32bpp.png new file mode 100644 index 000000000..226ad6494 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-partial-transparent-32bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-15x15-32bpp.ico b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-15x15-32bpp.ico new file mode 100644 index 000000000..1f1b6b51c Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-15x15-32bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-15x15-32bpp.png b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-15x15-32bpp.png new file mode 100644 index 000000000..e1287430d Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-15x15-32bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-16x16-32bpp.ico b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-16x16-32bpp.ico new file mode 100644 index 000000000..7a8f01529 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-16x16-32bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-16x16-32bpp.png b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-16x16-32bpp.png new file mode 100644 index 000000000..c04869e72 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-16x16-32bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-17x17-32bpp.ico b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-17x17-32bpp.ico new file mode 100644 index 000000000..b92860be4 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-17x17-32bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-17x17-32bpp.png b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-17x17-32bpp.png new file mode 100644 index 000000000..00fb8e4f3 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-17x17-32bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-1x1-32bpp.ico b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-1x1-32bpp.ico new file mode 100644 index 000000000..5ad60c575 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-1x1-32bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-1x1-32bpp.png b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-1x1-32bpp.png new file mode 100644 index 000000000..c05f5fef8 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-1x1-32bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-256x256-32bpp.ico b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-256x256-32bpp.ico new file mode 100644 index 000000000..f8b530ef0 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-256x256-32bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-256x256-32bpp.png b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-256x256-32bpp.png new file mode 100644 index 000000000..84bfada76 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-256x256-32bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-2x2-32bpp.ico b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-2x2-32bpp.ico new file mode 100644 index 000000000..e5b2bf7e7 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-2x2-32bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-2x2-32bpp.png b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-2x2-32bpp.png new file mode 100644 index 000000000..e512d3f9b Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-2x2-32bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-31x31-32bpp.ico b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-31x31-32bpp.ico new file mode 100644 index 000000000..ddcbde85f Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-31x31-32bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-31x31-32bpp.png b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-31x31-32bpp.png new file mode 100644 index 000000000..e4a864251 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-31x31-32bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-32x32-32bpp.ico b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-32x32-32bpp.ico new file mode 100644 index 000000000..a89c01648 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-32x32-32bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-32x32-32bpp.png b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-32x32-32bpp.png new file mode 100644 index 000000000..3a6fbe8ee Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-32x32-32bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-33x33-32bpp.ico b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-33x33-32bpp.ico new file mode 100644 index 000000000..cda9133f8 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-33x33-32bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-33x33-32bpp.png b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-33x33-32bpp.png new file mode 100644 index 000000000..72ef7eb63 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-33x33-32bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-3x3-32bpp.ico b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-3x3-32bpp.ico new file mode 100644 index 000000000..3894ccf21 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-3x3-32bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-3x3-32bpp.png b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-3x3-32bpp.png new file mode 100644 index 000000000..cb42ec4f8 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-3x3-32bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-4x4-32bpp.ico b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-4x4-32bpp.ico new file mode 100644 index 000000000..828494c66 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-4x4-32bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-4x4-32bpp.png b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-4x4-32bpp.png new file mode 100644 index 000000000..e6afafd89 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-4x4-32bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-5x5-32bpp.ico b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-5x5-32bpp.ico new file mode 100644 index 000000000..4f0a2bcc7 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-5x5-32bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-5x5-32bpp.png b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-5x5-32bpp.png new file mode 100644 index 000000000..a844aff76 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-5x5-32bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-6x6-32bpp.ico b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-6x6-32bpp.ico new file mode 100644 index 000000000..5524769e6 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-6x6-32bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-6x6-32bpp.png b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-6x6-32bpp.png new file mode 100644 index 000000000..415c2d9c6 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-6x6-32bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-7x7-32bpp.ico b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-7x7-32bpp.ico new file mode 100644 index 000000000..6aeebb898 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-7x7-32bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-7x7-32bpp.png b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-7x7-32bpp.png new file mode 100644 index 000000000..ab2f89274 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-7x7-32bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-8x8-32bpp.ico b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-8x8-32bpp.ico new file mode 100644 index 000000000..824c744a2 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-8x8-32bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-8x8-32bpp.png b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-8x8-32bpp.png new file mode 100644 index 000000000..fe2ff40a1 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-8x8-32bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-9x9-32bpp.ico b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-9x9-32bpp.ico new file mode 100644 index 000000000..cf1f6e9c8 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-9x9-32bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-size-9x9-32bpp.png b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-9x9-32bpp.png new file mode 100644 index 000000000..18ab4b25d Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-size-9x9-32bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-transparent-32bpp.ico b/image/test/reftest/ico/ico-bmp-32bpp/ico-transparent-32bpp.ico new file mode 100644 index 000000000..151b7cb36 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-transparent-32bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/ico-transparent-32bpp.png b/image/test/reftest/ico/ico-bmp-32bpp/ico-transparent-32bpp.png new file mode 100644 index 000000000..062152e3b Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-32bpp/ico-transparent-32bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-32bpp/reftest-stylo.list b/image/test/reftest/ico/ico-bmp-32bpp/reftest-stylo.list new file mode 100644 index 000000000..3ee7e7b00 --- /dev/null +++ b/image/test/reftest/ico/ico-bmp-32bpp/reftest-stylo.list @@ -0,0 +1,23 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# ICO BMP 32BPP tests + +# Images of various sizes +== ico-size-1x1-32bpp.ico ico-size-1x1-32bpp.ico +== ico-size-2x2-32bpp.ico ico-size-2x2-32bpp.ico +== ico-size-3x3-32bpp.ico ico-size-3x3-32bpp.ico +== ico-size-4x4-32bpp.ico ico-size-4x4-32bpp.ico +== ico-size-5x5-32bpp.ico ico-size-5x5-32bpp.ico +== ico-size-6x6-32bpp.ico ico-size-6x6-32bpp.ico +== ico-size-7x7-32bpp.ico ico-size-7x7-32bpp.ico +== ico-size-8x8-32bpp.ico ico-size-8x8-32bpp.ico +== ico-size-9x9-32bpp.ico ico-size-9x9-32bpp.ico +== ico-size-15x15-32bpp.ico ico-size-15x15-32bpp.ico +== ico-size-16x16-32bpp.ico ico-size-16x16-32bpp.ico +== ico-size-17x17-32bpp.ico ico-size-17x17-32bpp.ico +== ico-size-31x31-32bpp.ico ico-size-31x31-32bpp.ico +== ico-size-32x32-32bpp.ico ico-size-32x32-32bpp.ico +== ico-size-33x33-32bpp.ico ico-size-33x33-32bpp.ico +== ico-size-256x256-32bpp.ico ico-size-256x256-32bpp.ico +== ico-partial-transparent-32bpp.ico ico-partial-transparent-32bpp.ico +== ico-transparent-32bpp.ico ico-transparent-32bpp.ico +== ico-not-square-transparent-32bpp.ico ico-not-square-transparent-32bpp.ico diff --git a/image/test/reftest/ico/ico-bmp-32bpp/reftest.list b/image/test/reftest/ico/ico-bmp-32bpp/reftest.list new file mode 100644 index 000000000..e05355a2b --- /dev/null +++ b/image/test/reftest/ico/ico-bmp-32bpp/reftest.list @@ -0,0 +1,22 @@ +# ICO BMP 32BPP tests + +# Images of various sizes +== ico-size-1x1-32bpp.ico ico-size-1x1-32bpp.png +== ico-size-2x2-32bpp.ico ico-size-2x2-32bpp.png +== ico-size-3x3-32bpp.ico ico-size-3x3-32bpp.png +== ico-size-4x4-32bpp.ico ico-size-4x4-32bpp.png +== ico-size-5x5-32bpp.ico ico-size-5x5-32bpp.png +== ico-size-6x6-32bpp.ico ico-size-6x6-32bpp.png +== ico-size-7x7-32bpp.ico ico-size-7x7-32bpp.png +== ico-size-8x8-32bpp.ico ico-size-8x8-32bpp.png +== ico-size-9x9-32bpp.ico ico-size-9x9-32bpp.png +== ico-size-15x15-32bpp.ico ico-size-15x15-32bpp.png +== ico-size-16x16-32bpp.ico ico-size-16x16-32bpp.png +== ico-size-17x17-32bpp.ico ico-size-17x17-32bpp.png +== ico-size-31x31-32bpp.ico ico-size-31x31-32bpp.png +== ico-size-32x32-32bpp.ico ico-size-32x32-32bpp.png +== ico-size-33x33-32bpp.ico ico-size-33x33-32bpp.png +== ico-size-256x256-32bpp.ico ico-size-256x256-32bpp.png +== ico-partial-transparent-32bpp.ico ico-partial-transparent-32bpp.png +== ico-transparent-32bpp.ico ico-transparent-32bpp.png +== ico-not-square-transparent-32bpp.ico ico-not-square-transparent-32bpp.png diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-not-square-transparent-4bpp.ico b/image/test/reftest/ico/ico-bmp-4bpp/ico-not-square-transparent-4bpp.ico new file mode 100644 index 000000000..d502d2ef6 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-not-square-transparent-4bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-not-square-transparent-4bpp.png b/image/test/reftest/ico/ico-bmp-4bpp/ico-not-square-transparent-4bpp.png new file mode 100644 index 000000000..3e556ad29 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-not-square-transparent-4bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-partial-transparent-4bpp.ico b/image/test/reftest/ico/ico-bmp-4bpp/ico-partial-transparent-4bpp.ico new file mode 100644 index 000000000..7bd3b8a69 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-partial-transparent-4bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-partial-transparent-4bpp.png b/image/test/reftest/ico/ico-bmp-4bpp/ico-partial-transparent-4bpp.png new file mode 100644 index 000000000..9ff0ce41f Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-partial-transparent-4bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-15x15-4bpp.ico b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-15x15-4bpp.ico new file mode 100644 index 000000000..de5c49e2a Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-15x15-4bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-15x15-4bpp.png b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-15x15-4bpp.png new file mode 100644 index 000000000..5d4a3f953 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-15x15-4bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-16x16-4bpp.ico b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-16x16-4bpp.ico new file mode 100644 index 000000000..b856b3f37 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-16x16-4bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-16x16-4bpp.png b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-16x16-4bpp.png new file mode 100644 index 000000000..d45d63f53 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-16x16-4bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-17x17-4bpp.ico b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-17x17-4bpp.ico new file mode 100644 index 000000000..44e055d2f Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-17x17-4bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-17x17-4bpp.png b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-17x17-4bpp.png new file mode 100644 index 000000000..bf4890329 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-17x17-4bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-1x1-4bpp.ico b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-1x1-4bpp.ico new file mode 100644 index 000000000..fd46c328d Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-1x1-4bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-1x1-4bpp.png b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-1x1-4bpp.png new file mode 100644 index 000000000..d41dd645b Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-1x1-4bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-256x256-4bpp.ico b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-256x256-4bpp.ico new file mode 100644 index 000000000..6d28edaa8 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-256x256-4bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-256x256-4bpp.png b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-256x256-4bpp.png new file mode 100644 index 000000000..3acdef830 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-256x256-4bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-2x2-4bpp.ico b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-2x2-4bpp.ico new file mode 100644 index 000000000..7dc4afde6 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-2x2-4bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-2x2-4bpp.png b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-2x2-4bpp.png new file mode 100644 index 000000000..b2d605041 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-2x2-4bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-31x31-4bpp.ico b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-31x31-4bpp.ico new file mode 100644 index 000000000..0471332d6 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-31x31-4bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-31x31-4bpp.png b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-31x31-4bpp.png new file mode 100644 index 000000000..cb12a3448 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-31x31-4bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-32x32-4bpp.ico b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-32x32-4bpp.ico new file mode 100644 index 000000000..ef005dc5b Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-32x32-4bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-32x32-4bpp.png b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-32x32-4bpp.png new file mode 100644 index 000000000..58d867d12 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-32x32-4bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-33x33-4bpp.ico b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-33x33-4bpp.ico new file mode 100644 index 000000000..4c71963a4 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-33x33-4bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-33x33-4bpp.png b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-33x33-4bpp.png new file mode 100644 index 000000000..064fde198 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-33x33-4bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-3x3-4bpp.ico b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-3x3-4bpp.ico new file mode 100644 index 000000000..aaa6350e9 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-3x3-4bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-3x3-4bpp.png b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-3x3-4bpp.png new file mode 100644 index 000000000..e34114d5c Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-3x3-4bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-4x4-4bpp.ico b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-4x4-4bpp.ico new file mode 100644 index 000000000..767bebed4 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-4x4-4bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-4x4-4bpp.png b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-4x4-4bpp.png new file mode 100644 index 000000000..3efa55562 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-4x4-4bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-5x5-4bpp.ico b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-5x5-4bpp.ico new file mode 100644 index 000000000..309b6fe5b Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-5x5-4bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-5x5-4bpp.png b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-5x5-4bpp.png new file mode 100644 index 000000000..02ebf57a5 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-5x5-4bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-6x6-4bpp.ico b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-6x6-4bpp.ico new file mode 100644 index 000000000..255fda6a8 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-6x6-4bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-6x6-4bpp.png b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-6x6-4bpp.png new file mode 100644 index 000000000..1f5769d09 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-6x6-4bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-7x7-4bpp.ico b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-7x7-4bpp.ico new file mode 100644 index 000000000..1a3963452 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-7x7-4bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-7x7-4bpp.png b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-7x7-4bpp.png new file mode 100644 index 000000000..59a1b98b5 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-7x7-4bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-8x8-4bpp.ico b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-8x8-4bpp.ico new file mode 100644 index 000000000..40bc9f893 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-8x8-4bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-8x8-4bpp.png b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-8x8-4bpp.png new file mode 100644 index 000000000..cf44f5967 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-8x8-4bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-9x9-4bpp.ico b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-9x9-4bpp.ico new file mode 100644 index 000000000..bda12f32b Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-9x9-4bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-size-9x9-4bpp.png b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-9x9-4bpp.png new file mode 100644 index 000000000..2e0736413 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-size-9x9-4bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-transparent-4bpp.ico b/image/test/reftest/ico/ico-bmp-4bpp/ico-transparent-4bpp.ico new file mode 100644 index 000000000..8e361306c Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-transparent-4bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/ico-transparent-4bpp.png b/image/test/reftest/ico/ico-bmp-4bpp/ico-transparent-4bpp.png new file mode 100644 index 000000000..062152e3b Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-4bpp/ico-transparent-4bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-4bpp/reftest-stylo.list b/image/test/reftest/ico/ico-bmp-4bpp/reftest-stylo.list new file mode 100644 index 000000000..073755a4b --- /dev/null +++ b/image/test/reftest/ico/ico-bmp-4bpp/reftest-stylo.list @@ -0,0 +1,24 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# ICO BMP 4BPP tests + +# Images of various sizes +== ico-size-1x1-4bpp.ico ico-size-1x1-4bpp.ico +== ico-size-2x2-4bpp.ico ico-size-2x2-4bpp.ico +== ico-size-3x3-4bpp.ico ico-size-3x3-4bpp.ico +== ico-size-4x4-4bpp.ico ico-size-4x4-4bpp.ico +== ico-size-5x5-4bpp.ico ico-size-5x5-4bpp.ico +== ico-size-6x6-4bpp.ico ico-size-6x6-4bpp.ico +== ico-size-7x7-4bpp.ico ico-size-7x7-4bpp.ico +== ico-size-8x8-4bpp.ico ico-size-8x8-4bpp.ico +== ico-size-9x9-4bpp.ico ico-size-9x9-4bpp.ico +== ico-size-15x15-4bpp.ico ico-size-15x15-4bpp.ico +== ico-size-16x16-4bpp.ico ico-size-16x16-4bpp.ico +== ico-size-17x17-4bpp.ico ico-size-17x17-4bpp.ico +== ico-size-31x31-4bpp.ico ico-size-31x31-4bpp.ico +== ico-size-32x32-4bpp.ico ico-size-32x32-4bpp.ico +== ico-size-33x33-4bpp.ico ico-size-33x33-4bpp.ico +== ico-size-256x256-4bpp.ico ico-size-256x256-4bpp.ico +== ico-partial-transparent-4bpp.ico ico-partial-transparent-4bpp.ico +== ico-transparent-4bpp.ico ico-transparent-4bpp.ico +== ico-not-square-transparent-4bpp.ico ico-not-square-transparent-4bpp.ico + diff --git a/image/test/reftest/ico/ico-bmp-4bpp/reftest.list b/image/test/reftest/ico/ico-bmp-4bpp/reftest.list new file mode 100644 index 000000000..6caac4ac8 --- /dev/null +++ b/image/test/reftest/ico/ico-bmp-4bpp/reftest.list @@ -0,0 +1,23 @@ +# ICO BMP 4BPP tests + +# Images of various sizes +== ico-size-1x1-4bpp.ico ico-size-1x1-4bpp.png +== ico-size-2x2-4bpp.ico ico-size-2x2-4bpp.png +== ico-size-3x3-4bpp.ico ico-size-3x3-4bpp.png +== ico-size-4x4-4bpp.ico ico-size-4x4-4bpp.png +== ico-size-5x5-4bpp.ico ico-size-5x5-4bpp.png +== ico-size-6x6-4bpp.ico ico-size-6x6-4bpp.png +== ico-size-7x7-4bpp.ico ico-size-7x7-4bpp.png +== ico-size-8x8-4bpp.ico ico-size-8x8-4bpp.png +== ico-size-9x9-4bpp.ico ico-size-9x9-4bpp.png +== ico-size-15x15-4bpp.ico ico-size-15x15-4bpp.png +== ico-size-16x16-4bpp.ico ico-size-16x16-4bpp.png +== ico-size-17x17-4bpp.ico ico-size-17x17-4bpp.png +== ico-size-31x31-4bpp.ico ico-size-31x31-4bpp.png +== ico-size-32x32-4bpp.ico ico-size-32x32-4bpp.png +== ico-size-33x33-4bpp.ico ico-size-33x33-4bpp.png +== ico-size-256x256-4bpp.ico ico-size-256x256-4bpp.png +== ico-partial-transparent-4bpp.ico ico-partial-transparent-4bpp.png +== ico-transparent-4bpp.ico ico-transparent-4bpp.png +== ico-not-square-transparent-4bpp.ico ico-not-square-transparent-4bpp.png + diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-not-square-transparent-8bpp.ico b/image/test/reftest/ico/ico-bmp-8bpp/ico-not-square-transparent-8bpp.ico new file mode 100644 index 000000000..d28b9a04e Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-not-square-transparent-8bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-not-square-transparent-8bpp.png b/image/test/reftest/ico/ico-bmp-8bpp/ico-not-square-transparent-8bpp.png new file mode 100644 index 000000000..36a4eb512 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-not-square-transparent-8bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-partial-transparent-8bpp.ico b/image/test/reftest/ico/ico-bmp-8bpp/ico-partial-transparent-8bpp.ico new file mode 100644 index 000000000..9074caa40 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-partial-transparent-8bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-partial-transparent-8bpp.png b/image/test/reftest/ico/ico-bmp-8bpp/ico-partial-transparent-8bpp.png new file mode 100644 index 000000000..6f990f257 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-partial-transparent-8bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-15x15-8bpp.ico b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-15x15-8bpp.ico new file mode 100644 index 000000000..f3f3a1353 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-15x15-8bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-15x15-8bpp.png b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-15x15-8bpp.png new file mode 100644 index 000000000..e1287430d Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-15x15-8bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-16x16-8bpp.ico b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-16x16-8bpp.ico new file mode 100644 index 000000000..24c20e23e Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-16x16-8bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-16x16-8bpp.png b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-16x16-8bpp.png new file mode 100644 index 000000000..2e66b2e5f Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-16x16-8bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-17x17-8bpp.ico b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-17x17-8bpp.ico new file mode 100644 index 000000000..7fa66b9b2 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-17x17-8bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-17x17-8bpp.png b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-17x17-8bpp.png new file mode 100644 index 000000000..4d11d7561 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-17x17-8bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-1x1-8bpp.ico b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-1x1-8bpp.ico new file mode 100644 index 000000000..3cf3320ea Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-1x1-8bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-1x1-8bpp.png b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-1x1-8bpp.png new file mode 100644 index 000000000..c05f5fef8 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-1x1-8bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-256x256-8bpp.ico b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-256x256-8bpp.ico new file mode 100644 index 000000000..524b6f7c8 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-256x256-8bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-256x256-8bpp.png b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-256x256-8bpp.png new file mode 100644 index 000000000..f367468c9 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-256x256-8bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-2x2-8bpp.ico b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-2x2-8bpp.ico new file mode 100644 index 000000000..95d8375a0 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-2x2-8bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-2x2-8bpp.png b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-2x2-8bpp.png new file mode 100644 index 000000000..e512d3f9b Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-2x2-8bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-31x31-8bpp.ico b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-31x31-8bpp.ico new file mode 100644 index 000000000..780675447 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-31x31-8bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-31x31-8bpp.png b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-31x31-8bpp.png new file mode 100644 index 000000000..84bf61078 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-31x31-8bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-32x32-8bpp.ico b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-32x32-8bpp.ico new file mode 100644 index 000000000..d21cc5b96 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-32x32-8bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-32x32-8bpp.png b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-32x32-8bpp.png new file mode 100644 index 000000000..349fd4df2 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-32x32-8bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-33x33-8bpp.ico b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-33x33-8bpp.ico new file mode 100644 index 000000000..1b419b263 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-33x33-8bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-33x33-8bpp.png b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-33x33-8bpp.png new file mode 100644 index 000000000..a4c100649 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-33x33-8bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-3x3-8bpp.ico b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-3x3-8bpp.ico new file mode 100644 index 000000000..869f74fcd Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-3x3-8bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-3x3-8bpp.png b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-3x3-8bpp.png new file mode 100644 index 000000000..cb42ec4f8 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-3x3-8bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-4x4-8bpp.ico b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-4x4-8bpp.ico new file mode 100644 index 000000000..396756372 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-4x4-8bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-4x4-8bpp.png b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-4x4-8bpp.png new file mode 100644 index 000000000..e6afafd89 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-4x4-8bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-5x5-8bpp.ico b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-5x5-8bpp.ico new file mode 100644 index 000000000..92814e366 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-5x5-8bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-5x5-8bpp.png b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-5x5-8bpp.png new file mode 100644 index 000000000..a844aff76 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-5x5-8bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-6x6-8bpp.ico b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-6x6-8bpp.ico new file mode 100644 index 000000000..1af478a8a Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-6x6-8bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-6x6-8bpp.png b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-6x6-8bpp.png new file mode 100644 index 000000000..415c2d9c6 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-6x6-8bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-7x7-8bpp.ico b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-7x7-8bpp.ico new file mode 100644 index 000000000..1c70820eb Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-7x7-8bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-7x7-8bpp.png b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-7x7-8bpp.png new file mode 100644 index 000000000..ab2f89274 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-7x7-8bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-8x8-8bpp.ico b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-8x8-8bpp.ico new file mode 100644 index 000000000..782ae220d Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-8x8-8bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-8x8-8bpp.png b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-8x8-8bpp.png new file mode 100644 index 000000000..fe2ff40a1 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-8x8-8bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-9x9-8bpp.ico b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-9x9-8bpp.ico new file mode 100644 index 000000000..6825372b4 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-9x9-8bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-size-9x9-8bpp.png b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-9x9-8bpp.png new file mode 100644 index 000000000..18ab4b25d Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-size-9x9-8bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-transparent-8bpp.ico b/image/test/reftest/ico/ico-bmp-8bpp/ico-transparent-8bpp.ico new file mode 100644 index 000000000..8e361306c Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-transparent-8bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/ico-transparent-8bpp.png b/image/test/reftest/ico/ico-bmp-8bpp/ico-transparent-8bpp.png new file mode 100644 index 000000000..062152e3b Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-8bpp/ico-transparent-8bpp.png differ diff --git a/image/test/reftest/ico/ico-bmp-8bpp/reftest-stylo.list b/image/test/reftest/ico/ico-bmp-8bpp/reftest-stylo.list new file mode 100644 index 000000000..c5269d3c8 --- /dev/null +++ b/image/test/reftest/ico/ico-bmp-8bpp/reftest-stylo.list @@ -0,0 +1,25 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# ICO BMP 8BPP tests + +# Images of various sizes +== ico-size-1x1-8bpp.ico ico-size-1x1-8bpp.ico +== ico-size-2x2-8bpp.ico ico-size-2x2-8bpp.ico +== ico-size-3x3-8bpp.ico ico-size-3x3-8bpp.ico +== ico-size-4x4-8bpp.ico ico-size-4x4-8bpp.ico +== ico-size-5x5-8bpp.ico ico-size-5x5-8bpp.ico +skip == ico-size-6x6-8bpp.ico ico-size-6x6-8bpp.ico +== ico-size-7x7-8bpp.ico ico-size-7x7-8bpp.ico +== ico-size-8x8-8bpp.ico ico-size-8x8-8bpp.ico +== ico-size-9x9-8bpp.ico ico-size-9x9-8bpp.ico +== ico-size-15x15-8bpp.ico ico-size-15x15-8bpp.ico +== ico-size-16x16-8bpp.ico ico-size-16x16-8bpp.ico +== ico-size-17x17-8bpp.ico ico-size-17x17-8bpp.ico +== ico-size-31x31-8bpp.ico ico-size-31x31-8bpp.ico +== ico-size-32x32-8bpp.ico ico-size-32x32-8bpp.ico +== ico-size-33x33-8bpp.ico ico-size-33x33-8bpp.ico +skip-if(B2G) == ico-size-256x256-8bpp.ico ico-size-256x256-8bpp.ico +# bug 773482 +== ico-partial-transparent-8bpp.ico ico-partial-transparent-8bpp.ico +== ico-transparent-8bpp.ico ico-transparent-8bpp.ico +== ico-not-square-transparent-8bpp.ico ico-not-square-transparent-8bpp.ico + diff --git a/image/test/reftest/ico/ico-bmp-8bpp/reftest.list b/image/test/reftest/ico/ico-bmp-8bpp/reftest.list new file mode 100644 index 000000000..5a6b54323 --- /dev/null +++ b/image/test/reftest/ico/ico-bmp-8bpp/reftest.list @@ -0,0 +1,23 @@ +# ICO BMP 8BPP tests + +# Images of various sizes +== ico-size-1x1-8bpp.ico ico-size-1x1-8bpp.png +== ico-size-2x2-8bpp.ico ico-size-2x2-8bpp.png +== ico-size-3x3-8bpp.ico ico-size-3x3-8bpp.png +== ico-size-4x4-8bpp.ico ico-size-4x4-8bpp.png +== ico-size-5x5-8bpp.ico ico-size-5x5-8bpp.png +== ico-size-6x6-8bpp.ico ico-size-6x6-8bpp.png +== ico-size-7x7-8bpp.ico ico-size-7x7-8bpp.png +== ico-size-8x8-8bpp.ico ico-size-8x8-8bpp.png +== ico-size-9x9-8bpp.ico ico-size-9x9-8bpp.png +== ico-size-15x15-8bpp.ico ico-size-15x15-8bpp.png +== ico-size-16x16-8bpp.ico ico-size-16x16-8bpp.png +== ico-size-17x17-8bpp.ico ico-size-17x17-8bpp.png +== ico-size-31x31-8bpp.ico ico-size-31x31-8bpp.png +== ico-size-32x32-8bpp.ico ico-size-32x32-8bpp.png +== ico-size-33x33-8bpp.ico ico-size-33x33-8bpp.png +== ico-size-256x256-8bpp.ico ico-size-256x256-8bpp.png +== ico-partial-transparent-8bpp.ico ico-partial-transparent-8bpp.png +== ico-transparent-8bpp.ico ico-transparent-8bpp.png +== ico-not-square-transparent-8bpp.ico ico-not-square-transparent-8bpp.png + diff --git a/image/test/reftest/ico/ico-bmp-corrupted/16x16.png b/image/test/reftest/ico/ico-bmp-corrupted/16x16.png new file mode 100644 index 000000000..c04869e72 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-corrupted/16x16.png differ diff --git a/image/test/reftest/ico/ico-bmp-corrupted/invalid-bpp.ico b/image/test/reftest/ico/ico-bmp-corrupted/invalid-bpp.ico new file mode 100644 index 000000000..1189e4c04 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-corrupted/invalid-bpp.ico differ diff --git a/image/test/reftest/ico/ico-bmp-corrupted/invalid-compression-RLE4.ico b/image/test/reftest/ico/ico-bmp-corrupted/invalid-compression-RLE4.ico new file mode 100644 index 000000000..8fd0a5d65 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-corrupted/invalid-compression-RLE4.ico differ diff --git a/image/test/reftest/ico/ico-bmp-corrupted/invalid-compression-RLE8.ico b/image/test/reftest/ico/ico-bmp-corrupted/invalid-compression-RLE8.ico new file mode 100644 index 000000000..1f185ca62 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-corrupted/invalid-compression-RLE8.ico differ diff --git a/image/test/reftest/ico/ico-bmp-corrupted/invalid-compression.ico b/image/test/reftest/ico/ico-bmp-corrupted/invalid-compression.ico new file mode 100644 index 000000000..a49a783c5 Binary files /dev/null and b/image/test/reftest/ico/ico-bmp-corrupted/invalid-compression.ico differ diff --git a/image/test/reftest/ico/ico-bmp-corrupted/reftest-stylo.list b/image/test/reftest/ico/ico-bmp-corrupted/reftest-stylo.list new file mode 100644 index 000000000..1dd1a43ae --- /dev/null +++ b/image/test/reftest/ico/ico-bmp-corrupted/reftest-stylo.list @@ -0,0 +1,11 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# ICOs containing corrupted BMP tests + +# Invalid value for bits per pixel (BPP) - detected when decoding the header. +skip == wrapper.html?invalid-bpp.ico wrapper.html?invalid-bpp.ico +# Invalid BPP values for RLE4 - detected when decoding the image data. +skip == wrapper.html?invalid-compression-RLE4.ico wrapper.html?invalid-compression-RLE4.ico +# Invalid BPP values for RLE8 - detected when decoding the image data. +skip == wrapper.html?invalid-compression-RLE8.ico wrapper.html?invalid-compression-RLE8.ico +# Invalid compression value - detected when decoding the image data. +skip == wrapper.html?invalid-compression.ico wrapper.html?invalid-compression.ico diff --git a/image/test/reftest/ico/ico-bmp-corrupted/reftest.list b/image/test/reftest/ico/ico-bmp-corrupted/reftest.list new file mode 100644 index 000000000..2467b1323 --- /dev/null +++ b/image/test/reftest/ico/ico-bmp-corrupted/reftest.list @@ -0,0 +1,10 @@ +# ICOs containing corrupted BMP tests + +# Invalid value for bits per pixel (BPP) - detected when decoding the header. +== wrapper.html?invalid-bpp.ico about:blank +# Invalid BPP values for RLE4 - detected when decoding the image data. +== wrapper.html?invalid-compression-RLE4.ico about:blank +# Invalid BPP values for RLE8 - detected when decoding the image data. +== wrapper.html?invalid-compression-RLE8.ico about:blank +# Invalid compression value - detected when decoding the image data. +== wrapper.html?invalid-compression.ico about:blank diff --git a/image/test/reftest/ico/ico-bmp-corrupted/wrapper.html b/image/test/reftest/ico/ico-bmp-corrupted/wrapper.html new file mode 100644 index 000000000..5935f3763 --- /dev/null +++ b/image/test/reftest/ico/ico-bmp-corrupted/wrapper.html @@ -0,0 +1,80 @@ + + + +Image reftest wrapper + + + + + + + + + diff --git a/image/test/reftest/ico/ico-mixed/mixed-bmp-png.ico b/image/test/reftest/ico/ico-mixed/mixed-bmp-png.ico new file mode 100644 index 000000000..32e2c4995 Binary files /dev/null and b/image/test/reftest/ico/ico-mixed/mixed-bmp-png.ico differ diff --git a/image/test/reftest/ico/ico-mixed/mixed-bmp-png.png b/image/test/reftest/ico/ico-mixed/mixed-bmp-png.png new file mode 100644 index 000000000..b6aee7409 Binary files /dev/null and b/image/test/reftest/ico/ico-mixed/mixed-bmp-png.png differ diff --git a/image/test/reftest/ico/ico-mixed/mixed-bmp-png32.png b/image/test/reftest/ico/ico-mixed/mixed-bmp-png32.png new file mode 100644 index 000000000..a05899127 Binary files /dev/null and b/image/test/reftest/ico/ico-mixed/mixed-bmp-png32.png differ diff --git a/image/test/reftest/ico/ico-mixed/mixed-bmp-png48.png b/image/test/reftest/ico/ico-mixed/mixed-bmp-png48.png new file mode 100644 index 000000000..61bea5c80 Binary files /dev/null and b/image/test/reftest/ico/ico-mixed/mixed-bmp-png48.png differ diff --git a/image/test/reftest/ico/ico-mixed/reftest-stylo.list b/image/test/reftest/ico/ico-mixed/reftest-stylo.list new file mode 100644 index 000000000..a095c2481 --- /dev/null +++ b/image/test/reftest/ico/ico-mixed/reftest-stylo.list @@ -0,0 +1,4 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# ICO BMP and PNG mixed tests + +skip == mixed-bmp-png.ico mixed-bmp-png.ico diff --git a/image/test/reftest/ico/ico-mixed/reftest.list b/image/test/reftest/ico/ico-mixed/reftest.list new file mode 100644 index 000000000..36134e40a --- /dev/null +++ b/image/test/reftest/ico/ico-mixed/reftest.list @@ -0,0 +1,3 @@ +# ICO BMP and PNG mixed tests + +== mixed-bmp-png.ico mixed-bmp-png48.png diff --git a/image/test/reftest/ico/ico-png/corrupted_x00n0g01.ico b/image/test/reftest/ico/ico-png/corrupted_x00n0g01.ico new file mode 100644 index 000000000..18b97b0b7 Binary files /dev/null and b/image/test/reftest/ico/ico-png/corrupted_x00n0g01.ico differ diff --git a/image/test/reftest/ico/ico-png/corrupted_xxcrn0g04.ico b/image/test/reftest/ico/ico-png/corrupted_xxcrn0g04.ico new file mode 100644 index 000000000..3fa5285c5 Binary files /dev/null and b/image/test/reftest/ico/ico-png/corrupted_xxcrn0g04.ico differ diff --git a/image/test/reftest/ico/ico-png/ico-size-15x15-png.ico b/image/test/reftest/ico/ico-png/ico-size-15x15-png.ico new file mode 100644 index 000000000..e67644a89 Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-15x15-png.ico differ diff --git a/image/test/reftest/ico/ico-png/ico-size-15x15-png.png b/image/test/reftest/ico/ico-png/ico-size-15x15-png.png new file mode 100644 index 000000000..e1287430d Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-15x15-png.png differ diff --git a/image/test/reftest/ico/ico-png/ico-size-16x16-png.ico b/image/test/reftest/ico/ico-png/ico-size-16x16-png.ico new file mode 100644 index 000000000..442ab4dc8 Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-16x16-png.ico differ diff --git a/image/test/reftest/ico/ico-png/ico-size-16x16-png.png b/image/test/reftest/ico/ico-png/ico-size-16x16-png.png new file mode 100644 index 000000000..c04869e72 Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-16x16-png.png differ diff --git a/image/test/reftest/ico/ico-png/ico-size-17x17-png.ico b/image/test/reftest/ico/ico-png/ico-size-17x17-png.ico new file mode 100644 index 000000000..f135385d7 Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-17x17-png.ico differ diff --git a/image/test/reftest/ico/ico-png/ico-size-17x17-png.png b/image/test/reftest/ico/ico-png/ico-size-17x17-png.png new file mode 100644 index 000000000..00fb8e4f3 Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-17x17-png.png differ diff --git a/image/test/reftest/ico/ico-png/ico-size-1x1-png.ico b/image/test/reftest/ico/ico-png/ico-size-1x1-png.ico new file mode 100644 index 000000000..8eb80c7db Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-1x1-png.ico differ diff --git a/image/test/reftest/ico/ico-png/ico-size-1x1-png.png b/image/test/reftest/ico/ico-png/ico-size-1x1-png.png new file mode 100644 index 000000000..c05f5fef8 Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-1x1-png.png differ diff --git a/image/test/reftest/ico/ico-png/ico-size-256x256-png.ico b/image/test/reftest/ico/ico-png/ico-size-256x256-png.ico new file mode 100644 index 000000000..ecb88edf3 Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-256x256-png.ico differ diff --git a/image/test/reftest/ico/ico-png/ico-size-256x256-png.png b/image/test/reftest/ico/ico-png/ico-size-256x256-png.png new file mode 100644 index 000000000..2d2f52d6c Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-256x256-png.png differ diff --git a/image/test/reftest/ico/ico-png/ico-size-2x2-png.ico b/image/test/reftest/ico/ico-png/ico-size-2x2-png.ico new file mode 100644 index 000000000..5799953c9 Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-2x2-png.ico differ diff --git a/image/test/reftest/ico/ico-png/ico-size-2x2-png.png b/image/test/reftest/ico/ico-png/ico-size-2x2-png.png new file mode 100644 index 000000000..e512d3f9b Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-2x2-png.png differ diff --git a/image/test/reftest/ico/ico-png/ico-size-31x31-png.ico b/image/test/reftest/ico/ico-png/ico-size-31x31-png.ico new file mode 100644 index 000000000..2e9fbd8f9 Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-31x31-png.ico differ diff --git a/image/test/reftest/ico/ico-png/ico-size-31x31-png.png b/image/test/reftest/ico/ico-png/ico-size-31x31-png.png new file mode 100644 index 000000000..e4a864251 Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-31x31-png.png differ diff --git a/image/test/reftest/ico/ico-png/ico-size-32x32-png.ico b/image/test/reftest/ico/ico-png/ico-size-32x32-png.ico new file mode 100644 index 000000000..af97a8663 Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-32x32-png.ico differ diff --git a/image/test/reftest/ico/ico-png/ico-size-32x32-png.png b/image/test/reftest/ico/ico-png/ico-size-32x32-png.png new file mode 100644 index 000000000..3a6fbe8ee Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-32x32-png.png differ diff --git a/image/test/reftest/ico/ico-png/ico-size-33x33-png.ico b/image/test/reftest/ico/ico-png/ico-size-33x33-png.ico new file mode 100644 index 000000000..2509c8c1f Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-33x33-png.ico differ diff --git a/image/test/reftest/ico/ico-png/ico-size-33x33-png.png b/image/test/reftest/ico/ico-png/ico-size-33x33-png.png new file mode 100644 index 000000000..72ef7eb63 Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-33x33-png.png differ diff --git a/image/test/reftest/ico/ico-png/ico-size-3x3-png.ico b/image/test/reftest/ico/ico-png/ico-size-3x3-png.ico new file mode 100644 index 000000000..d2cd649c8 Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-3x3-png.ico differ diff --git a/image/test/reftest/ico/ico-png/ico-size-3x3-png.png b/image/test/reftest/ico/ico-png/ico-size-3x3-png.png new file mode 100644 index 000000000..cb42ec4f8 Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-3x3-png.png differ diff --git a/image/test/reftest/ico/ico-png/ico-size-4x4-png.ico b/image/test/reftest/ico/ico-png/ico-size-4x4-png.ico new file mode 100644 index 000000000..60180aad5 Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-4x4-png.ico differ diff --git a/image/test/reftest/ico/ico-png/ico-size-4x4-png.png b/image/test/reftest/ico/ico-png/ico-size-4x4-png.png new file mode 100644 index 000000000..e6afafd89 Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-4x4-png.png differ diff --git a/image/test/reftest/ico/ico-png/ico-size-5x5-png.ico b/image/test/reftest/ico/ico-png/ico-size-5x5-png.ico new file mode 100644 index 000000000..089c0c885 Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-5x5-png.ico differ diff --git a/image/test/reftest/ico/ico-png/ico-size-5x5-png.png b/image/test/reftest/ico/ico-png/ico-size-5x5-png.png new file mode 100644 index 000000000..a844aff76 Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-5x5-png.png differ diff --git a/image/test/reftest/ico/ico-png/ico-size-6x6-png.ico b/image/test/reftest/ico/ico-png/ico-size-6x6-png.ico new file mode 100644 index 000000000..2ee75d25a Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-6x6-png.ico differ diff --git a/image/test/reftest/ico/ico-png/ico-size-6x6-png.png b/image/test/reftest/ico/ico-png/ico-size-6x6-png.png new file mode 100644 index 000000000..415c2d9c6 Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-6x6-png.png differ diff --git a/image/test/reftest/ico/ico-png/ico-size-7x7-png.ico b/image/test/reftest/ico/ico-png/ico-size-7x7-png.ico new file mode 100644 index 000000000..ade9a3ecd Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-7x7-png.ico differ diff --git a/image/test/reftest/ico/ico-png/ico-size-7x7-png.png b/image/test/reftest/ico/ico-png/ico-size-7x7-png.png new file mode 100644 index 000000000..ab2f89274 Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-7x7-png.png differ diff --git a/image/test/reftest/ico/ico-png/ico-size-8x8-png.ico b/image/test/reftest/ico/ico-png/ico-size-8x8-png.ico new file mode 100644 index 000000000..a0a150bad Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-8x8-png.ico differ diff --git a/image/test/reftest/ico/ico-png/ico-size-8x8-png.png b/image/test/reftest/ico/ico-png/ico-size-8x8-png.png new file mode 100644 index 000000000..fe2ff40a1 Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-8x8-png.png differ diff --git a/image/test/reftest/ico/ico-png/ico-size-9x9-png.ico b/image/test/reftest/ico/ico-png/ico-size-9x9-png.ico new file mode 100644 index 000000000..a53357b44 Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-9x9-png.ico differ diff --git a/image/test/reftest/ico/ico-png/ico-size-9x9-png.png b/image/test/reftest/ico/ico-png/ico-size-9x9-png.png new file mode 100644 index 000000000..18ab4b25d Binary files /dev/null and b/image/test/reftest/ico/ico-png/ico-size-9x9-png.png differ diff --git a/image/test/reftest/ico/ico-png/reftest-stylo.list b/image/test/reftest/ico/ico-png/reftest-stylo.list new file mode 100644 index 000000000..1fd990c89 --- /dev/null +++ b/image/test/reftest/ico/ico-png/reftest-stylo.list @@ -0,0 +1,30 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# ICO PNG tests + +# Images of various sizes +skip == ico-size-1x1-png.ico ico-size-1x1-png.ico +== ico-size-2x2-png.ico ico-size-2x2-png.ico +skip == ico-size-3x3-png.ico ico-size-3x3-png.ico +skip == ico-size-4x4-png.ico ico-size-4x4-png.ico +skip == ico-size-5x5-png.ico ico-size-5x5-png.ico +skip == ico-size-6x6-png.ico ico-size-6x6-png.ico +== ico-size-7x7-png.ico ico-size-7x7-png.ico +fails skip == ico-size-8x8-png.ico ico-size-8x8-png.ico +skip == ico-size-9x9-png.ico ico-size-9x9-png.ico +skip == ico-size-15x15-png.ico ico-size-15x15-png.ico +skip == ico-size-16x16-png.ico ico-size-16x16-png.ico +skip == ico-size-17x17-png.ico ico-size-17x17-png.ico +skip == ico-size-31x31-png.ico ico-size-31x31-png.ico +skip == ico-size-32x32-png.ico ico-size-32x32-png.ico +skip == ico-size-33x33-png.ico ico-size-33x33-png.ico +# skip == ico-size-256x256-png.ico ico-size-256x256-png.ico + +# Corrupted files so no image should be loaded +# x00n0g01 - empty 0x0 grayscale file +skip == wrapper.html?x00n0g01.ico wrapper.html?x00n0g01.ico +# xcrn0g04 - added cr bytes +skip == wrapper.html?xcrn0g04.ico wrapper.html?xcrn0g04.ico + +# Test ICO PNG transparency +== transparent-png.ico transparent-png.ico + diff --git a/image/test/reftest/ico/ico-png/reftest.list b/image/test/reftest/ico/ico-png/reftest.list new file mode 100644 index 000000000..002d0e4f3 --- /dev/null +++ b/image/test/reftest/ico/ico-png/reftest.list @@ -0,0 +1,29 @@ +# ICO PNG tests + +# Images of various sizes +== ico-size-1x1-png.ico ico-size-1x1-png.png +== ico-size-2x2-png.ico ico-size-2x2-png.png +== ico-size-3x3-png.ico ico-size-3x3-png.png +== ico-size-4x4-png.ico ico-size-4x4-png.png +== ico-size-5x5-png.ico ico-size-5x5-png.png +== ico-size-6x6-png.ico ico-size-6x6-png.png +== ico-size-7x7-png.ico ico-size-7x7-png.png +== ico-size-8x8-png.ico ico-size-8x8-png.png +== ico-size-9x9-png.ico ico-size-9x9-png.png +== ico-size-15x15-png.ico ico-size-15x15-png.png +== ico-size-16x16-png.ico ico-size-16x16-png.png +== ico-size-17x17-png.ico ico-size-17x17-png.png +== ico-size-31x31-png.ico ico-size-31x31-png.png +== ico-size-32x32-png.ico ico-size-32x32-png.png +== ico-size-33x33-png.ico ico-size-33x33-png.png +== ico-size-256x256-png.ico ico-size-256x256-png.png + +# Corrupted files so no image should be loaded +# x00n0g01 - empty 0x0 grayscale file +== wrapper.html?x00n0g01.ico about:blank +# xcrn0g04 - added cr bytes +== wrapper.html?xcrn0g04.ico about:blank + +# Test ICO PNG transparency +== transparent-png.ico transparent-png.png + diff --git a/image/test/reftest/ico/ico-png/tmp.ico b/image/test/reftest/ico/ico-png/tmp.ico new file mode 100644 index 000000000..5723a2e77 Binary files /dev/null and b/image/test/reftest/ico/ico-png/tmp.ico differ diff --git a/image/test/reftest/ico/ico-png/transparent-png.ico b/image/test/reftest/ico/ico-png/transparent-png.ico new file mode 100644 index 000000000..cc8a4a31d Binary files /dev/null and b/image/test/reftest/ico/ico-png/transparent-png.ico differ diff --git a/image/test/reftest/ico/ico-png/transparent-png.png b/image/test/reftest/ico/ico-png/transparent-png.png new file mode 100644 index 000000000..29e3a2435 Binary files /dev/null and b/image/test/reftest/ico/ico-png/transparent-png.png differ diff --git a/image/test/reftest/ico/ico-png/wrapper.html b/image/test/reftest/ico/ico-png/wrapper.html new file mode 100644 index 000000000..0015856df --- /dev/null +++ b/image/test/reftest/ico/ico-png/wrapper.html @@ -0,0 +1,28 @@ + + + +Image reftest wrapper + + + + + + + + + diff --git a/image/test/reftest/ico/ico-png/x00n0g01.png b/image/test/reftest/ico/ico-png/x00n0g01.png new file mode 100644 index 000000000..db3a5fda7 Binary files /dev/null and b/image/test/reftest/ico/ico-png/x00n0g01.png differ diff --git a/image/test/reftest/ico/ico-png/xcrn0g04.png b/image/test/reftest/ico/ico-png/xcrn0g04.png new file mode 100644 index 000000000..5bce9f3ad Binary files /dev/null and b/image/test/reftest/ico/ico-png/xcrn0g04.png differ diff --git a/image/test/reftest/ico/reftest-stylo.list b/image/test/reftest/ico/reftest-stylo.list new file mode 100644 index 000000000..52cb9bc6c --- /dev/null +++ b/image/test/reftest/ico/reftest-stylo.list @@ -0,0 +1,13 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# ICO tests + +# bmp tests cause lots of intermittents +# include ico-bmp-1bpp/reftest-stylo.list +# include ico-bmp-4bpp/reftest-stylo.list +# include ico-bmp-8bpp/reftest-stylo.list +# include ico-bmp-24bpp/reftest-stylo.list +# include ico-bmp-32bpp/reftest-stylo.list +# include ico-bmp-corrupted/reftest-stylo.list +include ico-png/reftest-stylo.list +include ico-mixed/reftest-stylo.list +include cur/reftest-stylo.list diff --git a/image/test/reftest/ico/reftest.list b/image/test/reftest/ico/reftest.list new file mode 100644 index 000000000..22ed9b4fe --- /dev/null +++ b/image/test/reftest/ico/reftest.list @@ -0,0 +1,11 @@ +# ICO tests + +include ico-bmp-1bpp/reftest.list +include ico-bmp-4bpp/reftest.list +include ico-bmp-8bpp/reftest.list +include ico-bmp-24bpp/reftest.list +include ico-bmp-32bpp/reftest.list +include ico-bmp-corrupted/reftest.list +include ico-png/reftest.list +include ico-mixed/reftest.list +include cur/reftest.list diff --git a/image/test/reftest/img2html.html b/image/test/reftest/img2html.html new file mode 100644 index 000000000..57f45bbdd --- /dev/null +++ b/image/test/reftest/img2html.html @@ -0,0 +1,122 @@ + + +Image-to-html converter + + + +

    Image-to-html converter

    +

    Enter the relative path to an image file, and this will convert it +to a pure HTML representation (no images).

    + + +
    + Path to image:
    + + Fill canvas with (instead of transparency).
    + +

    +
    + (img / canvas/ imghtml) +

    + Result:
    + +
    + + + + + + diff --git a/image/test/reftest/jpeg/blue.jpg b/image/test/reftest/jpeg/blue.jpg new file mode 100644 index 000000000..b5fef5d26 Binary files /dev/null and b/image/test/reftest/jpeg/blue.jpg differ diff --git a/image/test/reftest/jpeg/jpg-cmyk-1.jpg b/image/test/reftest/jpeg/jpg-cmyk-1.jpg new file mode 100644 index 000000000..ddb2c106f Binary files /dev/null and b/image/test/reftest/jpeg/jpg-cmyk-1.jpg differ diff --git a/image/test/reftest/jpeg/jpg-cmyk-1.png b/image/test/reftest/jpeg/jpg-cmyk-1.png new file mode 100644 index 000000000..06915d5bc Binary files /dev/null and b/image/test/reftest/jpeg/jpg-cmyk-1.png differ diff --git a/image/test/reftest/jpeg/jpg-cmyk-2.jpg b/image/test/reftest/jpeg/jpg-cmyk-2.jpg new file mode 100644 index 000000000..b955bde54 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-cmyk-2.jpg differ diff --git a/image/test/reftest/jpeg/jpg-cmyk-2.png b/image/test/reftest/jpeg/jpg-cmyk-2.png new file mode 100644 index 000000000..9691e42b6 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-cmyk-2.png differ diff --git a/image/test/reftest/jpeg/jpg-gray.jpg b/image/test/reftest/jpeg/jpg-gray.jpg new file mode 100644 index 000000000..af0413e3c Binary files /dev/null and b/image/test/reftest/jpeg/jpg-gray.jpg differ diff --git a/image/test/reftest/jpeg/jpg-gray.png b/image/test/reftest/jpeg/jpg-gray.png new file mode 100644 index 000000000..c5aedc34d Binary files /dev/null and b/image/test/reftest/jpeg/jpg-gray.png differ diff --git a/image/test/reftest/jpeg/jpg-progressive.jpg b/image/test/reftest/jpeg/jpg-progressive.jpg new file mode 100644 index 000000000..db3cf59c2 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-progressive.jpg differ diff --git a/image/test/reftest/jpeg/jpg-progressive.png b/image/test/reftest/jpeg/jpg-progressive.png new file mode 100644 index 000000000..3a6fbe8ee Binary files /dev/null and b/image/test/reftest/jpeg/jpg-progressive.png differ diff --git a/image/test/reftest/jpeg/jpg-size-15x15.jpg b/image/test/reftest/jpeg/jpg-size-15x15.jpg new file mode 100644 index 000000000..efe120a27 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-15x15.jpg differ diff --git a/image/test/reftest/jpeg/jpg-size-15x15.png b/image/test/reftest/jpeg/jpg-size-15x15.png new file mode 100644 index 000000000..e1287430d Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-15x15.png differ diff --git a/image/test/reftest/jpeg/jpg-size-16x16.jpg b/image/test/reftest/jpeg/jpg-size-16x16.jpg new file mode 100644 index 000000000..148ec733f Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-16x16.jpg differ diff --git a/image/test/reftest/jpeg/jpg-size-16x16.png b/image/test/reftest/jpeg/jpg-size-16x16.png new file mode 100644 index 000000000..c04869e72 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-16x16.png differ diff --git a/image/test/reftest/jpeg/jpg-size-17x17.jpg b/image/test/reftest/jpeg/jpg-size-17x17.jpg new file mode 100644 index 000000000..b06bdb0d6 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-17x17.jpg differ diff --git a/image/test/reftest/jpeg/jpg-size-17x17.png b/image/test/reftest/jpeg/jpg-size-17x17.png new file mode 100644 index 000000000..00fb8e4f3 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-17x17.png differ diff --git a/image/test/reftest/jpeg/jpg-size-1x1.jpg b/image/test/reftest/jpeg/jpg-size-1x1.jpg new file mode 100644 index 000000000..73b68dfc0 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-1x1.jpg differ diff --git a/image/test/reftest/jpeg/jpg-size-1x1.png b/image/test/reftest/jpeg/jpg-size-1x1.png new file mode 100644 index 000000000..c05f5fef8 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-1x1.png differ diff --git a/image/test/reftest/jpeg/jpg-size-2x2.jpg b/image/test/reftest/jpeg/jpg-size-2x2.jpg new file mode 100644 index 000000000..bc50260ea Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-2x2.jpg differ diff --git a/image/test/reftest/jpeg/jpg-size-2x2.png b/image/test/reftest/jpeg/jpg-size-2x2.png new file mode 100644 index 000000000..e512d3f9b Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-2x2.png differ diff --git a/image/test/reftest/jpeg/jpg-size-31x31.jpg b/image/test/reftest/jpeg/jpg-size-31x31.jpg new file mode 100644 index 000000000..8fa0cc236 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-31x31.jpg differ diff --git a/image/test/reftest/jpeg/jpg-size-31x31.png b/image/test/reftest/jpeg/jpg-size-31x31.png new file mode 100644 index 000000000..e4a864251 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-31x31.png differ diff --git a/image/test/reftest/jpeg/jpg-size-32x32.jpg b/image/test/reftest/jpeg/jpg-size-32x32.jpg new file mode 100644 index 000000000..b11d62df6 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-32x32.jpg differ diff --git a/image/test/reftest/jpeg/jpg-size-32x32.png b/image/test/reftest/jpeg/jpg-size-32x32.png new file mode 100644 index 000000000..3a6fbe8ee Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-32x32.png differ diff --git a/image/test/reftest/jpeg/jpg-size-33x33.jpg b/image/test/reftest/jpeg/jpg-size-33x33.jpg new file mode 100644 index 000000000..5ac1169b4 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-33x33.jpg differ diff --git a/image/test/reftest/jpeg/jpg-size-33x33.png b/image/test/reftest/jpeg/jpg-size-33x33.png new file mode 100644 index 000000000..72ef7eb63 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-33x33.png differ diff --git a/image/test/reftest/jpeg/jpg-size-3x3.jpg b/image/test/reftest/jpeg/jpg-size-3x3.jpg new file mode 100644 index 000000000..cf370d8ec Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-3x3.jpg differ diff --git a/image/test/reftest/jpeg/jpg-size-3x3.png b/image/test/reftest/jpeg/jpg-size-3x3.png new file mode 100644 index 000000000..cb42ec4f8 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-3x3.png differ diff --git a/image/test/reftest/jpeg/jpg-size-4x4.jpg b/image/test/reftest/jpeg/jpg-size-4x4.jpg new file mode 100644 index 000000000..5adf760a1 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-4x4.jpg differ diff --git a/image/test/reftest/jpeg/jpg-size-4x4.png b/image/test/reftest/jpeg/jpg-size-4x4.png new file mode 100644 index 000000000..e6afafd89 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-4x4.png differ diff --git a/image/test/reftest/jpeg/jpg-size-5x5.jpg b/image/test/reftest/jpeg/jpg-size-5x5.jpg new file mode 100644 index 000000000..4d5fd0501 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-5x5.jpg differ diff --git a/image/test/reftest/jpeg/jpg-size-5x5.png b/image/test/reftest/jpeg/jpg-size-5x5.png new file mode 100644 index 000000000..a844aff76 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-5x5.png differ diff --git a/image/test/reftest/jpeg/jpg-size-6x6.jpg b/image/test/reftest/jpeg/jpg-size-6x6.jpg new file mode 100644 index 000000000..415c2d9c6 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-6x6.jpg differ diff --git a/image/test/reftest/jpeg/jpg-size-6x6.png b/image/test/reftest/jpeg/jpg-size-6x6.png new file mode 100644 index 000000000..415c2d9c6 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-6x6.png differ diff --git a/image/test/reftest/jpeg/jpg-size-7x7.jpg b/image/test/reftest/jpeg/jpg-size-7x7.jpg new file mode 100644 index 000000000..5495f7e43 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-7x7.jpg differ diff --git a/image/test/reftest/jpeg/jpg-size-7x7.png b/image/test/reftest/jpeg/jpg-size-7x7.png new file mode 100644 index 000000000..ab2f89274 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-7x7.png differ diff --git a/image/test/reftest/jpeg/jpg-size-8x8.jpg b/image/test/reftest/jpeg/jpg-size-8x8.jpg new file mode 100644 index 000000000..84a5c8f42 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-8x8.jpg differ diff --git a/image/test/reftest/jpeg/jpg-size-8x8.png b/image/test/reftest/jpeg/jpg-size-8x8.png new file mode 100644 index 000000000..fe2ff40a1 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-8x8.png differ diff --git a/image/test/reftest/jpeg/jpg-size-9x9.jpg b/image/test/reftest/jpeg/jpg-size-9x9.jpg new file mode 100644 index 000000000..d0a15e599 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-9x9.jpg differ diff --git a/image/test/reftest/jpeg/jpg-size-9x9.png b/image/test/reftest/jpeg/jpg-size-9x9.png new file mode 100644 index 000000000..18ab4b25d Binary files /dev/null and b/image/test/reftest/jpeg/jpg-size-9x9.png differ diff --git a/image/test/reftest/jpeg/jpg-srgb-icc.jpg b/image/test/reftest/jpeg/jpg-srgb-icc.jpg new file mode 100644 index 000000000..3ebefac73 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-srgb-icc.jpg differ diff --git a/image/test/reftest/jpeg/jpg-srgb-icc.png b/image/test/reftest/jpeg/jpg-srgb-icc.png new file mode 100644 index 000000000..1d8efc687 Binary files /dev/null and b/image/test/reftest/jpeg/jpg-srgb-icc.png differ diff --git a/image/test/reftest/jpeg/red.jpg b/image/test/reftest/jpeg/red.jpg new file mode 100644 index 000000000..8fca4b938 Binary files /dev/null and b/image/test/reftest/jpeg/red.jpg differ diff --git a/image/test/reftest/jpeg/reftest-stylo.list b/image/test/reftest/jpeg/reftest-stylo.list new file mode 100644 index 000000000..a906cde8e --- /dev/null +++ b/image/test/reftest/jpeg/reftest-stylo.list @@ -0,0 +1,57 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# JPEG tests + +# Images of various sizes. +fails == jpg-size-1x1.jpg jpg-size-1x1.jpg +fails == jpg-size-2x2.jpg jpg-size-2x2.jpg +fails == jpg-size-3x3.jpg jpg-size-3x3.jpg +fails == jpg-size-4x4.jpg jpg-size-4x4.jpg +fails == jpg-size-5x5.jpg jpg-size-5x5.jpg +== jpg-size-6x6.jpg jpg-size-6x6.jpg +fails == jpg-size-7x7.jpg jpg-size-7x7.jpg +fails == jpg-size-8x8.jpg jpg-size-8x8.jpg +fails == jpg-size-9x9.jpg jpg-size-9x9.jpg +fails == jpg-size-15x15.jpg jpg-size-15x15.jpg +fails == jpg-size-16x16.jpg jpg-size-16x16.jpg +fails == jpg-size-17x17.jpg jpg-size-17x17.jpg +fails == jpg-size-31x31.jpg jpg-size-31x31.jpg +fails == jpg-size-32x32.jpg jpg-size-32x32.jpg +fails == jpg-size-33x33.jpg jpg-size-33x33.jpg +# Progressive encoding +fails == jpg-progressive.jpg jpg-progressive.jpg +# Grayscale colorspace +fails == jpg-gray.jpg jpg-gray.jpg +# CMYK colorspace +fails == jpg-cmyk-1.jpg jpg-cmyk-1.jpg +fails == jpg-cmyk-2.jpg jpg-cmyk-2.jpg +# This intermittently fails on Android due to async image decoding (bug #685516) +# Sometimes the image decodes in time and the test passes, other times the image +# appears blank and the test fails. This only seems to be triggered since the +# switch to 24-bit colour (bug #803299). +fails random-if(Android) == jpg-srgb-icc.jpg jpg-srgb-icc.jpg + +# webcam-simulacrum.mjpg is a hand-edited file containing red.jpg and blue.jpg, +# concatenated together with the relevant headers for +# multipart/x-mixed-replace. Specifically, with the headers in +# webcam-simulacrum.mjpg^headers^, the web browser will get the following: +# +# HTTP 200 OK +# Content-Type: multipart/x-mixed-replace;boundary=BOUNDARYOMG +# +# --BOUNDARYOMG\r\n +# Content-Type: image/jpeg\r\n +# \r\n +# (no newline) +# --BOUNDARYOMG\r\n +# Content-Type: image/jpeg\r\n +# \r\n +# (no newline) +# --BOUNDARYOMG--\r\n +# +# (The boundary is arbitrary, and just has to be defined as something that +# won't be in the text of the contents themselves. --$(boundary)\r\n means +# "Here is the beginning of a boundary," and --$(boundary)-- means "All done +# sending you parts.") +skip HTTP == webcam-simulacrum.mjpg webcam-simulacrum.mjpg +skip pref(image.mozsamplesize.enabled,true) == jpg-size-32x32.jpg#-moz-samplesize=2 jpg-size-32x32.jpg#-moz-samplesize=2 +skip pref(image.mozsamplesize.enabled,true) == jpg-size-32x32.jpg#-moz-samplesize=8 jpg-size-32x32.jpg#-moz-samplesize=8 diff --git a/image/test/reftest/jpeg/reftest.list b/image/test/reftest/jpeg/reftest.list new file mode 100644 index 000000000..0eafb77e7 --- /dev/null +++ b/image/test/reftest/jpeg/reftest.list @@ -0,0 +1,56 @@ +# JPEG tests + +# Images of various sizes. +== jpg-size-1x1.jpg jpg-size-1x1.png +== jpg-size-2x2.jpg jpg-size-2x2.png +== jpg-size-3x3.jpg jpg-size-3x3.png +== jpg-size-4x4.jpg jpg-size-4x4.png +== jpg-size-5x5.jpg jpg-size-5x5.png +== jpg-size-6x6.jpg jpg-size-6x6.png +== jpg-size-7x7.jpg jpg-size-7x7.png +== jpg-size-8x8.jpg jpg-size-8x8.png +== jpg-size-9x9.jpg jpg-size-9x9.png +== jpg-size-15x15.jpg jpg-size-15x15.png +== jpg-size-16x16.jpg jpg-size-16x16.png +== jpg-size-17x17.jpg jpg-size-17x17.png +== jpg-size-31x31.jpg jpg-size-31x31.png +== jpg-size-32x32.jpg jpg-size-32x32.png +== jpg-size-33x33.jpg jpg-size-33x33.png +# Progressive encoding +== jpg-progressive.jpg jpg-progressive.png +# Grayscale colorspace +== jpg-gray.jpg jpg-gray.png +# CMYK colorspace +== jpg-cmyk-1.jpg jpg-cmyk-1.png +== jpg-cmyk-2.jpg jpg-cmyk-2.png +# This intermittently fails on Android due to async image decoding (bug #685516) +# Sometimes the image decodes in time and the test passes, other times the image +# appears blank and the test fails. This only seems to be triggered since the +# switch to 24-bit colour (bug #803299). +random-if(Android) == jpg-srgb-icc.jpg jpg-srgb-icc.png + +# webcam-simulacrum.mjpg is a hand-edited file containing red.jpg and blue.jpg, +# concatenated together with the relevant headers for +# multipart/x-mixed-replace. Specifically, with the headers in +# webcam-simulacrum.mjpg^headers^, the web browser will get the following: +# +# HTTP 200 OK +# Content-Type: multipart/x-mixed-replace;boundary=BOUNDARYOMG +# +# --BOUNDARYOMG\r\n +# Content-Type: image/jpeg\r\n +# \r\n +# (no newline) +# --BOUNDARYOMG\r\n +# Content-Type: image/jpeg\r\n +# \r\n +# (no newline) +# --BOUNDARYOMG--\r\n +# +# (The boundary is arbitrary, and just has to be defined as something that +# won't be in the text of the contents themselves. --$(boundary)\r\n means +# "Here is the beginning of a boundary," and --$(boundary)-- means "All done +# sending you parts.") +HTTP == webcam-simulacrum.mjpg blue.jpg +pref(image.mozsamplesize.enabled,true) fuzzy(21,256) == jpg-size-32x32.jpg#-moz-samplesize=2 jpg-size-16x16.png +pref(image.mozsamplesize.enabled,true) fuzzy(92,16) == jpg-size-32x32.jpg#-moz-samplesize=8 jpg-size-4x4.png diff --git a/image/test/reftest/jpeg/webcam-simulacrum.mjpg b/image/test/reftest/jpeg/webcam-simulacrum.mjpg new file mode 100644 index 000000000..a593273c0 Binary files /dev/null and b/image/test/reftest/jpeg/webcam-simulacrum.mjpg differ diff --git a/image/test/reftest/jpeg/webcam-simulacrum.mjpg^headers^ b/image/test/reftest/jpeg/webcam-simulacrum.mjpg^headers^ new file mode 100644 index 000000000..f5e846508 --- /dev/null +++ b/image/test/reftest/jpeg/webcam-simulacrum.mjpg^headers^ @@ -0,0 +1,3 @@ +HTTP 200 OK +Content-Type: multipart/x-mixed-replace;boundary=BOUNDARYOMG +Cache-Control: no-cache diff --git a/image/test/reftest/pngsuite-ancillary/ccwn2c08.html b/image/test/reftest/pngsuite-ancillary/ccwn2c08.html new file mode 100644 index 000000000..dc4996e2b --- /dev/null +++ b/image/test/reftest/pngsuite-ancillary/ccwn2c08.html @@ -0,0 +1,1242 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-ancillary/ccwn2c08.png b/image/test/reftest/pngsuite-ancillary/ccwn2c08.png new file mode 100644 index 000000000..47c24817b Binary files /dev/null and b/image/test/reftest/pngsuite-ancillary/ccwn2c08.png differ diff --git a/image/test/reftest/pngsuite-ancillary/ccwn3p08.html b/image/test/reftest/pngsuite-ancillary/ccwn3p08.html new file mode 100644 index 000000000..52e636eaa --- /dev/null +++ b/image/test/reftest/pngsuite-ancillary/ccwn3p08.html @@ -0,0 +1,1272 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-ancillary/ccwn3p08.png b/image/test/reftest/pngsuite-ancillary/ccwn3p08.png new file mode 100644 index 000000000..8bb2c1098 Binary files /dev/null and b/image/test/reftest/pngsuite-ancillary/ccwn3p08.png differ diff --git a/image/test/reftest/pngsuite-ancillary/cdfn2c08.html b/image/test/reftest/pngsuite-ancillary/cdfn2c08.html new file mode 100644 index 000000000..aaae670ec --- /dev/null +++ b/image/test/reftest/pngsuite-ancillary/cdfn2c08.html @@ -0,0 +1,326 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-ancillary/cdfn2c08.png b/image/test/reftest/pngsuite-ancillary/cdfn2c08.png new file mode 100644 index 000000000..559e5261e Binary files /dev/null and b/image/test/reftest/pngsuite-ancillary/cdfn2c08.png differ diff --git a/image/test/reftest/pngsuite-ancillary/cdhn2c08.html b/image/test/reftest/pngsuite-ancillary/cdhn2c08.html new file mode 100644 index 000000000..d56ebf2e1 --- /dev/null +++ b/image/test/reftest/pngsuite-ancillary/cdhn2c08.html @@ -0,0 +1,278 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-ancillary/cdhn2c08.png b/image/test/reftest/pngsuite-ancillary/cdhn2c08.png new file mode 100644 index 000000000..3e07e8ecb Binary files /dev/null and b/image/test/reftest/pngsuite-ancillary/cdhn2c08.png differ diff --git a/image/test/reftest/pngsuite-ancillary/cdsn2c08.html b/image/test/reftest/pngsuite-ancillary/cdsn2c08.html new file mode 100644 index 000000000..3ba83a6f5 --- /dev/null +++ b/image/test/reftest/pngsuite-ancillary/cdsn2c08.html @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-ancillary/cdsn2c08.png b/image/test/reftest/pngsuite-ancillary/cdsn2c08.png new file mode 100644 index 000000000..076c32cc0 Binary files /dev/null and b/image/test/reftest/pngsuite-ancillary/cdsn2c08.png differ diff --git a/image/test/reftest/pngsuite-ancillary/cdun2c08.html b/image/test/reftest/pngsuite-ancillary/cdun2c08.html new file mode 100644 index 000000000..b78233718 --- /dev/null +++ b/image/test/reftest/pngsuite-ancillary/cdun2c08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-ancillary/cdun2c08.png b/image/test/reftest/pngsuite-ancillary/cdun2c08.png new file mode 100644 index 000000000..846033be6 Binary files /dev/null and b/image/test/reftest/pngsuite-ancillary/cdun2c08.png differ diff --git a/image/test/reftest/pngsuite-ancillary/ch1n3p04.html b/image/test/reftest/pngsuite-ancillary/ch1n3p04.html new file mode 100644 index 000000000..dc2a121de --- /dev/null +++ b/image/test/reftest/pngsuite-ancillary/ch1n3p04.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-ancillary/ch1n3p04.png b/image/test/reftest/pngsuite-ancillary/ch1n3p04.png new file mode 100644 index 000000000..17cd12dfc Binary files /dev/null and b/image/test/reftest/pngsuite-ancillary/ch1n3p04.png differ diff --git a/image/test/reftest/pngsuite-ancillary/ch2n3p08.html b/image/test/reftest/pngsuite-ancillary/ch2n3p08.html new file mode 100644 index 000000000..78b72c61c --- /dev/null +++ b/image/test/reftest/pngsuite-ancillary/ch2n3p08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-ancillary/ch2n3p08.png b/image/test/reftest/pngsuite-ancillary/ch2n3p08.png new file mode 100644 index 000000000..25c17987a Binary files /dev/null and b/image/test/reftest/pngsuite-ancillary/ch2n3p08.png differ diff --git a/image/test/reftest/pngsuite-ancillary/cm0n0g04.html b/image/test/reftest/pngsuite-ancillary/cm0n0g04.html new file mode 100644 index 000000000..25d3abca3 --- /dev/null +++ b/image/test/reftest/pngsuite-ancillary/cm0n0g04.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-ancillary/cm0n0g04.png b/image/test/reftest/pngsuite-ancillary/cm0n0g04.png new file mode 100644 index 000000000..9fba5db3b Binary files /dev/null and b/image/test/reftest/pngsuite-ancillary/cm0n0g04.png differ diff --git a/image/test/reftest/pngsuite-ancillary/cm7n0g04.html b/image/test/reftest/pngsuite-ancillary/cm7n0g04.html new file mode 100644 index 000000000..25d3abca3 --- /dev/null +++ b/image/test/reftest/pngsuite-ancillary/cm7n0g04.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-ancillary/cm7n0g04.png b/image/test/reftest/pngsuite-ancillary/cm7n0g04.png new file mode 100644 index 000000000..f7dc46e68 Binary files /dev/null and b/image/test/reftest/pngsuite-ancillary/cm7n0g04.png differ diff --git a/image/test/reftest/pngsuite-ancillary/cm9n0g04.html b/image/test/reftest/pngsuite-ancillary/cm9n0g04.html new file mode 100644 index 000000000..25d3abca3 --- /dev/null +++ b/image/test/reftest/pngsuite-ancillary/cm9n0g04.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-ancillary/cm9n0g04.png b/image/test/reftest/pngsuite-ancillary/cm9n0g04.png new file mode 100644 index 000000000..dd70911ad Binary files /dev/null and b/image/test/reftest/pngsuite-ancillary/cm9n0g04.png differ diff --git a/image/test/reftest/pngsuite-ancillary/cs3n2c16.html b/image/test/reftest/pngsuite-ancillary/cs3n2c16.html new file mode 100644 index 000000000..bc4ab1488 --- /dev/null +++ b/image/test/reftest/pngsuite-ancillary/cs3n2c16.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-ancillary/cs3n2c16.png b/image/test/reftest/pngsuite-ancillary/cs3n2c16.png new file mode 100644 index 000000000..bf5fd20a2 Binary files /dev/null and b/image/test/reftest/pngsuite-ancillary/cs3n2c16.png differ diff --git a/image/test/reftest/pngsuite-ancillary/cs3n3p08.html b/image/test/reftest/pngsuite-ancillary/cs3n3p08.html new file mode 100644 index 000000000..21557a400 --- /dev/null +++ b/image/test/reftest/pngsuite-ancillary/cs3n3p08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-ancillary/cs3n3p08.png b/image/test/reftest/pngsuite-ancillary/cs3n3p08.png new file mode 100644 index 000000000..f4a66237b Binary files /dev/null and b/image/test/reftest/pngsuite-ancillary/cs3n3p08.png differ diff --git a/image/test/reftest/pngsuite-ancillary/cs5n2c08.html b/image/test/reftest/pngsuite-ancillary/cs5n2c08.html new file mode 100644 index 000000000..d1642a1bf --- /dev/null +++ b/image/test/reftest/pngsuite-ancillary/cs5n2c08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-ancillary/cs5n2c08.png b/image/test/reftest/pngsuite-ancillary/cs5n2c08.png new file mode 100644 index 000000000..40f947c33 Binary files /dev/null and b/image/test/reftest/pngsuite-ancillary/cs5n2c08.png differ diff --git a/image/test/reftest/pngsuite-ancillary/cs5n3p08.html b/image/test/reftest/pngsuite-ancillary/cs5n3p08.html new file mode 100644 index 000000000..d1642a1bf --- /dev/null +++ b/image/test/reftest/pngsuite-ancillary/cs5n3p08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-ancillary/cs5n3p08.png b/image/test/reftest/pngsuite-ancillary/cs5n3p08.png new file mode 100644 index 000000000..dfd6e6e6e Binary files /dev/null and b/image/test/reftest/pngsuite-ancillary/cs5n3p08.png differ diff --git a/image/test/reftest/pngsuite-ancillary/cs8n2c08.html b/image/test/reftest/pngsuite-ancillary/cs8n2c08.html new file mode 100644 index 000000000..549341e76 --- /dev/null +++ b/image/test/reftest/pngsuite-ancillary/cs8n2c08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-ancillary/cs8n2c08.png b/image/test/reftest/pngsuite-ancillary/cs8n2c08.png new file mode 100644 index 000000000..8e01d3294 Binary files /dev/null and b/image/test/reftest/pngsuite-ancillary/cs8n2c08.png differ diff --git a/image/test/reftest/pngsuite-ancillary/cs8n3p08.html b/image/test/reftest/pngsuite-ancillary/cs8n3p08.html new file mode 100644 index 000000000..549341e76 --- /dev/null +++ b/image/test/reftest/pngsuite-ancillary/cs8n3p08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-ancillary/cs8n3p08.png b/image/test/reftest/pngsuite-ancillary/cs8n3p08.png new file mode 100644 index 000000000..a44066eb6 Binary files /dev/null and b/image/test/reftest/pngsuite-ancillary/cs8n3p08.png differ diff --git a/image/test/reftest/pngsuite-ancillary/ct0n0g04.html b/image/test/reftest/pngsuite-ancillary/ct0n0g04.html new file mode 100644 index 000000000..25d3abca3 --- /dev/null +++ b/image/test/reftest/pngsuite-ancillary/ct0n0g04.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-ancillary/ct0n0g04.png b/image/test/reftest/pngsuite-ancillary/ct0n0g04.png new file mode 100644 index 000000000..40d1e062f Binary files /dev/null and b/image/test/reftest/pngsuite-ancillary/ct0n0g04.png differ diff --git a/image/test/reftest/pngsuite-ancillary/ct1n0g04.html b/image/test/reftest/pngsuite-ancillary/ct1n0g04.html new file mode 100644 index 000000000..25d3abca3 --- /dev/null +++ b/image/test/reftest/pngsuite-ancillary/ct1n0g04.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-ancillary/ct1n0g04.png b/image/test/reftest/pngsuite-ancillary/ct1n0g04.png new file mode 100644 index 000000000..3ba110aa7 Binary files /dev/null and b/image/test/reftest/pngsuite-ancillary/ct1n0g04.png differ diff --git a/image/test/reftest/pngsuite-ancillary/ctzn0g04.html b/image/test/reftest/pngsuite-ancillary/ctzn0g04.html new file mode 100644 index 000000000..25d3abca3 --- /dev/null +++ b/image/test/reftest/pngsuite-ancillary/ctzn0g04.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-ancillary/ctzn0g04.png b/image/test/reftest/pngsuite-ancillary/ctzn0g04.png new file mode 100644 index 000000000..b4401c9cf Binary files /dev/null and b/image/test/reftest/pngsuite-ancillary/ctzn0g04.png differ diff --git a/image/test/reftest/pngsuite-ancillary/qcms-asm-check.js b/image/test/reftest/pngsuite-ancillary/qcms-asm-check.js new file mode 100644 index 000000000..32e4434aa --- /dev/null +++ b/image/test/reftest/pngsuite-ancillary/qcms-asm-check.js @@ -0,0 +1,28 @@ +// This is a workaround for bug 465088, that the qcms assembly doesn't +// quite match the non-assembly output. + +function check_qcms_has_assembly() +{ + // We have assembly code on x86 and x86_64 architectures. + // Unfortunately, detecting that is a little complicated. + + if (navigator.platform == "MacIntel") { + return true; + } + + if (navigator.platform.indexOf("Win") == 0 || navigator.platform == "OS/2") { + // Assume all Windows and OS/2 is x86 or x86_64. We don't + // expose any way for Web content to check. + return true; + } + + // On most Unix-like platforms, navigator.platform is basically + // |uname -sm|. + if (navigator.platform.match(/(i[3456]86|x86_64|amd64|i86)/)) { + return true; + } + + return false; +} + +var qcms_has_assembly = check_qcms_has_assembly(); diff --git a/image/test/reftest/pngsuite-ancillary/reftest-stylo.list b/image/test/reftest/pngsuite-ancillary/reftest-stylo.list new file mode 100644 index 000000000..38b0d64e1 --- /dev/null +++ b/image/test/reftest/pngsuite-ancillary/reftest-stylo.list @@ -0,0 +1,63 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# PngSuite - Ancillary chunks + +# cHRM chunks +# +# ccwn2c08 - gamma 1.0000 chunk, chroma chunk w:0.3127,0.3290 r:0.64,0.33 g:0.30,0.60 b:0.15,0.06 +fails fails-if(prefs.getIntPref("gfx.color_management.mode")!=2) fuzzy-if(winWidget,8,569) == ccwn2c08.png ccwn2c08.png +# ccwn3p08 - gamma 1.0000 chunk, chroma chunk w:0.3127,0.3290 r:0.64,0.33 g:0.30,0.60 b:0.15,0.06 +fails fails-if(prefs.getIntPref("gfx.color_management.mode")!=2) fuzzy-if(winWidget,8,577) == ccwn3p08.png ccwn3p08.png + +# pHYs chunks +# +# PngSuite implies these first 3 should end up as 32x32 bitmaps, but +# per discussion in bug 408622 that's not actually true. +# +# cdfn2c08 - physical pixel dimensions, 8x32 flat pixels +fails == cdfn2c08.png cdfn2c08.png +# cdhn2c08 - physical pixel dimensions, 32x8 high pixels +fails == cdhn2c08.png cdhn2c08.png +# cdsn2c08 - physical pixel dimensions, 8x8 square pixels +fails == cdsn2c08.png cdsn2c08.png +# cdun2c08 - physical pixel dimensions, 1000 pixels per 1 meter +fails == cdun2c08.png cdun2c08.png + +# hISt chunks (shouldn't affect display on 24bit systems) +# +# ch1n3p04 - histogram 15 colors +fails == ch1n3p04.png ch1n3p04.png +# ch2n3p08 - histogram 256 colors +fails == ch2n3p08.png ch2n3p08.png + +# tIME chunks (doesn't affect display) +# +# cm0n0g04 - modification time, 01-jan-2000 12:34:56 +fails == cm0n0g04.png cm0n0g04.png +# cm7n0g04 - modification time, 01-jan-1970 00:00:00 +fails == cm7n0g04.png cm7n0g04.png +# cm9n0g04 - modification time, 31-dec-1999 23:59:59 +fails == cm9n0g04.png cm9n0g04.png + +# sBIT chunks +# +# cs3n2c16 - color, 13 significant bits +fails == cs3n2c16.png cs3n2c16.png +# cs3n3p08 - paletted, 3 significant bits +fails == cs3n3p08.png cs3n3p08.png +# cs5n2c08 - color, 5 significant bits +fails == cs5n2c08.png cs5n2c08.png +# cs5n3p08 - paletted, 5 significant bits +fails == cs5n3p08.png cs5n3p08.png +# cs8n2c08 - color, 8 significant bits (reference) +fails == cs8n2c08.png cs8n2c08.png +# cs8n3p08 - paletted, 8 significant bits (reference) +fails == cs8n3p08.png cs8n3p08.png + +# tEXt chunks (doesn't affect display) +# +# ct0n0g04 - no textual data +fails == ct0n0g04.png ct0n0g04.png +# ct1n0g04 - with textual data +fails == ct1n0g04.png ct1n0g04.png +# ctzn0g04 - with compressed textual data +fails == ctzn0g04.png ctzn0g04.png diff --git a/image/test/reftest/pngsuite-ancillary/reftest.list b/image/test/reftest/pngsuite-ancillary/reftest.list new file mode 100644 index 000000000..7a8900b95 --- /dev/null +++ b/image/test/reftest/pngsuite-ancillary/reftest.list @@ -0,0 +1,62 @@ +# PngSuite - Ancillary chunks + +# cHRM chunks +# +# ccwn2c08 - gamma 1.0000 chunk, chroma chunk w:0.3127,0.3290 r:0.64,0.33 g:0.30,0.60 b:0.15,0.06 +fails-if(prefs.getIntPref("gfx.color_management.mode")!=2) fuzzy-if(winWidget,8,569) == ccwn2c08.png ccwn2c08.html +# ccwn3p08 - gamma 1.0000 chunk, chroma chunk w:0.3127,0.3290 r:0.64,0.33 g:0.30,0.60 b:0.15,0.06 +fails-if(prefs.getIntPref("gfx.color_management.mode")!=2) fuzzy-if(winWidget,8,577) == ccwn3p08.png ccwn3p08.html + +# pHYs chunks +# +# PngSuite implies these first 3 should end up as 32x32 bitmaps, but +# per discussion in bug 408622 that's not actually true. +# +# cdfn2c08 - physical pixel dimensions, 8x32 flat pixels +== cdfn2c08.png cdfn2c08.html +# cdhn2c08 - physical pixel dimensions, 32x8 high pixels +== cdhn2c08.png cdhn2c08.html +# cdsn2c08 - physical pixel dimensions, 8x8 square pixels +== cdsn2c08.png cdsn2c08.html +# cdun2c08 - physical pixel dimensions, 1000 pixels per 1 meter +== cdun2c08.png cdun2c08.html + +# hISt chunks (shouldn't affect display on 24bit systems) +# +# ch1n3p04 - histogram 15 colors +== ch1n3p04.png ch1n3p04.html +# ch2n3p08 - histogram 256 colors +== ch2n3p08.png ch2n3p08.html + +# tIME chunks (doesn't affect display) +# +# cm0n0g04 - modification time, 01-jan-2000 12:34:56 +== cm0n0g04.png cm0n0g04.html +# cm7n0g04 - modification time, 01-jan-1970 00:00:00 +== cm7n0g04.png cm7n0g04.html +# cm9n0g04 - modification time, 31-dec-1999 23:59:59 +== cm9n0g04.png cm9n0g04.html + +# sBIT chunks +# +# cs3n2c16 - color, 13 significant bits +== cs3n2c16.png cs3n2c16.html +# cs3n3p08 - paletted, 3 significant bits +== cs3n3p08.png cs3n3p08.html +# cs5n2c08 - color, 5 significant bits +== cs5n2c08.png cs5n2c08.html +# cs5n3p08 - paletted, 5 significant bits +== cs5n3p08.png cs5n3p08.html +# cs8n2c08 - color, 8 significant bits (reference) +== cs8n2c08.png cs8n2c08.html +# cs8n3p08 - paletted, 8 significant bits (reference) +== cs8n3p08.png cs8n3p08.html + +# tEXt chunks (doesn't affect display) +# +# ct0n0g04 - no textual data +== ct0n0g04.png ct0n0g04.html +# ct1n0g04 - with textual data +== ct1n0g04.png ct1n0g04.html +# ctzn0g04 - with compressed textual data +== ctzn0g04.png ctzn0g04.html diff --git a/image/test/reftest/pngsuite-background/bg__4a08.html b/image/test/reftest/pngsuite-background/bg__4a08.html new file mode 100644 index 000000000..743ad1200 --- /dev/null +++ b/image/test/reftest/pngsuite-background/bg__4a08.html @@ -0,0 +1,1092 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-background/bg__4a16.html b/image/test/reftest/pngsuite-background/bg__4a16.html new file mode 100644 index 000000000..b15b280f1 --- /dev/null +++ b/image/test/reftest/pngsuite-background/bg__4a16.html @@ -0,0 +1,1092 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-background/bg__6a08.html b/image/test/reftest/pngsuite-background/bg__6a08.html new file mode 100644 index 000000000..1ab2721f3 --- /dev/null +++ b/image/test/reftest/pngsuite-background/bg__6a08.html @@ -0,0 +1,1092 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-background/bg__6a16.html b/image/test/reftest/pngsuite-background/bg__6a16.html new file mode 100644 index 000000000..8ead05a34 --- /dev/null +++ b/image/test/reftest/pngsuite-background/bg__6a16.html @@ -0,0 +1,1092 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-background/bgai4a08.png b/image/test/reftest/pngsuite-background/bgai4a08.png new file mode 100644 index 000000000..398132be5 Binary files /dev/null and b/image/test/reftest/pngsuite-background/bgai4a08.png differ diff --git a/image/test/reftest/pngsuite-background/bgai4a16.png b/image/test/reftest/pngsuite-background/bgai4a16.png new file mode 100644 index 000000000..51192e731 Binary files /dev/null and b/image/test/reftest/pngsuite-background/bgai4a16.png differ diff --git a/image/test/reftest/pngsuite-background/bgan6a08.png b/image/test/reftest/pngsuite-background/bgan6a08.png new file mode 100644 index 000000000..e60873876 Binary files /dev/null and b/image/test/reftest/pngsuite-background/bgan6a08.png differ diff --git a/image/test/reftest/pngsuite-background/bgan6a16.png b/image/test/reftest/pngsuite-background/bgan6a16.png new file mode 100644 index 000000000..984a99525 Binary files /dev/null and b/image/test/reftest/pngsuite-background/bgan6a16.png differ diff --git a/image/test/reftest/pngsuite-background/bgbn4a08.png b/image/test/reftest/pngsuite-background/bgbn4a08.png new file mode 100644 index 000000000..7cbefc3bf Binary files /dev/null and b/image/test/reftest/pngsuite-background/bgbn4a08.png differ diff --git a/image/test/reftest/pngsuite-background/bggn4a16.png b/image/test/reftest/pngsuite-background/bggn4a16.png new file mode 100644 index 000000000..13fd85ba1 Binary files /dev/null and b/image/test/reftest/pngsuite-background/bggn4a16.png differ diff --git a/image/test/reftest/pngsuite-background/bgwn6a08.png b/image/test/reftest/pngsuite-background/bgwn6a08.png new file mode 100644 index 000000000..a67ff205b Binary files /dev/null and b/image/test/reftest/pngsuite-background/bgwn6a08.png differ diff --git a/image/test/reftest/pngsuite-background/bgyn6a16.png b/image/test/reftest/pngsuite-background/bgyn6a16.png new file mode 100644 index 000000000..ae3e9be58 Binary files /dev/null and b/image/test/reftest/pngsuite-background/bgyn6a16.png differ diff --git a/image/test/reftest/pngsuite-background/reftest-stylo.list b/image/test/reftest/pngsuite-background/reftest-stylo.list new file mode 100644 index 000000000..567b36b5b --- /dev/null +++ b/image/test/reftest/pngsuite-background/reftest-stylo.list @@ -0,0 +1,23 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# PngSuite - Background colors +# +# Note 1: The first 4 images have no bKGD chunk, the last 4 do. The background +# color indicated by bKGD isn't used, so the two sets of images are rendered +# identically and thus share common reference HTML files. + +# bgai4a08 - 8 bit grayscale, alpha, no background chunk, interlaced +skip fuzzy-if(cocoaWidget||skiaContent,1,1024) == wrapper.html?bgai4a08.png wrapper.html?bgai4a08.png +# bgai4a16 - 16 bit grayscale, alpha, no background chunk, interlaced +skip fuzzy-if(cocoaWidget||skiaContent,1,1024) == wrapper.html?bgai4a16.png wrapper.html?bgai4a16.png +# bgan6a08 - 3x8 bits rgb color, alpha, no background chunk +skip fuzzy-if(cocoaWidget||skiaContent,1,1024) == wrapper.html?bgan6a08.png wrapper.html?bgan6a08.png +# bgan6a16 - 3x16 bits rgb color, alpha, no background chunk +skip fuzzy-if(cocoaWidget||skiaContent,1,1024) == wrapper.html?bgan6a16.png wrapper.html?bgan6a16.png +# bgbn4a08 - 8 bit grayscale, alpha, black background chunk +skip fuzzy-if(cocoaWidget||skiaContent,1,1024) == wrapper.html?bgbn4a08.png wrapper.html?bgbn4a08.png +# bggn4a16 - 16 bit grayscale, alpha, gray background chunk +skip fuzzy-if(cocoaWidget||skiaContent,1,1024) == wrapper.html?bggn4a16.png wrapper.html?bggn4a16.png +# bgwn6a08 - 3x8 bits rgb color, alpha, white background chunk +skip fuzzy-if(cocoaWidget||skiaContent,1,1024) == wrapper.html?bgwn6a08.png wrapper.html?bgwn6a08.png +# bgyn6a16 - 3x16 bits rgb color, alpha, yellow background chunk +skip fuzzy-if(cocoaWidget||skiaContent,1,1024) == wrapper.html?bgyn6a16.png wrapper.html?bgyn6a16.png diff --git a/image/test/reftest/pngsuite-background/reftest.list b/image/test/reftest/pngsuite-background/reftest.list new file mode 100644 index 000000000..f15cfa079 --- /dev/null +++ b/image/test/reftest/pngsuite-background/reftest.list @@ -0,0 +1,22 @@ +# PngSuite - Background colors +# +# Note 1: The first 4 images have no bKGD chunk, the last 4 do. The background +# color indicated by bKGD isn't used, so the two sets of images are rendered +# identically and thus share common reference HTML files. + +# bgai4a08 - 8 bit grayscale, alpha, no background chunk, interlaced +fuzzy-if(cocoaWidget||skiaContent,1,1024) == wrapper.html?bgai4a08.png bg__4a08.html +# bgai4a16 - 16 bit grayscale, alpha, no background chunk, interlaced +fuzzy-if(cocoaWidget||skiaContent,1,1024) == wrapper.html?bgai4a16.png bg__4a16.html +# bgan6a08 - 3x8 bits rgb color, alpha, no background chunk +fuzzy-if(cocoaWidget||skiaContent,1,1024) == wrapper.html?bgan6a08.png bg__6a08.html +# bgan6a16 - 3x16 bits rgb color, alpha, no background chunk +fuzzy-if(cocoaWidget||skiaContent,1,1024) == wrapper.html?bgan6a16.png bg__6a16.html +# bgbn4a08 - 8 bit grayscale, alpha, black background chunk +fuzzy-if(cocoaWidget||skiaContent,1,1024) == wrapper.html?bgbn4a08.png bg__4a08.html +# bggn4a16 - 16 bit grayscale, alpha, gray background chunk +fuzzy-if(cocoaWidget||skiaContent,1,1024) == wrapper.html?bggn4a16.png bg__4a16.html +# bgwn6a08 - 3x8 bits rgb color, alpha, white background chunk +fuzzy-if(cocoaWidget||skiaContent,1,1024) == wrapper.html?bgwn6a08.png bg__6a08.html +# bgyn6a16 - 3x16 bits rgb color, alpha, yellow background chunk +fuzzy-if(cocoaWidget||skiaContent,1,1024) == wrapper.html?bgyn6a16.png bg__6a16.html diff --git a/image/test/reftest/pngsuite-background/wrapper.html b/image/test/reftest/pngsuite-background/wrapper.html new file mode 100644 index 000000000..5bbe75e01 --- /dev/null +++ b/image/test/reftest/pngsuite-background/wrapper.html @@ -0,0 +1,27 @@ + + + +Image reftest wrapper + + + + + + + + + diff --git a/image/test/reftest/pngsuite-basic-i/basi0g01.html b/image/test/reftest/pngsuite-basic-i/basi0g01.html new file mode 100644 index 000000000..7389a1b66 --- /dev/null +++ b/image/test/reftest/pngsuite-basic-i/basi0g01.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-basic-i/basi0g01.png b/image/test/reftest/pngsuite-basic-i/basi0g01.png new file mode 100644 index 000000000..556fa7270 Binary files /dev/null and b/image/test/reftest/pngsuite-basic-i/basi0g01.png differ diff --git a/image/test/reftest/pngsuite-basic-i/basi0g02.html b/image/test/reftest/pngsuite-basic-i/basi0g02.html new file mode 100644 index 000000000..538afad14 --- /dev/null +++ b/image/test/reftest/pngsuite-basic-i/basi0g02.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-basic-i/basi0g02.png b/image/test/reftest/pngsuite-basic-i/basi0g02.png new file mode 100644 index 000000000..ce09821ef Binary files /dev/null and b/image/test/reftest/pngsuite-basic-i/basi0g02.png differ diff --git a/image/test/reftest/pngsuite-basic-i/basi0g04.html b/image/test/reftest/pngsuite-basic-i/basi0g04.html new file mode 100644 index 000000000..d782230d4 --- /dev/null +++ b/image/test/reftest/pngsuite-basic-i/basi0g04.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-basic-i/basi0g04.png b/image/test/reftest/pngsuite-basic-i/basi0g04.png new file mode 100644 index 000000000..3853273f9 Binary files /dev/null and b/image/test/reftest/pngsuite-basic-i/basi0g04.png differ diff --git a/image/test/reftest/pngsuite-basic-i/basi0g08.html b/image/test/reftest/pngsuite-basic-i/basi0g08.html new file mode 100644 index 000000000..5aaf11cab --- /dev/null +++ b/image/test/reftest/pngsuite-basic-i/basi0g08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-basic-i/basi0g08.png b/image/test/reftest/pngsuite-basic-i/basi0g08.png new file mode 100644 index 000000000..faed8bec4 Binary files /dev/null and b/image/test/reftest/pngsuite-basic-i/basi0g08.png differ diff --git a/image/test/reftest/pngsuite-basic-i/basi0g16.html b/image/test/reftest/pngsuite-basic-i/basi0g16.html new file mode 100644 index 000000000..fc18c727b --- /dev/null +++ b/image/test/reftest/pngsuite-basic-i/basi0g16.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-basic-i/basi0g16.png b/image/test/reftest/pngsuite-basic-i/basi0g16.png new file mode 100644 index 000000000..a9f28165e Binary files /dev/null and b/image/test/reftest/pngsuite-basic-i/basi0g16.png differ diff --git a/image/test/reftest/pngsuite-basic-i/basi2c08.html b/image/test/reftest/pngsuite-basic-i/basi2c08.html new file mode 100644 index 000000000..e30216bdf --- /dev/null +++ b/image/test/reftest/pngsuite-basic-i/basi2c08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-basic-i/basi2c08.png b/image/test/reftest/pngsuite-basic-i/basi2c08.png new file mode 100644 index 000000000..2aab44d42 Binary files /dev/null and b/image/test/reftest/pngsuite-basic-i/basi2c08.png differ diff --git a/image/test/reftest/pngsuite-basic-i/basi2c16.html b/image/test/reftest/pngsuite-basic-i/basi2c16.html new file mode 100644 index 000000000..dd08f0e3d --- /dev/null +++ b/image/test/reftest/pngsuite-basic-i/basi2c16.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-basic-i/basi2c16.png b/image/test/reftest/pngsuite-basic-i/basi2c16.png new file mode 100644 index 000000000..cd7e50f91 Binary files /dev/null and b/image/test/reftest/pngsuite-basic-i/basi2c16.png differ diff --git a/image/test/reftest/pngsuite-basic-i/basi3p01.html b/image/test/reftest/pngsuite-basic-i/basi3p01.html new file mode 100644 index 000000000..2cb512200 --- /dev/null +++ b/image/test/reftest/pngsuite-basic-i/basi3p01.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-basic-i/basi3p01.png b/image/test/reftest/pngsuite-basic-i/basi3p01.png new file mode 100644 index 000000000..00a7cea6c Binary files /dev/null and b/image/test/reftest/pngsuite-basic-i/basi3p01.png differ diff --git a/image/test/reftest/pngsuite-basic-i/basi3p02.html b/image/test/reftest/pngsuite-basic-i/basi3p02.html new file mode 100644 index 000000000..4555fbb9b --- /dev/null +++ b/image/test/reftest/pngsuite-basic-i/basi3p02.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-basic-i/basi3p02.png b/image/test/reftest/pngsuite-basic-i/basi3p02.png new file mode 100644 index 000000000..bb16b44b3 Binary files /dev/null and b/image/test/reftest/pngsuite-basic-i/basi3p02.png differ diff --git a/image/test/reftest/pngsuite-basic-i/basi3p04.html b/image/test/reftest/pngsuite-basic-i/basi3p04.html new file mode 100644 index 000000000..dc2a121de --- /dev/null +++ b/image/test/reftest/pngsuite-basic-i/basi3p04.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-basic-i/basi3p04.png b/image/test/reftest/pngsuite-basic-i/basi3p04.png new file mode 100644 index 000000000..b4e888e24 Binary files /dev/null and b/image/test/reftest/pngsuite-basic-i/basi3p04.png differ diff --git a/image/test/reftest/pngsuite-basic-i/basi3p08.html b/image/test/reftest/pngsuite-basic-i/basi3p08.html new file mode 100644 index 000000000..78b72c61c --- /dev/null +++ b/image/test/reftest/pngsuite-basic-i/basi3p08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-basic-i/basi3p08.png b/image/test/reftest/pngsuite-basic-i/basi3p08.png new file mode 100644 index 000000000..50a6d1cac Binary files /dev/null and b/image/test/reftest/pngsuite-basic-i/basi3p08.png differ diff --git a/image/test/reftest/pngsuite-basic-i/basi4a08.png b/image/test/reftest/pngsuite-basic-i/basi4a08.png new file mode 100644 index 000000000..398132be5 Binary files /dev/null and b/image/test/reftest/pngsuite-basic-i/basi4a08.png differ diff --git a/image/test/reftest/pngsuite-basic-i/basi4a16.png b/image/test/reftest/pngsuite-basic-i/basi4a16.png new file mode 100644 index 000000000..51192e731 Binary files /dev/null and b/image/test/reftest/pngsuite-basic-i/basi4a16.png differ diff --git a/image/test/reftest/pngsuite-basic-i/basi6a08.png b/image/test/reftest/pngsuite-basic-i/basi6a08.png new file mode 100644 index 000000000..aecb32e0d Binary files /dev/null and b/image/test/reftest/pngsuite-basic-i/basi6a08.png differ diff --git a/image/test/reftest/pngsuite-basic-i/basi6a16.png b/image/test/reftest/pngsuite-basic-i/basi6a16.png new file mode 100644 index 000000000..4181533ad Binary files /dev/null and b/image/test/reftest/pngsuite-basic-i/basi6a16.png differ diff --git a/image/test/reftest/pngsuite-basic-i/reftest-stylo.list b/image/test/reftest/pngsuite-basic-i/reftest-stylo.list new file mode 100644 index 000000000..cef5dbc6c --- /dev/null +++ b/image/test/reftest/pngsuite-basic-i/reftest-stylo.list @@ -0,0 +1,34 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# PngSuite - Basic formats (interlaced) + + +# basi0g01 - black & white +fails == basi0g01.png basi0g01.png +# basi0g02 - 2 bit (4 level) grayscale +fails == basi0g02.png basi0g02.png +# basi0g04 - 4 bit (16 level) grayscale +fails == basi0g04.png basi0g04.png +# basi0g08 - 8 bit (256 level) grayscale +fails == basi0g08.png basi0g08.png +# basi0g16 - 16 bit (64k level) grayscale +fails == basi0g16.png basi0g16.png +# basi2c08 - 3x8 bits rgb color +fails == basi2c08.png basi2c08.png +# basi2c16 - 3x16 bits rgb color +fails == basi2c16.png basi2c16.png +# basi3p01 - 1 bit (2 color) paletted +fails == basi3p01.png basi3p01.png +# basi3p02 - 2 bit (4 color) paletted +fails == basi3p02.png basi3p02.png +# basi3p04 - 4 bit (16 color) paletted +fails == basi3p04.png basi3p04.png +# basi3p08 - 8 bit (256 color) paletted +# fails == basi3p08.png basi3p08.png +# basi4a08 - 8 bit grayscale + 8 bit alpha-channel +#== basi4a08.png basi4a08.png +# basi4a16 - 16 bit grayscale + 16 bit alpha-channel +#== basi4a16.png basi4a16.png +# basi6a08 - 3x8 bits rgb color + 8 bit alpha-channel +#== basi6a08.png basi6a08.png +# basi6a16 - 3x16 bits rgb color + 16 bit alpha-channel +#== basi6a16.png basi6a16.png diff --git a/image/test/reftest/pngsuite-basic-i/reftest.list b/image/test/reftest/pngsuite-basic-i/reftest.list new file mode 100644 index 000000000..bc61af89d --- /dev/null +++ b/image/test/reftest/pngsuite-basic-i/reftest.list @@ -0,0 +1,33 @@ +# PngSuite - Basic formats (interlaced) + + +# basi0g01 - black & white +== basi0g01.png basi0g01.html +# basi0g02 - 2 bit (4 level) grayscale +== basi0g02.png basi0g02.html +# basi0g04 - 4 bit (16 level) grayscale +== basi0g04.png basi0g04.html +# basi0g08 - 8 bit (256 level) grayscale +== basi0g08.png basi0g08.html +# basi0g16 - 16 bit (64k level) grayscale +== basi0g16.png basi0g16.html +# basi2c08 - 3x8 bits rgb color +== basi2c08.png basi2c08.html +# basi2c16 - 3x16 bits rgb color +== basi2c16.png basi2c16.html +# basi3p01 - 1 bit (2 color) paletted +== basi3p01.png basi3p01.html +# basi3p02 - 2 bit (4 color) paletted +== basi3p02.png basi3p02.html +# basi3p04 - 4 bit (16 color) paletted +== basi3p04.png basi3p04.html +# basi3p08 - 8 bit (256 color) paletted +== basi3p08.png basi3p08.html +# basi4a08 - 8 bit grayscale + 8 bit alpha-channel +#== basi4a08.png basi4a08.html +# basi4a16 - 16 bit grayscale + 16 bit alpha-channel +#== basi4a16.png basi4a16.html +# basi6a08 - 3x8 bits rgb color + 8 bit alpha-channel +#== basi6a08.png basi6a08.html +# basi6a16 - 3x16 bits rgb color + 16 bit alpha-channel +#== basi6a16.png basi6a16.html diff --git a/image/test/reftest/pngsuite-basic-n/basn0g01.html b/image/test/reftest/pngsuite-basic-n/basn0g01.html new file mode 100644 index 000000000..7389a1b66 --- /dev/null +++ b/image/test/reftest/pngsuite-basic-n/basn0g01.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-basic-n/basn0g01.png b/image/test/reftest/pngsuite-basic-n/basn0g01.png new file mode 100644 index 000000000..1d722423a Binary files /dev/null and b/image/test/reftest/pngsuite-basic-n/basn0g01.png differ diff --git a/image/test/reftest/pngsuite-basic-n/basn0g02.html b/image/test/reftest/pngsuite-basic-n/basn0g02.html new file mode 100644 index 000000000..538afad14 --- /dev/null +++ b/image/test/reftest/pngsuite-basic-n/basn0g02.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-basic-n/basn0g02.png b/image/test/reftest/pngsuite-basic-n/basn0g02.png new file mode 100644 index 000000000..508332418 Binary files /dev/null and b/image/test/reftest/pngsuite-basic-n/basn0g02.png differ diff --git a/image/test/reftest/pngsuite-basic-n/basn0g04.html b/image/test/reftest/pngsuite-basic-n/basn0g04.html new file mode 100644 index 000000000..d782230d4 --- /dev/null +++ b/image/test/reftest/pngsuite-basic-n/basn0g04.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-basic-n/basn0g04.png b/image/test/reftest/pngsuite-basic-n/basn0g04.png new file mode 100644 index 000000000..0bf368786 Binary files /dev/null and b/image/test/reftest/pngsuite-basic-n/basn0g04.png differ diff --git a/image/test/reftest/pngsuite-basic-n/basn0g08.html b/image/test/reftest/pngsuite-basic-n/basn0g08.html new file mode 100644 index 000000000..5aaf11cab --- /dev/null +++ b/image/test/reftest/pngsuite-basic-n/basn0g08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-basic-n/basn0g08.png b/image/test/reftest/pngsuite-basic-n/basn0g08.png new file mode 100644 index 000000000..23c82379a Binary files /dev/null and b/image/test/reftest/pngsuite-basic-n/basn0g08.png differ diff --git a/image/test/reftest/pngsuite-basic-n/basn0g16.html b/image/test/reftest/pngsuite-basic-n/basn0g16.html new file mode 100644 index 000000000..fc18c727b --- /dev/null +++ b/image/test/reftest/pngsuite-basic-n/basn0g16.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-basic-n/basn0g16.png b/image/test/reftest/pngsuite-basic-n/basn0g16.png new file mode 100644 index 000000000..e7c82f78e Binary files /dev/null and b/image/test/reftest/pngsuite-basic-n/basn0g16.png differ diff --git a/image/test/reftest/pngsuite-basic-n/basn2c08.html b/image/test/reftest/pngsuite-basic-n/basn2c08.html new file mode 100644 index 000000000..e30216bdf --- /dev/null +++ b/image/test/reftest/pngsuite-basic-n/basn2c08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-basic-n/basn2c08.png b/image/test/reftest/pngsuite-basic-n/basn2c08.png new file mode 100644 index 000000000..db5ad1586 Binary files /dev/null and b/image/test/reftest/pngsuite-basic-n/basn2c08.png differ diff --git a/image/test/reftest/pngsuite-basic-n/basn2c16.html b/image/test/reftest/pngsuite-basic-n/basn2c16.html new file mode 100644 index 000000000..dd08f0e3d --- /dev/null +++ b/image/test/reftest/pngsuite-basic-n/basn2c16.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-basic-n/basn2c16.png b/image/test/reftest/pngsuite-basic-n/basn2c16.png new file mode 100644 index 000000000..50c1cb91a Binary files /dev/null and b/image/test/reftest/pngsuite-basic-n/basn2c16.png differ diff --git a/image/test/reftest/pngsuite-basic-n/basn3p01.html b/image/test/reftest/pngsuite-basic-n/basn3p01.html new file mode 100644 index 000000000..2cb512200 --- /dev/null +++ b/image/test/reftest/pngsuite-basic-n/basn3p01.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-basic-n/basn3p01.png b/image/test/reftest/pngsuite-basic-n/basn3p01.png new file mode 100644 index 000000000..b145c2b8e Binary files /dev/null and b/image/test/reftest/pngsuite-basic-n/basn3p01.png differ diff --git a/image/test/reftest/pngsuite-basic-n/basn3p02.html b/image/test/reftest/pngsuite-basic-n/basn3p02.html new file mode 100644 index 000000000..4555fbb9b --- /dev/null +++ b/image/test/reftest/pngsuite-basic-n/basn3p02.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-basic-n/basn3p02.png b/image/test/reftest/pngsuite-basic-n/basn3p02.png new file mode 100644 index 000000000..8985b3d81 Binary files /dev/null and b/image/test/reftest/pngsuite-basic-n/basn3p02.png differ diff --git a/image/test/reftest/pngsuite-basic-n/basn3p04.html b/image/test/reftest/pngsuite-basic-n/basn3p04.html new file mode 100644 index 000000000..dc2a121de --- /dev/null +++ b/image/test/reftest/pngsuite-basic-n/basn3p04.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-basic-n/basn3p04.png b/image/test/reftest/pngsuite-basic-n/basn3p04.png new file mode 100644 index 000000000..0fbf9e827 Binary files /dev/null and b/image/test/reftest/pngsuite-basic-n/basn3p04.png differ diff --git a/image/test/reftest/pngsuite-basic-n/basn3p08.html b/image/test/reftest/pngsuite-basic-n/basn3p08.html new file mode 100644 index 000000000..78b72c61c --- /dev/null +++ b/image/test/reftest/pngsuite-basic-n/basn3p08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-basic-n/basn3p08.png b/image/test/reftest/pngsuite-basic-n/basn3p08.png new file mode 100644 index 000000000..0ddad07e5 Binary files /dev/null and b/image/test/reftest/pngsuite-basic-n/basn3p08.png differ diff --git a/image/test/reftest/pngsuite-basic-n/basn4a08.png b/image/test/reftest/pngsuite-basic-n/basn4a08.png new file mode 100644 index 000000000..3e1305220 Binary files /dev/null and b/image/test/reftest/pngsuite-basic-n/basn4a08.png differ diff --git a/image/test/reftest/pngsuite-basic-n/basn4a16.png b/image/test/reftest/pngsuite-basic-n/basn4a16.png new file mode 100644 index 000000000..8243644d0 Binary files /dev/null and b/image/test/reftest/pngsuite-basic-n/basn4a16.png differ diff --git a/image/test/reftest/pngsuite-basic-n/basn6a08.png b/image/test/reftest/pngsuite-basic-n/basn6a08.png new file mode 100644 index 000000000..e60873876 Binary files /dev/null and b/image/test/reftest/pngsuite-basic-n/basn6a08.png differ diff --git a/image/test/reftest/pngsuite-basic-n/basn6a16.png b/image/test/reftest/pngsuite-basic-n/basn6a16.png new file mode 100644 index 000000000..984a99525 Binary files /dev/null and b/image/test/reftest/pngsuite-basic-n/basn6a16.png differ diff --git a/image/test/reftest/pngsuite-basic-n/reftest-stylo.list b/image/test/reftest/pngsuite-basic-n/reftest-stylo.list new file mode 100644 index 000000000..a4f594645 --- /dev/null +++ b/image/test/reftest/pngsuite-basic-n/reftest-stylo.list @@ -0,0 +1,34 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# PngSuite - Basic formats (non-interlaced) + + +# basn0g01 - black & white +fails == basn0g01.png basn0g01.png +# basn0g02 - 2 bit (4 level) grayscale +fails == basn0g02.png basn0g02.png +# basn0g04 - 4 bit (16 level) grayscale +fails == basn0g04.png basn0g04.png +# basn0g08 - 8 bit (256 level) grayscale +fails == basn0g08.png basn0g08.png +# basn0g16 - 16 bit (64k level) grayscale +fails == basn0g16.png basn0g16.png +# basn2c08 - 3x8 bits rgb color +fails == basn2c08.png basn2c08.png +# basn2c16 - 3x16 bits rgb color +fails == basn2c16.png basn2c16.png +# basn3p01 - 1 bit (2 color) paletted +fails == basn3p01.png basn3p01.png +# basn3p02 - 2 bit (4 color) paletted +fails == basn3p02.png basn3p02.png +# basn3p04 - 4 bit (16 color) paletted +fails == basn3p04.png basn3p04.png +# basn3p08 - 8 bit (256 color) paletted +fails == basn3p08.png basn3p08.png +# basn4a08 - 8 bit grayscale + 8 bit alpha-channel +#== basn4a08.png basn4a08.png +# basn4a16 - 16 bit grayscale + 16 bit alpha-channel +#== basn4a16.png basn4a16.png +# basn6a08 - 3x8 bits rgb color + 8 bit alpha-channel +#== basn6a08.png basn6a08.png +# basn6a16 - 3x16 bits rgb color + 16 bit alpha-channel +#== basn6a16.png basn6a16.png diff --git a/image/test/reftest/pngsuite-basic-n/reftest.list b/image/test/reftest/pngsuite-basic-n/reftest.list new file mode 100644 index 000000000..c59a5a7e4 --- /dev/null +++ b/image/test/reftest/pngsuite-basic-n/reftest.list @@ -0,0 +1,33 @@ +# PngSuite - Basic formats (non-interlaced) + + +# basn0g01 - black & white +== basn0g01.png basn0g01.html +# basn0g02 - 2 bit (4 level) grayscale +== basn0g02.png basn0g02.html +# basn0g04 - 4 bit (16 level) grayscale +== basn0g04.png basn0g04.html +# basn0g08 - 8 bit (256 level) grayscale +== basn0g08.png basn0g08.html +# basn0g16 - 16 bit (64k level) grayscale +== basn0g16.png basn0g16.html +# basn2c08 - 3x8 bits rgb color +== basn2c08.png basn2c08.html +# basn2c16 - 3x16 bits rgb color +== basn2c16.png basn2c16.html +# basn3p01 - 1 bit (2 color) paletted +== basn3p01.png basn3p01.html +# basn3p02 - 2 bit (4 color) paletted +== basn3p02.png basn3p02.html +# basn3p04 - 4 bit (16 color) paletted +== basn3p04.png basn3p04.html +# basn3p08 - 8 bit (256 color) paletted +== basn3p08.png basn3p08.html +# basn4a08 - 8 bit grayscale + 8 bit alpha-channel +#== basn4a08.png basn4a08.html +# basn4a16 - 16 bit grayscale + 16 bit alpha-channel +#== basn4a16.png basn4a16.html +# basn6a08 - 3x8 bits rgb color + 8 bit alpha-channel +#== basn6a08.png basn6a08.html +# basn6a16 - 3x16 bits rgb color + 16 bit alpha-channel +#== basn6a16.png basn6a16.html diff --git a/image/test/reftest/pngsuite-chunkorder/color.html b/image/test/reftest/pngsuite-chunkorder/color.html new file mode 100644 index 000000000..dd08f0e3d --- /dev/null +++ b/image/test/reftest/pngsuite-chunkorder/color.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-chunkorder/grayscale.html b/image/test/reftest/pngsuite-chunkorder/grayscale.html new file mode 100644 index 000000000..fc18c727b --- /dev/null +++ b/image/test/reftest/pngsuite-chunkorder/grayscale.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-chunkorder/oi1n0g16.png b/image/test/reftest/pngsuite-chunkorder/oi1n0g16.png new file mode 100644 index 000000000..e7c82f78e Binary files /dev/null and b/image/test/reftest/pngsuite-chunkorder/oi1n0g16.png differ diff --git a/image/test/reftest/pngsuite-chunkorder/oi1n2c16.png b/image/test/reftest/pngsuite-chunkorder/oi1n2c16.png new file mode 100644 index 000000000..50c1cb91a Binary files /dev/null and b/image/test/reftest/pngsuite-chunkorder/oi1n2c16.png differ diff --git a/image/test/reftest/pngsuite-chunkorder/oi2n0g16.png b/image/test/reftest/pngsuite-chunkorder/oi2n0g16.png new file mode 100644 index 000000000..14d64c583 Binary files /dev/null and b/image/test/reftest/pngsuite-chunkorder/oi2n0g16.png differ diff --git a/image/test/reftest/pngsuite-chunkorder/oi2n2c16.png b/image/test/reftest/pngsuite-chunkorder/oi2n2c16.png new file mode 100644 index 000000000..4c2e3e335 Binary files /dev/null and b/image/test/reftest/pngsuite-chunkorder/oi2n2c16.png differ diff --git a/image/test/reftest/pngsuite-chunkorder/oi4n0g16.png b/image/test/reftest/pngsuite-chunkorder/oi4n0g16.png new file mode 100644 index 000000000..69e73ede3 Binary files /dev/null and b/image/test/reftest/pngsuite-chunkorder/oi4n0g16.png differ diff --git a/image/test/reftest/pngsuite-chunkorder/oi4n2c16.png b/image/test/reftest/pngsuite-chunkorder/oi4n2c16.png new file mode 100644 index 000000000..93691e373 Binary files /dev/null and b/image/test/reftest/pngsuite-chunkorder/oi4n2c16.png differ diff --git a/image/test/reftest/pngsuite-chunkorder/oi9n0g16.png b/image/test/reftest/pngsuite-chunkorder/oi9n0g16.png new file mode 100644 index 000000000..924841357 Binary files /dev/null and b/image/test/reftest/pngsuite-chunkorder/oi9n0g16.png differ diff --git a/image/test/reftest/pngsuite-chunkorder/oi9n2c16.png b/image/test/reftest/pngsuite-chunkorder/oi9n2c16.png new file mode 100644 index 000000000..f0512e49f Binary files /dev/null and b/image/test/reftest/pngsuite-chunkorder/oi9n2c16.png differ diff --git a/image/test/reftest/pngsuite-chunkorder/reftest-stylo.list b/image/test/reftest/pngsuite-chunkorder/reftest-stylo.list new file mode 100644 index 000000000..57415ac0e --- /dev/null +++ b/image/test/reftest/pngsuite-chunkorder/reftest-stylo.list @@ -0,0 +1,22 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# PngSuite - Chunk ordering +# +# The resulting images of a type (color or grayscale) should all look the +# same, so they share common HTML reference files. + +# oi1n0g16 - grayscale mother image with 1 idat-chunk +fails == oi1n0g16.png oi1n0g16.png +# oi1n2c16 - color mother image with 1 idat-chunk +fails == oi1n2c16.png oi1n2c16.png +# oi2n0g16 - grayscale image with 2 idat-chunks +fails == oi2n0g16.png oi2n0g16.png +# oi2n2c16 - color image with 2 idat-chunks +fails == oi2n2c16.png oi2n2c16.png +# oi4n0g16 - grayscale image with 4 unequal sized idat-chunks +fails == oi4n0g16.png oi4n0g16.png +# oi4n2c16 - color image with 4 unequal sized idat-chunks +fails == oi4n2c16.png oi4n2c16.png +# oi9n0g16 - grayscale image with all idat-chunks length one +fails == oi9n0g16.png oi9n0g16.png +# oi9n2c16 - color image with all idat-chunks length one +fails == oi9n2c16.png oi9n2c16.png diff --git a/image/test/reftest/pngsuite-chunkorder/reftest.list b/image/test/reftest/pngsuite-chunkorder/reftest.list new file mode 100644 index 000000000..2e161d0d3 --- /dev/null +++ b/image/test/reftest/pngsuite-chunkorder/reftest.list @@ -0,0 +1,21 @@ +# PngSuite - Chunk ordering +# +# The resulting images of a type (color or grayscale) should all look the +# same, so they share common HTML reference files. + +# oi1n0g16 - grayscale mother image with 1 idat-chunk +== oi1n0g16.png grayscale.html +# oi1n2c16 - color mother image with 1 idat-chunk +== oi1n2c16.png color.html +# oi2n0g16 - grayscale image with 2 idat-chunks +== oi2n0g16.png grayscale.html +# oi2n2c16 - color image with 2 idat-chunks +== oi2n2c16.png color.html +# oi4n0g16 - grayscale image with 4 unequal sized idat-chunks +== oi4n0g16.png grayscale.html +# oi4n2c16 - color image with 4 unequal sized idat-chunks +== oi4n2c16.png color.html +# oi9n0g16 - grayscale image with all idat-chunks length one +== oi9n0g16.png grayscale.html +# oi9n2c16 - color image with all idat-chunks length one +== oi9n2c16.png color.html diff --git a/image/test/reftest/pngsuite-corrupted/reftest-stylo.list b/image/test/reftest/pngsuite-corrupted/reftest-stylo.list new file mode 100644 index 000000000..ed4baead8 --- /dev/null +++ b/image/test/reftest/pngsuite-corrupted/reftest-stylo.list @@ -0,0 +1,11 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# PngSuite - Corrupted files +# +# Note: these are corrupt files, and so no image should be rendered. + +# x00n0g01 - empty 0x0 grayscale file +skip == wrapper.html?x00n0g01.png wrapper.html?x00n0g01.png +# xcrn0g04 - added cr bytes +skip == wrapper.html?xcrn0g04.png wrapper.html?xcrn0g04.png +# xlfn0g04 - added lf bytes +skip == wrapper.html?xlfn0g04.png wrapper.html?xlfn0g04.png diff --git a/image/test/reftest/pngsuite-corrupted/reftest.list b/image/test/reftest/pngsuite-corrupted/reftest.list new file mode 100644 index 000000000..86c5880f2 --- /dev/null +++ b/image/test/reftest/pngsuite-corrupted/reftest.list @@ -0,0 +1,10 @@ +# PngSuite - Corrupted files +# +# Note: these are corrupt files, and so no image should be rendered. + +# x00n0g01 - empty 0x0 grayscale file +== wrapper.html?x00n0g01.png about:blank +# xcrn0g04 - added cr bytes +== wrapper.html?xcrn0g04.png about:blank +# xlfn0g04 - added lf bytes +== wrapper.html?xlfn0g04.png about:blank diff --git a/image/test/reftest/pngsuite-corrupted/wrapper.html b/image/test/reftest/pngsuite-corrupted/wrapper.html new file mode 100644 index 000000000..0015856df --- /dev/null +++ b/image/test/reftest/pngsuite-corrupted/wrapper.html @@ -0,0 +1,28 @@ + + + +Image reftest wrapper + + + + + + + + + diff --git a/image/test/reftest/pngsuite-corrupted/x00n0g01.png b/image/test/reftest/pngsuite-corrupted/x00n0g01.png new file mode 100644 index 000000000..db3a5fda7 Binary files /dev/null and b/image/test/reftest/pngsuite-corrupted/x00n0g01.png differ diff --git a/image/test/reftest/pngsuite-corrupted/xcrn0g04.png b/image/test/reftest/pngsuite-corrupted/xcrn0g04.png new file mode 100644 index 000000000..5bce9f3ad Binary files /dev/null and b/image/test/reftest/pngsuite-corrupted/xcrn0g04.png differ diff --git a/image/test/reftest/pngsuite-corrupted/xlfn0g04.png b/image/test/reftest/pngsuite-corrupted/xlfn0g04.png new file mode 100644 index 000000000..1fd104ba6 --- /dev/null +++ b/image/test/reftest/pngsuite-corrupted/xlfn0g04.png @@ -0,0 +1,13 @@ +‰PNG + + + + + +IHDR “áÈ)ÈIDATxœ]ÑÁ +Â0 P*@ð¡#° + +#TâÈ10lPF`Ø F=•ŸÄIQâ*çÅuí”`%qk +Hžñšˆ©ñ´€m÷Íüµàߟ Ñ=,¸fìOK + +ç ÐtŽÀ(Èïä’צíF ;èPº€¯¾{xpç]9‡/p*$(ì*éyìÕƒ ×þÚéçè@÷C¼  cÔqž‹NÛU#„)11·.räðfä0°ägh(¥týÙÂEøÿ‰kIEND®B`‚ \ No newline at end of file diff --git a/image/test/reftest/pngsuite-filtering/f00n0g08.html b/image/test/reftest/pngsuite-filtering/f00n0g08.html new file mode 100644 index 000000000..3df624891 --- /dev/null +++ b/image/test/reftest/pngsuite-filtering/f00n0g08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-filtering/f00n0g08.png b/image/test/reftest/pngsuite-filtering/f00n0g08.png new file mode 100644 index 000000000..45a007596 Binary files /dev/null and b/image/test/reftest/pngsuite-filtering/f00n0g08.png differ diff --git a/image/test/reftest/pngsuite-filtering/f00n2c08.html b/image/test/reftest/pngsuite-filtering/f00n2c08.html new file mode 100644 index 000000000..2e5f1e186 --- /dev/null +++ b/image/test/reftest/pngsuite-filtering/f00n2c08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-filtering/f00n2c08.png b/image/test/reftest/pngsuite-filtering/f00n2c08.png new file mode 100644 index 000000000..d6a1ffff6 Binary files /dev/null and b/image/test/reftest/pngsuite-filtering/f00n2c08.png differ diff --git a/image/test/reftest/pngsuite-filtering/f01n0g08.html b/image/test/reftest/pngsuite-filtering/f01n0g08.html new file mode 100644 index 000000000..2e056ecb9 --- /dev/null +++ b/image/test/reftest/pngsuite-filtering/f01n0g08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-filtering/f01n0g08.png b/image/test/reftest/pngsuite-filtering/f01n0g08.png new file mode 100644 index 000000000..4a1107b46 Binary files /dev/null and b/image/test/reftest/pngsuite-filtering/f01n0g08.png differ diff --git a/image/test/reftest/pngsuite-filtering/f01n2c08.html b/image/test/reftest/pngsuite-filtering/f01n2c08.html new file mode 100644 index 000000000..25c4fe044 --- /dev/null +++ b/image/test/reftest/pngsuite-filtering/f01n2c08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-filtering/f01n2c08.png b/image/test/reftest/pngsuite-filtering/f01n2c08.png new file mode 100644 index 000000000..26fee958c Binary files /dev/null and b/image/test/reftest/pngsuite-filtering/f01n2c08.png differ diff --git a/image/test/reftest/pngsuite-filtering/f02n0g08.html b/image/test/reftest/pngsuite-filtering/f02n0g08.html new file mode 100644 index 000000000..c9a6263f4 --- /dev/null +++ b/image/test/reftest/pngsuite-filtering/f02n0g08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-filtering/f02n0g08.png b/image/test/reftest/pngsuite-filtering/f02n0g08.png new file mode 100644 index 000000000..bfe410c5e Binary files /dev/null and b/image/test/reftest/pngsuite-filtering/f02n0g08.png differ diff --git a/image/test/reftest/pngsuite-filtering/f02n2c08.html b/image/test/reftest/pngsuite-filtering/f02n2c08.html new file mode 100644 index 000000000..051691ab9 --- /dev/null +++ b/image/test/reftest/pngsuite-filtering/f02n2c08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-filtering/f02n2c08.png b/image/test/reftest/pngsuite-filtering/f02n2c08.png new file mode 100644 index 000000000..e590f1234 Binary files /dev/null and b/image/test/reftest/pngsuite-filtering/f02n2c08.png differ diff --git a/image/test/reftest/pngsuite-filtering/f03n0g08.html b/image/test/reftest/pngsuite-filtering/f03n0g08.html new file mode 100644 index 000000000..f40bbe51b --- /dev/null +++ b/image/test/reftest/pngsuite-filtering/f03n0g08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-filtering/f03n0g08.png b/image/test/reftest/pngsuite-filtering/f03n0g08.png new file mode 100644 index 000000000..ed01e2923 Binary files /dev/null and b/image/test/reftest/pngsuite-filtering/f03n0g08.png differ diff --git a/image/test/reftest/pngsuite-filtering/f03n2c08.html b/image/test/reftest/pngsuite-filtering/f03n2c08.html new file mode 100644 index 000000000..3d3c85e6c --- /dev/null +++ b/image/test/reftest/pngsuite-filtering/f03n2c08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-filtering/f03n2c08.png b/image/test/reftest/pngsuite-filtering/f03n2c08.png new file mode 100644 index 000000000..758115059 Binary files /dev/null and b/image/test/reftest/pngsuite-filtering/f03n2c08.png differ diff --git a/image/test/reftest/pngsuite-filtering/f04n0g08.html b/image/test/reftest/pngsuite-filtering/f04n0g08.html new file mode 100644 index 000000000..3c7ce550b --- /dev/null +++ b/image/test/reftest/pngsuite-filtering/f04n0g08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-filtering/f04n0g08.png b/image/test/reftest/pngsuite-filtering/f04n0g08.png new file mode 100644 index 000000000..663fdae3e Binary files /dev/null and b/image/test/reftest/pngsuite-filtering/f04n0g08.png differ diff --git a/image/test/reftest/pngsuite-filtering/f04n2c08.html b/image/test/reftest/pngsuite-filtering/f04n2c08.html new file mode 100644 index 000000000..77c90face --- /dev/null +++ b/image/test/reftest/pngsuite-filtering/f04n2c08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-filtering/f04n2c08.png b/image/test/reftest/pngsuite-filtering/f04n2c08.png new file mode 100644 index 000000000..3c8b5116e Binary files /dev/null and b/image/test/reftest/pngsuite-filtering/f04n2c08.png differ diff --git a/image/test/reftest/pngsuite-filtering/reftest-stylo.list b/image/test/reftest/pngsuite-filtering/reftest-stylo.list new file mode 100644 index 000000000..d69ff484e --- /dev/null +++ b/image/test/reftest/pngsuite-filtering/reftest-stylo.list @@ -0,0 +1,23 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# PngSuite - Image filtering + +# f00n0g08 - grayscale, no interlacing, filter-type 0 +fails == f00n0g08.png f00n0g08.png +# f00n2c08 - color, no interlacing, filter-type 0 +fails == f00n2c08.png f00n2c08.png +# f01n0g08 - grayscale, no interlacing, filter-type 1 +fails == f01n0g08.png f01n0g08.png +# f01n2c08 - color, no interlacing, filter-type 1 +skip == f01n2c08.png f01n2c08.png +# f02n0g08 - grayscale, no interlacing, filter-type 2 +fails == f02n0g08.png f02n0g08.png +# f02n2c08 - color, no interlacing, filter-type 2 +fails == f02n2c08.png f02n2c08.png +# f03n0g08 - grayscale, no interlacing, filter-type 3 +fails == f03n0g08.png f03n0g08.png +# f03n2c08 - color, no interlacing, filter-type 3 +fails == f03n2c08.png f03n2c08.png +# f04n0g08 - grayscale, no interlacing, filter-type 4 +fails == f04n0g08.png f04n0g08.png +# f04n2c08 - color, no interlacing, filter-type 4 +skip == f04n2c08.png f04n2c08.png diff --git a/image/test/reftest/pngsuite-filtering/reftest.list b/image/test/reftest/pngsuite-filtering/reftest.list new file mode 100644 index 000000000..81b4ac5f3 --- /dev/null +++ b/image/test/reftest/pngsuite-filtering/reftest.list @@ -0,0 +1,22 @@ +# PngSuite - Image filtering + +# f00n0g08 - grayscale, no interlacing, filter-type 0 +== f00n0g08.png f00n0g08.html +# f00n2c08 - color, no interlacing, filter-type 0 +== f00n2c08.png f00n2c08.html +# f01n0g08 - grayscale, no interlacing, filter-type 1 +== f01n0g08.png f01n0g08.html +# f01n2c08 - color, no interlacing, filter-type 1 +== f01n2c08.png f01n2c08.html +# f02n0g08 - grayscale, no interlacing, filter-type 2 +== f02n0g08.png f02n0g08.html +# f02n2c08 - color, no interlacing, filter-type 2 +== f02n2c08.png f02n2c08.html +# f03n0g08 - grayscale, no interlacing, filter-type 3 +== f03n0g08.png f03n0g08.html +# f03n2c08 - color, no interlacing, filter-type 3 +== f03n2c08.png f03n2c08.html +# f04n0g08 - grayscale, no interlacing, filter-type 4 +== f04n0g08.png f04n0g08.html +# f04n2c08 - color, no interlacing, filter-type 4 +== f04n2c08.png f04n2c08.html diff --git a/image/test/reftest/pngsuite-gamma/g03n0g16.html b/image/test/reftest/pngsuite-gamma/g03n0g16.html new file mode 100644 index 000000000..dc15a536b --- /dev/null +++ b/image/test/reftest/pngsuite-gamma/g03n0g16.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-gamma/g03n0g16.png b/image/test/reftest/pngsuite-gamma/g03n0g16.png new file mode 100644 index 000000000..41083ca80 Binary files /dev/null and b/image/test/reftest/pngsuite-gamma/g03n0g16.png differ diff --git a/image/test/reftest/pngsuite-gamma/g03n2c08.html b/image/test/reftest/pngsuite-gamma/g03n2c08.html new file mode 100644 index 000000000..c2d02beed --- /dev/null +++ b/image/test/reftest/pngsuite-gamma/g03n2c08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-gamma/g03n2c08.png b/image/test/reftest/pngsuite-gamma/g03n2c08.png new file mode 100644 index 000000000..a9354dbee Binary files /dev/null and b/image/test/reftest/pngsuite-gamma/g03n2c08.png differ diff --git a/image/test/reftest/pngsuite-gamma/g03n3p04.html b/image/test/reftest/pngsuite-gamma/g03n3p04.html new file mode 100644 index 000000000..efcf39f29 --- /dev/null +++ b/image/test/reftest/pngsuite-gamma/g03n3p04.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-gamma/g03n3p04.png b/image/test/reftest/pngsuite-gamma/g03n3p04.png new file mode 100644 index 000000000..60396c95a Binary files /dev/null and b/image/test/reftest/pngsuite-gamma/g03n3p04.png differ diff --git a/image/test/reftest/pngsuite-gamma/g04n0g16.html b/image/test/reftest/pngsuite-gamma/g04n0g16.html new file mode 100644 index 000000000..5bec9867f --- /dev/null +++ b/image/test/reftest/pngsuite-gamma/g04n0g16.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-gamma/g04n0g16.png b/image/test/reftest/pngsuite-gamma/g04n0g16.png new file mode 100644 index 000000000..32395b76c Binary files /dev/null and b/image/test/reftest/pngsuite-gamma/g04n0g16.png differ diff --git a/image/test/reftest/pngsuite-gamma/g04n2c08.html b/image/test/reftest/pngsuite-gamma/g04n2c08.html new file mode 100644 index 000000000..b3b0556c6 --- /dev/null +++ b/image/test/reftest/pngsuite-gamma/g04n2c08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-gamma/g04n2c08.png b/image/test/reftest/pngsuite-gamma/g04n2c08.png new file mode 100644 index 000000000..a652b0ce8 Binary files /dev/null and b/image/test/reftest/pngsuite-gamma/g04n2c08.png differ diff --git a/image/test/reftest/pngsuite-gamma/g04n3p04.html b/image/test/reftest/pngsuite-gamma/g04n3p04.html new file mode 100644 index 000000000..337dcb49d --- /dev/null +++ b/image/test/reftest/pngsuite-gamma/g04n3p04.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-gamma/g04n3p04.png b/image/test/reftest/pngsuite-gamma/g04n3p04.png new file mode 100644 index 000000000..5661cc313 Binary files /dev/null and b/image/test/reftest/pngsuite-gamma/g04n3p04.png differ diff --git a/image/test/reftest/pngsuite-gamma/g05n0g16.html b/image/test/reftest/pngsuite-gamma/g05n0g16.html new file mode 100644 index 000000000..ab100e638 --- /dev/null +++ b/image/test/reftest/pngsuite-gamma/g05n0g16.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-gamma/g05n0g16.png b/image/test/reftest/pngsuite-gamma/g05n0g16.png new file mode 100644 index 000000000..70b37f01e Binary files /dev/null and b/image/test/reftest/pngsuite-gamma/g05n0g16.png differ diff --git a/image/test/reftest/pngsuite-gamma/g05n2c08.html b/image/test/reftest/pngsuite-gamma/g05n2c08.html new file mode 100644 index 000000000..475ecd21d --- /dev/null +++ b/image/test/reftest/pngsuite-gamma/g05n2c08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-gamma/g05n2c08.png b/image/test/reftest/pngsuite-gamma/g05n2c08.png new file mode 100644 index 000000000..932c13653 Binary files /dev/null and b/image/test/reftest/pngsuite-gamma/g05n2c08.png differ diff --git a/image/test/reftest/pngsuite-gamma/g05n3p04.html b/image/test/reftest/pngsuite-gamma/g05n3p04.html new file mode 100644 index 000000000..d71689c29 --- /dev/null +++ b/image/test/reftest/pngsuite-gamma/g05n3p04.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-gamma/g05n3p04.png b/image/test/reftest/pngsuite-gamma/g05n3p04.png new file mode 100644 index 000000000..961993058 Binary files /dev/null and b/image/test/reftest/pngsuite-gamma/g05n3p04.png differ diff --git a/image/test/reftest/pngsuite-gamma/g07n0g16.html b/image/test/reftest/pngsuite-gamma/g07n0g16.html new file mode 100644 index 000000000..b9f1a1c3e --- /dev/null +++ b/image/test/reftest/pngsuite-gamma/g07n0g16.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-gamma/g07n0g16.png b/image/test/reftest/pngsuite-gamma/g07n0g16.png new file mode 100644 index 000000000..d6a47c2d5 Binary files /dev/null and b/image/test/reftest/pngsuite-gamma/g07n0g16.png differ diff --git a/image/test/reftest/pngsuite-gamma/g07n2c08.html b/image/test/reftest/pngsuite-gamma/g07n2c08.html new file mode 100644 index 000000000..0a5b63bf0 --- /dev/null +++ b/image/test/reftest/pngsuite-gamma/g07n2c08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-gamma/g07n2c08.png b/image/test/reftest/pngsuite-gamma/g07n2c08.png new file mode 100644 index 000000000..597346460 Binary files /dev/null and b/image/test/reftest/pngsuite-gamma/g07n2c08.png differ diff --git a/image/test/reftest/pngsuite-gamma/g07n3p04.html b/image/test/reftest/pngsuite-gamma/g07n3p04.html new file mode 100644 index 000000000..7303ed0d7 --- /dev/null +++ b/image/test/reftest/pngsuite-gamma/g07n3p04.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-gamma/g07n3p04.png b/image/test/reftest/pngsuite-gamma/g07n3p04.png new file mode 100644 index 000000000..c73fb6136 Binary files /dev/null and b/image/test/reftest/pngsuite-gamma/g07n3p04.png differ diff --git a/image/test/reftest/pngsuite-gamma/g10n0g16.html b/image/test/reftest/pngsuite-gamma/g10n0g16.html new file mode 100644 index 000000000..29301dd71 --- /dev/null +++ b/image/test/reftest/pngsuite-gamma/g10n0g16.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-gamma/g10n0g16.png b/image/test/reftest/pngsuite-gamma/g10n0g16.png new file mode 100644 index 000000000..85f2c958e Binary files /dev/null and b/image/test/reftest/pngsuite-gamma/g10n0g16.png differ diff --git a/image/test/reftest/pngsuite-gamma/g10n2c08.html b/image/test/reftest/pngsuite-gamma/g10n2c08.html new file mode 100644 index 000000000..24e8637b7 --- /dev/null +++ b/image/test/reftest/pngsuite-gamma/g10n2c08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-gamma/g10n2c08.png b/image/test/reftest/pngsuite-gamma/g10n2c08.png new file mode 100644 index 000000000..b3039970c Binary files /dev/null and b/image/test/reftest/pngsuite-gamma/g10n2c08.png differ diff --git a/image/test/reftest/pngsuite-gamma/g10n3p04.html b/image/test/reftest/pngsuite-gamma/g10n3p04.html new file mode 100644 index 000000000..7c25d439d --- /dev/null +++ b/image/test/reftest/pngsuite-gamma/g10n3p04.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-gamma/g10n3p04.png b/image/test/reftest/pngsuite-gamma/g10n3p04.png new file mode 100644 index 000000000..1b6a6be2c Binary files /dev/null and b/image/test/reftest/pngsuite-gamma/g10n3p04.png differ diff --git a/image/test/reftest/pngsuite-gamma/g25n0g16.html b/image/test/reftest/pngsuite-gamma/g25n0g16.html new file mode 100644 index 000000000..7f3d84edf --- /dev/null +++ b/image/test/reftest/pngsuite-gamma/g25n0g16.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-gamma/g25n0g16.png b/image/test/reftest/pngsuite-gamma/g25n0g16.png new file mode 100644 index 000000000..a9f6787c7 Binary files /dev/null and b/image/test/reftest/pngsuite-gamma/g25n0g16.png differ diff --git a/image/test/reftest/pngsuite-gamma/g25n2c08.html b/image/test/reftest/pngsuite-gamma/g25n2c08.html new file mode 100644 index 000000000..2476d2cc9 --- /dev/null +++ b/image/test/reftest/pngsuite-gamma/g25n2c08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-gamma/g25n2c08.png b/image/test/reftest/pngsuite-gamma/g25n2c08.png new file mode 100644 index 000000000..03f505a64 Binary files /dev/null and b/image/test/reftest/pngsuite-gamma/g25n2c08.png differ diff --git a/image/test/reftest/pngsuite-gamma/g25n3p04.html b/image/test/reftest/pngsuite-gamma/g25n3p04.html new file mode 100644 index 000000000..3cb0205be --- /dev/null +++ b/image/test/reftest/pngsuite-gamma/g25n3p04.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-gamma/g25n3p04.png b/image/test/reftest/pngsuite-gamma/g25n3p04.png new file mode 100644 index 000000000..4f943c617 Binary files /dev/null and b/image/test/reftest/pngsuite-gamma/g25n3p04.png differ diff --git a/image/test/reftest/pngsuite-gamma/reftest-stylo.list b/image/test/reftest/pngsuite-gamma/reftest-stylo.list new file mode 100644 index 000000000..25439123a --- /dev/null +++ b/image/test/reftest/pngsuite-gamma/reftest-stylo.list @@ -0,0 +1,39 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# PngSuite - Gamma values + +# g03n0g16 - grayscale, file-gamma = 0.35 +fails == g03n0g16.png g03n0g16.png +# g03n2c08 - color, file-gamma = 0.35 +fails == g03n2c08.png g03n2c08.png +# g03n3p04 - paletted, file-gamma = 0.35 +fails == g03n3p04.png g03n3p04.png +# g04n0g16 - grayscale, file-gamma = 0.45 +fails == g04n0g16.png g04n0g16.png +# g04n2c08 - color, file-gamma = 0.45 +skip == g04n2c08.png g04n2c08.png +# g04n3p04 - paletted, file-gamma = 0.45 +fails == g04n3p04.png g04n3p04.png +# g05n0g16 - grayscale, file-gamma = 0.55 +fails == g05n0g16.png g05n0g16.png +# g05n2c08 - color, file-gamma = 0.55 +fails == g05n2c08.png g05n2c08.png +# g05n3p04 - paletted, file-gamma = 0.55 +fails == g05n3p04.png g05n3p04.png +# g07n0g16 - grayscale, file-gamma = 0.70 +fails == g07n0g16.png g07n0g16.png +# g07n2c08 - color, file-gamma = 0.70 +fails == g07n2c08.png g07n2c08.png +# g07n3p04 - paletted, file-gamma = 0.70 +fails == g07n3p04.png g07n3p04.png +# g10n0g16 - grayscale, file-gamma = 1.00 +fails == g10n0g16.png g10n0g16.png +# g10n2c08 - color, file-gamma = 1.00 +fails == g10n2c08.png g10n2c08.png +# g10n3p04 - paletted, file-gamma = 1.00 +fails == g10n3p04.png g10n3p04.png +# g25n0g16 - grayscale, file-gamma = 2.50 +fails == g25n0g16.png g25n0g16.png +# g25n2c08 - color, file-gamma = 2.50 +fails == g25n2c08.png g25n2c08.png +# g25n3p04 - paletted, file-gamma = 2.50 +fails == g25n3p04.png g25n3p04.png diff --git a/image/test/reftest/pngsuite-gamma/reftest.list b/image/test/reftest/pngsuite-gamma/reftest.list new file mode 100644 index 000000000..1b5abdda7 --- /dev/null +++ b/image/test/reftest/pngsuite-gamma/reftest.list @@ -0,0 +1,38 @@ +# PngSuite - Gamma values + +# g03n0g16 - grayscale, file-gamma = 0.35 +== g03n0g16.png g03n0g16.html +# g03n2c08 - color, file-gamma = 0.35 +== g03n2c08.png g03n2c08.html +# g03n3p04 - paletted, file-gamma = 0.35 +== g03n3p04.png g03n3p04.html +# g04n0g16 - grayscale, file-gamma = 0.45 +== g04n0g16.png g04n0g16.html +# g04n2c08 - color, file-gamma = 0.45 +== g04n2c08.png g04n2c08.html +# g04n3p04 - paletted, file-gamma = 0.45 +== g04n3p04.png g04n3p04.html +# g05n0g16 - grayscale, file-gamma = 0.55 +== g05n0g16.png g05n0g16.html +# g05n2c08 - color, file-gamma = 0.55 +== g05n2c08.png g05n2c08.html +# g05n3p04 - paletted, file-gamma = 0.55 +== g05n3p04.png g05n3p04.html +# g07n0g16 - grayscale, file-gamma = 0.70 +== g07n0g16.png g07n0g16.html +# g07n2c08 - color, file-gamma = 0.70 +== g07n2c08.png g07n2c08.html +# g07n3p04 - paletted, file-gamma = 0.70 +== g07n3p04.png g07n3p04.html +# g10n0g16 - grayscale, file-gamma = 1.00 +== g10n0g16.png g10n0g16.html +# g10n2c08 - color, file-gamma = 1.00 +== g10n2c08.png g10n2c08.html +# g10n3p04 - paletted, file-gamma = 1.00 +== g10n3p04.png g10n3p04.html +# g25n0g16 - grayscale, file-gamma = 2.50 +== g25n0g16.png g25n0g16.html +# g25n2c08 - color, file-gamma = 2.50 +== g25n2c08.png g25n2c08.html +# g25n3p04 - paletted, file-gamma = 2.50 +== g25n3p04.png g25n3p04.html diff --git a/image/test/reftest/pngsuite-oddsizes/reftest-stylo.list b/image/test/reftest/pngsuite-oddsizes/reftest-stylo.list new file mode 100644 index 000000000..21254621c --- /dev/null +++ b/image/test/reftest/pngsuite-oddsizes/reftest-stylo.list @@ -0,0 +1,78 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# PngSuite - Odd sizes +# +# Note: For each size, there are 2 PNGs (one interlaced, one not). Both +# versions look identical, so they share a common HTML reference file. + +# s01i3p01 - 1x1 paletted file, interlaced +fails == s01i3p01.png s01i3p01.png +# s01n3p01 - 1x1 paletted file, no interlacing +fails == s01n3p01.png s01n3p01.png +# s02i3p01 - 2x2 paletted file, interlaced +fails == s02i3p01.png s02i3p01.png +# s02n3p01 - 2x2 paletted file, no interlacing +fails == s02n3p01.png s02n3p01.png +# s03i3p01 - 3x3 paletted file, interlaced +fails == s03i3p01.png s03i3p01.png +# s03n3p01 - 3x3 paletted file, no interlacing +fails == s03n3p01.png s03n3p01.png +# s04i3p01 - 4x4 paletted file, interlaced +fails == s04i3p01.png s04i3p01.png +# s04n3p01 - 4x4 paletted file, no interlacing +fails == s04n3p01.png s04n3p01.png +# s05i3p02 - 5x5 paletted file, interlaced +fails == s05i3p02.png s05i3p02.png +# s05n3p02 - 5x5 paletted file, no interlacing +skip == s05n3p02.png s05n3p02.png +# s06i3p02 - 6x6 paletted file, interlaced +fails == s06i3p02.png s06i3p02.png +# s06n3p02 - 6x6 paletted file, no interlacing +fails == s06n3p02.png s06n3p02.png +# s07i3p02 - 7x7 paletted file, interlaced +fails == s07i3p02.png s07i3p02.png +# s07n3p02 - 7x7 paletted file, no interlacing +fails == s07n3p02.png s07n3p02.png +# s08i3p02 - 8x8 paletted file, interlaced +fails == s08i3p02.png s08i3p02.png +# s08n3p02 - 8x8 paletted file, no interlacing +fails == s08n3p02.png s08n3p02.png +# s09i3p02 - 9x9 paletted file, interlaced +fails == s09i3p02.png s09i3p02.png +# s09n3p02 - 9x9 paletted file, no interlacing +fails == s09n3p02.png s09n3p02.png +# s32i3p04 - 32x32 paletted file, interlaced +fails == s32i3p04.png s32i3p04.png +# s32n3p04 - 32x32 paletted file, no interlacing +fails == s32n3p04.png s32n3p04.png +# s33i3p04 - 33x33 paletted file, interlaced +fails == s33i3p04.png s33i3p04.png +# s33n3p04 - 33x33 paletted file, no interlacing +fails == s33n3p04.png s33n3p04.png +# s34i3p04 - 34x34 paletted file, interlaced +fails == s34i3p04.png s34i3p04.png +# s34n3p04 - 34x34 paletted file, no interlacing +fails == s34n3p04.png s34n3p04.png +# s35i3p04 - 35x35 paletted file, interlaced +fails == s35i3p04.png s35i3p04.png +# s35n3p04 - 35x35 paletted file, no interlacing +fails == s35n3p04.png s35n3p04.png +# s36i3p04 - 36x36 paletted file, interlaced +fails == s36i3p04.png s36i3p04.png +# s36n3p04 - 36x36 paletted file, no interlacing +fails == s36n3p04.png s36n3p04.png +# s37i3p04 - 37x37 paletted file, interlaced +fails == s37i3p04.png s37i3p04.png +# s37n3p04 - 37x37 paletted file, no interlacing +fails == s37n3p04.png s37n3p04.png +# s38i3p04 - 38x38 paletted file, interlaced +fails == s38i3p04.png s38i3p04.png +# s38n3p04 - 38x38 paletted file, no interlacing +fails == s38n3p04.png s38n3p04.png +# s39i3p04 - 39x39 paletted file, interlaced +fails == s39i3p04.png s39i3p04.png +# s39n3p04 - 39x39 paletted file, no interlacing +fails == s39n3p04.png s39n3p04.png +# s40i3p04 - 40x40 paletted file, interlaced +fails == s40i3p04.png s40i3p04.png +# s40n3p04 - 40x40 paletted file, no interlacing +fails == s40n3p04.png s40n3p04.png diff --git a/image/test/reftest/pngsuite-oddsizes/reftest.list b/image/test/reftest/pngsuite-oddsizes/reftest.list new file mode 100644 index 000000000..fa72e005b --- /dev/null +++ b/image/test/reftest/pngsuite-oddsizes/reftest.list @@ -0,0 +1,77 @@ +# PngSuite - Odd sizes +# +# Note: For each size, there are 2 PNGs (one interlaced, one not). Both +# versions look identical, so they share a common HTML reference file. + +# s01i3p01 - 1x1 paletted file, interlaced +== s01i3p01.png s01_3p01.html +# s01n3p01 - 1x1 paletted file, no interlacing +== s01n3p01.png s01_3p01.html +# s02i3p01 - 2x2 paletted file, interlaced +== s02i3p01.png s02_3p01.html +# s02n3p01 - 2x2 paletted file, no interlacing +== s02n3p01.png s02_3p01.html +# s03i3p01 - 3x3 paletted file, interlaced +== s03i3p01.png s03_3p01.html +# s03n3p01 - 3x3 paletted file, no interlacing +== s03n3p01.png s03_3p01.html +# s04i3p01 - 4x4 paletted file, interlaced +== s04i3p01.png s04_3p01.html +# s04n3p01 - 4x4 paletted file, no interlacing +== s04n3p01.png s04_3p01.html +# s05i3p02 - 5x5 paletted file, interlaced +== s05i3p02.png s05_3p02.html +# s05n3p02 - 5x5 paletted file, no interlacing +== s05n3p02.png s05_3p02.html +# s06i3p02 - 6x6 paletted file, interlaced +== s06i3p02.png s06_3p02.html +# s06n3p02 - 6x6 paletted file, no interlacing +== s06n3p02.png s06_3p02.html +# s07i3p02 - 7x7 paletted file, interlaced +== s07i3p02.png s07_3p02.html +# s07n3p02 - 7x7 paletted file, no interlacing +== s07n3p02.png s07_3p02.html +# s08i3p02 - 8x8 paletted file, interlaced +== s08i3p02.png s08_3p02.html +# s08n3p02 - 8x8 paletted file, no interlacing +== s08n3p02.png s08_3p02.html +# s09i3p02 - 9x9 paletted file, interlaced +== s09i3p02.png s09_3p02.html +# s09n3p02 - 9x9 paletted file, no interlacing +== s09n3p02.png s09_3p02.html +# s32i3p04 - 32x32 paletted file, interlaced +== s32i3p04.png s32_3p04.html +# s32n3p04 - 32x32 paletted file, no interlacing +== s32n3p04.png s32_3p04.html +# s33i3p04 - 33x33 paletted file, interlaced +== s33i3p04.png s33_3p04.html +# s33n3p04 - 33x33 paletted file, no interlacing +== s33n3p04.png s33_3p04.html +# s34i3p04 - 34x34 paletted file, interlaced +== s34i3p04.png s34_3p04.html +# s34n3p04 - 34x34 paletted file, no interlacing +== s34n3p04.png s34_3p04.html +# s35i3p04 - 35x35 paletted file, interlaced +== s35i3p04.png s35_3p04.html +# s35n3p04 - 35x35 paletted file, no interlacing +== s35n3p04.png s35_3p04.html +# s36i3p04 - 36x36 paletted file, interlaced +== s36i3p04.png s36_3p04.html +# s36n3p04 - 36x36 paletted file, no interlacing +== s36n3p04.png s36_3p04.html +# s37i3p04 - 37x37 paletted file, interlaced +== s37i3p04.png s37_3p04.html +# s37n3p04 - 37x37 paletted file, no interlacing +== s37n3p04.png s37_3p04.html +# s38i3p04 - 38x38 paletted file, interlaced +== s38i3p04.png s38_3p04.html +# s38n3p04 - 38x38 paletted file, no interlacing +== s38n3p04.png s38_3p04.html +# s39i3p04 - 39x39 paletted file, interlaced +== s39i3p04.png s39_3p04.html +# s39n3p04 - 39x39 paletted file, no interlacing +== s39n3p04.png s39_3p04.html +# s40i3p04 - 40x40 paletted file, interlaced +== s40i3p04.png s40_3p04.html +# s40n3p04 - 40x40 paletted file, no interlacing +== s40n3p04.png s40_3p04.html diff --git a/image/test/reftest/pngsuite-oddsizes/s01_3p01.html b/image/test/reftest/pngsuite-oddsizes/s01_3p01.html new file mode 100644 index 000000000..f38d98f1a --- /dev/null +++ b/image/test/reftest/pngsuite-oddsizes/s01_3p01.html @@ -0,0 +1,9 @@ + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-oddsizes/s01i3p01.png b/image/test/reftest/pngsuite-oddsizes/s01i3p01.png new file mode 100644 index 000000000..6c0fad1fc Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s01i3p01.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s01n3p01.png b/image/test/reftest/pngsuite-oddsizes/s01n3p01.png new file mode 100644 index 000000000..cb2c8c782 Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s01n3p01.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s02_3p01.html b/image/test/reftest/pngsuite-oddsizes/s02_3p01.html new file mode 100644 index 000000000..ad4660a24 --- /dev/null +++ b/image/test/reftest/pngsuite-oddsizes/s02_3p01.html @@ -0,0 +1,14 @@ + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-oddsizes/s02i3p01.png b/image/test/reftest/pngsuite-oddsizes/s02i3p01.png new file mode 100644 index 000000000..2defaed91 Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s02i3p01.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s02n3p01.png b/image/test/reftest/pngsuite-oddsizes/s02n3p01.png new file mode 100644 index 000000000..2b1b66964 Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s02n3p01.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s03_3p01.html b/image/test/reftest/pngsuite-oddsizes/s03_3p01.html new file mode 100644 index 000000000..adff34db3 --- /dev/null +++ b/image/test/reftest/pngsuite-oddsizes/s03_3p01.html @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-oddsizes/s03i3p01.png b/image/test/reftest/pngsuite-oddsizes/s03i3p01.png new file mode 100644 index 000000000..c23fdc463 Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s03i3p01.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s03n3p01.png b/image/test/reftest/pngsuite-oddsizes/s03n3p01.png new file mode 100644 index 000000000..6d96ee4f8 Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s03n3p01.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s04_3p01.html b/image/test/reftest/pngsuite-oddsizes/s04_3p01.html new file mode 100644 index 000000000..d97c2d42c --- /dev/null +++ b/image/test/reftest/pngsuite-oddsizes/s04_3p01.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-oddsizes/s04i3p01.png b/image/test/reftest/pngsuite-oddsizes/s04i3p01.png new file mode 100644 index 000000000..0e710c2c3 Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s04i3p01.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s04n3p01.png b/image/test/reftest/pngsuite-oddsizes/s04n3p01.png new file mode 100644 index 000000000..956396c45 Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s04n3p01.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s05_3p02.html b/image/test/reftest/pngsuite-oddsizes/s05_3p02.html new file mode 100644 index 000000000..e5664fb9d --- /dev/null +++ b/image/test/reftest/pngsuite-oddsizes/s05_3p02.html @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-oddsizes/s05i3p02.png b/image/test/reftest/pngsuite-oddsizes/s05i3p02.png new file mode 100644 index 000000000..d14cbd351 Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s05i3p02.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s05n3p02.png b/image/test/reftest/pngsuite-oddsizes/s05n3p02.png new file mode 100644 index 000000000..bf940f057 Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s05n3p02.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s06_3p02.html b/image/test/reftest/pngsuite-oddsizes/s06_3p02.html new file mode 100644 index 000000000..6d13dc56f --- /dev/null +++ b/image/test/reftest/pngsuite-oddsizes/s06_3p02.html @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-oddsizes/s06i3p02.png b/image/test/reftest/pngsuite-oddsizes/s06i3p02.png new file mode 100644 index 000000000..456ada320 Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s06i3p02.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s06n3p02.png b/image/test/reftest/pngsuite-oddsizes/s06n3p02.png new file mode 100644 index 000000000..501064dc2 Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s06n3p02.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s07_3p02.html b/image/test/reftest/pngsuite-oddsizes/s07_3p02.html new file mode 100644 index 000000000..3fc2d42a7 --- /dev/null +++ b/image/test/reftest/pngsuite-oddsizes/s07_3p02.html @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-oddsizes/s07i3p02.png b/image/test/reftest/pngsuite-oddsizes/s07i3p02.png new file mode 100644 index 000000000..44b66bab9 Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s07i3p02.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s07n3p02.png b/image/test/reftest/pngsuite-oddsizes/s07n3p02.png new file mode 100644 index 000000000..6a582593d Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s07n3p02.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s08_3p02.html b/image/test/reftest/pngsuite-oddsizes/s08_3p02.html new file mode 100644 index 000000000..52079c319 --- /dev/null +++ b/image/test/reftest/pngsuite-oddsizes/s08_3p02.html @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-oddsizes/s08i3p02.png b/image/test/reftest/pngsuite-oddsizes/s08i3p02.png new file mode 100644 index 000000000..acf74f3fc Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s08i3p02.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s08n3p02.png b/image/test/reftest/pngsuite-oddsizes/s08n3p02.png new file mode 100644 index 000000000..b7094e1b4 Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s08n3p02.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s09_3p02.html b/image/test/reftest/pngsuite-oddsizes/s09_3p02.html new file mode 100644 index 000000000..3b994e128 --- /dev/null +++ b/image/test/reftest/pngsuite-oddsizes/s09_3p02.html @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-oddsizes/s09i3p02.png b/image/test/reftest/pngsuite-oddsizes/s09i3p02.png new file mode 100644 index 000000000..0bfae8e45 Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s09i3p02.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s09n3p02.png b/image/test/reftest/pngsuite-oddsizes/s09n3p02.png new file mode 100644 index 000000000..711ab8245 Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s09n3p02.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s32_3p04.html b/image/test/reftest/pngsuite-oddsizes/s32_3p04.html new file mode 100644 index 000000000..a10399ba6 --- /dev/null +++ b/image/test/reftest/pngsuite-oddsizes/s32_3p04.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-oddsizes/s32i3p04.png b/image/test/reftest/pngsuite-oddsizes/s32i3p04.png new file mode 100644 index 000000000..0841910b7 Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s32i3p04.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s32n3p04.png b/image/test/reftest/pngsuite-oddsizes/s32n3p04.png new file mode 100644 index 000000000..fa58e3e3f Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s32n3p04.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s33_3p04.html b/image/test/reftest/pngsuite-oddsizes/s33_3p04.html new file mode 100644 index 000000000..d845558ce --- /dev/null +++ b/image/test/reftest/pngsuite-oddsizes/s33_3p04.html @@ -0,0 +1,1161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-oddsizes/s33i3p04.png b/image/test/reftest/pngsuite-oddsizes/s33i3p04.png new file mode 100644 index 000000000..ab0dc14ab Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s33i3p04.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s33n3p04.png b/image/test/reftest/pngsuite-oddsizes/s33n3p04.png new file mode 100644 index 000000000..764f1a3dc Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s33n3p04.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s34_3p04.html b/image/test/reftest/pngsuite-oddsizes/s34_3p04.html new file mode 100644 index 000000000..605ff9264 --- /dev/null +++ b/image/test/reftest/pngsuite-oddsizes/s34_3p04.html @@ -0,0 +1,1230 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-oddsizes/s34i3p04.png b/image/test/reftest/pngsuite-oddsizes/s34i3p04.png new file mode 100644 index 000000000..bd99039be Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s34i3p04.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s34n3p04.png b/image/test/reftest/pngsuite-oddsizes/s34n3p04.png new file mode 100644 index 000000000..9cbc68b3b Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s34n3p04.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s35_3p04.html b/image/test/reftest/pngsuite-oddsizes/s35_3p04.html new file mode 100644 index 000000000..6a5f720a9 --- /dev/null +++ b/image/test/reftest/pngsuite-oddsizes/s35_3p04.html @@ -0,0 +1,1301 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-oddsizes/s35i3p04.png b/image/test/reftest/pngsuite-oddsizes/s35i3p04.png new file mode 100644 index 000000000..e2a5e0a65 Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s35i3p04.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s35n3p04.png b/image/test/reftest/pngsuite-oddsizes/s35n3p04.png new file mode 100644 index 000000000..90b892eba Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s35n3p04.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s36_3p04.html b/image/test/reftest/pngsuite-oddsizes/s36_3p04.html new file mode 100644 index 000000000..68a5ae4be --- /dev/null +++ b/image/test/reftest/pngsuite-oddsizes/s36_3p04.html @@ -0,0 +1,1374 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-oddsizes/s36i3p04.png b/image/test/reftest/pngsuite-oddsizes/s36i3p04.png new file mode 100644 index 000000000..eb61b6f9a Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s36i3p04.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s36n3p04.png b/image/test/reftest/pngsuite-oddsizes/s36n3p04.png new file mode 100644 index 000000000..b38d17977 Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s36n3p04.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s37_3p04.html b/image/test/reftest/pngsuite-oddsizes/s37_3p04.html new file mode 100644 index 000000000..0f19b653a --- /dev/null +++ b/image/test/reftest/pngsuite-oddsizes/s37_3p04.html @@ -0,0 +1,1449 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-oddsizes/s37i3p04.png b/image/test/reftest/pngsuite-oddsizes/s37i3p04.png new file mode 100644 index 000000000..6e2b1e9b7 Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s37i3p04.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s37n3p04.png b/image/test/reftest/pngsuite-oddsizes/s37n3p04.png new file mode 100644 index 000000000..4d3054da5 Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s37n3p04.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s38_3p04.html b/image/test/reftest/pngsuite-oddsizes/s38_3p04.html new file mode 100644 index 000000000..38a869298 --- /dev/null +++ b/image/test/reftest/pngsuite-oddsizes/s38_3p04.html @@ -0,0 +1,1526 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-oddsizes/s38i3p04.png b/image/test/reftest/pngsuite-oddsizes/s38i3p04.png new file mode 100644 index 000000000..a0a8a140a Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s38i3p04.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s38n3p04.png b/image/test/reftest/pngsuite-oddsizes/s38n3p04.png new file mode 100644 index 000000000..1233ed048 Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s38n3p04.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s39_3p04.html b/image/test/reftest/pngsuite-oddsizes/s39_3p04.html new file mode 100644 index 000000000..6a00026dc --- /dev/null +++ b/image/test/reftest/pngsuite-oddsizes/s39_3p04.html @@ -0,0 +1,1605 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-oddsizes/s39i3p04.png b/image/test/reftest/pngsuite-oddsizes/s39i3p04.png new file mode 100644 index 000000000..04fee93ea Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s39i3p04.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s39n3p04.png b/image/test/reftest/pngsuite-oddsizes/s39n3p04.png new file mode 100644 index 000000000..c750100d5 Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s39n3p04.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s40_3p04.html b/image/test/reftest/pngsuite-oddsizes/s40_3p04.html new file mode 100644 index 000000000..59b18da4d --- /dev/null +++ b/image/test/reftest/pngsuite-oddsizes/s40_3p04.html @@ -0,0 +1,1686 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-oddsizes/s40i3p04.png b/image/test/reftest/pngsuite-oddsizes/s40i3p04.png new file mode 100644 index 000000000..68f358b82 Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s40i3p04.png differ diff --git a/image/test/reftest/pngsuite-oddsizes/s40n3p04.png b/image/test/reftest/pngsuite-oddsizes/s40n3p04.png new file mode 100644 index 000000000..864b6b967 Binary files /dev/null and b/image/test/reftest/pngsuite-oddsizes/s40n3p04.png differ diff --git a/image/test/reftest/pngsuite-palettes/pp0n2c16.html b/image/test/reftest/pngsuite-palettes/pp0n2c16.html new file mode 100644 index 000000000..dd08f0e3d --- /dev/null +++ b/image/test/reftest/pngsuite-palettes/pp0n2c16.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-palettes/pp0n2c16.png b/image/test/reftest/pngsuite-palettes/pp0n2c16.png new file mode 100644 index 000000000..8f2aad733 Binary files /dev/null and b/image/test/reftest/pngsuite-palettes/pp0n2c16.png differ diff --git a/image/test/reftest/pngsuite-palettes/pp0n6a08.png b/image/test/reftest/pngsuite-palettes/pp0n6a08.png new file mode 100644 index 000000000..4ed7a30e4 Binary files /dev/null and b/image/test/reftest/pngsuite-palettes/pp0n6a08.png differ diff --git a/image/test/reftest/pngsuite-palettes/ps1n0g08.html b/image/test/reftest/pngsuite-palettes/ps1n0g08.html new file mode 100644 index 000000000..5aaf11cab --- /dev/null +++ b/image/test/reftest/pngsuite-palettes/ps1n0g08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-palettes/ps1n0g08.png b/image/test/reftest/pngsuite-palettes/ps1n0g08.png new file mode 100644 index 000000000..2053df2ba Binary files /dev/null and b/image/test/reftest/pngsuite-palettes/ps1n0g08.png differ diff --git a/image/test/reftest/pngsuite-palettes/ps1n2c16.html b/image/test/reftest/pngsuite-palettes/ps1n2c16.html new file mode 100644 index 000000000..dd08f0e3d --- /dev/null +++ b/image/test/reftest/pngsuite-palettes/ps1n2c16.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-palettes/ps1n2c16.png b/image/test/reftest/pngsuite-palettes/ps1n2c16.png new file mode 100644 index 000000000..b03ecfc66 Binary files /dev/null and b/image/test/reftest/pngsuite-palettes/ps1n2c16.png differ diff --git a/image/test/reftest/pngsuite-palettes/ps2n0g08.html b/image/test/reftest/pngsuite-palettes/ps2n0g08.html new file mode 100644 index 000000000..5aaf11cab --- /dev/null +++ b/image/test/reftest/pngsuite-palettes/ps2n0g08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-palettes/ps2n0g08.png b/image/test/reftest/pngsuite-palettes/ps2n0g08.png new file mode 100644 index 000000000..beeab8ff3 Binary files /dev/null and b/image/test/reftest/pngsuite-palettes/ps2n0g08.png differ diff --git a/image/test/reftest/pngsuite-palettes/ps2n2c16.html b/image/test/reftest/pngsuite-palettes/ps2n2c16.html new file mode 100644 index 000000000..dd08f0e3d --- /dev/null +++ b/image/test/reftest/pngsuite-palettes/ps2n2c16.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-palettes/ps2n2c16.png b/image/test/reftest/pngsuite-palettes/ps2n2c16.png new file mode 100644 index 000000000..c256f9091 Binary files /dev/null and b/image/test/reftest/pngsuite-palettes/ps2n2c16.png differ diff --git a/image/test/reftest/pngsuite-palettes/reftest-stylo.list b/image/test/reftest/pngsuite-palettes/reftest-stylo.list new file mode 100644 index 000000000..702529b29 --- /dev/null +++ b/image/test/reftest/pngsuite-palettes/reftest-stylo.list @@ -0,0 +1,15 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# PngSuite - Additional palettes + +# pp0n2c16 - six-cube palette-chunk in true-color image +fails == pp0n2c16.png pp0n2c16.png +# pp0n6a08 - six-cube palette-chunk in true-color+alpha image +#== pp0n6a08.png pp0n6a08.png +# ps1n0g08 - six-cube suggested palette (1 byte) in grayscale image +fails == ps1n0g08.png ps1n0g08.png +# ps1n2c16 - six-cube suggested palette (1 byte) in true-color image +fails == ps1n2c16.png ps1n2c16.png +# ps2n0g08 - six-cube suggested palette (2 bytes) in grayscale image +fails == ps2n0g08.png ps2n0g08.png +# ps2n2c16 - six-cube suggested palette (2 bytes) in true-color image +fails == ps2n2c16.png ps2n2c16.png diff --git a/image/test/reftest/pngsuite-palettes/reftest.list b/image/test/reftest/pngsuite-palettes/reftest.list new file mode 100644 index 000000000..56eb56e0a --- /dev/null +++ b/image/test/reftest/pngsuite-palettes/reftest.list @@ -0,0 +1,14 @@ +# PngSuite - Additional palettes + +# pp0n2c16 - six-cube palette-chunk in true-color image +== pp0n2c16.png pp0n2c16.html +# pp0n6a08 - six-cube palette-chunk in true-color+alpha image +#== pp0n6a08.png pp0n6a08.html +# ps1n0g08 - six-cube suggested palette (1 byte) in grayscale image +== ps1n0g08.png ps1n0g08.html +# ps1n2c16 - six-cube suggested palette (1 byte) in true-color image +== ps1n2c16.png ps1n2c16.html +# ps2n0g08 - six-cube suggested palette (2 bytes) in grayscale image +== ps2n0g08.png ps2n0g08.html +# ps2n2c16 - six-cube suggested palette (2 bytes) in true-color image +== ps2n2c16.png ps2n2c16.html diff --git a/image/test/reftest/pngsuite-transparency/reftest-stylo.list b/image/test/reftest/pngsuite-transparency/reftest-stylo.list new file mode 100644 index 000000000..90543ab95 --- /dev/null +++ b/image/test/reftest/pngsuite-transparency/reftest-stylo.list @@ -0,0 +1,27 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# PngSuite - Transparency + +# tbbn1g04 - transparent, black background chunk +skip == wrapper.html?tbbn1g04.png wrapper.html?tbbn1g04.png +# tbbn2c16 - transparent, blue background chunk +skip == wrapper.html?tbbn2c16.png wrapper.html?tbbn2c16.png +# tbbn3p08 - transparent, black background chunk +skip == wrapper.html?tbbn3p08.png wrapper.html?tbbn3p08.png +# tbgn2c16 - transparent, green background chunk +skip == wrapper.html?tbgn2c16.png wrapper.html?tbgn2c16.png +# tbgn3p08 - transparent, light-gray background chunk +skip == wrapper.html?tbgn3p08.png wrapper.html?tbgn3p08.png +# tbrn2c08 - transparent, red background chunk +skip == wrapper.html?tbrn2c08.png wrapper.html?tbrn2c08.png +# tbwn1g16 - transparent, white background chunk +skip == wrapper.html?tbwn1g16.png wrapper.html?tbwn1g16.png +# tbwn3p08 - transparent, white background chunk +skip == wrapper.html?tbwn3p08.png wrapper.html?tbwn3p08.png +# tbyn3p08 - transparent, yellow background chunk +skip == wrapper.html?tbyn3p08.png wrapper.html?tbyn3p08.png +# tp0n1g08 - not transparent for reference (logo on gray) +# tp0n2c08 - not transparent for reference (logo on gray) +# tp0n3p08 - not transparent for reference (logo on gray) +# ...these 3 not tested because they're not transparent. +# tp1n3p08 - transparent, but no background chunk +skip == wrapper.html?tp1n3p08.png wrapper.html?tp1n3p08.png diff --git a/image/test/reftest/pngsuite-transparency/reftest.list b/image/test/reftest/pngsuite-transparency/reftest.list new file mode 100644 index 000000000..2b574c5d8 --- /dev/null +++ b/image/test/reftest/pngsuite-transparency/reftest.list @@ -0,0 +1,26 @@ +# PngSuite - Transparency + +# tbbn1g04 - transparent, black background chunk +== wrapper.html?tbbn1g04.png tbbn1g04.html +# tbbn2c16 - transparent, blue background chunk +== wrapper.html?tbbn2c16.png tbbn2c16.html +# tbbn3p08 - transparent, black background chunk +== wrapper.html?tbbn3p08.png tbbn3p08.html +# tbgn2c16 - transparent, green background chunk +== wrapper.html?tbgn2c16.png tbgn2c16.html +# tbgn3p08 - transparent, light-gray background chunk +== wrapper.html?tbgn3p08.png tbgn3p08.html +# tbrn2c08 - transparent, red background chunk +== wrapper.html?tbrn2c08.png tbrn2c08.html +# tbwn1g16 - transparent, white background chunk +== wrapper.html?tbwn1g16.png tbwn1g16.html +# tbwn3p08 - transparent, white background chunk +== wrapper.html?tbwn3p08.png tbwn3p08.html +# tbyn3p08 - transparent, yellow background chunk +== wrapper.html?tbyn3p08.png tbyn3p08.html +# tp0n1g08 - not transparent for reference (logo on gray) +# tp0n2c08 - not transparent for reference (logo on gray) +# tp0n3p08 - not transparent for reference (logo on gray) +# ...these 3 not tested because they're not transparent. +# tp1n3p08 - transparent, but no background chunk +== wrapper.html?tp1n3p08.png tp1n3p08.html diff --git a/image/test/reftest/pngsuite-transparency/tbbn1g04.html b/image/test/reftest/pngsuite-transparency/tbbn1g04.html new file mode 100644 index 000000000..c2e5780b0 --- /dev/null +++ b/image/test/reftest/pngsuite-transparency/tbbn1g04.html @@ -0,0 +1,1092 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-transparency/tbbn1g04.png b/image/test/reftest/pngsuite-transparency/tbbn1g04.png new file mode 100644 index 000000000..fc8002053 Binary files /dev/null and b/image/test/reftest/pngsuite-transparency/tbbn1g04.png differ diff --git a/image/test/reftest/pngsuite-transparency/tbbn2c16.html b/image/test/reftest/pngsuite-transparency/tbbn2c16.html new file mode 100644 index 000000000..849c66faf --- /dev/null +++ b/image/test/reftest/pngsuite-transparency/tbbn2c16.html @@ -0,0 +1,1092 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-transparency/tbbn2c16.png b/image/test/reftest/pngsuite-transparency/tbbn2c16.png new file mode 100644 index 000000000..5abfbbb3a Binary files /dev/null and b/image/test/reftest/pngsuite-transparency/tbbn2c16.png differ diff --git a/image/test/reftest/pngsuite-transparency/tbbn3p08.html b/image/test/reftest/pngsuite-transparency/tbbn3p08.html new file mode 100644 index 000000000..9248bb274 --- /dev/null +++ b/image/test/reftest/pngsuite-transparency/tbbn3p08.html @@ -0,0 +1,1092 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-transparency/tbbn3p08.png b/image/test/reftest/pngsuite-transparency/tbbn3p08.png new file mode 100644 index 000000000..4210d1683 Binary files /dev/null and b/image/test/reftest/pngsuite-transparency/tbbn3p08.png differ diff --git a/image/test/reftest/pngsuite-transparency/tbgn2c16.html b/image/test/reftest/pngsuite-transparency/tbgn2c16.html new file mode 100644 index 000000000..849c66faf --- /dev/null +++ b/image/test/reftest/pngsuite-transparency/tbgn2c16.html @@ -0,0 +1,1092 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-transparency/tbgn2c16.png b/image/test/reftest/pngsuite-transparency/tbgn2c16.png new file mode 100644 index 000000000..236c81dcf Binary files /dev/null and b/image/test/reftest/pngsuite-transparency/tbgn2c16.png differ diff --git a/image/test/reftest/pngsuite-transparency/tbgn3p08.html b/image/test/reftest/pngsuite-transparency/tbgn3p08.html new file mode 100644 index 000000000..9248bb274 --- /dev/null +++ b/image/test/reftest/pngsuite-transparency/tbgn3p08.html @@ -0,0 +1,1092 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-transparency/tbgn3p08.png b/image/test/reftest/pngsuite-transparency/tbgn3p08.png new file mode 100644 index 000000000..42db2325b Binary files /dev/null and b/image/test/reftest/pngsuite-transparency/tbgn3p08.png differ diff --git a/image/test/reftest/pngsuite-transparency/tbrn2c08.html b/image/test/reftest/pngsuite-transparency/tbrn2c08.html new file mode 100644 index 000000000..9248bb274 --- /dev/null +++ b/image/test/reftest/pngsuite-transparency/tbrn2c08.html @@ -0,0 +1,1092 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-transparency/tbrn2c08.png b/image/test/reftest/pngsuite-transparency/tbrn2c08.png new file mode 100644 index 000000000..8c214746d Binary files /dev/null and b/image/test/reftest/pngsuite-transparency/tbrn2c08.png differ diff --git a/image/test/reftest/pngsuite-transparency/tbwn1g16.html b/image/test/reftest/pngsuite-transparency/tbwn1g16.html new file mode 100644 index 000000000..381cc427c --- /dev/null +++ b/image/test/reftest/pngsuite-transparency/tbwn1g16.html @@ -0,0 +1,1092 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-transparency/tbwn1g16.png b/image/test/reftest/pngsuite-transparency/tbwn1g16.png new file mode 100644 index 000000000..dba2cbb6c Binary files /dev/null and b/image/test/reftest/pngsuite-transparency/tbwn1g16.png differ diff --git a/image/test/reftest/pngsuite-transparency/tbwn3p08.html b/image/test/reftest/pngsuite-transparency/tbwn3p08.html new file mode 100644 index 000000000..9248bb274 --- /dev/null +++ b/image/test/reftest/pngsuite-transparency/tbwn3p08.html @@ -0,0 +1,1092 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-transparency/tbwn3p08.png b/image/test/reftest/pngsuite-transparency/tbwn3p08.png new file mode 100644 index 000000000..7922135aa Binary files /dev/null and b/image/test/reftest/pngsuite-transparency/tbwn3p08.png differ diff --git a/image/test/reftest/pngsuite-transparency/tbyn3p08.html b/image/test/reftest/pngsuite-transparency/tbyn3p08.html new file mode 100644 index 000000000..9248bb274 --- /dev/null +++ b/image/test/reftest/pngsuite-transparency/tbyn3p08.html @@ -0,0 +1,1092 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-transparency/tbyn3p08.png b/image/test/reftest/pngsuite-transparency/tbyn3p08.png new file mode 100644 index 000000000..5b2c6cbba Binary files /dev/null and b/image/test/reftest/pngsuite-transparency/tbyn3p08.png differ diff --git a/image/test/reftest/pngsuite-transparency/tp1n3p08.html b/image/test/reftest/pngsuite-transparency/tp1n3p08.html new file mode 100644 index 000000000..9248bb274 --- /dev/null +++ b/image/test/reftest/pngsuite-transparency/tp1n3p08.html @@ -0,0 +1,1092 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-transparency/tp1n3p08.png b/image/test/reftest/pngsuite-transparency/tp1n3p08.png new file mode 100644 index 000000000..6c5fd6ec3 Binary files /dev/null and b/image/test/reftest/pngsuite-transparency/tp1n3p08.png differ diff --git a/image/test/reftest/pngsuite-transparency/wrapper.html b/image/test/reftest/pngsuite-transparency/wrapper.html new file mode 100644 index 000000000..5bbe75e01 --- /dev/null +++ b/image/test/reftest/pngsuite-transparency/wrapper.html @@ -0,0 +1,27 @@ + + + +Image reftest wrapper + + + + + + + + + diff --git a/image/test/reftest/pngsuite-zlib/reftest-stylo.list b/image/test/reftest/pngsuite-zlib/reftest-stylo.list new file mode 100644 index 000000000..35753fa4e --- /dev/null +++ b/image/test/reftest/pngsuite-zlib/reftest-stylo.list @@ -0,0 +1,9 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# z00n2c08 - color, no interlacing, compression level 0 (none) +fails == z00n2c08.png z00n2c08.png +# z03n2c08 - color, no interlacing, compression level 3 +fails == z03n2c08.png z03n2c08.png +# z06n2c08 - color, no interlacing, compression level 6 (default) +fails == z06n2c08.png z06n2c08.png +# z09n2c08 - color, no interlacing, compression level 9 (maximum) +fails == z09n2c08.png z09n2c08.png diff --git a/image/test/reftest/pngsuite-zlib/reftest.list b/image/test/reftest/pngsuite-zlib/reftest.list new file mode 100644 index 000000000..ec153449f --- /dev/null +++ b/image/test/reftest/pngsuite-zlib/reftest.list @@ -0,0 +1,8 @@ +# z00n2c08 - color, no interlacing, compression level 0 (none) +== z00n2c08.png z00n2c08.html +# z03n2c08 - color, no interlacing, compression level 3 +== z03n2c08.png z03n2c08.html +# z06n2c08 - color, no interlacing, compression level 6 (default) +== z06n2c08.png z06n2c08.html +# z09n2c08 - color, no interlacing, compression level 9 (maximum) +== z09n2c08.png z09n2c08.html diff --git a/image/test/reftest/pngsuite-zlib/z00n2c08.html b/image/test/reftest/pngsuite-zlib/z00n2c08.html new file mode 100644 index 000000000..c878a03ff --- /dev/null +++ b/image/test/reftest/pngsuite-zlib/z00n2c08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-zlib/z00n2c08.png b/image/test/reftest/pngsuite-zlib/z00n2c08.png new file mode 100644 index 000000000..7669eb838 Binary files /dev/null and b/image/test/reftest/pngsuite-zlib/z00n2c08.png differ diff --git a/image/test/reftest/pngsuite-zlib/z03n2c08.html b/image/test/reftest/pngsuite-zlib/z03n2c08.html new file mode 100644 index 000000000..c878a03ff --- /dev/null +++ b/image/test/reftest/pngsuite-zlib/z03n2c08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-zlib/z03n2c08.png b/image/test/reftest/pngsuite-zlib/z03n2c08.png new file mode 100644 index 000000000..bfb10de8d Binary files /dev/null and b/image/test/reftest/pngsuite-zlib/z03n2c08.png differ diff --git a/image/test/reftest/pngsuite-zlib/z06n2c08.html b/image/test/reftest/pngsuite-zlib/z06n2c08.html new file mode 100644 index 000000000..c878a03ff --- /dev/null +++ b/image/test/reftest/pngsuite-zlib/z06n2c08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-zlib/z06n2c08.png b/image/test/reftest/pngsuite-zlib/z06n2c08.png new file mode 100644 index 000000000..b90ebc10f Binary files /dev/null and b/image/test/reftest/pngsuite-zlib/z06n2c08.png differ diff --git a/image/test/reftest/pngsuite-zlib/z09n2c08.html b/image/test/reftest/pngsuite-zlib/z09n2c08.html new file mode 100644 index 000000000..c878a03ff --- /dev/null +++ b/image/test/reftest/pngsuite-zlib/z09n2c08.html @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/image/test/reftest/pngsuite-zlib/z09n2c08.png b/image/test/reftest/pngsuite-zlib/z09n2c08.png new file mode 100644 index 000000000..5f191a78e Binary files /dev/null and b/image/test/reftest/pngsuite-zlib/z09n2c08.png differ diff --git a/image/test/reftest/reftest-stylo.list b/image/test/reftest/reftest-stylo.list new file mode 100644 index 000000000..8c76cce79 --- /dev/null +++ b/image/test/reftest/reftest-stylo.list @@ -0,0 +1,65 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# Check for 24-bit color mode (test for bug 414720) +skip-if(Android) == colordepth.html colordepth.html + +# "PngSuite, the official set of PNG test images" +# Images by Willem van Schaik +# +# http://www.schaik.com/pngsuite/pngsuite.html +# http://www.libpng.org/pub/png/pngsuite.html +skip-if(B2G) include pngsuite-basic-n/reftest-stylo.list +# bug 783632 +skip-if(B2G) include pngsuite-basic-i/reftest-stylo.list +# bug 783632 +skip-if(B2G) include pngsuite-ancillary/reftest-stylo.list +# bug 783632 +skip-if(B2G) include pngsuite-background/reftest-stylo.list +# bug 783632 +skip-if(B2G) include pngsuite-chunkorder/reftest-stylo.list +# bug 783632 +skip-if(B2G) include pngsuite-corrupted/reftest-stylo.list +# bug 783632 +skip-if(B2G) include pngsuite-filtering/reftest-stylo.list +# bug 783632 +skip-if(B2G) include pngsuite-gamma/reftest-stylo.list +# bug 783632 +skip-if(B2G) include pngsuite-oddsizes/reftest-stylo.list +# bug 783632 +skip-if(B2G) include pngsuite-palettes/reftest-stylo.list +# bug 783632 +skip-if(B2G) include pngsuite-transparency/reftest-stylo.list +# bug 783632 +skip-if(B2G) include pngsuite-zlib/reftest-stylo.list +# bug 783632 + +# Disabled, lots of intermittents here +# BMP tests +#skip-if(Android) include bmp/reftest-stylo.list + +# ICO tests +#skip-if(Android) include ico/reftest-stylo.list + +# JPEG tests +# include jpeg/reftest-stylo.list + +# GIF tests +# include gif/reftest-stylo.list + +# APNG tests +include apng/reftest-stylo.list + +# Generic image tests +include generic/reftest-stylo.list + +# Color management test +include color-management/reftest-stylo.list + +# Downscaling tests +# include downscaling/reftest-stylo.list + +# Blob URI tests +include blob/reftest-stylo.list + +# Lossless encoders +# skip-if(Android||B2G) include encoders-lossless/reftest-stylo.list +# bug 783621 diff --git a/image/test/reftest/reftest.list b/image/test/reftest/reftest.list new file mode 100644 index 000000000..bfa154e6a --- /dev/null +++ b/image/test/reftest/reftest.list @@ -0,0 +1,50 @@ +# Check for 24-bit color mode (test for bug 414720) +skip-if(Android) == colordepth.html about:blank + +# "PngSuite, the official set of PNG test images" +# Images by Willem van Schaik +# +# http://www.schaik.com/pngsuite/pngsuite.html +# http://www.libpng.org/pub/png/pngsuite.html +include pngsuite-basic-n/reftest.list +include pngsuite-basic-i/reftest.list +include pngsuite-ancillary/reftest.list +include pngsuite-background/reftest.list +include pngsuite-chunkorder/reftest.list +include pngsuite-corrupted/reftest.list +include pngsuite-filtering/reftest.list +include pngsuite-gamma/reftest.list +include pngsuite-oddsizes/reftest.list +include pngsuite-palettes/reftest.list +include pngsuite-transparency/reftest.list +include pngsuite-zlib/reftest.list + +# BMP tests +skip-if(Android) include bmp/reftest.list + +# ICO tests +skip-if(Android) include ico/reftest.list + +# JPEG tests +include jpeg/reftest.list + +# GIF tests +include gif/reftest.list + +# APNG tests +include apng/reftest.list + +# Generic image tests +include generic/reftest.list + +# Color management test +include color-management/reftest.list + +# Downscaling tests +include downscaling/reftest.list + +# Blob URI tests +include blob/reftest.list + +# Lossless encoders +skip-if(Android) include encoders-lossless/reftest.list diff --git a/image/test/unit/async_load_tests.js b/image/test/unit/async_load_tests.js new file mode 100644 index 000000000..fafb26f75 --- /dev/null +++ b/image/test/unit/async_load_tests.js @@ -0,0 +1,214 @@ +/* + * Test to ensure that image loading/decoding notifications are always + * delivered async, and in the order we expect. + * + * Must be included from a file that has a uri of the image to test defined in + * var uri. + */ + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; +var Cr = Components.results; + +Cu.import("resource://testing-common/httpd.js"); +Cu.import("resource://gre/modules/NetUtil.jsm"); + +var server = new HttpServer(); +server.registerDirectory("/", do_get_file('')); +server.registerContentType("sjs", "sjs"); +server.start(-1); + + +load('image_load_helpers.js'); + +var requests = []; + +// Return a closure that holds on to the listener from the original +// imgIRequest, and compares its results to the cloned one. +function getCloneStopCallback(original_listener) +{ + return function cloneStop(listener) { + do_check_eq(original_listener.state, listener.state); + + // Sanity check to make sure we didn't accidentally use the same listener + // twice. + do_check_neq(original_listener, listener); + do_test_finished(); + } +} + +// Make sure that cloned requests get all the same callbacks as the original, +// but they aren't synchronous right now. +function checkClone(other_listener, aRequest) +{ + do_test_pending(); + + // For as long as clone notification is synchronous, we can't test the clone state reliably. + var listener = new ImageListener(null, function(foo, bar) { do_test_finished(); } /*getCloneStopCallback(other_listener)*/); + listener.synchronous = false; + var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools) + .createScriptedObserver(listener); + var clone = aRequest.clone(outer); + requests.push(clone); +} + +// Ensure that all the callbacks were called on aRequest. +function checkSizeAndLoad(listener, aRequest) +{ + do_check_neq(listener.state & SIZE_AVAILABLE, 0); + do_check_neq(listener.state & LOAD_COMPLETE, 0); + + do_test_finished(); +} + +function secondLoadDone(oldlistener, aRequest) +{ + do_test_pending(); + + try { + var staticrequest = aRequest.getStaticRequest(); + + // For as long as clone notification is synchronous, we can't test the + // clone state reliably. + var listener = new ImageListener(null, checkSizeAndLoad); + listener.synchronous = false; + var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools) + .createScriptedObserver(listener); + var staticrequestclone = staticrequest.clone(outer); + requests.push(staticrequestclone); + } catch(e) { + // We can't create a static request. Most likely the request we started + // with didn't load successfully. + do_test_finished(); + } + + run_loadImageWithChannel_tests(); + + do_test_finished(); +} + +// Load the request a second time. This should come from the image cache, and +// therefore would be at most risk of being served synchronously. +function checkSecondLoad() +{ + do_test_pending(); + + var listener = new ImageListener(checkClone, secondLoadDone); + var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools) + .createScriptedObserver(listener); + requests.push(gCurrentLoader.loadImageXPCOM(uri, null, null, "default", null, null, outer, null, 0, null)); + listener.synchronous = false; +} + +function firstLoadDone(oldlistener, aRequest) +{ + checkSecondLoad(uri); + + do_test_finished(); +} + +// Return a closure that allows us to check the stream listener's status when the +// image finishes loading. +function getChannelLoadImageStopCallback(streamlistener, next) +{ + return function channelLoadStop(imglistener, aRequest) { + + next(); + + do_test_finished(); + } +} + +// Load the request a second time. This should come from the image cache, and +// therefore would be at most risk of being served synchronously. +function checkSecondChannelLoad() +{ + do_test_pending(); + var channel = NetUtil.newChannel({uri: uri, loadUsingSystemPrincipal: true}); + var channellistener = new ChannelListener(); + channel.asyncOpen2(channellistener); + + var listener = new ImageListener(null, + getChannelLoadImageStopCallback(channellistener, + all_done_callback)); + var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools) + .createScriptedObserver(listener); + var outlistener = {}; + requests.push(gCurrentLoader.loadImageWithChannelXPCOM(channel, outer, null, outlistener)); + channellistener.outputListener = outlistener.value; + + listener.synchronous = false; +} + +function run_loadImageWithChannel_tests() +{ + // To ensure we're testing what we expect to, create a new loader and cache. + gCurrentLoader = Cc["@mozilla.org/image/loader;1"].createInstance(Ci.imgILoader); + + do_test_pending(); + var channel = NetUtil.newChannel({uri: uri, loadUsingSystemPrincipal: true}); + var channellistener = new ChannelListener(); + channel.asyncOpen2(channellistener); + + var listener = new ImageListener(null, + getChannelLoadImageStopCallback(channellistener, + checkSecondChannelLoad)); + var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools) + .createScriptedObserver(listener); + var outlistener = {}; + requests.push(gCurrentLoader.loadImageWithChannelXPCOM(channel, outer, null, outlistener)); + channellistener.outputListener = outlistener.value; + + listener.synchronous = false; +} + +function all_done_callback() +{ + server.stop(function() { do_test_finished(); }); +} + +function startImageCallback(otherCb) +{ + return function(listener, request) + { + // Make sure we can load the same image immediately out of the cache. + do_test_pending(); + var listener2 = new ImageListener(null, function(foo, bar) { do_test_finished(); }); + var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools) + .createScriptedObserver(listener2); + requests.push(gCurrentLoader.loadImageXPCOM(uri, null, null, "default", null, null, outer, null, 0, null)); + listener2.synchronous = false; + + // Now that we've started another load, chain to the callback. + otherCb(listener, request); + } +} + +var gCurrentLoader; + +function cleanup() +{ + for (var i = 0; i < requests.length; ++i) { + requests[i].cancelAndForgetObserver(0); + } +} + +function run_test() +{ + do_register_cleanup(cleanup); + + gCurrentLoader = Cc["@mozilla.org/image/loader;1"].createInstance(Ci.imgILoader); + + do_test_pending(); + var listener = new ImageListener(startImageCallback(checkClone), firstLoadDone); + var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools) + .createScriptedObserver(listener); + var req = gCurrentLoader.loadImageXPCOM(uri, null, null, "default", null, null, outer, null, 0, null); + requests.push(req); + + // Ensure that we don't cause any mayhem when we lock an image. + req.lockImage(); + + listener.synchronous = false; +} diff --git a/image/test/unit/bug413512.ico b/image/test/unit/bug413512.ico new file mode 100644 index 000000000..b2db0429f Binary files /dev/null and b/image/test/unit/bug413512.ico differ diff --git a/image/test/unit/bug815359.ico b/image/test/unit/bug815359.ico new file mode 100644 index 000000000..a24b8fb6b Binary files /dev/null and b/image/test/unit/bug815359.ico differ diff --git a/image/test/unit/image1.png b/image/test/unit/image1.png new file mode 100644 index 000000000..2fb37aeec Binary files /dev/null and b/image/test/unit/image1.png differ diff --git a/image/test/unit/image1png16x16.jpg b/image/test/unit/image1png16x16.jpg new file mode 100644 index 000000000..ea14dbede Binary files /dev/null and b/image/test/unit/image1png16x16.jpg differ diff --git a/image/test/unit/image1png64x64.jpg b/image/test/unit/image1png64x64.jpg new file mode 100644 index 000000000..11c34f6c6 Binary files /dev/null and b/image/test/unit/image1png64x64.jpg differ diff --git a/image/test/unit/image2.jpg b/image/test/unit/image2.jpg new file mode 100644 index 000000000..b2131bf0c Binary files /dev/null and b/image/test/unit/image2.jpg differ diff --git a/image/test/unit/image2jpg16x16-win.png b/image/test/unit/image2jpg16x16-win.png new file mode 100644 index 000000000..a821626c0 Binary files /dev/null and b/image/test/unit/image2jpg16x16-win.png differ diff --git a/image/test/unit/image2jpg16x16.png b/image/test/unit/image2jpg16x16.png new file mode 100644 index 000000000..5722223c2 Binary files /dev/null and b/image/test/unit/image2jpg16x16.png differ diff --git a/image/test/unit/image2jpg16x16cropped.jpg b/image/test/unit/image2jpg16x16cropped.jpg new file mode 100644 index 000000000..fca22cb30 Binary files /dev/null and b/image/test/unit/image2jpg16x16cropped.jpg differ diff --git a/image/test/unit/image2jpg16x16cropped2.jpg b/image/test/unit/image2jpg16x16cropped2.jpg new file mode 100644 index 000000000..e51d3530d Binary files /dev/null and b/image/test/unit/image2jpg16x16cropped2.jpg differ diff --git a/image/test/unit/image2jpg16x32cropped3.jpg b/image/test/unit/image2jpg16x32cropped3.jpg new file mode 100644 index 000000000..13a3d26e5 Binary files /dev/null and b/image/test/unit/image2jpg16x32cropped3.jpg differ diff --git a/image/test/unit/image2jpg16x32scaled.jpg b/image/test/unit/image2jpg16x32scaled.jpg new file mode 100644 index 000000000..6abef0f99 Binary files /dev/null and b/image/test/unit/image2jpg16x32scaled.jpg differ diff --git a/image/test/unit/image2jpg32x16cropped4.jpg b/image/test/unit/image2jpg32x16cropped4.jpg new file mode 100644 index 000000000..46f34918c Binary files /dev/null and b/image/test/unit/image2jpg32x16cropped4.jpg differ diff --git a/image/test/unit/image2jpg32x16scaled.jpg b/image/test/unit/image2jpg32x16scaled.jpg new file mode 100644 index 000000000..e302fbafd Binary files /dev/null and b/image/test/unit/image2jpg32x16scaled.jpg differ diff --git a/image/test/unit/image2jpg32x32-win.png b/image/test/unit/image2jpg32x32-win.png new file mode 100644 index 000000000..4d84df26a Binary files /dev/null and b/image/test/unit/image2jpg32x32-win.png differ diff --git a/image/test/unit/image2jpg32x32.jpg b/image/test/unit/image2jpg32x32.jpg new file mode 100644 index 000000000..cf9a10a37 Binary files /dev/null and b/image/test/unit/image2jpg32x32.jpg differ diff --git a/image/test/unit/image2jpg32x32.png b/image/test/unit/image2jpg32x32.png new file mode 100644 index 000000000..723008771 Binary files /dev/null and b/image/test/unit/image2jpg32x32.png differ diff --git a/image/test/unit/image3.ico b/image/test/unit/image3.ico new file mode 100644 index 000000000..d44438903 Binary files /dev/null and b/image/test/unit/image3.ico differ diff --git a/image/test/unit/image3ico16x16.png b/image/test/unit/image3ico16x16.png new file mode 100644 index 000000000..e9e520cb6 Binary files /dev/null and b/image/test/unit/image3ico16x16.png differ diff --git a/image/test/unit/image3ico32x32.png b/image/test/unit/image3ico32x32.png new file mode 100644 index 000000000..58497e3fa Binary files /dev/null and b/image/test/unit/image3ico32x32.png differ diff --git a/image/test/unit/image4.gif b/image/test/unit/image4.gif new file mode 100644 index 000000000..b1530bc81 Binary files /dev/null and b/image/test/unit/image4.gif differ diff --git a/image/test/unit/image4gif16x16bmp24bpp.ico b/image/test/unit/image4gif16x16bmp24bpp.ico new file mode 100644 index 000000000..890c81c27 Binary files /dev/null and b/image/test/unit/image4gif16x16bmp24bpp.ico differ diff --git a/image/test/unit/image4gif16x16bmp32bpp.ico b/image/test/unit/image4gif16x16bmp32bpp.ico new file mode 100644 index 000000000..f8a9eb8ad Binary files /dev/null and b/image/test/unit/image4gif16x16bmp32bpp.ico differ diff --git a/image/test/unit/image4gif32x32bmp24bpp.ico b/image/test/unit/image4gif32x32bmp24bpp.ico new file mode 100644 index 000000000..28092818d Binary files /dev/null and b/image/test/unit/image4gif32x32bmp24bpp.ico differ diff --git a/image/test/unit/image4gif32x32bmp32bpp.ico b/image/test/unit/image4gif32x32bmp32bpp.ico new file mode 100644 index 000000000..0e2d28c82 Binary files /dev/null and b/image/test/unit/image4gif32x32bmp32bpp.ico differ diff --git a/image/test/unit/image_load_helpers.js b/image/test/unit/image_load_helpers.js new file mode 100644 index 000000000..e8d9a29f8 --- /dev/null +++ b/image/test/unit/image_load_helpers.js @@ -0,0 +1,122 @@ +/* + * Helper structures to track callbacks from image and channel loads. + */ + +// START_REQUEST and STOP_REQUEST are used by ChannelListener, and +// stored in ChannelListener.requestStatus. +const START_REQUEST = 0x01; +const STOP_REQUEST = 0x02; +const DATA_AVAILABLE = 0x04; + +// One bit per callback that imageListener below implements. Stored in +// ImageListener.state. +const SIZE_AVAILABLE = 0x01; +const FRAME_UPDATE = 0x02; +const FRAME_COMPLETE = 0x04; +const LOAD_COMPLETE = 0x08; +const DECODE_COMPLETE = 0x10; + +// Safebrowsing requires that the profile dir is set. +do_get_profile(); + +// An implementation of imgIScriptedNotificationObserver with the ability to +// call specified functions on onStartRequest and onStopRequest. +function ImageListener(start_callback, stop_callback) +{ + this.sizeAvailable = function onSizeAvailable(aRequest) + { + do_check_false(this.synchronous); + + this.state |= SIZE_AVAILABLE; + + if (this.start_callback) + this.start_callback(this, aRequest); + } + this.frameComplete = function onFrameComplete(aRequest) + { + do_check_false(this.synchronous); + + this.state |= FRAME_COMPLETE; + } + this.decodeComplete = function onDecodeComplete(aRequest) + { + do_check_false(this.synchronous); + + this.state |= DECODE_COMPLETE; + } + this.loadComplete = function onLoadcomplete(aRequest) + { + do_check_false(this.synchronous); + + this.state |= LOAD_COMPLETE; + + if (this.stop_callback) + this.stop_callback(this, aRequest); + } + this.frameUpdate = function onFrameUpdate(aRequest) + { + } + this.isAnimated = function onIsAnimated() + { + } + + // Initialize the synchronous flag to true to start. This must be set to + // false before exiting to the event loop! + this.synchronous = true; + + // A function to call when onStartRequest is called. + this.start_callback = start_callback; + + // A function to call when onStopRequest is called. + this.stop_callback = stop_callback; + + // The image load/decode state. + // A bitfield that tracks which callbacks have been called. Takes the bits + // defined above. + this.state = 0; +} + +function NS_FAILED(val) +{ + return !!(val & 0x80000000); +} + +function ChannelListener() +{ + this.onStartRequest = function onStartRequest(aRequest, aContext) + { + if (this.outputListener) + this.outputListener.onStartRequest(aRequest, aContext); + + this.requestStatus |= START_REQUEST; + } + + this.onDataAvailable = function onDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount) + { + if (this.outputListener) + this.outputListener.onDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount); + + this.requestStatus |= DATA_AVAILABLE; + } + + this.onStopRequest = function onStopRequest(aRequest, aContext, aStatusCode) + { + if (this.outputListener) + this.outputListener.onStopRequest(aRequest, aContext, aStatusCode); + + // If we failed (or were canceled - failure is implied if canceled), + // there's no use tracking our state, since it's meaningless. + if (NS_FAILED(aStatusCode)) + this.requestStatus = 0; + else + this.requestStatus |= STOP_REQUEST; + } + + // A listener to pass the notifications we get to. + this.outputListener = null; + + // The request's status. A bitfield that holds one or both of START_REQUEST + // and STOP_REQUEST, according to which callbacks have been called on the + // associated request. + this.requestStatus = 0; +} diff --git a/image/test/unit/test_async_notification.js b/image/test/unit/test_async_notification.js new file mode 100644 index 000000000..63d04b99b --- /dev/null +++ b/image/test/unit/test_async_notification.js @@ -0,0 +1,10 @@ +/* + * Test for asynchronous image load/decode notifications in the case that the image load works. + */ + +// A simple 3x3 png; rows go red, green, blue. Stolen from the PNG encoder test. +var pngspec = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAEUlEQVQImWP4z8AAQTAamQkAhpcI+DeMzFcAAAAASUVORK5CYII="; +var ioService = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService); +var uri = ioService.newURI(pngspec, null, null); + +load('async_load_tests.js'); diff --git a/image/test/unit/test_async_notification_404.js b/image/test/unit/test_async_notification_404.js new file mode 100644 index 000000000..4949d282d --- /dev/null +++ b/image/test/unit/test_async_notification_404.js @@ -0,0 +1,16 @@ +/* + * Test to ensure that load/decode notifications are delivered completely and + * asynchronously when dealing with a file that's a 404. + */ +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +var ioService = Components.classes["@mozilla.org/network/io-service;1"] + .getService(Components.interfaces.nsIIOService); + +XPCOMUtils.defineLazyGetter(this, "uri", function() { + return ioService.newURI("http://localhost:" + + server.identity.primaryPort + + "/async-notification-never-here.jpg", null, null); +}); + +load('async_load_tests.js'); diff --git a/image/test/unit/test_async_notification_animated.js b/image/test/unit/test_async_notification_animated.js new file mode 100644 index 000000000..bfe6e2d0d --- /dev/null +++ b/image/test/unit/test_async_notification_animated.js @@ -0,0 +1,14 @@ +/* + * Test for asynchronous image load/decode notifications in the case that the + * image load works, but for an animated image. + * + * If this fails because a request wasn't cancelled, it's possible that + * imgContainer::ExtractFrame didn't set the new image's status correctly. + */ + +// transparent-animation.gif from the gif reftests. +var spec = "data:image/gif;base64,R0lGODlhZABkAIABAP8AAP///yH5BAkBAAEALAAAAABLAGQAAAK8jI+py+0Po5y02ouz3rz7D4biSJbmiabqyrbuC8fyTNf2jef6zvf+DwwKh8Si8YhMKpchgPMJjUqnVOipis1ir9qul+sNV8HistVkTj/JajG7/UXDy+95tm4fy/NdPF/q93dWIqgVWAhwWKgoyPjnyAeZJ2lHOWcJh9mmqcaZ5mkGSreHOCXqRloadRrGGkeoapoa6+TaN0tra4gbq3vHq+q7BVwqrMeEnKy8zNzs/AwdLT1NXW19jZ1tUgAAIfkECQEAAQAsAAAAADQAZAAAArCMj6nL7Q+jnLTai7PevPsPhuJIluaJpurKtu4Lx/JM1/aN5/rO9/7vAAiHxKLxiCRCkswmc+mMSqHSapJqzSof2u4Q67WCw1MuOTs+N9Pqq7kdZcON8vk2aF+/88g6358HaCc4Rwhn2IaopnjGSOYYBukl2UWpZYm2x0enuXnX4NnXGQqAKTYaalqlWoZH+snwWsQah+pJ64Sr5ypbCvQLHCw8TFxsfIycrLzM3PxQAAAh+QQJAQABACwAAAAAGwBkAAACUIyPqcvtD6OctNqLs968+w+G4kiW5omm6sq27gTE8kzX9o3n+s73/g8MCofEovGITCqXzKbzCY1Kp9Sq9YrNarfcrvdrfYnH5LL5jE6r16sCADs="; +var ioService = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService); +var uri = ioService.newURI(spec, null, null); + +load('async_load_tests.js'); diff --git a/image/test/unit/test_encoder_apng.js b/image/test/unit/test_encoder_apng.js new file mode 100644 index 000000000..e7b90c28b --- /dev/null +++ b/image/test/unit/test_encoder_apng.js @@ -0,0 +1,470 @@ +/* + * Test for APNG encoding in ImageLib + * + */ + + +var Ci = Components.interfaces; +var Cc = Components.classes; + + // dispose=[none|background|previous] + // blend=[source|over] + +var apng1A = { + // A 3x3 image with 3 frames, alternating red, green, blue. RGB format. + width : 3, height : 3, skipFirstFrame : false, + format : Ci.imgIEncoder.INPUT_FORMAT_RGB, + transparency : null, + plays : 0, + + frames : [ + { // frame #1 + width : 3, height : 3, + x_offset : 0, y_offset : 0, + dispose : "none", blend : "source", delay : 500, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGB, stride : 9, + transparency : null, + + pixels : [ + 255,0,0, 255,0,0, 255,0,0, + 255,0,0, 255,0,0, 255,0,0, + 255,0,0, 255,0,0, 255,0,0 + ] + }, + + { // frame #2 + width : 3, height : 3, + x_offset : 0, y_offset : 0, + dispose : "none", blend : "source", delay : 500, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGB, stride : 9, + transparency : null, + + pixels : [ + 0,255,0, 0,255,0, 0,255,0, + 0,255,0, 0,255,0, 0,255,0, + 0,255,0, 0,255,0, 0,255,0 + ] + }, + + { // frame #3 + width : 3, height : 3, + x_offset : 0, y_offset : 0, + dispose : "none", blend : "source", delay : 500, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGB, stride : 9, + transparency : null, + + pixels : [ + 0,0,255, 0,0,255, 0,0,255, + 0,0,255, 0,0,255, 0,0,255, + 0,0,255, 0,0,255, 0,0,255 + ] + } + + ], + expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAABBJREFUCJlj+M/AAEEMWFgAj44I+H2CySsAAAAaZmNUTAAAAAEAAAADAAAAAwAAAAAAAAAAAfQD6AAA7mXKzAAAABNmZEFUAAAAAgiZY2D4zwBFWFgAhpcI+I731VcAAAAaZmNUTAAAAAMAAAADAAAAAwAAAAAAAAAAAfQD6AAAA/MZJQAAABNmZEFUAAAABAiZY2Bg+A9DmCwAfaAI+AGmQVoAAAAASUVORK5CYII=" +}; + + +var apng1B = { + // A 3x3 image with 3 frames, alternating red, green, blue. RGBA format. + width : 3, height : 3, skipFirstFrame : false, + format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, + transparency : null, + plays : 0, + + frames : [ + { // frame #1 + width : 3, height : 3, + x_offset : 0, y_offset : 0, + dispose : "none", blend : "source", delay : 500, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, + + pixels : [ + 255,0,0,255, 255,0,0,255, 255,0,0,255, + 255,0,0,255, 255,0,0,255, 255,0,0,255, + 255,0,0,255, 255,0,0,255, 255,0,0,255 + ] + }, + + { // frame #2 + width : 3, height : 3, + x_offset : 0, y_offset : 0, + dispose : "none", blend : "source", delay : 500, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, + + pixels : [ + 0,255,0,255, 0,255,0,255, 0,255,0,255, + 0,255,0,255, 0,255,0,255, 0,255,0,255, + 0,255,0,255, 0,255,0,255, 0,255,0,255 + ] + }, + + { // frame #3 + width : 3, height : 3, + x_offset : 0, y_offset : 0, + dispose : "none", blend : "source", delay : 500, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, + + pixels : [ + 0,0,255,255, 0,0,255,255, 0,0,255,255, + 0,0,255,255, 0,0,255,255, 0,0,255,255, + 0,0,255,255, 0,0,255,255, 0,0,255,255 + ] + } + + ], + expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAABFJREFUCJlj+M/A8B+GGXByAF3XEe/CoiJ1AAAAGmZjVEwAAAABAAAAAwAAAAMAAAAAAAAAAAH0A+gAAO5lyswAAAASZmRBVAAAAAIImWNg+I8EcXIAVOAR77Vyl9QAAAAaZmNUTAAAAAMAAAADAAAAAwAAAAAAAAAAAfQD6AAAA/MZJQAAABRmZEFUAAAABAiZY2Bg+P8fgXFxAEvpEe8rClxSAAAAAElFTkSuQmCC" +}; + + +var apng1C = { + // A 3x3 image with 3 frames, alternating red, green, blue. RGBA format. + // The first frame is skipped, so it will only flash green/blue (or static red in an APNG-unaware viewer) + width : 3, height : 3, skipFirstFrame : true, + format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, + transparency : null, + plays : 0, + + frames : [ + { // frame #1 + width : 3, height : 3, + x_offset : 0, y_offset : 0, + dispose : "none", blend : "source", delay : 500, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, + + pixels : [ + 255,0,0,255, 255,0,0,255, 255,0,0,255, + 255,0,0,255, 255,0,0,255, 255,0,0,255, + 255,0,0,255, 255,0,0,255, 255,0,0,255 + ] + }, + + { // frame #2 + width : 3, height : 3, + x_offset : 0, y_offset : 0, + dispose : "none", blend : "source", delay : 500, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, + + pixels : [ + 0,255,0,255, 0,255,0,255, 0,255,0,255, + 0,255,0,255, 0,255,0,255, 0,255,0,255, + 0,255,0,255, 0,255,0,255, 0,255,0,255 + ] + }, + + { // frame #3 + width : 3, height : 3, + x_offset : 0, y_offset : 0, + dispose : "none", blend : "source", delay : 500, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, + + pixels : [ + 0,0,255,255, 0,0,255,255, 0,0,255,255, + 0,0,255,255, 0,0,255,255, 0,0,255,255, + 0,0,255,255, 0,0,255,255, 0,0,255,255 + ] + } + + ], + expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAACAAAAAPONk3AAAAARSURBVAiZY/jPwPAfhhlwcgBd1xHvwqIidQAAABpmY1RMAAAAAAAAAAMAAAADAAAAAAAAAAAB9APoAAB1FiAYAAAAEmZkQVQAAAABCJljYPiPBHFyAFTgEe+kD/2tAAAAGmZjVEwAAAACAAAAAwAAAAMAAAAAAAAAAAH0A+gAAJiA8/EAAAAUZmRBVAAAAAMImWNgYPj/H4FxcQBL6RHvC5ggGQAAAABJRU5ErkJggg==" +}; + + +var apng2A = { + // A 3x3 image with 3 frames, alternating red, green, blue. RGBA format. + // blend = over mode + // (The green frame is a horizontal gradient, and the blue frame is a + // vertical gradient. They stack as they animate.) + width : 3, height : 3, skipFirstFrame : false, + format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, + transparency : null, + plays : 0, + + frames : [ + { // frame #1 + width : 3, height : 3, + x_offset : 0, y_offset : 0, + dispose : "none", blend : "source", delay : 500, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, + + pixels : [ + 255,0,0,255, 255,0,0,255, 255,0,0,255, + 255,0,0,255, 255,0,0,255, 255,0,0,255, + 255,0,0,255, 255,0,0,255, 255,0,0,255 + ] + }, + + { // frame #2 + width : 3, height : 3, + x_offset : 0, y_offset : 0, + dispose : "none", blend : "over", delay : 500, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, + + pixels : [ + 0,255,0,255, 0,255,0,180, 0,255,0,75, + 0,255,0,255, 0,255,0,180, 0,255,0,75, + 0,255,0,255, 0,255,0,180, 0,255,0,75 + ] + }, + + { // frame #3 + width : 3, height : 3, + x_offset : 0, y_offset : 0, + dispose : "none", blend : "over", delay : 500, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, + + pixels : [ + 0,0,255,75, 0,0,255,75, 0,0,255,75, + 0,0,255,180, 0,0,255,180, 0,0,255,180, + 0,0,255,255, 0,0,255,255, 0,0,255,255 + ] + } + + ], + expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAABFJREFUCJlj+M/A8B+GGXByAF3XEe/CoiJ1AAAAGmZjVEwAAAABAAAAAwAAAAMAAAAAAAAAAAH0A+gAAZli+loAAAAcZmRBVAAAAAIImWNg+M/wn+E/wxaG/wzeDDg5ACeGDvKVa3pyAAAAGmZjVEwAAAADAAAAAwAAAAMAAAAAAAAAAAH0A+gAAXT0KbMAAAAcZmRBVAAAAAQImWNgYPjvjcAM/7cgMMP//zAMAPqkDvLn1m3SAAAAAElFTkSuQmCC" +}; + + +var apng2B = { + // A 3x3 image with 3 frames, alternating red, green, blue. RGBA format. + // blend = over, dispose = background + // (The green frame is a horizontal gradient, and the blue frame is a + // vertical gradient. Each frame is displayed individually, blended to + // whatever the background is.) + width : 3, height : 3, skipFirstFrame : false, + format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, + transparency : null, + plays : 0, + + frames : [ + { // frame #1 + width : 3, height : 3, + x_offset : 0, y_offset : 0, + dispose : "background", blend : "source", delay : 500, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, + + pixels : [ + 255,0,0,255, 255,0,0,255, 255,0,0,255, + 255,0,0,255, 255,0,0,255, 255,0,0,255, + 255,0,0,255, 255,0,0,255, 255,0,0,255 + ] + }, + + { // frame #2 + width : 3, height : 3, + x_offset : 0, y_offset : 0, + dispose : "background", blend : "over", delay : 500, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, + + pixels : [ + 0,255,0,255, 0,255,0,180, 0,255,0,75, + 0,255,0,255, 0,255,0,180, 0,255,0,75, + 0,255,0,255, 0,255,0,180, 0,255,0,75 + ] + }, + + { // frame #3 + width : 3, height : 3, + x_offset : 0, y_offset : 0, + dispose : "background", blend : "over", delay : 500, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, + + pixels : [ + 0,0,255,75, 0,0,255,75, 0,0,255,75, + 0,0,255,180, 0,0,255,180, 0,0,255,180, + 0,0,255,255, 0,0,255,255, 0,0,255,255 + ] + } + + ], + expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AEAbA0RWQAAABFJREFUCJlj+M/A8B+GGXByAF3XEe/CoiJ1AAAAGmZjVEwAAAABAAAAAwAAAAMAAAAAAAAAAAH0A+gBAYB5yxsAAAAcZmRBVAAAAAIImWNg+M/wn+E/wxaG/wzeDDg5ACeGDvKVa3pyAAAAGmZjVEwAAAADAAAAAwAAAAMAAAAAAAAAAAH0A+gBAW3vGPIAAAAcZmRBVAAAAAQImWNgYPjvjcAM/7cgMMP//zAMAPqkDvLn1m3SAAAAAElFTkSuQmCC" +}; + + +var apng3 = { + // A 3x3 image with 4 frames. First frame is white, then 1x1 frames draw a diagonal line + width : 3, height : 3, skipFirstFrame : false, + format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, + transparency : null, + plays : 0, + + frames : [ + { // frame #1 + width : 3, height : 3, + x_offset : 0, y_offset : 0, + dispose : "none", blend : "source", delay : 500, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, + + pixels : [ + + 255,255,255,255, 255,255,255,255, 255,255,255,255, + 255,255,255,255, 255,255,255,255, 255,255,255,255, + 255,255,255,255, 255,255,255,255, 255,255,255,255 + ] + }, + + { // frame #2 + width : 1, height : 1, + x_offset : 0, y_offset : 0, + dispose : "none", blend : "source", delay : 500, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, + + pixels : [ + 0,0,0,255 + ] + }, + + { // frame #3 + width : 1, height : 1, + x_offset : 1, y_offset : 1, + dispose : "none", blend : "source", delay : 500, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, + + pixels : [ + 0,0,0,255 + ] + }, + + { // frame #4 + width : 1, height : 1, + x_offset : 2, y_offset : 2, + dispose : "none", blend : "source", delay : 500, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, + + pixels : [ + 0,0,0,255 + ] + } + ], + + expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAAEAAAAAHzNZtAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAAA5JREFUCJlj+I8EGHByALuHI91kueRqAAAAGmZjVEwAAAABAAAAAQAAAAEAAAAAAAAAAAH0A+gAADJXfawAAAARZmRBVAAAAAIImWNgYGD4DwABBAEAbr5mpgAAABpmY1RMAAAAAwAAAAEAAAABAAAAAQAAAAEB9APoAAC4OHoxAAAAEWZkQVQAAAAECJljYGBg+A8AAQQBAJZ8LRAAAAAaZmNUTAAAAAUAAAABAAAAAQAAAAIAAAACAfQD6AAA/fh01wAAABFmZEFUAAAABgiZY2BgYPgPAAEEAQB3Eum9AAAAAElFTkSuQmCC" +}; + +// Main test entry point. +function run_test() { + dump("Checking apng1A...\n"); + run_test_for(apng1A); + dump("Checking apng1B...\n"); + run_test_for(apng1B); + dump("Checking apng1C...\n"); + run_test_for(apng1C); + + dump("Checking apng2A...\n"); + run_test_for(apng2A); + dump("Checking apng2B...\n"); + run_test_for(apng2B); + + dump("Checking apng3...\n"); + run_test_for(apng3); +}; + + +function run_test_for(input) { + var encoder, dataURL; + + encoder = encodeImage(input); + dataURL = makeDataURL(encoder, "image/png"); + do_check_eq(dataURL, input.expected); +}; + + +function encodeImage(input) { + var encoder = Cc["@mozilla.org/image/encoder;2?type=image/png"].createInstance(); + encoder.QueryInterface(Ci.imgIEncoder); + + var options = ""; + if (input.transparency) { options += "transparency=" + input.transparency; } + options += ";frames=" + input.frames.length; + options += ";skipfirstframe=" + (input.skipFirstFrame ? "yes" : "no"); + options += ";plays=" + input.plays; + encoder.startImageEncode(input.width, input.height, input.format, options); + + for (var i = 0; i < input.frames.length; i++) { + var frame = input.frames[i]; + + options = ""; + if (frame.transparency) { options += "transparency=" + input.transparency; } + options += ";delay=" + frame.delay; + options += ";dispose=" + frame.dispose; + options += ";blend=" + frame.blend; + if (frame.x_offset > 0) { options += ";xoffset=" + frame.x_offset; } + if (frame.y_offset > 0) { options += ";yoffset=" + frame.y_offset; } + + encoder.addImageFrame(frame.pixels, frame.pixels.length, + frame.width, frame.height, frame.stride, frame.format, options); + } + + encoder.endImageEncode(); + + return encoder; +} + + +function makeDataURL(encoder, mimetype) { + var rawStream = encoder.QueryInterface(Ci.nsIInputStream); + + var stream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(); + stream.QueryInterface(Ci.nsIBinaryInputStream); + + stream.setInputStream(rawStream); + + var bytes = stream.readByteArray(stream.available()); // returns int[] + + var base64String = toBase64(bytes); + + return "data:" + mimetype + ";base64," + base64String; +} + +/* toBase64 copied from extensions/xml-rpc/src/nsXmlRpcClient.js */ + +/* Convert data (an array of integers) to a Base64 string. */ +const toBase64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' + + '0123456789+/'; +const base64Pad = '='; +function toBase64(data) { + var result = ''; + var length = data.length; + var i; + // Convert every three bytes to 4 ascii characters. + for (i = 0; i < (length - 2); i += 3) { + result += toBase64Table[data[i] >> 2]; + result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)]; + result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)]; + result += toBase64Table[data[i+2] & 0x3f]; + } + + // Convert the remaining 1 or 2 bytes, pad out to 4 characters. + if (length%3) { + i = length - (length%3); + result += toBase64Table[data[i] >> 2]; + if ((length%3) == 2) { + result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)]; + result += toBase64Table[(data[i+1] & 0x0f) << 2]; + result += base64Pad; + } else { + result += toBase64Table[(data[i] & 0x03) << 4]; + result += base64Pad + base64Pad; + } + } + + return result; +} diff --git a/image/test/unit/test_encoder_png.js b/image/test/unit/test_encoder_png.js new file mode 100644 index 000000000..67beb840f --- /dev/null +++ b/image/test/unit/test_encoder_png.js @@ -0,0 +1,256 @@ +/* + * Test for PNG encoding in ImageLib + * + */ + +var Ci = Components.interfaces; +var Cc = Components.classes; + +var png1A = { + // A 3x3 image, rows are red, green, blue. + // RGB format, transparency defaults. + + transparency : null, + + frames : [ + { + width : 3, height : 3, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGB, stride : 9, + + pixels : [ + 255,0,0, 255,0,0, 255,0,0, + 0,255,0, 0,255,0, 0,255,0, + 0,0,255, 0,0,255, 0,0,255, + ] + } + + ], + expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAEUlEQVQImWP4z8AAQTAamQkAhpcI+DeMzFcAAAAASUVORK5CYII=" +}; + +var png1B = { + // A 3x3 image, rows are red, green, blue. + // RGB format, transparency=none. + + transparency : "none", + + frames : [ + { + width : 3, height : 3, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGB, stride : 9, + + pixels : [ + 255,0,0, 255,0,0, 255,0,0, + 0,255,0, 0,255,0, 0,255,0, + 0,0,255, 0,0,255, 0,0,255, + ] + } + + ], + expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAEUlEQVQImWP4z8AAQTAamQkAhpcI+DeMzFcAAAAASUVORK5CYII=" +}; + +var png2A = { + // A 3x3 image, rows are: red, green, blue. Columns are: 0%, 33%, 66% transparent. + + transparency : null, + + frames : [ + { + width : 3, height : 3, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, + + pixels : [ + 255,0,0,255, 255,0,0,170, 255,0,0,85, + 0,255,0,255, 0,255,0,170, 0,255,0,85, + 0,0,255,255, 0,0,255,170, 0,0,255,85 + ] + } + + ], + expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAAIElEQVQImSXJMQEAMAwEIUy+yZi8DmVFFBcjycn86GgPJw4O8v9DkkEAAAAASUVORK5CYII=" +}; + +var png2B = { + // A 3x3 image, rows are: red, green, blue. Columns are: 0%, 33%, 66% transparent, + // but transparency will be ignored. + + transparency : "none", + + frames : [ + { + width : 3, height : 3, + + format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12, + + pixels : [ + 255,0,0,255, 255,0,0,170, 255,0,0,85, + 0,255,0,255, 0,255,0,170, 0,255,0,85, + 0,0,255,255, 0,0,255,170, 0,0,255,85 + ] + } + + ], + expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAEUlEQVQImWP4z8AAQTAamQkAhpcI+DeMzFcAAAAASUVORK5CYII=" +}; + +// Main test entry point. +function run_test() { + dump("Checking png1A...\n") + run_test_for(png1A); + dump("Checking png1B...\n") + run_test_for(png1B); + dump("Checking png2A...\n") + run_test_for(png2A); + dump("Checking png2B...\n") + run_test_for(png2B); +}; + + +function run_test_for(input) { + var encoder, dataURL; + + encoder = encodeImage(input); + dataURL = makeDataURL(encoder, "image/png"); + do_check_eq(dataURL, input.expected); + + encoder = encodeImageAsync(input); + dataURL = makeDataURLFromAsync(encoder, "image/png", input.expected); +}; + + +function encodeImage(input) { + var encoder = Cc["@mozilla.org/image/encoder;2?type=image/png"].createInstance(); + encoder.QueryInterface(Ci.imgIEncoder); + + var options = ""; + if (input.transparency) { + options += "transparency=" + input.transparency; + } + + var frame = input.frames[0]; + encoder.initFromData(frame.pixels, frame.pixels.length, + frame.width, frame.height, frame.stride, + frame.format, options); + return encoder; +} + +function _encodeImageAsyncFactory(frame, options, encoder) +{ + function finishEncode() { + encoder.addImageFrame(frame.pixels, frame.pixels.length, + frame.width, frame.height, frame.stride, + frame.format, options); + encoder.endImageEncode(); + } + return finishEncode; +} + +function encodeImageAsync(input) +{ + var encoder = Cc["@mozilla.org/image/encoder;2?type=image/png"].createInstance(); + encoder.QueryInterface(Ci.imgIEncoder); + + var options = ""; + if (input.transparency) { + options += "transparency=" + input.transparency; + } + + var frame = input.frames[0]; + encoder.startImageEncode(frame.width, frame.height, + frame.format, options); + + do_timeout(50, _encodeImageAsyncFactory(frame, options, encoder)); + return encoder; +} + + +function makeDataURL(encoder, mimetype) { + var rawStream = encoder.QueryInterface(Ci.nsIInputStream); + + var stream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(); + stream.QueryInterface(Ci.nsIBinaryInputStream); + + stream.setInputStream(rawStream); + + var bytes = stream.readByteArray(stream.available()); // returns int[] + + var base64String = toBase64(bytes); + + return "data:" + mimetype + ";base64," + base64String; +} + +function makeDataURLFromAsync(encoder, mimetype, expected) { + do_test_pending(); + var rawStream = encoder.QueryInterface(Ci.nsIAsyncInputStream); + + var currentThread = Cc["@mozilla.org/thread-manager;1"].getService().currentThread; + + var bytes = []; + + var binarystream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(); + binarystream.QueryInterface(Ci.nsIBinaryInputStream); + + var asyncReader = + { + onInputStreamReady: function(stream) + { + binarystream.setInputStream(stream); + var available = 0; + try { + available = stream.available(); + } catch(e) { } + + if (available > 0) + { + bytes = bytes.concat(binarystream.readByteArray(available)); + stream.asyncWait(this, 0, 0, currentThread); + } else { + var base64String = toBase64(bytes); + var dataURL = "data:" + mimetype + ";base64," + base64String; + do_check_eq(dataURL, expected); + do_test_finished(); + } + + } + }; + rawStream.asyncWait(asyncReader, 0, 0, currentThread); +} + +/* toBase64 copied from extensions/xml-rpc/src/nsXmlRpcClient.js */ + +/* Convert data (an array of integers) to a Base64 string. */ +const toBase64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' + + '0123456789+/'; +const base64Pad = '='; +function toBase64(data) { + var result = ''; + var length = data.length; + var i; + // Convert every three bytes to 4 ascii characters. + for (i = 0; i < (length - 2); i += 3) { + result += toBase64Table[data[i] >> 2]; + result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)]; + result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)]; + result += toBase64Table[data[i+2] & 0x3f]; + } + + // Convert the remaining 1 or 2 bytes, pad out to 4 characters. + if (length%3) { + i = length - (length%3); + result += toBase64Table[data[i] >> 2]; + if ((length%3) == 2) { + result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)]; + result += toBase64Table[(data[i+1] & 0x0f) << 2]; + result += base64Pad; + } else { + result += toBase64Table[(data[i] & 0x03) << 4]; + result += base64Pad + base64Pad; + } + } + + return result; +} diff --git a/image/test/unit/test_imgtools.js b/image/test/unit/test_imgtools.js new file mode 100644 index 000000000..b9b8da82b --- /dev/null +++ b/image/test/unit/test_imgtools.js @@ -0,0 +1,737 @@ +/* + * Tests for imgITools + */ + +var Ci = Components.interfaces; +var Cc = Components.classes; + + +/* + * dumpToFile() + * + * For test development, dumps the specified array to a file. + * Call |dumpToFile(outData);| in a test to file to a file. + */ +function dumpToFile(aData) { + var outputFile = do_get_tempdir(); + outputFile.append("testdump.png"); + + var outputStream = Cc["@mozilla.org/network/file-output-stream;1"]. + createInstance(Ci.nsIFileOutputStream); + // WR_ONLY|CREAT|TRUNC + outputStream.init(outputFile, 0x02 | 0x08 | 0x20, 0o644, null); + + var bos = Cc["@mozilla.org/binaryoutputstream;1"]. + createInstance(Ci.nsIBinaryOutputStream); + bos.setOutputStream(outputStream); + + bos.writeByteArray(aData, aData.length); + + outputStream.close(); +} + + +/* + * getFileInputStream() + * + * Returns an input stream for the specified file. + */ +function getFileInputStream(aFile) { + var inputStream = Cc["@mozilla.org/network/file-input-stream;1"]. + createInstance(Ci.nsIFileInputStream); + // init the stream as RD_ONLY, -1 == default permissions. + inputStream.init(aFile, 0x01, -1, null); + + // Blah. The image decoders use ReadSegments, which isn't implemented on + // file input streams. Use a buffered stream to make it work. + var bis = Cc["@mozilla.org/network/buffered-input-stream;1"]. + createInstance(Ci.nsIBufferedInputStream); + bis.init(inputStream, 1024); + + return bis; +} + + +/* + * streamToArray() + * + * Consumes an input stream, and returns its bytes as an array. + */ +function streamToArray(aStream) { + var size = aStream.available(); + + // use a binary input stream to grab the bytes. + var bis = Cc["@mozilla.org/binaryinputstream;1"]. + createInstance(Ci.nsIBinaryInputStream); + bis.setInputStream(aStream); + + var bytes = bis.readByteArray(size); + if (size != bytes.length) + throw "Didn't read expected number of bytes"; + + return bytes; +} + + +/* + * compareArrays + * + * Compares two arrays, and throws if there's a difference. + */ +function compareArrays(aArray1, aArray2) { + do_check_eq(aArray1.length, aArray2.length); + + for (var i = 0; i < aArray1.length; i++) + if (aArray1[i] != aArray2[i]) + throw "arrays differ at index " + i; +} + + +/* + * checkExpectedError + * + * Checks to see if a thrown error was expected or not, and if it + * matches the expected value. + */ +function checkExpectedError (aExpectedError, aActualError) { + if (aExpectedError) { + if (!aActualError) + throw "Didn't throw as expected (" + aExpectedError + ")"; + + if (!aExpectedError.test(aActualError)) + throw "Threw (" + aActualError + "), not (" + aExpectedError; + + // We got the expected error, so make a note in the test log. + dump("...that error was expected.\n\n"); + } else if (aActualError) { + throw "Threw unexpected error: " + aActualError; + } +} + + +function run_test() { + +try { + + +/* ========== 0 ========== */ +var testnum = 0; +var testdesc = "imgITools setup"; +var err = null; + +var imgTools = Cc["@mozilla.org/image/tools;1"]. + getService(Ci.imgITools); + +if (!imgTools) + throw "Couldn't get imgITools service" + +// Ugh, this is an ugly hack. The pixel values we get in Windows are sometimes +// +/- 1 value compared to other platforms, so we need to compare against a +// different set of reference images. nsIXULRuntime.OS doesn't seem to be +// available in xpcshell, so we'll use this as a kludgy way to figure out if +// we're running on Windows. +var isWindows = mozinfo.os == "win"; + + +/* ========== 1 ========== */ +testnum++; +testdesc = "test decoding a PNG"; + +// 64x64 png, 8415 bytes. +var imgName = "image1.png"; +var inMimeType = "image/png"; +var imgFile = do_get_file(imgName); + +var istream = getFileInputStream(imgFile); +do_check_eq(istream.available(), 8415); + +// Use decodeImageData for this test even though it's deprecated to ensure that +// it correctly forwards to decodeImage and continues to work. +var outParam = { value: null }; +imgTools.decodeImageData(istream, inMimeType, outParam); +var container = outParam.value; + +// It's not easy to look at the pixel values from JS, so just +// check the container's size. +do_check_eq(container.width, 64); +do_check_eq(container.height, 64); + + +/* ========== 2 ========== */ +testnum++; +testdesc = "test encoding a scaled JPEG"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeScaledImage(container, "image/jpeg", 16, 16); + +var encodedBytes = streamToArray(istream); +// Get bytes for exected result +var refName = "image1png16x16.jpg"; +var refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 1051); +var referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + + +/* ========== 3 ========== */ +testnum++; +testdesc = "test encoding an unscaled JPEG"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeImage(container, "image/jpeg"); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image1png64x64.jpg"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 4503); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + + +/* ========== 4 ========== */ +testnum++; +testdesc = "test decoding a JPEG"; + +// 32x32 jpeg, 3494 bytes. +imgName = "image2.jpg"; +inMimeType = "image/jpeg"; +imgFile = do_get_file(imgName); + +istream = getFileInputStream(imgFile); +do_check_eq(istream.available(), 3494); + +container = imgTools.decodeImage(istream, inMimeType); + +// It's not easy to look at the pixel values from JS, so just +// check the container's size. +do_check_eq(container.width, 32); +do_check_eq(container.height, 32); + + +/* ========== 5 ========== */ +testnum++; +testdesc = "test encoding a scaled PNG"; + +if (!isWindows) { +// we'll reuse the container from the previous test +istream = imgTools.encodeScaledImage(container, "image/png", 16, 16); + +encodedBytes = streamToArray(istream); +// Get bytes for exected result +refName = isWindows ? "image2jpg16x16-win.png" : "image2jpg16x16.png"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 950); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); +} + + +/* ========== 6 ========== */ +testnum++; +testdesc = "test encoding an unscaled PNG"; + +if (!isWindows) { +// we'll reuse the container from the previous test +istream = imgTools.encodeImage(container, "image/png"); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = isWindows ? "image2jpg32x32-win.png" : "image2jpg32x32.png"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 3105); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); +} + + +/* ========== 7 ========== */ +testnum++; +testdesc = "test decoding a ICO"; + +// 16x16 ico, 1406 bytes. +imgName = "image3.ico"; +inMimeType = "image/x-icon"; +imgFile = do_get_file(imgName); + +istream = getFileInputStream(imgFile); +do_check_eq(istream.available(), 1406); + +container = imgTools.decodeImage(istream, inMimeType); + +// It's not easy to look at the pixel values from JS, so just +// check the container's size. +do_check_eq(container.width, 16); +do_check_eq(container.height, 16); + + +/* ========== 8 ========== */ +testnum++; +testdesc = "test encoding a scaled PNG"; // note that we're scaling UP + +// we'll reuse the container from the previous test +istream = imgTools.encodeScaledImage(container, "image/png", 32, 32); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image3ico32x32.png"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 2285); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + + +/* ========== 9 ========== */ +testnum++; +testdesc = "test encoding an unscaled PNG"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeImage(container, "image/png"); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image3ico16x16.png"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 330); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + + +/* ========== 10 ========== */ +testnum++; +testdesc = "test decoding a GIF"; + +// 32x32 gif, 1809 bytes. +imgName = "image4.gif"; +inMimeType = "image/gif"; +imgFile = do_get_file(imgName); + +istream = getFileInputStream(imgFile); +do_check_eq(istream.available(), 1809); + +container = imgTools.decodeImage(istream, inMimeType); + +// It's not easy to look at the pixel values from JS, so just +// check the container's size. +do_check_eq(container.width, 32); +do_check_eq(container.height, 32); + +/* ========== 11 ========== */ +testnum++; +testdesc = "test encoding an unscaled ICO with format options " + + "(format=bmp;bpp=32)"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeImage(container, + "image/vnd.microsoft.icon", + "format=bmp;bpp=32"); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image4gif32x32bmp32bpp.ico"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 4286); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + +/* ========== 12 ========== */ +testnum++; +testdesc = "test encoding a scaled ICO with format options " + + "(format=bmp;bpp=32)"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeScaledImage(container, + "image/vnd.microsoft.icon", + 16, + 16, + "format=bmp;bpp=32"); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image4gif16x16bmp32bpp.ico"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 1150); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + +/* ========== 13 ========== */ +testnum++; +testdesc = "test encoding an unscaled ICO with format options " + + "(format=bmp;bpp=24)"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeImage(container, + "image/vnd.microsoft.icon", + "format=bmp;bpp=24"); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image4gif32x32bmp24bpp.ico"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 3262); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + +/* ========== 14 ========== */ +testnum++; +testdesc = "test encoding a scaled ICO with format options " + + "(format=bmp;bpp=24)"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeScaledImage(container, + "image/vnd.microsoft.icon", + 16, + 16, + "format=bmp;bpp=24"); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image4gif16x16bmp24bpp.ico"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 894); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + + +/* ========== 15 ========== */ +testnum++; +testdesc = "test cropping a JPG"; + +// 32x32 jpeg, 3494 bytes. +imgName = "image2.jpg"; +inMimeType = "image/jpeg"; +imgFile = do_get_file(imgName); + +istream = getFileInputStream(imgFile); +do_check_eq(istream.available(), 3494); + +container = imgTools.decodeImage(istream, inMimeType); + +// It's not easy to look at the pixel values from JS, so just +// check the container's size. +do_check_eq(container.width, 32); +do_check_eq(container.height, 32); + +// encode a cropped image +istream = imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, 16, 16); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image2jpg16x16cropped.jpg"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 879); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + + +/* ========== 16 ========== */ +testnum++; +testdesc = "test cropping a JPG with an offset"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeCroppedImage(container, "image/jpeg", 16, 16, 16, 16); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image2jpg16x16cropped2.jpg"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 878); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + + +/* ========== 17 ========== */ +testnum++; +testdesc = "test cropping a JPG without a given height"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, 16, 0); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image2jpg16x32cropped3.jpg"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 1127); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + + +/* ========== 18 ========== */ +testnum++; +testdesc = "test cropping a JPG without a given width"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, 0, 16); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image2jpg32x16cropped4.jpg"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 1135); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + + +/* ========== 19 ========== */ +testnum++; +testdesc = "test cropping a JPG without a given width and height"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, 0, 0); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image2jpg32x32.jpg"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 1634); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + + +/* ========== 20 ========== */ +testnum++; +testdesc = "test scaling a JPG without a given width"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeScaledImage(container, "image/jpeg", 0, 16); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image2jpg32x16scaled.jpg"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 1227); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + + +/* ========== 21 ========== */ +testnum++; +testdesc = "test scaling a JPG without a given height"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeScaledImage(container, "image/jpeg", 16, 0); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image2jpg16x32scaled.jpg"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 1219); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + + +/* ========== 22 ========== */ +testnum++; +testdesc = "test scaling a JPG without a given width and height"; + +// we'll reuse the container from the previous test +istream = imgTools.encodeScaledImage(container, "image/jpeg", 0, 0); +encodedBytes = streamToArray(istream); + +// Get bytes for exected result +refName = "image2jpg32x32.jpg"; +refFile = do_get_file(refName); +istream = getFileInputStream(refFile); +do_check_eq(istream.available(), 1634); +referenceBytes = streamToArray(istream); + +// compare the encoder's output to the reference file. +compareArrays(encodedBytes, referenceBytes); + + +/* ========== 22 ========== */ +testnum++; +testdesc = "test invalid arguments for cropping"; + +var numErrors = 0; + +try { + // width/height can't be negative + imgTools.encodeScaledImage(container, "image/jpeg", -1, -1); +} catch (e) { numErrors++; } + +try { + // offsets can't be negative + imgTools.encodeCroppedImage(container, "image/jpeg", -1, -1, 16, 16); +} catch (e) { numErrors++; } + +try { + // width/height can't be negative + imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, -1, -1); +} catch (e) { numErrors++; } + +try { + // out of bounds + imgTools.encodeCroppedImage(container, "image/jpeg", 17, 17, 16, 16); +} catch (e) { numErrors++; } + +try { + // out of bounds + imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, 33, 33); +} catch (e) { numErrors++; } + +try { + // out of bounds + imgTools.encodeCroppedImage(container, "image/jpeg", 1, 1, 0, 0); +} catch (e) { numErrors++; } + +do_check_eq(numErrors, 6); + + +/* ========== bug 363986 ========== */ +testnum = 363986; +testdesc = "test PNG and JPEG encoders' Read/ReadSegments methods"; + +var testData = + [{preImage: "image3.ico", + preImageMimeType: "image/x-icon", + refImage: "image3ico16x16.png", + refImageMimeType: "image/png"}, + {preImage: "image1.png", + preImageMimeType: "image/png", + refImage: "image1png64x64.jpg", + refImageMimeType: "image/jpeg"}]; + +for(var i=0; i